NanoPi Neo Core底层开发

一、编译下载镜像

各个部分编译后的文件都在对应的文件夹中,如编译friendlycore-xenial_4.14_armhf系统,对应的各个镜像就都在friendlycore-xenial_4.14_armhf文件夹中

最后生成的img文件会在out文件夹中

1. 编译

1.1编译uboot

1
UBOOT_SRC=../UBOOT/u-boot-sunxi-v2017.x ./build-uboot.sh friendlywrt_4.14_armhf

1.2编译Linux内核

1
KERNEL_SRC=../LINUX/linux-sunxi-4.14.y ./build-kernel.sh friendlycore-xenial_4.14_armhf

编译内核时会自动编译设备树,如果想重新单独编译设备树,则有:

单独编译设备树
1
2
3
4
make distclean
touch .scmversion
make ARCH=arm sunxi_defconfig
make dtbs ARCH=arm CROSS_COMPILE=arm-linux- -j8
反编译设备树
1
./scripts/dtc/dtc -I dtb -O dts -o ../sun8i-h3-nanopi-neo-core.dts ./arch/arm/boot/dts/sun8i-h3-nanopi-neo-core.dtb

1.3编译根文件系统

1
2
echo hello > friendlycore-xenial_4.14_armhf/rootfs/root/welcome.txt
./build-rootfs-img.sh friendlycore-xenial_4.14_armhf/rootfs friendlycore-xenial_4.14_armhf

2. 下载

2.1生成img镜像

生成SD卡的img镜像
1
sudo ./mk-sd-image.sh friendlycore-xenial_4.14_armhf [h3-sd-friendlycore.img(镜像名字,默认按照时间命名)]
烧录img镜像到SD卡
1
sudo dd if=out/h3-sd-friendlycore.img bs=1M of=/dev/sdX

2.2直接烧录

1
sudo ./fusing.sh /dev/sdX friendlycore-xenial_4.14_armhf

3.分区情况

分区名字 文件系统 起始地址 长度 镜像名字 内容
boot0 raw 0x2000 0x17FE000(24M-8k) u-boot-sunxi-with-spl.bin uboot
boot fat 0x1800000 0x2800000(40M) boot.img kernel(zImage)+dtb
rootfs ext4 0x4000000 0x48400000(1156M) rootfs.img 根文件系统
userdata ext4 0x4c400000 0x0 userdata.img

3.1自己创建img镜像文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dd if=/dev/zero of=镜像文件 bs=1024 count=0 seek=镜像大小#单位:k,uboot+kernel(zImage)+dtb+rootfs=1220M,脚本中默认(7800*1000*1000)/1024

#sfdisk : 查看分区信息
#将MBR扇区签名写入到镜像
sfdisk -u S -L -q 镜像文件 2>/dev/null << EOF
2048,,0x0C,-
EOF

# 找到一个loop设备
losetup -f
# 将镜像文件虚拟成块设备
sudo losetup 找到的loop设备 镜像文件#eg: losetup /dev/loop1 floppy.img

# 利用友善之臂的工具sd_update,将分区表同目录下的uboot.bin、boot.img、rootfs.img、userdata.img拷贝到loop设备中并生成img镜像
sudo ./tools/sd_update -d 找到的loop设备 -p ./friendlycore-xenial_4.14_armhf/partmap.txt(分区表)
# 读取 找到的loop设备 分区信息
sudo partprobe 找到的loop设备 -s 2>/dev/null
# 调整文件系统的大小,这里是p3,意为第三个分区,即userdata分区
sudo resize2fs -f 找到的loop设备p3;

# 卸载loop设备
sudo losetup -d 找到的loop设备

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
dd if=/dev/zero of=out/busyboxflash.img bs=1024 count=0 seek=1331200#1331200k -> 1300M ==> 留给userdata 80M,修改partmap.txt中userdata大小为0x500 0000
# 实测貌似最少需要8G才能用ubuntucore文件系统


#sfdisk : 查看分区信息
#将MBR扇区签名写入到镜像
sfdisk -u S -L -q out/busyboxflash.img 2>/dev/null << EOF
2048,,0x0C,-
EOF

# 找到一个loop设备
losetup -f
# 将镜像文件虚拟成块设备
sudo losetup /dev/loop17 out/busyboxflash.img
# 利用友善之臂的工具sd_update,将分区表同目录下的uboot.bin、boot.img、rootfs.img、userdata.img拷贝到loop设备中并生成img镜像
sudo ./tools/sd_update -d /dev/loop17 -p ./friendlycore-xenial_4.14_armhf/partmap.txt(分区表)
# 读取/dev/loop17分区信息
sudo partprobe /dev/loop17 -s 2>/dev/null
# 调整文件系统的大小,这里是p3,意为第三个分区,即userdata分区
sudo resize2fs -f /dev/loop17p3;

# 卸载loop设备
sudo losetup -d /dev/loop17
dd命令详解

dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。

  • if=文件名:输入文件名,缺省为标准输入。即指定源文件。<if=inputfile>
  • of=文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >
  • ibs=bytes:一次读入bytes个字节,即指定一个块大小为bytes个字节。
    obs=bytes:一次输出bytes个字节,即指定一个块大小为bytes个字节。
    bs=bytes:同时设置读入/输出的块大小为bytes个字节。
  • cbs=bytes:一次转换bytes个字节,即指定转换缓冲区大小。
  • skip=blocks:从输入文件开头跳过blocks个块后再开始复制。
  • seek=blocks:从输出文件开头跳过blocks个块后再开始复制。
    (注意:通常只用当输出文件是磁盘或磁带时才有效,即备份到磁盘或磁带时才有效。
  • count=blocks:仅拷贝blocks个块,块大小等于ibs指定的字节数。
  • conv=conversion:用指定的参数转换文件。
    • ascii:转换ebcdic为ascii
    • ebcdic:转换ascii为ebcdic
    • ibm:转换ascii为alternateebcdic
    • block:把每一行转换为长度为cbs,不足部分用空格填充
    • unblock:使每一行的长度都为cbs,不足部分用空格填充
    • lcase:把大写字符转换为小写字符
    • ucase:把小写字符转换为大写字符
    • swab:交换输入的每对字节
    • noerror:出错时不停止
    • notrunc:不截短输出文件
    • sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。
/dev/null和/dev/zero的区别
  • /dev/null,外号叫无底洞,你可以向它输出任何数据,它通吃,并且不会撑着!
  • /dev/zero,是一个输入设备,你可你用它来初始化文件。该设备无穷尽地提供0,可以使用任何你需要的数目——设备提供的要多的多。他可以用于向设备或文件写入字符串0。

二、开发环境搭建

使用官方提供ubuntucore文件系统

切换成root用户:

1
su root

普通用户:

1
2
用户名: pi
密码: pi

Root用户:

1
2
用户名: root
密码: fa

可能会遇到无法切换的行为,此时可以修改默认登录账号为root:

1
2
3
4
5
6
7
8
9
10
cd /etc/systemd/system/serial-getty@ttyS0.service.d/
vi autologin.conf
修改前:
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin pi --keep-baud 115200,38400,9600 %I $TERM
修改后:
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --keep-baud 115200,38400,9600 %I $TERM

更新软件包

1
apt-get update

自己制作根文件系统

详见另一篇博客——《构造根文件系统》

1
2
3
4
5
6
7
8
9
10
# 拷贝lib库
sudo cp ../Toolchain/4.9.3/arm-cortexa9-linux-gnueabihf/lib/*so* ./lib/ -d
sudo cp ../Toolchain/4.9.3/arm-cortexa9-linux-gnueabihf/lib/*.a ./lib/ -d

# 拷贝lib库
sudo cp ../Toolchain/4.9.3/arm-cortexa9-linux-gnueabihf/sys-root/usr/lib/*so* ./usr/lib/ -d
sudo cp ../Toolchain/4.9.3/arm-cortexa9-linux-gnueabihf/sys-root/usr/lib/*.a ./usr/lib/ -d

# 查看lib文件大小
du ./lib/ ./usr/lib/ -sh

etc/init.d/rcS文件:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATH runlevel

mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

chmod 777 rcS

etc/fstab文件:

1
2
3
4
5
#device mount-point     type    options         dump    pass
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0

etc/inittab文件:

1
2
3
4
5
6
7
#etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

打包成img镜像

1
2
3
4
5
6
7
8
9
10
# 获得待打包成img镜像的rootfs文件夹大小
du -s -B 1 friendlycore-xenial_4.14_armhf/rootfs | cut -f1
# 利用友善之臂提供的工具make_ext4fs制作临时的ext4格式文件系统
./tools/make_ext4fs -s -l 最大镜像大小 -a root -L rootfs /dev/null 待打包成img镜像的rootfs文件夹 > tempfile
# 获得镜像大小
cat tempfile | grep "Suggest size:" | cut -f2 -d ':' | awk '{gsub(/^\s+|\s+$/, "");print}'
# 注意:获得待打包成img镜像的rootfs文件夹大小 < 获得镜像大小

# 真正制作ext4格式文件系统
./tools/make_ext4fs -s -l 刚刚获得的镜像大小 -a root -L rootfs 镜像文件.img 待打包成img镜像的rootfs文件夹

上述这一系列操作已经被友善之臂写入脚本build-rootfs-img.sh中,具体调用方面为:

1
./build-rootfs-img rootfs文件夹所在目录 生成img的镜像文件所在的目录
1
2
3
4
eg:
./build-rootfs-img.sh friendlycore-xenial_4.14_armhf/rootfs friendlycore-xenial_4.14_armhf

./build-rootfs-img.sh ~/nfs_root ~/nfs_root

**注:**重新配置文件系统后后,需要重新运行友善之臂的脚本build-kernel.sh编译一次内核,该脚本会将内核中的模块安装至该文件系统镜像中

自己做的rootfs平台,build-kernel.sh中需要注释一个平台检测的代码块,同时在./tools/update_kernel_bin_to_img.sh脚本中第122行需要将echo "IMG_SIZE=${IMG_SIZE}" > ${OUT}/${TARGET_OS}_rootfs-img.info改为echo "IMG_SIZE=${IMG_SIZE}"

1
KERNEL_SRC=../LINUX/linux-sunxi-4.14.y ./build-kernel-others.sh 放rootfs.img和放生成的boot.img的文件夹

参考前文,下载img镜像方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dd if=/dev/zero of=out/busyboxflash.img bs=1024 count=0 seek=133120#133120k -> 130M 删除partmap.txt中userdata分区

#sfdisk : 查看分区信息
#将MBR扇区签名写入到镜像
sfdisk -u S -L -q out/busyboxflash.img 2>/dev/null << EOF
2048,,0x0C,-
EOF

# 找到一个loop设备
losetup -f
# 将镜像文件虚拟成块设备
sudo losetup /dev/loop17 out/busyboxflash.img
# 利用友善之臂的工具sd_update,将分区表同目录下的uboot.bin、boot.img、rootfs.img、userdata.img拷贝到loop设备中并生成img镜像
sudo ./tools/sd_update -d /dev/loop17 -p ./busybox_rootfs/partmap_no_userdata.txt
# 读取/dev/loop17分区信息
sudo partprobe /dev/loop17 -s 2>/dev/null

# 卸载loop设备
sudo losetup -d /dev/loop17

经测试,没有重新编译内核会导致内核启动失败,错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
U-Boot SPL 2017.11 (Oct 12 2019 - 17:03:15)
DRAM: 512 MiB(408MHz)
CPU Freq: 408MHz
memory test: 1
Pattern 55aa Writing...Reading...OK
Trying to boot from MMC1
Boot device: sd


U-Boot 2017.11 (Oct 12 2019 - 17:03:15 +0800) Allwinner Technology

CPU: Allwinner H3 (SUN8I 1680)
Model: FriendlyElec NanoPi H3
DRAM: 512 MiB
CPU Freq: 1008MHz
MMC: SUNXI SD/MMC: 0, SUNXI SD/MMC: 1
*** Warning - bad CRC, using default environment

In: serial
Out: serial
Err: serial
Net: No ethernet found.
BOARD: nanopi-neo-core
starting USB...
No controllers found
Hit any key to stop autoboot: 0
** Unrecognized filesystem type **
## Executing script at 43100000
Wrong image format for "source" command
=>

根据日志分析貌似是在43100000地址处找不到脚本,但是这个地址是在rootfs中的。。。。。。

不会搞了,留个坑以后来填。。。。。。

三、驱动编写

暂时采用官方镜像,镜像名字为nanopi-neo-core_sd_friendlycore-xenial_4.14_armhf_20190823.img,该镜像中采用的rootfs为UbuntuCore 18.04

写在前面

内核版本查看方法

1
2
cat /proc/version
uname -a

挂载pc上文件系统

1
2
3
mount -t nfs -o nolock 192.168.2.101:/home/null/Code/NanoPi_NEO_Core/nfs /mnt

mount -t nfs -o nolock 192.168.2.101:/home/null/nfs_root /mnt

挂载报错尝试安装nfs-common

报错:

1
2
3
4
5
6
7
mount: wrong fs type, bad option, bad superblock on 192.168.2.101:/home/null/nfs_root,
missing codepage or helper program, or other error
(for several filesystems (e.g. nfs, cifs) you might
need a /sbin/mount.<type> helper program)

In some cases useful info is found in syslog - try
dmesg | tail or so.

安装nfs-common:

1
sudo apt-get install nfs-common

pc端检查是否配置成功(自己挂接自己)(绝对路径)

1
2
3
4
5
6
sudo mount -t nfs -o nolock localhost://home/null/Code/NanoPi_NEO_Core/nfs /mnt/nfs#挂接
sudo mount -t nfs -o nolock localhost://home/null/nfs_root /mnt/nfs_root#挂接
ls -l /mnt/nfs#检查
ls -l /mnt/nfs_root#检查
sudo umount /mnt/nfs#卸载
sudo umount /mnt/nfs_root#卸载
一键挂载脚本
1
2
3
4
5
6
7
8
#!/bin/bash
if [ $(id -u) -ne 0 ]; then
echo "Re-running script under sudo..."
sudo "$0" "$@"
exit
fi
mount -t nfs -o nolock 192.168.2.101:/home/null/Code/NanoPi_NEO_Core/nfs /mnt
echo "mount -> /mnt"

记得chmod +x

卸载脚本
1
2
3
4
5
6
7
8
#!/bin/bash
if [ $(id -u) -ne 0 ]; then
echo "Re-running script under sudo..."
sudo "$0" "$@"
exit
fi
umount /mnt
echo "umount /mnt OK"

记得chmod +x

更换源

参考: http://mirrors.ustc.edu.cn/help/ubuntu-ports.html

修改 /etc/apt/sources.list 文件中镜像源服务器地址 http://ports.ubuntu.com/http://mirrors.ustc.edu.cn/ubuntu-ports/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic main restricted universe multiverse
#deb http://ports.ubuntu.com/ bionic main restricted universe multiverse
# deb-src http://ports.ubuntu.com/ xenial main restricted universe multiverse

deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-security main restricted universe multiverse
#deb http://ports.ubuntu.com/ bionic-security main restricted universe multiverse
# deb-src http://ports.ubuntu.com/ xenial-security main restricted universe multiverse

deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-updates main restricted universe multiverse
#deb http://ports.ubuntu.com/ bionic-updates main restricted universe multiverse
# deb-src http://ports.ubuntu.com/ xenial-updates main restricted universe multiverse

deb http://mirrors.ustc.edu.cn/ubuntu-ports/ bionic-backports main restricted universe multiverse
#deb http://ports.ubuntu.com/ bionic-backports main restricted universe multiverse
# deb-src http://ports.ubuntu.com/ xenial-backports main restricted universe multiverse

更新源:

1
sudo apt update

1.按键驱动程序

1.1修改设备树

编译设备树
1
make dtbs ARCH=arm CROSS_COMPILE=arm-linux- -j8
查看系统中是否存在对应节点
1
2
3
4
5
6
7
ls /proc/device-tree/

cat /proc/device-tree/key/compatible

# 注意:hexdump查看的是dtb文件原始数据,其中的数据是按照大字节序排放的(低地址放高位)
hexdump /proc/device-tree/key/key-gpio
busybox hexdump /proc/device-tree/key/key-gpio
设备树中的GPIO节点

假设PA6和PA7上接入两个LED,通过编写一个GPIO的驱动程序来控制LED的亮灭,在设备树文件sun8i-h3-nanopi-m1.dts的根节点增加一个myleds子节点 :

1
2
3
4
myleds {       
compatible = "usr,myleds";
led-gpios = <&pio 0 7 GPIO_ACTIVE_HIGH>,<&pio 0 6 GPIO_ACTIVE_HIGH>;/* 0表示PA组的io口, 因h3里第0组的io口就是以PA开始命名的 */
};

节点中的led-gpios属性,引用了pinctrl信息,sunxi-h3-h5.dtsi中有关于该控制器的描述,

pio: pinctrl@01c20800 {
    /* compatible is in per SoC .dtsi file */
    reg = <0x01c20800 0x400>;
    interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>,
    <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&ccu CLK_BUS_PIO>, <&osc24M>, <&osc32k>;
    clock-names = "apb", "hosc", "losc";
    gpio-controller;
    #gpio-cells = <3>;
    interrupt-controller;
    #interrupt-cells = <3>;
    ......
};

myleds子节点描述了两个GPIO信息,具有多个gpio口信息的属性值: <&gpio控制器节点名 具体gpio口的标识符>,<&gpio控制器节点名 具体gpio口的标识符> …;具体gpio口的标识符是由多个数字组成, 数字的个数由所用的gpio控制器节点里的#gpio-cells属性值指定.。全志H3 共有七组GPIO ,也即七个bank,分别为PA (PA0-21),PC(PC0-16) PD (PD0-17), PE(PE0-15),PF(PF0-6) PG(PG0-13) ,PL(PL0-PL11),在内核中PA-PG是一个pinctrl控制器,而PL是另一个pinctrl控制器 。

由#gpio-cells = <3>可以推出GPIO属性需要用3个u32的数据描述,第一个参数代表该GPIO位于哪个bank,第二个参数代表该bank下的序号,第三个参数代表默认电平,myleds节点下的GPIO_ACTIVE_HIGH这个宏就定义于include/dt-bindings/gpio/gpio.h

1.2编写驱动

1.2.1检查注册的驱动
  • 驱动安装好后,会在/dev/目录下创建同名目录

  • 或者通过cat /proc/devices命令查看多出来的驱动

1.2.2gpiolib及gpio操作
经典接口
  • gpio_request:驱动中要想使用某一个gpio,就必须先调用gpio_request接口来向内核申请,得到允许后才可以去使用这个gpio
  • gpio_free: 对应gpio_request,用来释放申请后用完了的gpio
  • gpiochip_is_requested: 接口用来判断某一个gpio是否已经被申请了
  • gpio_direction_input/gpio_direction_output: 接口用来设置GPIO为输入/输出模式(不推荐直接设置寄存器)
  • 一般来说,gpio的资源申请和释放操作应该放在驱动模块的加载与卸载函数内
  • gpio_request的第一个参数是需要申请的gpio号。第二个参数是我们给该gpio起个名字
  • 申请完了之后对这个gpio进行模式设置,我们这里用gpio_direction_output 设置成输出模式,并且默认输出1让led灭
新接口
  • devm_gpio_request_one: 自带初始化电平功能,并且会在模块卸载时自动释放gpio,十分简便
读写gpio
  • gpio_get_value:读gpio
  • gpio_set_value:写gpio
控制台中查看当前gpio占用情况的方法

内核中提供了虚拟文件系统debugfs,里面有一个gpio文件,提供了gpio的使用信息

  • 使用 mount -t debugfs debugfs /tmp 把debugfs挂接到/tmp下,再重新进入/tmp后就能看到一个名为gpio的文件
  • cat /tmp/gpio即可得到gpio的所有信息,使用完后umount /tmp卸载掉debugfs

1.3调试

printk
  • 查看printk输出的日志:

    通过dmesgmoretaillessgrep的结合查看日志

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #输出全部信息
    dmesg
    #输出前20行
    dmesg | head -20
    #输出后20行
    dmesg | tail -20
    #输出包含usb的信息(-i:忽略大小写)
    dmesg | grep -i usb
    #清空dmesg环形缓冲区中的日志
    dmesg -c
  • 或者直接设置日志的输出的等级:

    1
    2
    3
    echo $level > /proc/sys/kernel/printk#默认为4 4 1 7
    #如:
    echo 8 4 1 7 > /proc/sys/kernel/printk#所有级别日志均可打印输出

    /proc/sys/kernel/printk中对应的四个数分别对应于:

    • ①控制台日志级别:优先级高于该值的消息将被打印至控制台。
    • ②缺省的消息日志级别:将用该值来打印没有优先级的消息。
    • ③最低的控制台日志级别:控制台日志级别可能被设置的最小值。
    • ④缺省的控制台:控制台日志级别的缺省值。

    日志缓冲区的每一行文本开头具有级别标记, 级别值越小则优先级越高.

    系统定义了8个消息级别, 级别号从0到7分别为:

    • 致命级(KERN_EMESG),

    • 警戒级(KERN_ALERT),

    • 临界级(KERN_CRIT),

    • 错误级(KERN_ERR),

    • 告警级(KERN_WARN),

    • 注意级(KERN_NOTICE),

    • 通知级(KERN_INFO),

    • 调试级(KERN_DEBUG).

后台运行
  • &

    当在前台运行某个作业时,终端被该作业占据;可以在命令后面加上& 实现后台运行。例如:sh test.sh &

    当成功地提交进程以后,就会显示出一个进程号,可以用它来监控该进程,或杀死它。(ps -ef | grep 进程号 或者 kill -9 进程号

  • nohup

    使用&命令后,作业被提交到后台运行,当前控制台没有被占用,但是一但把当前控制台关掉(退出帐户时),作业就会停止运行。nohup命令可以在你退出帐户之后继续运行相应的进程。nohup就是不挂起的意思( no hang up)。该命令的一般形式为:

    1
    nohup command &

    如果使用nohup命令提交作业,那么在缺省情况下该作业的所有输出都被重定向到一个名为nohup.out的文件中,除非另外指定了输出文件:

    1
    nohup command > myout.file 2>&1 &

    使用了nohup之后,很多人就这样不管了,其实这样有可能在当前账户非正常退出或者结束的时候,命令还是自己结束了。所以在使用nohup命令后台运行命令之后,需要使用exit正常退出当前账户,这样才能保证命令一直在后台运行。

  • ctrl + z

    可以将一个正在前台执行的命令放到后台,并且处于暂停状态。

  • jobs
    查看当前有多少在后台运行的命令。
    jobs -l选项可显示所有任务的PID,jobs的状态可以是running, stopped, Terminated。但是如果任务被终止了(kill),shell 从当前的shell环境已知的列表中删除任务的进程标识。

2.内核定时器

3.中断

上半部和下半部

  • 软中断

    软中断必须在编译的时候静态注册

    • open_sotfirq:打开软中断
    • raise_sotfirq:触发软中断
  • tasklet:利用软中断来实现的另一种下半部机制,在软中断和tasklet之间建议选tasklet

    • tasklet_init:初始化tasklet_struct结构体,或直接使用宏DECLARE_TASKLET定义+初始化
    • tasklet_schedule:一般在上半部中调用,使要调度的tasklet在合适的时候运行
  • 工作队列:在进程上下文执行,允许睡眠或重新调度(下半部可以睡眠就可以交给工作队列),要执行的工作会交给一个内核工作者线程去执行

    • 宏INIT_WORK:初始化工作
    • 宏DECLARE_WORK:定义+初始化工作
    • schedule_work:类似tasklet_schedule,调度传入的工作

3.1修改设备树

参考内核绑定信息:Documentation/devicetree/bindings/arm/gic.txt

对于一般的ARM处理器来说,#interrupt-cells=<3>对应的三个cell分别为:

  1. 中断类型
    • 0:SPI中断
    • 1:PPI中断
  2. 中断号
    • SPI:0~987
    • PPI:0~15
  3. 标志
    • bit[3:0]:中断触发类型
      • 1:上升沿
      • 2:下降沿
      • 4:高电平
      • 8:低电平
    • bit[15:8]:PPI中断的CPU掩码
相关函数
  • irq_of_parse_and_map:从设备树中中断节点中的interrupts属性提取对应的中断号
  • gpio_to_irq:获取gpio对应的中断号
编译设备树
1
make dtbs ARCH=arm CROSS_COMPILE=arm-linux- -j8
查看系统中是否存在对应节点
1
2
3
4
5
6
7
ls /proc/device-tree/

cat /proc/device-tree/key/compatible

# 注意:hexdump查看的是dtb文件原始数据,其中的数据是按照大字节序排放的(低地址放高位)
hexdump /proc/device-tree/key/interrupts
busybox hexdump /proc/device-tree/key/interrupts
设备树中的中断节点
  • interrupt-parent = <&pio>;//属于pio中断控制器
  • interrupts = <0 7 IRQ_TYPE_EDGE_BOTH>;
    • 第一个参数:第几个GPIO(这里是GPIOA)
    • 第二个参数:第几个外部中断
    • 第三个参数:触发沿

3.2编写驱动

3.3调试

中断注册成功后可以通过以下命令来查看是否注册到系统中:

1
cat /proc/interrupts

4.input子系统

4.1修改设备树

如果需要使用内核自带的gpio_keys.c驱动,可以参考内核中Documentation/devicetree/bindings/input/gpio-keys.txt,需要设备树中的节点满足以下要求:

  • 节点名字为“gpio-keys”

  • gpio-keys节点的compatible属性必须为“gpio-keys”

  • 所有的按键都是gpio-keys的子节点,并可以使用如下属性描述自己:

    • gpios:按键连接的gpio信息
    • interrupts:按键使用的中断信息(可选)
    • lable:按键名字
    • linux,code:要模拟的按键值
  • 如果要支持连按的话需要加入autorepeat属性

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    gpio_keys {
    compatible = "gpio-keys";
    autorepeat;//支持连按
    k1 {
    label = "k1";//按键名称
    linux,code = <KEY_POWER>;//要模拟的按键值
    gpios = <&pio 0 7 GPIO_ACTIVE_LOW>;//按键连接的gpio信息
    interrupts = <0 7 IRQ_TYPE_EDGE_BOTH>;//中断信息 PA7 --> EINT7
    };
    };

4.2编写驱动

Linux中已经实现了input输入子系统框架,因此无需再次注册设备,系统中input框架中的所有设备的主设备号均为13

  • input设备结构体input_dev
    • evbit成员:输入事件类型,可选事件见下文
    • keybit成员:按键值
    • relbit成员:相对坐标
    • absbit成员:绝对坐标

设置input_dev结构体中成员的方法:

  • __set_bit
  • BIT_MASK宏
  • input_set_capability:仅仅设置keybit成员
相关函数
  • input_allocate_device:申请一个input_dev结构体
  • input_free_device:释放申请到的input_dev结构体
  • input_register_device:向内核注册input_dev结构体
  • input_unregister_device:注销input_dev结构体
  • input_event:上报指定事件及对应的值
    • input_report_key:封装一层,专门上报按键事件
    • input_report_rel
    • input_report_abs
    • input_report_ff_status
    • input_report_switch
    • input_mt_sync
  • input_sync:同步事件
input_dev的相关事件
  • EV_SYN:同步
  • EV_KEY:按键
  • EV_REL:相对坐标
  • EV_ABS:绝对坐标
  • EV_MSC:杂项(其他)
  • EV_SW:开关
  • EV_LED:LED
  • EV_SND:声音(sound)
  • EV_REP:重复事件
  • EV_FF:压力
  • EV_PWR:电源
  • EV_FF_STATUS:压力状态

4.3调试

input设备注册成功后可以通过以下命令来查看是否注册到系统中:

1
ls /dev/input/ -l

通过以下命令查看是否有效:

1
busybox hexdump /dev/input/event1

5.IIC框架驱动

5.1设备树

  • clock-frequency属性:iic总线的速度(默认100000,即100k)
  • status:是否使能该总线
    • okay
    • disabled
  • iic设备节点需要写到iic总线节点中,作为总线节点的子节点
  • 总线节点需要利用pinctrl-names = "default";pinctrl-0指定iic引脚(已经设置好)
  • iic控制器节点compatible属性必须为allwinner,sun6i-a31-i2c或allwinner,sun4i-a10-i2c,这里在sunxi-h3-h5.dtsi中已经设置好,无需再次设置

设备树修改成功后可以通过以下命令来查看是否注册到系统中:

1
2
3
#查看设备节点,该目录下放着所有的i2c设备
#节点是以i2c地址结尾的文件夹
ls /sys/bus/i2c/devices/

5.2驱动

基本数据结构
  • IIC总线结构体i2c_bus_type
    • match函数:驱动和设备匹配是调用该函数来决定是否匹配
  • IIC适配器(控制器)驱动结构体i2c_adapter
    • algo:总线访问算法成员
      • master_xfer:IIC适配器的传输函数
      • smbus_xfer:SMBUS总线传输函数
  • IIC设备结构体i2c_client
    • addr:芯片地址,存在低7位
    • name:名字
    • adapter:对应的IIC适配器
    • dev:设备结构体
    • irq:中断
  • IIC设备驱动结构体i2c_driver
    • probe函数:IIC设备和驱动匹配后调用,初始化用
    • remove函数:同理,卸载用
    • id_table:传统方式匹配ID列表
    • driver:
      • owner:所有者,一般为宏THIS_MODULE
      • name:名字
      • of_match_table:设备树匹配列表
  • IIC发送数据结构体i2c_msg
    • addr:从机地址
    • flags:标志
    • len:消息长度
    • buf:消息数据
相关函数
  • IIC适配器(控制器)
    • i2c_add_adapter:向系统注册i2c_adapter成员,使用动态总线号
    • i2c_add_numbered_adapter:向系统注册i2c_adapter成员,使用静态总线号
    • i2c_del_adapter:删除IIC适配器
  • SPI设备
    • i2c_register_driver / i2c_add_driver:注册i2c_driver
    • i2c_del_driver:卸载i2c_driver
  • 传输数据
    • i2c_transfer:传输i2c_msg类型数据
    • i2c_master_send:IIC发送缓存中的数据,最后会调用i2c_transfer
    • i2c_master_recv:IIC接收数据到缓存中,最后会调用i2c_transfer

5.3调试

i2c-tools工具

得益于ubuntucore,直接运行sudo apt install i2c-tools安装i2c-tools工具

  • 查询i2c总线sudo i2cdetect -l
  • 检测i2c地址sudo i2cdetect -r -y 0(最后的0为i2c-0总线)

6.SPI框架驱动

6.1设备树

  • spi-max-frequency属性:spi总线的最大速度(默认100000,即100k)
  • status:是否使能该总线
    • okay
    • disabled
  • spi设备节点需要写到spi总线节点中,作为总线节点的子节点
  • 通过cs-gpios属性指定CS引脚
  • spi设备节点中reg和节点@后的数字均为该设备所使用的spi通道(一般0即可)
  • 总线节点需要利用pinctrl-names = "default";pinctrl-0指定spi引脚(已经设置好)
  • spi控制器节点compatible属性必须为allwinner,sun8i-h3-spi或allwinner,sun6i-a31-spi,这里在sunxi-h3-h5.dtsi中已经设置好,无需再次设置

设备树修改成功后可以通过以下命令来查看是否注册到系统中:

1
2
3
#查看设备节点,该目录下放着所有的i2c设备
#节点是以i2c地址结尾的文件夹
ls /sys/bus/spi/devices/

6.2驱动

基本数据结构
  • SPI总线结构体spi_bus_type
    • match函数:驱动和设备匹配是调用该函数来决定是否匹配
  • SPI主机驱动结构体spi_master
    • transfer函数:控制器数据传输函数
    • transfer_one_message函数:控制器数据传输函数,一次发一个spi_message包
  • SPI设备驱动结构体spi_driver
    • probe函数:SPI设备和驱动匹配后调用,初始化用
    • remove函数:同理,卸载用
    • id_table:传统方式匹配ID列表
    • driver:
      • owner:所有者,一般为宏THIS_MODULE
      • name:名字
      • of_match_table:设备树匹配列表
  • SPI传输信息结构体spi_transfer
    • tx_buf:发送数据
    • rx_buf:接收数据
    • len:数据长度
  • SPI消息结构体spi_message,由spi_transfer组成
    • complete函数:异步传输完成时调用
相关函数
  • SPI主机
    • spi_alloc_master:申请spi_master结构体
    • spi_master_put:释放spi_master结构体
    • spi_register_master:注册
    • spi_unregister_master:注销
  • SPI设备
    • spi_register_driver:注册
    • spi_unregister_driver:卸载
  • 数据传输
    • spi_message_init:初始化spi_message结构体
    • spi_message_add_tail:将spi_transfer添加到spi_message队列中
    • spi_sync:同步传输(会阻塞并等待SPI数据传输完成)
    • spi_async:异步传输(传输完成后会调用spi_message.complete函数)

6.3调试