1)实验平台:正点原子Linux
开发板
2)
摘自《正点原子I.MX6U嵌入式Linux驱动开发指南
》
关注官方微信号公众号,获取更多资料:正点原子
第五十七章Linux MISC驱动实验
misc的意思是混合、杂项的,因此MISC驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用MISC驱动。MISC驱动其实就是最简单的字符设备驱动,通常嵌套在platform总线驱动中,实现复杂的驱动,本章我们就来学习一下MISC驱动的编写。
57.1 MISC设备驱动简介所有的MISC设备驱动的主设备号都为10,不同的设备使用不同的从设备号。随着Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主主设备号,MISC设备驱动就用于解决此问题。MISC设备会自动创建cdev,不需要像我们以前那样手动创建,因此采用MISC设备驱动可以简化字符设备驱动的编写。我们需要向Linux注册一个miscdevice设备,miscdevice是一个结构体,定义在文件
示例代码57.1.1 miscdevice结构体代码
57struct miscdevice
{
58 int minor
; /* 子设备号 */
59 constchar
*name
; /* 设备名字 */
60 conststruct file_opera
tions
*fops
; /* 设备操作集 */
61 struct list_head list
;
62 struct device
*parent
;
63 struct device
*this_device
;
64 conststruct attribute_group
**groups
;
65 constchar
*nodename
;
66 umode_t mode
;
67
};
定义一个MISC设备(miscdevice类型)以后我们需要设置minor、name和fops这三个成员变量。minor表示子设备号,MISC设备的主设备号为10,这个是固定的,需要用户指定子设备号,Linux系统已经预定义了一些MISC设备的子设备号,这些预定义的子设备号定义在include/linux/miscdevice.h文件中,如下所示:
示例代码57.1.2 预定义的MISC设备子设备号
13 #define PSMOUSE_MINOR 1
14 #define MS_BUSMOUSE_MINOR 2/* unused */
15 #define ATIXL_BUSMOUSE_MINOR 3/* unused */
16/*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
17 #define ATARIMOUSE_MINOR 5 /* unused */
18 #define SUN_MOUSE_MINOR 6/* unused */
......
52 #define MISC_DYNAMIC_MINOR 255
我们在使用的时候可以从这些预定义的子设备号中挑选一个,当然也可以自己定义,只要这个子设备号没有被其他设备使用接口。
name就是此MISC设备名字,当此设备注册成功以后就会在/dev目录下生成一个名为name的设备文件。fops就是字符设备的操作集合,MISC设备驱动最终是需要使用用户提供的fops操作集合。
当设置好miscdevice以后就需要使用misc_register函数向系统中注册一个MISC设备,此函数原型如下:
int misc_register(struct miscdevice * misc)
函数参数和返回值含义如下:
misc:要注册的MISC设备。
返回值:负数,失败;0,成功。
以前我们需要自己调用一堆的函数去创建设备,比如在以前的字符设备驱动中我们会使用如下几个函数完成设备创建过程:
示例代码57.1.3 传统的创建设备过程
1 alloc_chrdev_region
(); /* 申请设备号 */
2 cdev_init
(); /* 初始化cdev */
3 cdev_add
(); /* 添加cdev */
4 class_create
(); /* 创建类 */
5 device_create
(); /* 创建设备 */
现在我们可以直接使用misc_register一个函数来完成示例代码57.1.2中的这些步骤。当我们卸载设备驱动模块的时候需要调用misc_deregister函数来注销掉MISC设备,函数原型如下:
int misc_deregister(struct miscdevice *misc)
函数参数和返回值含义如下:
misc:要注销的MISC设备。
返回值:负数,失败;0,成功。
以前注销设备驱动的时候,我们需要调用一堆的函数去删除此前创建的cdev、设备等等内容,如下所示:
示例代码57.1.3 传统的删除设备的过程
1 cdev_del
(); /* 删除cdev */
2 unregister_chrdev_region
(); /* 注销设备号 */
3 device_destroy
(); /* 删除设备 */
4 class_destroy
(); /* 删除类 */
现在我们只需要一个misc_deregister函数即可完成示例代码57.1.3中的这些工作。关于MISC设备驱动就讲解到这里,接下来我们就使用platform加MISC驱动框架来编写beep蜂鸣器驱动。
57.2 硬件原理图分析本章实验我们只使用到IMX6U-ALPHA开发板上的BEEP蜂鸣器,因此实验硬件原理图参考14.3小节即可。
57.3 实验程序编写本实验对应的例程路径为:开发板光盘->2、Linux驱动例程->17_misc。
本章实验我们采用platform加misc的方式编写beep驱动,这也是实际的Linux驱动中很常用的方法。采用platform来实现总线、设备和驱动,misc主要负责完成字符设备的创建。
57.3.1 修改设备树本章实验我们需要用到蜂鸣器,因此需要在imx6ull-alientek-emmc.dts文件中创建蜂鸣器设备节点,这里我们直接使用46.3.1小节创建的beep这个设备节点即可。
57.3.2 beep驱动程序编写新建名为"19_miscbeep"的文件夹,然后在19_miscbeep文件夹里面创建vscode工程,工作区命名为"miscbeep。新建名为miscbeep.c的驱动文件,在miscbeep.c中输入如下所示内容:
示例代码57.3.2.1 miscbeep.c文件代码段
1 #include
<linux
/types
.h
>
2 #include
<linux
/kernel
.h
>
3 #include
<linux
/delay
.h
>
4 #include
<linux
/ide
.h
>
5 #include
<linux
/init
.h
>
6 #include
<linux
/module
.h
>
7 #include
<linux
/errno
.h
>
8 #include
<linux
/gpio
.h
>
9 #include
<linux
/cdev
.h
>
10 #include
<linux
/device
.h
>
11 #include
<linux
/of
.h
>
12 #include
<linux
/of_address
.h
>
13 #include
<linux
/of_gpio
.h
>
14 #include
<linux
/platform_device
.h
>
15 #include
<linux
/miscdevice
.h
>
16 #include
<asm
/mach
/map
.h
>
17 #include
<asm
/uaccess
.h
>
18 #include
<asm
/io
.h
>
19/***************************************************************
20 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
21文件名 : miscbeep.c
22作者 : 左忠凯
23版本 : V1.0
24描述 : 采用MISC的蜂鸣器驱动程序。
25其他 : 无
26
william hill官网
: www.openedv.com
27日志 : 初版V1.0 2019/8/20 左忠凯创建
28 ***************************************************************/
29 #define MISCBEEP_NAME "miscbeep" /* 名字 */
30 #define MISCBEEP_MINOR 144 /* 子设备号 */
31 #define BEEPOFF 0 /* 关蜂鸣器 */
32 #define BEEPON 1 /* 开蜂鸣器 */
33
34/* miscbeep设备结构体 */
35struct miscbeep_dev
{
36 dev_t devid
; /* 设备号 */
37struct cdev cdev
; /* cdev */
38struct class
*class
; /* 类 */
39struct device
*device
; /* 设备 */
40struct device_node
*nd
; /* 设备节点 */
41int beep_gpio
; /* beep所使用的GPIO编号 */
42
};
43
44struct miscbeep_dev miscbeep
; /* beep设备 */
45
46/*
47 * @description : 打开设备
48 *
@param – inode : 传递给驱动的inode
49 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
50 * 一般在open的时候将private_data指向设备结构体。
51 *
@Return : 0 成功;其他失败
52 */
53staticint miscbeep_open
(struct inode
*inode
,struct file
*filp
)
54
{
55 filp
->private_data
=&miscbeep
;/* 设置私有数据 */
56
return0
;
57
}
58
59/*
60 * @description : 向设备写数据
61 * @param - filp : 设备文件,表示打开的文件描述符
62 * @param - buf : 要写给设备写入的数据
63 * @param - cnt : 要写入的数据长度
64 * @param - offt : 相对于文件首地址的偏移
65 * @return : 写入的字节数,如果为负值,表示写入失败
66 */
67static ssize_t miscbeep_write
(struct file
*filp
,
constchar __user
*buf
,size_t cnt
, loff_t
*offt
)
68
{
69int retvalue
;
70unsignedchar databuf
[1
];
71unsignedchar beepstat
;
72struct miscbeep_dev
*dev
= filp
->private_data
;
73
74 retvalue
= copy_from_user
(databuf
, buf
, cnt
);
75
if(retvalue
<0
){
76 printk
("kernel write failed!rn"
);
77
return-EFAULT
;
78
}
79
80 beepstat
= databuf
[0
]; /* 获取状态值 */
81
if(beepstat
== BEEPON
){
82 gpio_set_value
(dev
->beep_gpio
,0
); /* 打开蜂鸣器 */
83
}elseif(beepstat
== BEEPOFF
){
84 gpio_set_value
(dev
->beep_gpio
,1
); /* 关闭蜂鸣器 */
85
}
86
return0
;
87
}
88
89/* 设备操作函数 */
90staticstruct file_operations miscbeep_fops
={
91
.owner
= THIS_MODULE
,
92
.open
= miscbeep_open
,
93
.write
= miscbeep_write
,
94
};
95
96/* MISC设备结构体 */
97staticstruct miscdevice beep_miscdev
={
98
.minor
= MISCBEEP_MINOR
,
99
.name
= MISCBEEP_NAME
,
100
.fops
=&miscbeep_fops
,
101
};
102
103/*
104 * @description : flatform驱动的probe函数,当驱动与
105 * 设备匹配以后此函数就会执行
106 * @param - dev : platform设备
107 * @return : 0,成功;其他负值,失败
108 */
109staticint miscbeep_probe
(struct platform_device
*dev
)
110
{
111int ret
=0
;
112
113 printk
("beep driver and device was matched!rn"
);
114/* 设置BEEP所使用的GPIO */
115/* 1、获取设备节点:beep */
116 miscbeep
.nd
= of_find_node_by_path
("/beep"
);
117
if(miscbeep
.nd
==NULL){
118 printk
("beep node not find!rn"
);
119
return-EINVAL
;
120
}
121
122/* 2、获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
123 miscbeep
.beep_gpio
= of_get_named_gpio
(miscbeep
.nd
,"beep-gpio"
,
0
);
124
if(miscbeep
.beep_gpio
<0
){
125 printk
("can't get beep-gpio"
);
126
return-EINVAL
;
127
}
128
129/* 3、设置GPIO5_IO01为输出,并且输出高电平,默认关闭BEEP */
130 ret
= gpio_direction_output
(miscbeep
.beep_gpio
,1
);
131
if(ret
<0
){
132 printk
("can't set gpio!rn"
);
133
}
134
135/* 一般情况下会注册对应的字符设备,但是这里我们使用MISC设备
136 * 所以我们不需要自己注册字符设备驱动,只需要注册misc设备驱动即可
137 */
138 ret
= misc_register
(&beep_miscdev
);
139
if(ret
<0
){
140 printk
("misc device register failed!rn"
);
141
return-EFAULT
;
142
}
143
144
return0
;
145
}
146
147/*
148 * @description : remove函数,移除platform驱动的时候此函数会执行
149 * @param - dev : platform设备
150 * @return : 0,成功;其他负值,失败
151 */
152staticint miscbeep_remove
(struct platform_device
*dev
)
153
{
154/* 注销设备的时候关闭LED灯 */
155 gpio_set_value
(miscbeep
.beep_gpio
,1
);
156
157/* 注销misc设备驱动 */
158 misc_deregister
(&beep_miscdev
);
159
return0
;
160
}
161
162/* 匹配列表 */
163staticconststruct of_device_id beep_of_match
[]={
164
{.compatible
="atkalpha-beep"
},
165
{/* Sentinel */
}
166
};
167
168/* platform驱动结构体 */
169staticstruct platform_driver beep_driver
={
170
.driver
={
171
.name
="imx6ul-beep"
, /* 驱动名字 */
172
.of_match_table
= beep_of_match
, /* 设备树匹配表 */
173
},
174
.probe
= miscbeep_probe
,
175
.remove
= miscbeep_remove
,
176
};
177
178/*
179 * @description : 驱动入口函数
180 * @param : 无
181 * @return : 无
182 */
183staticint __init miscbeep_init
(void
)
184
{
185
return platform_driver_register
(&beep_driver
);
186
}
187
188/*
189 * @description : 驱动出口函数
190 * @param : 无
191 * @return : 无
192 */
193staticvoid __exit miscbeep_exit
(void
)
194
{
195 platform_driver_unregister
(&beep_driver
);
196
}
197
198 module_init
(miscbeep_init
);
199 module_exit
(miscbeep_exit
);
200 MODULE_LICENSE
("GPL"
);
201 MODULE_AUTHOR
("zuozhongkai"
);
第29~94行,标准的字符设备驱动。
第97~101行,MISC设备beep_miscdev,第98行设置子设备号为144,第99行设置设备名字为"miscbeep",这样当系统启动以后就会在/dev/目录下存在一个名为"miscbeep"的设备文件。第100行,设置MISC设备的操作函数集合,为file_operations类型。
第109~145行,platform框架的probe函数,当驱动与设备匹配以后此函数就会执行,首先在此函数中初始化BEEP所使用的IO。最后在138行通过misc_register函数向Linux内核注册MISC设备,也就是前面定义的beep_miscdev。
第152~160行,platform框架的remove函数,在此函数中调用misc_deregister函数来注销MISC设备。
第163~196,标准的platform驱动。
57.3.3 编写测试APP新建miscbeepApp.c文件,然后在里面输入如下所示内容:
示例代码57.3.2.2 miscbeepApp.c文件代码段
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8/***************************************************************
9 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
10文件名 : miscbeepApp.c
11作者 : 左忠凯
12版本 : V1.0
13描述 : MISC驱动框架下的beep测试APP。
14其他 : 无
15使用方法 :./miscbeepApp /dev/miscbeep 0 关闭蜂鸣器
16 ./misdcbeepApp /dev/miscbeep 1 打开蜂鸣器
17william hill官网
: www.openedv.com
18日志 : 初版V1.0 2019/8/20 左忠凯创建
19 ***************************************************************/
20 #define BEEPOFF 0
21 #define BEEPON 1
22
23/*
24 * @description : main主程序
25 * @param - argc : argv数组元素个数
26 * @param - argv : 具体参数
27 * @return : 0 成功;其他失败
28 */
29int main
(int argc
,char
*argv
[])
30
{
31 int fd
, retvalue
;
32 char
*filename
;
33 unsignedchar databuf
[1
];
34
35
if(argc
!=3
){
36 printf
("Error Usage!rn"
);
37
return-1
;
38
}
39
40 filename
= argv
[1
];
41 fd
= open
(filename
, O_RDWR
);/* 打开beep驱动 */
42
if(fd
<0
){
43 printf
("file %s open failed!rn"
, argv
[1
]);
44
return-1
;
45
}
46
47 databuf
[0
]= atoi
(argv
[2
]); /* 要执行的操作:打开或关闭 */
48 retvalue
= write
(fd
, databuf
,sizeof(databuf
));
49
if(retvalue
<0
){
50 printf
("BEEP Control Failed!rn"
);
51 close
(fd
);
52
return-1
;
53
}
54
55 retvalue
= close
(fd
); /* 关闭文件 */
56
if(retvalue
<0
){
57 printf
("file %s close failed!rn"
, argv
[1
]);
58
return-1
;
59
}
60
return0
;
61
}
miscbeepApp.c文件内容和其他例程的测试APP基本一致,很简单,这里就不讲解了。
57.4 运行测试57.4.1 编译驱动程序和测试APP1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为"leddevice.o leddriver.o",Makefile内容如下所示:
示例代码57.4.1.1 Makefile文件
1 KERNELDIR
:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := miscbeep.o
......
11 clean
:
12$(MAKE) -C $(KERNELDIR) M
=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为"miscbeep.o"。
输入如下命令编译出驱动模块文件:
make-j32
编译成功以后就会生成一个名为"miscbeep.ko"的驱动模块文件。
2、编译测试APP
输入如下命令编译测试miscbeepApp.c这个测试程序:
arm-linux-gnueabihf-gccmiscbeepApp.c -o miscbeepApp
编译成功以后就会生成miscbeepApp这个应用程序。
57.4.2 运行测试将上一小节编译出来miscbeep.ko和miscbeepApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载miscbeep.ko这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe miscbeep.ko //加载设备模块
当驱动模块加载成功以后我们可以在/sys/class/misc这个目录下看到一个名为"miscbeep"的子目录,如图57.4.2.1所示:
图57.4.2.1 mi***eep子目录
所有的misc设备都属于同一个类,/sys/class/misc目录下就是misc这个类的所有设备,每个设备对应一个子目录。
驱动与设备匹配成功以后就会生成/dev/miscbeep这个设备驱动文件,输入如下命令查看这个文件的主次设备号:
ls /dev/miscbeep -l
结果如图57.4.2.2所示:
图57.4.2.2 /dev/miscbeep设备文件
从图57.4.2.2可以看出,/dev/miscbeep这个设备的主设备号为10,次设备号为144,和我们驱动程序里面设置的一致。
输入如下命令打开BEEP:
./miscbeepApp /dev/miscbeep 1 //打开BEEP
在输入如下命令关闭LED灯:
./miscbeepApp /dev/miscbeep 0//关闭BEEP
观察一下BEEP能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
rmmod miscbeep.ko