0%

Hello驱动

Hello驱动(不涉及硬件操作)

驱动程序编写步骤:

① 确定主设备号,也可以让内核分配

② 定义自己的file_operations结构体

③ 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体

④ 把file_operations结构体告诉内核:register_chrdev

⑤ 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数

⑥ 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev

⑦ 其他完善:提供设备信息,自动创建设备节点:class_create, device_create

驱动程序:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/* 1. 确定主设备号 */
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;


#define MIN(a, b) (a < b ? a : b)

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_to_user(buf, kernel_buf, MIN(1024, size));
return MIN(1024, size);
}

static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(kernel_buf, buf, MIN(1024, size));
return MIN(1024, size);
}

static int hello_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}

static int hello_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}

/* 2. 定义自己的file_operations结构体 */
static struct file_operations hello_drv = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.read = hello_drv_read,
.write = hello_drv_write,
.release = hello_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{
int err;

printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "hello", &hello_drv); /* /dev/hello */


hello_class = class_create(THIS_MODULE, "hello_class");
err = PTR_ERR(hello_class);
if (IS_ERR(hello_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "hello");
return -1;
}

device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */

return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit hello_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(hello_class, MKDEV(major, 0));
class_destroy(hello_class);
unregister_chrdev(major, "hello");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点 */

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");



阅读一个驱动程序,从它的入口函数开始,第66行就是入口函数。它的主要工作就是第71行,向内核注册一个file_operations结构体:hello_drv,这就是字符设备驱动程序的核心。

file_operations结构体hello_drv在第56行定义,里面提供了open/read/write/release成员,应用程序调用open/read/write/close时就会导致这些成员函数被调用。

file_operations结构体hello_drv中的成员函数都比较简单,大多数只是打印而已。要注意的是,驱动程序和应用程序之间传递数据要使用copy_from_user/copy_to_user函数。

测试程序:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
* ./hello_drv_test -w abc
* ./hello_drv_test -r
*/
int main(int argc, char **argv)
{
int fd;
char buf[1024];
int len;

/* 1. 判断参数 */
if (argc < 2)
{
printf("Usage: %s -w <string>\n", argv[0]);
printf(" %s -r\n", argv[0]);
return -1;
}

/* 2. 打开文件 */
fd = open("/dev/hello", O_RDWR);
if (fd == -1)
{
printf("can not open file /dev/hello\n");
return -1;
}

/* 3. 写文件或读文件 */
if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
{
len = strlen(argv[2]) + 1;
len = len < 1024 ? len : 1024;
write(fd, argv[2], len);
}
else
{
len = read(fd, buf, 1024);
buf[1023] = '\0';
printf("APP read : %s\n", buf);
}

close(fd);

return 0;
}



编写驱动程序的Makefile

驱动程序中包含了很多头文件,这些头文件来自内核,不同的ARM板它的某些头文件可能不同。所以编译驱动程序时,需要指定板子所用的内核的源码路径。

要编译哪个文件?这也需要指定,设置obj-m变量即可

怎么把.c文件编译为驱动程序.ko?这要借助内核的顶层Makefile。

本驱动程序的Makefile内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册

KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4

all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c

clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f hello_drv_test

obj-m += hello_drv.o

上机实验

Ubuntu

make命令编译驱动程序和测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
book@100ask:~/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv$ make
make -C /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4 M=`pwd` modules
make[1]: Entering directory '/home/book/100ask_stm32mp157_pro-sdk/Linux-5.4'
CC [M] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.o
Building modules, stage 2.
MODPOST 1 modules
CC [M] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.mod.o
LD [M] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.ko
make[1]: Leaving directory '/home/book/100ask_stm32mp157_pro-sdk/Linux-5.4'
arm-buildroot-linux-gnueabihf-gcc -o hello_drv_test hello_drv_test.c

#拷贝驱动
book@100ask:~/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv$ cp hello_drv.ko hello_drv_test /home/book/nfs_rootfs/

开发板

挂载NFS

1
busybox  mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#安装驱动程序
root@ATK-stm32mp1:/mnt# insmod hello_drv.ko
[ 752.208665] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_init line 70
root@ATK-stm32mp1:/mnt# cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyRPMSG
7 vcs
10 misc
13 input
21 sg
29 fb
81 video4linux
89 i2c
90 mtd
108 ppp
116 alsa
128 ptm
136 pts
153 spi
166 ttyACM
180 usb
188 ttyUSB
189 usb_device
216 rfcomm
226 drm
240 hello
241 media
242 rpmb
243 ttyGS
244 ttyUSI
245 ttySTM
246 bsg
247 watchdog
248 tee
249 iio
250 ptp
251 pps
252 cec
253 rtc
254 gpiochip
root@ATK-stm32mp1:/mnt# lsmod
Module Size Used by
hello_drv 16384 0
stm32_dcmi 32768 0
videobuf2_dma_contig 20480 1 stm32_dcmi
videobuf2_memops 16384 1 videobuf2_dma_contig
videobuf2_v4l2 20480 1 stm32_dcmi
ov5640 28672 0
videobuf2_common 40960 2 stm32_dcmi,videobuf2_v4l2
v4l2_fwnode 20480 2 ov5640,stm32_dcmi
videodev 172032 5 ov5640,v4l2_fwnode,videobuf2_common,stm32_dcmi,videobuf2_v4l2
mc 36864 5 ov5640,videobuf2_common,videodev,stm32_dcmi,videobuf2_v4l2
stm32_cec 16384 0
sch_fq_codel 20480 3
ipv6 442368 40
nf_defrag_ipv6 20480 1 ipv6
root@ATK-stm32mp1:/mnt# ls /dev/hello -l
crw------- 1 root root 240, 0 Feb 7 16:03 /dev/hello

测试程序运行:

1
2
3
4
5
6
7
8
9
10
11
12
root@ATK-stm32mp1:/mnt# ./hello_drv_test
Usage: ./hello_drv_test -w <string>
./hello_drv_test -r
root@ATK-stm32mp1:/mnt# ./hello_drv_test -w aaa
[ 1120.583066] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_drv_open line 45
[ 1120.594824] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_drv_write line 38
[ 1120.610601] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_drv_close line 51
root@ATK-stm32mp1:/mnt# ./hello_drv_test -r
[ 1179.309364] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_drv_open line 45
[ 1179.321087] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_drv_read line 30
APP read[ 1179.334721] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_drv_close line 51
: aaa

卸载驱动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root@ATK-stm32mp1:/mnt# rmmod hello_drv
[ 2866.055626] /home/book/01_all_series_quickstart/05_嵌入式Linux驱动开发基础知识/source/01_hello_drv/hello_drv.c hello_exit line 90
root@ATK-stm32mp1:/mnt# lsmod
Module Size Used by
stm32_dcmi 32768 0
videobuf2_dma_contig 20480 1 stm32_dcmi
videobuf2_memops 16384 1 videobuf2_dma_contig
videobuf2_v4l2 20480 1 stm32_dcmi
ov5640 28672 0
videobuf2_common 40960 2 stm32_dcmi,videobuf2_v4l2
v4l2_fwnode 20480 2 ov5640,stm32_dcmi
videodev 172032 5 ov5640,v4l2_fwnode,videobuf2_common,stm32_dcmi,videobuf2_v4l2
mc 36864 5 ov5640,videobuf2_common,videodev,stm32_dcmi,videobuf2_v4l2
stm32_cec 16384 0
sch_fq_codel 20480 3
ipv6 442368 40
nf_defrag_ipv6 20480 1 ipv6