超级版主
- 积分
- 4671
- 金钱
- 4671
- 注册时间
- 2019-5-8
- 在线时间
- 1224 小时
|
本帖最后由 正点原子运营 于 2022-3-21 15:26 编辑
1)实验平台:正点原子领航者V2FPGA开发板
2) 章节摘自【正点原子】《领航者ZYNQ之FPGA开发指南 V2.0》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:[url=http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html]http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
第三十八章 设备树下的platform驱动编写
上一章我们详细的讲解了Linux下的驱动分离与分层,以及总线、设备和驱动这样的驱动框架。基于总线、设备和驱动这样的驱动框架,Linux内核提出来platform这个虚拟总线,相应的也有platform设备和platform驱动。上一章我们讲解了传统的、未采用设备树的platform设备和驱动编写方法。最新的Linux内核已经支持了设备树,因此在设备树下如何编写platform驱动就显得尤为重要,本章我们就来学习一下如何在设备树下编写platform驱动。
1.1设备树下的platform驱动简介
platform驱动框架分为总线、设备和驱动,其中总线不需要我们这些驱动程序员去管理,这个是Linux内核提供的,我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。在没有设备树的Linux内核下,我们需要分别编写并注册platform_device和platform_driver,分别代表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此platform_device就不需要我们去编写了,我们只需要实现platform_driver即可,当内核在解析设备树的时候会自动帮我们创建一个platform_device对象;在编写基于设备树的platform驱动的时候我们需要注意一下几点:
1、在设备树中创建设备节点
毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好compatible属性的值,因为platform总线需要通过设备节点的compatible属性值来匹配驱动!这点要切记。比如,使用如下所示的设备节点来描述我们本章实验要用到的LED这个设备:
- 示例代码 38.1.1 led设备节点
- 37 led {
- 38 compatible = "alientek,led";
- 39 status = "okay";
- 40 default-state = "on";
- 41 led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
- 42 };
复制代码
示例代码 38.1.1中的led节点其实就是27.3.1小节中创建的led设备节点,所以我们不用修改设备树了,直接使用前面章节创建的led节点即可!注意第38行的compatible属性值为“alientek,led”,因此一会在编写platform驱动的时候of_match_table属性表中要有“alientek,led”。
2、编写platform驱动的时候要注意兼容属性
上一章已经详细的讲解过了,在使用设备树的时候platform驱动会通过of_match_table来保存兼容性值,也就是表明此驱动兼容哪些设备。所以of_match_table将会尤为重要,比如本例程的platform驱动中platform_driver就可以按照如下所示设置:
- 示例代码 38.1.2 of_match_table匹配表的设置
- 1 static const struct of_device_id leds_of_match[] = {
- 2 { .compatible = "alientek,led" }, /* 兼容属性 */
- 3 { /* Sentinel */ }
- 4 };
- 5
- 6 MODULE_DEVICE_TABLE(of, leds_of_match);
- 7
- 8 static struct platform_driver leds_platform_driver = {
- 9 .driver = {
- 10 .name = "zynq-led",
- 11 .of_match_table = leds_of_match,
- 12 },
- 13 .probe = leds_probe,
- 14 .remove = leds_remove,
- 15 };
复制代码
第1~4行,of_device_id表,也就是驱动的兼容表,是一个数组,每个数组元素为of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备,那就是示例代码 38.1.1中的led这个设备。第2行的compatible值为“alientek,led”,驱动中的compatible属性和设备中的compatible属性相匹配,因此驱动中对应的probe函数就会执行。注意第3行是一个空元素,在编写of_device_id的时候最后一个元素一定要为空!
第6行,通过MODULE_DEVICE_TABLE声明一下leds_of_match这个设备匹配表。注意这个宏一般用于热拔插设备动态地进行驱动的加载与卸载,例如USB类设备。
第11行,将leds_of_match匹配表绑定到platform驱动结构体leds_platform_driver中,至此我们就设置好了platform驱动的匹配表了。
3、编写platform驱动
基于设备树的platform驱动和上一章无设备树的platform驱动基本一样,都是当驱动和设备匹配成功以后就会执行probe函数。我们需要在probe函数里面执行字符设备驱动那一套,当注销驱动模块的时候remove函数就会执行,都是大同小异的。
1.2硬件原理图分析
本章实验我们只使用到开发板上的PS_LED0灯,因此实验硬件原理图参考22.3小节即可。
1.3实验程序编写
本实验对应的例程路径为:领航者ZYNQ开发板光盘资料(A盘)\4_SourceCode\3_Embedded_Linux\Linux驱动例程\18_dtsplatform。
本章实验我们编写基于设备树的platform驱动,所以需要在设备树中添加设备节点,然后我们只需要编写platform驱动即可。
1.3.1修改设备树文件
首先修改设备树文件,加上我们需要的设备信息,本章我们就使用到一个LED灯,因此可以直接使用27.3.1小节编写的led节点即可,不需要再重复添加。
1.3.2platform驱动程序编写
设备已经准备好了,接下来就要编写相应的platform驱动了,在drivers目录下新建名为“18_dtsplatform”的文件夹作为本实验目录,在“18_dtsplatform”目录下新建名为leddriver.c的驱动文件,在leddriver.c中输入如下所示内容:
- 示例代码 38.3.1 leddriver.c文件代码段
- 1 /***************************************************************
- 2 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
- 3 文件名 : leddriver.c
- 4 作者 : 邓涛
- 5 版本 : V1.0
- 6 描述 : platform总线编程示例之platform驱动模块
- 7 其他 : 无
- 8 论坛 : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
- 9 日志 : 初版V1.0 2019/1/30 邓涛创建
- 10 ***************************************************************/
- 11
- 12 #include <linux/module.h>
- 13 #include <linux/of_gpio.h>
- 14 #include <linux/cdev.h>
- 15 #include <linux/uaccess.h>
- 16 #include <linux/platform_device.h>
- 17
- 18 #define MYLED_CNT 1 /* 设备号个数 */
- 19 #define MYLED_NAME "myled" /* 名字 */
- 20
- 21 /* LED设备结构体 */
- 22 struct myled_dev {
- 23 dev_t devid; /* 设备号 */
- 24 struct cdev cdev; /* cdev结构体 */
- 25 struct class *class; /* 类 */
- 26 struct device *device; /* 设备 */
- 27 int led_gpio; /* GPIO号 */
- 28 };
- 29
- 30 static struct myled_dev myled; /* led设备 */
- 31
- 32 /*
- 33 * @description : 打开设备
- 34 * [url=home.php?mod=space&uid=271674]@param[/url] – inode : 传递给驱动的inode
- 35 * @param – filp : 设备文件,file结构体有个叫做private_data的成员变量
- 36 * 一般在open的时候将private_data指向设备结构体。
- 37 * @return : 0 成功;其他 失败
- 38 */
- 39 static int myled_open(struct inode *inode, struct file *filp)
- 40 {
- 41 return 0;
- 42 }
- 43
- 44 /*
- 45 * @description : 向设备写数据
- 46 * @param – filp : 设备文件,表示打开的文件描述符
- 47 * @param – buf : 要写给设备写入的数据
- 48 * @param – cnt : 要写入的数据长度
- 49 * @param – offt : 相对于文件首地址的偏移
- 50 * @return : 写入的字节数,如果为负值,表示写入失败
- 51 */
- 52 static ssize_t myled_write(struct file *filp, const char __user *buf,
- 53 size_t cnt, loff_t *offt)
- 54 {
- 55 int ret;
- 56 char kern_buf[1];
- 57
- 58 ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据
- 59 if(0 > ret) {
- 60 printk(KERN_ERR "myled: Failed to copy data from user buffer\r\n");
- 61 return -EFAULT;
- 62 }
- 63
- 64 if (0 == kern_buf[0])
- 65 gpio_set_value(myled.led_gpio, 0); // 如果传递过来的数据是0则关闭led
- 66 else if (1 == kern_buf[0])
- 67 gpio_set_value(myled.led_gpio, 1); // 如果传递过来的数据是1则点亮led
- 68
- 69 return 0;
- 70 }
- 71
- 72 static int myled_init(struct device_node *nd)
- 73 {
- 74 const char *str;
- 75 int val;
- 76 int ret;
- 77
- 78 /* 从设备树中获取GPIO */
- 79 myled.led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
- 80 if(!gpio_is_valid(myled.led_gpio)) {
- 81 printk(KERN_ERR "myled: Failed to get led-gpio\n");
- 82 return -EINVAL;
- 83 }
- 84
- 85 /* 申请使用GPIO */
- 86 ret = gpio_request(myled.led_gpio, "PS_LED0 Gpio");
- 87 if (ret) {
- 88 printk(KERN_ERR "myled: Failed to request led-gpio\n");
- 89 return ret;
- 90 }
- 91
- 92 /* 确定LED初始状态 */
- 93 ret = of_property_read_string(nd, "default-state", &str);
- 94 if(!ret) {
- 95 if (!strcmp(str, "on"))
- 96 val = 1;
- 97 else
- 98 val = 0;
- 99 } else
- 100 val = 0;
- 101
- 102 /* 将GPIO设置为输出模式并设置GPIO初始电平状态 */
- 103 gpio_direction_output(myled.led_gpio, val);
- 104
- 105 return 0;
- 106 }
- 107
- 108 /* LED设备操作函数 */
- 109 static struct file_operations myled_fops = {
- 110 .owner = THIS_MODULE,
- 111 .open = myled_open,
- 112 .write = myled_write,
- 113 };
- 114
- 115 /*
- 116 * @description : platform驱动的probe函数,当驱动与设备
- 117 * 匹配成功以后此函数就会执行
- 118 * @param – pdev : platform设备指针
- 119 * @return : 0,成功;其他负值,失败
- 120 */
- 121 static int myled_probe(struct platform_device *pdev)
- 122 {
- 123 int ret;
- 124
- 125 printk(KERN_INFO "myled: led driver and device has matched!\r\n");
- 126
- 127 /* led初始化 */
- 128 ret = myled_init(pdev->dev.of_node);
- 129 if (ret)
- 130 return ret;
- 131
- 132 /* 初始化cdev */
- 133 ret = alloc_chrdev_region(&myled.devid, 0, MYLED_CNT, MYLED_NAME);
- 134 if (ret)
- 135 goto out1;
- 136
- 137 myled.cdev.owner = THIS_MODULE;
- 138 cdev_init(&myled.cdev, &myled_fops);
- 139
- 140 /* 添加cdev */
- 141 ret = cdev_add(&myled.cdev, myled.devid, MYLED_CNT);
- 142 if (ret)
- 143 goto out2;
- 144
- 145 /* 创建类class */
- 146 myled.class = class_create(THIS_MODULE, MYLED_NAME);
- 147 if (IS_ERR(myled.class)) {
- 148 ret = PTR_ERR(myled.class);
- 149 goto out3;
- 150 }
- 151
- 152 /* 创建设备 */
- 153 myled.device = device_create(myled.class, &pdev->dev,
- 154 myled.devid, NULL, MYLED_NAME);
- 155 if (IS_ERR(myled.device)) {
- 156 ret = PTR_ERR(myled.device);
- 157 goto out4;
- 158 }
- 159
- 160 return 0;
- 161
- 162 out4:
- 163 class_destroy(myled.class);
- 164
- 165 out3:
- 166 cdev_del(&myled.cdev);
- 167
- 168 out2:
- 169 unregister_chrdev_region(myled.devid, MYLED_CNT);
- 170
- 171 out1:
- 172 gpio_free(myled.led_gpio);
- 173
- 174 return ret;
- 175 }
- 176
- 177 /*
- 178 * @description : platform驱动模块卸载时此函数会执行
- 179 * @param – dev : platform设备指针
- 180 * @return : 0,成功;其他负值,失败
- 181 */
- 182 static int myled_remove(struct platform_device *dev)
- 183 {
- 184 printk(KERN_INFO "myled: led platform driver remove!\r\n");
- 185
- 186 /* 注销设备 */
- 187 device_destroy(myled.class, myled.devid);
- 188
- 189 /* 注销类 */
- 190 class_destroy(myled.class);
- 191
- 192 /* 删除cdev */
- 193 cdev_del(&myled.cdev);
- 194
- 195 /* 注销设备号 */
- 196 unregister_chrdev_region(myled.devid, MYLED_CNT);
- 197
- 198 /* 删除地址映射 */
- 199 gpio_free(myled.led_gpio);
- 200
- 201 return 0;
- 202 }
- 203
- 204 /* 匹配列表 */
- 205 static const struct of_device_id led_of_match[] = {
- 206 { .compatible = "alientek,led" },
- 207 { /* Sentinel */ }
- 208 };
- 209
- 210 /* platform驱动结构体 */
- 211 static struct platform_driver myled_driver = {
- 212 .driver = {
- 213 .name = "zynq-led", // 驱动名字,用于和设备匹配
- 214 .of_match_table = led_of_match, // 设备树匹配表,用于和设备树中定义的设备匹配
- 215 },
- 216 .probe = myled_probe, // probe函数
- 217 .remove = myled_remove, // remove函数
- 218 };
- 219
- 220 /*
- 221 * @description : 模块入口函数
- 222 * @param : 无
- 223 * @return : 无
- 224 */
- 225 static int __init myled_driver_init(void)
- 226 {
- 227 return platform_driver_register(&myled_driver);
- 228 }
- 229
- 230 /*
- 231 * @description : 模块出口函数
- 232 * @param : 无
- 233 * @return : 无
- 234 */
- 235 static void __exit myled_driver_exit(void)
- 236 {
- 237 platform_driver_unregister(&myled_driver);
- 238 }
- 239
- 240 module_init(myled_driver_init);
- 241 module_exit(myled_driver_exit);
- 242
- 243 MODULE_AUTHOR("DengTao <<a href="mailto:773904075@qq.com">773904075@qq.com</a>>");
- 244 MODULE_DESCRIPTION("Led Platform Driver");
- 245 MODULE_LICENSE("GPL");
复制代码
代码中以前讲过的知识点这里就不再重述了!
第72~106行,自定义函数myled_init,该函数的参数是struct device_node类型的指针,也就是led对应的设备节点,当调用函数的时候传递进来。
第121~175行,platform驱动的probe函数myled_probe,当设备树中的设备节点与驱动之间匹配成功以后此函数就会执行,第128行调用myled_init函数时,将pdev->dev.of_node作为参数传递到函数中,platform_device结构体中内置了一个struct device类型的变量dev,如示例代码 37.2.9中所示,在struct device结构体中定义了一个struct device_node类型的指针变量of_node,使用设备树方式进行匹配的情况下,当匹配成功之后,of_node会指向设备树中定义的节点,所以在这里我们不需要通过调用of_find_node_by_path("/led")函数得到led的节点。我们原来在驱动加载函数里面做的工作现在全部放到probe函数里面完成。
第182~202行,platform驱动的remobe函数myled_remove,当platform驱动模块被卸载时此函数就会执行。在此函数里面释放内存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到remove函数中完成。
第205~208行,匹配表,描述了此驱动都和什么样的设备匹配,第206行添加了一条值为"alientek,led"的compatible属性值,当设备树中某个设备节点的compatible属性值也为“alientek,led”的时候就会与此驱动匹配。
第211~218行,platform_driver驱动结构体变量myled_driver,213行设置这个platform驱动的名字为“zynq-led”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“zynq-led”的文件。第214行绑定platform驱动的of_match_table表。
第225~228行,platform驱动模块入口函数,在此函数里面通过platform_driver_register向Linux内核注册一个platform驱动led_driver。
第235~238行,platform驱动驱动模块出口函数,在此函数里面通过platform_driver_unregister从Linux内核卸载一个platform驱动led_driver。
1.3.3编写测试APP
测试APP就直接使用上一章编写的ledApp.c即可。
1.4运行测试
1.4.1编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,将上一章实验目录“17_platform”下的Makefile文件拷贝到本章实验目录中,打开Makefile文件,将obj-m变量的值改为“leddriver.o”,Makefile内容如下所示:
- 示例代码 38.4.1 Makefile文件
- 1 KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3
- 2
- 3 obj-m := leddriver.o
- 4
- 5 all:
- 6 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules
- 7
- 8 clean:
- 9 make -C $(KERN_DIR) M=`pwd` clean
复制代码
第3行,设置obj-m变量的值为“leddriver.o”。
文件修改完成之后保存退出,输入如下命令编译出驱动模块文件:
编译成功以后就会生成一个名为“leddriver.o”的驱动模块文件,如下所示:
图 38.4.1 编译驱动模块
2、编译测试APP
测试APP直接使用上一章的ledApp这个测试软件即可。
1.4.2运行测试
将上一小节编译出来leddriver.ko和ledApp拷贝到开发板根文件系统/lib/modules/4.14.0-xilinx目录中,重启开发板,进入到目录/lib/modules/4.14.0-xilinx,输入如下命令加载leddriver.ko这个驱动模块。
- depmod //第一次加载驱动的时候需要运行此命令
- modprobe leddriver.ko //加载驱动模块
复制代码
驱动模块加载完成以后到/sys/bus/platform/drivers/目录下查看驱动是否存在,我们在leddriver.c中设置myled_driver(platform_driver类型)的name字段为“zynq-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“zynq-led”这个文件,结果如图 38.4.2所示:
图 38.4.2 zynq-led驱动
同理,在/sys/bus/platform/devices/目录下也存在led的设备文件,也就是设备树中led这个节点,如图 38.4.3所示:
图 38.4.3 led设备
驱动和模块都存在,当驱动和设备匹配成功以后就会输出如所示一行语句:
图 38.4.4 驱动和设备匹配成功 驱动和设备匹配成功以后就可以测试开发板的PS_LED0了,输入如下命令打开LED灯:
- ./ledApp /dev/myled 1 //打开LED灯
复制代码
在输入如下命令关闭LED灯:
- ./ledApp /dev/myled 0 //关闭LED灯
复制代码
观察开发板上的PS_LED0能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
|
|