正点原子 发表于 2022-1-24 15:58:14

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

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








第三十八章 设备树下的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 /***************************************************************
2Copyright © 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* @param – 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;
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)
65         gpio_set_value(myled.led_gpio, 0);                // 如果传递过来的数据是0则关闭led
66   else if (1 == kern_buf)
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”。
       文件修改完成之后保存退出,输入如下命令编译出驱动模块文件:
make      编译成功以后就会生成一个名为“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能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的话输入如下命令即可:
rmmod leddriver.ko

页: [1]
查看完整版本: 《领航者ZYNQ之嵌入式Linux开发指南_V2.0》第三十八章 platform驱动