赞
踩
1)实验平台:正点原子ATK-DLRK3568开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=731866264428
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban
misc的意思是混合、杂项的,因此MISC驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用MISC驱动。MISC驱动其实就是最简单的字符设备驱动,通常嵌套在platform总线驱动中,实现复杂的驱动,本章我们就来学习一下MISC驱动的编写。
21.1 MISC设备驱动简介
所有的MISC设备驱动的主设备号都为10,不同的设备使用不同的从设备号。随着Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号,MISC设备驱动就用于解决此问题。MISC设备会自动创建cdev,不需要像我们以前那样手动创建,因此采用MISC设备驱动可以简化字符设备驱动的编写。我们需要向Linux注册一个miscdevice设备,miscdevice是一个结构体,定义在文件include/linux/miscdevice.h中,内容如下:
示例代码21.1.1 miscdevice结构体代码
1 struct miscdevice {
2 int minor; /* 子设备号 */
3 const char *name; /* 设备名字 */
4 const struct file_operations *fops; /* 设备操作集 */
5 struct list_head list;
6 struct device *parent;
7 struct device *this_device;
8 const struct attribute_group **groups;
9 const char *nodename;
10 umode_t mode;
11 };
定义一个MISC设备(miscdevice类型)以后我们需要设置minor、name和fops这三个成员变量。minor表示子设备号,MISC设备的主设备号为10,这个是固定的,需要用户指定子设备号,Linux系统已经预定义了一些MISC设备的子设备号,这些预定义的子设备号定义在include/linux/miscdevice.h文件中,如下所示:
示例代码21.1.2 预定义的MISC设备子设备号
1 #define PSMOUSE_MINOR 1
2 #define MS_BUSMOUSE_MINOR 2 /* unused */
3 #define ATIXL_BUSMOUSE_MINOR 3 /* unused */
4 /*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
5 #define ATARIMOUSE_MINOR 5 /* unused */
6 #define SUN_MOUSE_MINOR 6 /* unused */
......
47 #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,成功。
以前我们需要自己调用一堆的函数去创建设备,比如在以前的字符设备驱动中我们会使用如下几个函数完成设备创建过程:
示例代码21.1.3 传统的创建设备过程
1 alloc_chrdev_region(); /* 申请设备号 */
2 cdev_init(); /* 初始化cdev */
3 cdev_add(); /* 添加cdev */
4 class_create(); /* 创建类 */
5 device_create(); /* 创建设备 */
现在我们可以直接使用misc_register一个函数来完成示例代码21.1.3中的这些步骤。当我们卸载设备驱动模块的时候需要调用misc_deregister函数来注销掉MISC设备,函数原型如下:
int misc_deregister(struct miscdevice *misc)
函数参数和返回值含义如下:
misc:要注销的MISC设备。
返回值:负数,失败;0,成功。
以前注销设备驱动的时候,我们需要调用一堆的函数去删除此前创建的cdev、设备等等内容,如下所示:
示例代码21.1.4 传统的删除设备的过程
1 cdev_del(); /* 删除cdev */
2 unregister_chrdev_region(); /* 注销设备号 */
3 device_destroy(); /* 删除设备 */
4 class_destroy(); /* 删除类 */
现在我们只需要一个misc_deregister函数即可完成示例代码21.1.4中的这些工作。关于MISC设备驱动就讲解到这里,接下来我们就使用platform加MISC驱动框架来编写LED灯蜂鸣器驱动。
21.2 硬件原理图分析
本章实验我们只使用到正点原子的ATK-DLRK3568开发板上的LED,也就是绿色的LED灯。
21.3 实验程序编写
本实验对应的例程路径为:开发板光盘01、程序源码Linux驱动例程18_miscled。
本章实验我们采用platform加misc的方式编写led驱动,这也是实际的Linux驱动中很常用的方法。采用platform来实现总线、设备和驱动,misc主要负责完成字符设备的创建。
21.3.1 修改设备树
本章实验我们需要用到LED,因此需要在rk3568-evb.dtsi文件中LED设备节点。
1、关闭LED1默认功能
在上一章实验中,我们将ATK-DLRK3568开发板上的LED用Linux内核自带的LED驱动,并将其设置为“work”。此我们首先需要关闭上一章实验设置的这个LED功能,只需要将“work”这个节点中的status属性改为disabled即可,如图21.3.1.1所示:
图21.3.1.1 关闭LED1的默认功能
2、添加LED1的misc设备节点
在rk3568-atk-evb1-ddr4-v10.dtsi文件的“/”节点下中创建一个名为“miscled”的子节点,miscled子节点内容如下:
示例代码21.3.1.1 LED1杂项设备节点
1 miscled {
2 compatible = “alientek,miscled”;
3 miscled-gpio = <&gpio0 RK_PC0 GPIO_ACTIVE_HIGH>;
4 status = “okay”;
5 };
设备树修改完成,重新编译内核并烧写。
21.3.2 misc驱动程序编写
新建名为“18_miscled”的文件夹,然后在18_miscled文件夹里面创建vscode工程,工作区命名为“miscled。新建名为miscled.c的驱动文件,在miscled.c中输入如下所示内容:
示例代码21.3.2.1 miscled.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 #define MISCLED_NAME "miscled" /* 名字 */ 21 #define MISCLED_MINOR 144 /* 子设备号 */ 22 #define LEDOFF 0 /* 关LED */ 23 #define LEDON 1 /* 开LED */ 24 25 /* miscled设备结构体 */ 26 struct miscled_dev{ 27 dev_t devid; /* 设备号 */ 28 struct cdev cdev; /* cdev */ 29 struct class *class; /* 类 */ 30 struct device *device; /* 设备 */ 31 int led_gpio; /* led所使用的GPIO编号 */ 32 }; 33 34 struct miscled_dev miscled; /* led设备 */ 35 36 /* 37 * @description : beep相关初始化操作 38 * @param – pdev : platform_device指针,也就是platform设备指针 39 * @return : 成功返回0,失败返回负数 40 */ 41 static int led_gpio_init(struct device_node *nd) 42 { 43 int ret; 44 45 /* 从设备树中获取GPIO */ 46 miscled.led_gpio = of_get_named_gpio(nd, "miscled-gpio", 0); 47 if(!gpio_is_valid(miscled.led_gpio)) { 48 printk("miscled:Failed to get led-gpio\n"); 49 return -EINVAL; 50 } 51 52 /* 申请使用GPIO */ 53 ret = gpio_request(miscled.led_gpio, "led"); 54 if(ret) { 55 printk("led: Failed to request miscled-gpio\n"); 56 return ret; 57 } 58 59 /* 将GPIO设置为输出模式并设置GPIO初始化电平状态 */ 60 gpio_direction_output(miscled.led_gpio, 0); 61 62 return 0; 63 } 64 65 /* 66 * @description : 打开设备 67 * @param – inode : 传递给驱动的inode 68 * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量 69 * 一般在open的时候将private_data指向设备结构体。 70 * @return : 0 成功;其他 失败 71 */ 72 static int miscled_open(struct inode *inode, struct file *filp) 73 { 74 return 0; 75 } 76 77 /* 78 * @description : 向设备写数据 79 * @param - filp : 设备文件,表示打开的文件描述符 80 * @param - buf : 要写给设备写入的数据 81 * @param - cnt : 要写入的数据长度 82 * @param – offt : 相对于文件首地址的偏移 83 * @return : 写入的字节数,如果为负值,表示写入失败 84 */ 85 static ssize_t miscled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 86 { 87 int retvalue; 88 unsigned char databuf[1]; 89 unsigned char ledstat; 90 91 retvalue = copy_from_user(databuf, buf, cnt); 92 if(retvalue < 0) { 93 printk("kernel write failed!\r\n"); 94 return -EFAULT; 95 } 96 97 ledstat = databuf[0]; /* 获取状态值 */ 98 if(ledstat == LEDON) { 99 gpio_set_value(miscled.led_gpio, 1); /* 打开LED */ 100 } else if(ledstat == LEDOFF) { 101 gpio_set_value(miscled.led_gpio, 0); /* 关闭LED */ 102 } 103 return 0; 104 } 105 106 /* 设备操作函数 */ 107 static struct file_operations miscled_fops = { 108 .owner = THIS_MODULE, 109 .open = miscled_open, 110 .write = miscled_write, 111 }; 112 113 /* MISC设备结构体 */ 114 static struct miscdevice led_miscdev = { 115 .minor = MISCLED_MINOR, 116 .name = MISCLED_NAME, 117 .fops = &miscled_fops, 118 }; 119 120 /* 121 * @description : flatform驱动的probe函数,当驱动与 122 * 设备匹配以后此函数就会执行 123 * @param – dev : platform设备 124 * @return : 0,成功;其他负值,失败 125 */ 126 static int miscled_probe(struct platform_device *pdev) 127 { 128 int ret = 0; 129 130 printk("led driver and device was matched!\r\n"); 131 132 /* 初始化BEEP */ 133 ret = led_gpio_init(pdev->dev.of_node); 134 if(ret < 0) 135 return ret; 136 137 /* 一般情况下会注册对应的字符设备,但是这里我们使用MISC设备 138 * 所以我们不需要自己注册字符设备驱动,只需要注册misc设备驱动即可 139 */ 140 ret = misc_register(&led_miscdev); 141 if(ret < 0){ 142 printk("misc device register failed!\r\n"); 143 goto free_gpio; 144 } 145 146 return 0; 147 148 free_gpio: 149 gpio_free(miscled.led_gpio); 150 return -EINVAL; 151 } 152 153 /* 154 * @description : platform驱动的remove函数 155 * @param - dev : platform设备 156 * @return : 0,成功;其他负值,失败 157 */ 158 static int miscled_remove(struct platform_device *dev) 159 { 160 /* 注销设备的时候关闭LED灯 */ 161 gpio_set_value(miscled.led_gpio, 1); 162 163 /* 释放LED */ 164 gpio_free(miscled.led_gpio); 165 166 /* 注销misc设备 */ 167 misc_deregister(&led_miscdev); 168 return 0; 169 } 170 171 /* 匹配列表 */ 172 static const struct of_device_id led_of_match[] = { 173 { .compatible = "alientek,miscled" }, 174 { /* Sentinel */ } 175 }; 176 177 /* platform驱动结构体 */ 178 static struct platform_driver led_driver = { 179 .driver = { 180 .name = " alientek,miscled", /* 驱动名字,用于和设备匹配 */ 181 .of_match_table = led_of_match, /* 设备树匹配表 */ 182 }, 183 .probe = miscled_probe, 184 .remove = miscled_remove, 185 }; 186 187 /* 188 * @description : 驱动出口函数 189 * @param : 无 190 * @return : 无 191 */ 192 static int __init miscled_init(void) 193 { 194 return platform_driver_register(&led_driver); 195 } 196 197 /* 198 * @description : 驱动出口函数 199 * @param : 无 200 * @return : 无 201 */ 202 static void __exit miscled_exit(void) 203 { 204 platform_driver_unregister(&led_driver); 205 } 206 207 module_init(miscled_init); 208 module_exit(miscled_exit); 209 MODULE_LICENSE("GPL"); 210 MODULE_AUTHOR("ALIENTEK"); 211 MODULE_INFO(intree, "Y");
第72~111行,标准的字符设备驱动。
第114~118行,MISC设备led_miscdev,第115行设置子设备号为144,第116行设置设备名字为“miscled”,这样当系统启动以后就会在/dev/目录下存在一个名为“miscled”的设备文件。第117行,设置MISC设备的操作函数集合,为file_operations类型。
第126~151行,platform框架的probe函数,当驱动与设备匹配以后此函数就会执行,首先在此函数中初始化LED1所使用的IO。最后在140行通过misc_register函数向Linux内核注册MISC设备,也就是前面定义的led_miscdev。
第158~169行,platform框架的remove函数,在此函数中调用misc_deregister函数来注销MISC设备。
第192~205,标准的platform驱动。
21.3.3 编写测试APP
新建miscledApp.c文件,然后在里面输入如下所示内容:
示例代码21.3.2.2 miscledApp.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 /* 10 * @description : main主程序 11 * @param - argc : argv数组元素个数 12 * @param - argv : 具体参数 13 * @return : 0 成功;其他 失败 14 */ 15 int main(int argc, char *argv[]) 16 { 17 int fd, retvalue; 18 char *filename; 19 unsigned char databuf[1]; 20 21 if(argc != 3){ 22 printf("Error Usage!\r\n"); 23 return -1; 24 } 25 26 filename = argv[1]; 27 fd = open(filename, O_RDWR); /* 打开led驱动 */ 28 if(fd < 0){ 29 printf("file %s open failed!\r\n", argv[1]); 30 return -1; 31 } 32 33 databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */ 34 retvalue = write(fd, databuf, sizeof(databuf)); 35 if(retvalue < 0){ 36 printf("BEEP Control Failed!\r\n"); 37 close(fd); 38 return -1; 39 } 40 41 retvalue = close(fd); /* 关闭文件 */ 42 if(retvalue < 0){ 43 printf("file %s close failed!\r\n", argv[1]); 44 return -1; 45 } 46 return 0; 47 }
miscledApp.c文件内容和其他例程的测试APP基本一致,很简单,这里就不讲解了。
21.4 运行测试
21.4.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第五章实验基本一样,只是将obj-m变量的值改为“miscled.o”,Makefile内容如下所示:
示例代码21.4.1.1 Makefile文件
1 KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
......
4 obj-m := miscled.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,设置obj-m变量的值为“miscled.o”。
输入如下命令编译出驱动模块文件:
make ARCH=arm64 //ARCH=arm64必须指定,否则编译会失败
编译成功以后就会生成一个名为“miscled.ko”的驱动模块文件。
2、编译测试APP
输入如下命令编译测试miscledApp.c这个测试程序:
/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc miscledApp.c -o miscledApp
编译成功以后就会生成miscledApp这个应用程序。
21.4.2 运行测试
在Ubuntu中将上一小节编译出来的miscled.ko和miscledApp通过adb命令发送到开发板的/lib/modules/4.19.232目录下,命令如下:
adb push miscled.ko miscledApp /lib/modules/4.19.232
发送成功以后进入到开发板目录lib/modules/4.19.232中,输入如下命令加载miscled.ko这个驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe miscled //加载驱动模块
当驱动模块加载成功以后我们可以在/sys/class/misc这个目录下看到一个名为“miscled”的子目录,如图21.4.2.1所示:
图21.4.2.1 miscled子目录
所有的misc设备都属于同一个类,/sys/class/misc目录下就是misc这个类的所有设备,每个设备对应一个子目录。
驱动与设备匹配成功以后就会生成/dev/miscled这个设备驱动文件,输入如下命令查看这个文件的主次设备号:
ls /dev/miscled -l
结果如图21.4.2.2所示:
图21.4.2.2 /dev/miscled设备文件
从图21.4.2.2可以看出,/dev/miscled这个设备的主设备号为10,次设备号为144,和我们驱动程序里面设置的一致。
输入如下命令打开LED:
./miscledApp /dev/miscled 1 //打开LED
在输入如下命令关闭LED灯:
./miscledApp /dev/miscled 0 //关闭LED
观察一下LED能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
rmmod miscled.ko
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。