搜索
bottom↓
回复: 0

《领航者ZYNQ之嵌入式Linux开发指南_V2.0》第三十八章 platform驱动

[复制链接]

出0入234汤圆

发表于 2022-1-24 15:58:14 | 显示全部楼层 |阅读模式
1)实验平台:正点原子领航者V2 ZYNQ开发板
2)  章节摘自【正点原子】《领航者ZYNQ之嵌入式Linux开发指南_V2.0》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-329957-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:90562473
1.png

2.jpg


3.png



第三十八章 设备树下的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这个设备:
  1. 示例代码 38.1.1 led设备节点
  2. 37 led {
  3. 38     compatible = "alientek,led";
  4. 39     status = "okay";
  5. 40     default-state = "on";
  6. 41     led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
  7. 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就可以按照如下所示设置:
  1. 示例代码 38.1.2 of_match_table匹配表的设置
  2. 1 static const struct of_device_id leds_of_match[] = {
  3. 2     { .compatible = "alientek,led" },                /* 兼容属性 */
  4. 3     { /* Sentinel */ }
  5. 4 };
  6. 5
  7. 6 MODULE_DEVICE_TABLE(of, leds_of_match);
  8. 7
  9. 8 static struct platform_driver leds_platform_driver = {
  10. 9     .driver = {
  11. 10         .name           = "zynq-led",
  12. 11         .of_match_table = leds_of_match,
  13. 12     },
  14. 13     .probe          = leds_probe,
  15. 14     .remove         = leds_remove,
  16. 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中输入如下所示内容:
  1. 示例代码 38.3.1 leddriver.c文件代码段
  2.   1 /***************************************************************
  3.   2  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
  4.   3  文件名    : leddriver.c
  5.   4  作者      : 邓涛
  6.   5  版本      : V1.0
  7.   6  描述      : platform总线编程示例之platform驱动模块
  8.   7  其他      : 无
  9.   8  论坛      : <a href="www.openedv.com" target="_blank">www.openedv.com</a>
  10.   9  日志      : 初版V1.0 2019/1/30 邓涛创建
  11. 10  ***************************************************************/
  12. 11
  13. 12 #include <linux/module.h>
  14. 13 #include <linux/of_gpio.h>
  15. 14 #include <linux/cdev.h>
  16. 15 #include <linux/uaccess.h>
  17. 16 #include <linux/platform_device.h>
  18. 17
  19. 18 #define MYLED_CNT                1                        /* 设备号个数 */
  20. 19 #define MYLED_NAME                "myled"                /* 名字 */
  21. 20
  22. 21 /* LED设备结构体 */
  23. 22 struct myled_dev {
  24. 23     dev_t devid;                        /* 设备号 */
  25. 24     struct cdev cdev;                /* cdev结构体 */
  26. 25     struct class *class;                /* 类 */
  27. 26     struct device *device;        /* 设备 */
  28. 27     int led_gpio;                        /* GPIO号 */
  29. 28 };
  30. 29
  31. 30 static struct myled_dev myled;        /* led设备 */
  32. 31
  33. 32 /*
  34. 33  * @description                : 打开设备
  35. 34  * @param – inode                : 传递给驱动的inode
  36. 35  * @param – filp                : 设备文件,file结构体有个叫做private_data的成员变量
  37. 36  *                                           一般在open的时候将private_data指向设备结构体。
  38. 37  * @return                        : 0 成功;其他 失败
  39. 38  */
  40. 39 static int myled_open(struct inode *inode, struct file *filp)
  41. 40 {
  42. 41     return 0;
  43. 42 }
  44. 43
  45. 44 /*
  46. 45  * @description                : 向设备写数据
  47. 46  * @param – filp                : 设备文件,表示打开的文件描述符
  48. 47  * @param – buf                : 要写给设备写入的数据
  49. 48  * @param – cnt                : 要写入的数据长度
  50. 49  * @param – offt                : 相对于文件首地址的偏移
  51. 50  * @return                        : 写入的字节数,如果为负值,表示写入失败
  52. 51  */
  53. 52 static ssize_t myled_write(struct file *filp, const char __user *buf,
  54. 53             size_t cnt, loff_t *offt)
  55. 54 {
  56. 55     int ret;
  57. 56     char kern_buf[1];
  58. 57
  59. 58     ret = copy_from_user(kern_buf, buf, cnt);                // 得到应用层传递过来的数据
  60. 59     if(0 > ret) {
  61. 60         printk(KERN_ERR "myled: Failed to copy data from user buffer\r\n");
  62. 61         return -EFAULT;
  63. 62     }
  64. 63
  65. 64     if (0 == kern_buf[0])
  66. 65         gpio_set_value(myled.led_gpio, 0);                // 如果传递过来的数据是0则关闭led
  67. 66     else if (1 == kern_buf[0])
  68. 67         gpio_set_value(myled.led_gpio, 1);                // 如果传递过来的数据是1则点亮led
  69. 68
  70. 69     return 0;
  71. 70 }
  72. 71
  73. 72 static int myled_init(struct device_node *nd)
  74. 73 {
  75. 74     const char *str;
  76. 75     int val;
  77. 76     int ret;
  78. 77
  79. 78     /* 从设备树中获取GPIO */
  80. 79     myled.led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
  81. 80     if(!gpio_is_valid(myled.led_gpio)) {
  82. 81         printk(KERN_ERR "myled: Failed to get led-gpio\n");
  83. 82         return -EINVAL;
  84. 83     }
  85. 84
  86. 85     /* 申请使用GPIO */
  87. 86     ret = gpio_request(myled.led_gpio, "PS_LED0 Gpio");
  88. 87     if (ret) {
  89. 88         printk(KERN_ERR "myled: Failed to request led-gpio\n");
  90. 89         return ret;
  91. 90     }
  92. 91
  93. 92     /* 确定LED初始状态 */
  94. 93     ret = of_property_read_string(nd, "default-state", &str);
  95. 94     if(!ret) {
  96. 95         if (!strcmp(str, "on"))
  97. 96             val = 1;
  98. 97         else
  99. 98             val = 0;
  100. 99     } else
  101. 100         val = 0;
  102. 101
  103. 102     /* 将GPIO设置为输出模式并设置GPIO初始电平状态 */
  104. 103     gpio_direction_output(myled.led_gpio, val);
  105. 104
  106. 105     return 0;
  107. 106 }
  108. 107
  109. 108 /* LED设备操作函数 */
  110. 109 static struct file_operations myled_fops = {
  111. 110     .owner = THIS_MODULE,
  112. 111     .open = myled_open,
  113. 112     .write = myled_write,
  114. 113 };
  115. 114
  116. 115 /*
  117. 116  * @description                : platform驱动的probe函数,当驱动与设备
  118. 117  *                                          匹配成功以后此函数就会执行
  119. 118  * @param – pdev                : platform设备指针
  120. 119  * @return                        : 0,成功;其他负值,失败
  121. 120  */
  122. 121 static int myled_probe(struct platform_device *pdev)
  123. 122 {
  124. 123     int ret;
  125. 124
  126. 125     printk(KERN_INFO "myled: led driver and device has matched!\r\n");
  127. 126
  128. 127     /* led初始化 */
  129. 128     ret = myled_init(pdev->dev.of_node);
  130. 129     if (ret)
  131. 130         return ret;
  132. 131
  133. 132     /* 初始化cdev */
  134. 133     ret = alloc_chrdev_region(&myled.devid, 0, MYLED_CNT, MYLED_NAME);
  135. 134     if (ret)
  136. 135         goto out1;
  137. 136
  138. 137     myled.cdev.owner = THIS_MODULE;
  139. 138     cdev_init(&myled.cdev, &myled_fops);
  140. 139
  141. 140     /* 添加cdev */
  142. 141     ret = cdev_add(&myled.cdev, myled.devid, MYLED_CNT);
  143. 142     if (ret)
  144. 143         goto out2;
  145. 144
  146. 145     /* 创建类class */
  147. 146     myled.class = class_create(THIS_MODULE, MYLED_NAME);
  148. 147     if (IS_ERR(myled.class)) {
  149. 148         ret = PTR_ERR(myled.class);
  150. 149         goto out3;
  151. 150     }
  152. 151
  153. 152     /* 创建设备 */
  154. 153     myled.device = device_create(myled.class, &pdev->dev,
  155. 154                 myled.devid, NULL, MYLED_NAME);
  156. 155     if (IS_ERR(myled.device)) {
  157. 156         ret = PTR_ERR(myled.device);
  158. 157         goto out4;
  159. 158     }
  160. 159
  161. 160     return 0;
  162. 161
  163. 162 out4:
  164. 163     class_destroy(myled.class);
  165. 164         
  166. 165 out3:
  167. 166     cdev_del(&myled.cdev);
  168. 167         
  169. 168 out2:
  170. 169     unregister_chrdev_region(myled.devid, MYLED_CNT);
  171. 170         
  172. 171 out1:
  173. 172     gpio_free(myled.led_gpio);
  174. 173         
  175. 174     return ret;
  176. 175 }
  177. 176
  178. 177 /*
  179. 178  * @description                : platform驱动模块卸载时此函数会执行
  180. 179  * @param – dev                : platform设备指针
  181. 180  * @return                        : 0,成功;其他负值,失败
  182. 181  */
  183. 182 static int myled_remove(struct platform_device *dev)
  184. 183 {
  185. 184     printk(KERN_INFO "myled: led platform driver remove!\r\n");
  186. 185
  187. 186     /* 注销设备 */
  188. 187     device_destroy(myled.class, myled.devid);
  189. 188
  190. 189     /* 注销类 */
  191. 190     class_destroy(myled.class);
  192. 191
  193. 192     /* 删除cdev */
  194. 193     cdev_del(&myled.cdev);
  195. 194
  196. 195     /* 注销设备号 */
  197. 196     unregister_chrdev_region(myled.devid, MYLED_CNT);
  198. 197
  199. 198     /* 删除地址映射 */
  200. 199     gpio_free(myled.led_gpio);
  201. 200
  202. 201     return 0;
  203. 202 }
  204. 203
  205. 204 /* 匹配列表 */
  206. 205 static const struct of_device_id led_of_match[] = {
  207. 206     { .compatible = "alientek,led" },
  208. 207     { /* Sentinel */ }
  209. 208 };
  210. 209
  211. 210 /* platform驱动结构体 */
  212. 211 static struct platform_driver myled_driver = {
  213. 212     .driver = {
  214. 213         .name                   = "zynq-led",        // 驱动名字,用于和设备匹配
  215. 214         .of_match_table = led_of_match,                        // 设备树匹配表,用于和设备树中定义的设备匹配
  216. 215     },
  217. 216     .probe          = myled_probe,                // probe函数
  218. 217     .remove         = myled_remove,        // remove函数
  219. 218 };
  220. 219
  221. 220 /*
  222. 221  * @description                : 模块入口函数
  223. 222  * @param                        : 无
  224. 223  * @return                        : 无
  225. 224  */
  226. 225 static int __init myled_driver_init(void)
  227. 226 {
  228. 227     return platform_driver_register(&myled_driver);
  229. 228 }
  230. 229
  231. 230 /*
  232. 231  * @description                : 模块出口函数
  233. 232  * @param                        : 无
  234. 233  * @return                        : 无
  235. 234  */
  236. 235 static void __exit myled_driver_exit(void)
  237. 236 {
  238. 237     platform_driver_unregister(&myled_driver);
  239. 238 }
  240. 239
  241. 240 module_init(myled_driver_init);
  242. 241 module_exit(myled_driver_exit);
  243. 242
  244. 243 MODULE_AUTHOR("DengTao <<a href="mailto:773904075@qq.com">773904075@qq.com</a>>");
  245. 244 MODULE_DESCRIPTION("Led Platform Driver");
  246. 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内容如下所示:
  1. 示例代码 38.4.1 Makefile文件
  2. 1 KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3
  3. 2
  4. 3 obj-m := leddriver.o
  5. 4
  6. 5 all:
  7. 6                 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules
  8. 7
  9. 8 clean:
  10. 9                 make -C $(KERN_DIR) M=`pwd` clean
复制代码
       第3行,设置obj-m变量的值为“leddriver.o”。
       文件修改完成之后保存退出,输入如下命令编译出驱动模块文件:
  1. make
复制代码
        编译成功以后就会生成一个名为“leddriver.o”的驱动模块文件,如下所示:
第三十八章 设备树下的platform驱动编写11305.png

图 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这个驱动模块。
  1. depmod                                //第一次加载驱动的时候需要运行此命令
  2. 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所示:
第三十八章 设备树下的platform驱动编写11813.png

图 38.4.2 zynq-led驱动

       同理,在/sys/bus/platform/devices/目录下也存在led的设备文件,也就是设备树中led这个节点,如图 38.4.3所示:
第三十八章 设备树下的platform驱动编写11975.png

图 38.4.3 led设备

       驱动和模块都存在,当驱动和设备匹配成功以后就会输出如所示一行语句:
第三十八章 设备树下的platform驱动编写12069.png

图 38.4.4 驱动和设备匹配成功

       驱动和设备匹配成功以后就可以测试开发板的PS_LED0了,输入如下命令打开LED灯:
  1. ./ledApp /dev/myled 1                //打开LED灯
复制代码
       在输入如下命令关闭LED灯:
  1. ./ledApp /dev/myled 0                //关闭LED灯
复制代码
        观察开发板上的PS_LED0能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
  1. rmmod leddriver.ko
复制代码


阿莫论坛20周年了!感谢大家的支持与爱护!!

如果想吃一顿饺子,就得从冰箱里取出肉,剁馅儿,倒面粉、揉面、醒面,擀成皮儿,下锅……
一整个繁琐流程,就是为了出锅时那一嘴滚烫流油的热饺子。

如果这个过程,禁不住饿,零食下肚了,饺子出锅时也就不香了……《非诚勿扰3》
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-28 19:55

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表