目录一 、Platform总线结合dts与之前的区别1.相当于把driver.c和plat.c结合起来通过of_device_id函数和paltform来进行匹配2.在初始化中注册palt_driver在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)二、gpio子系统函数解释设备树1.这里面是引脚配置和电气属性2.表示上电后的默认电平三、中断函数解释设备树睡眠与唤醒机制四、错误处理五、总结与补充遇到的问题1模块加载不进去返回错误码-16busy2模块加载不进去返回错误码-23为什么按键不用of_get_named_gpio显式获取 GPIO补充1Vim替换字符串2IRQF_ONESHOT中断宏六、扩展练习dht11传感器实现代码为遇到的问题问题1问题2问题3一 、Platform总线结合dts在上一篇文章中我们使用的是仅 Platform 总线和仅dts的方法。现在我们使用如今最新的方法Platform总线结合dts传统方式仅 Platform 每支持一块新板子就要在arch/arm/mach-xxx/下新增一个 C 文件或少则几百行多则上千行里面充满platform_device_register、resource定义、gpio数组等。这些代码大多重复且难以阅读。DTS 方式Platform总线结合dts板级描述变成几百行结构化的文本不再需要在内核源码里为每块板子增加 C 文件。主流架构的mach-xxx目录如今只剩下少量核心代码大量板级文件已被删除。结果内核更干净维护者更容易审查硬件描述。设备树这种方式添加到总线的好处在于它会匹配上了再寻找设备节点少做很多无用功匹配的是compatible变量跟名字pt_led和name1没关系。代码为#include linux/init.h #include linux/printk.h #include linux/kdev_t.h #include linux/types.h #include linux/fs.h #include linux/export.h #include asm/uaccess.h #include asm/string.h #include asm/io.h #include linux/miscdevice.h #include linux/module.h #include linux/of.h #define DEV_NAME led static volatile unsigned int * sw_mux; static volatile unsigned int * sw_pad; static volatile unsigned int * gpio1_dr; static volatile unsigned int * gpio1_gdir; static void led_init(void) { *sw_mux 0x05; *sw_pad 0x10b0; *gpio1_gdir | (1 3); *gpio1_dr | (1 3); } static void led_on(void) { *gpio1_dr ~(1 3); } static void led_off(void) { *gpio1_dr | (1 3); } static int open(struct inode * node, struct file * file) { led_init(); printk(led open...\n); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { //copy_to_user(); printk(led read...\n); return 0; } static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset) { // ledon on ledoff off unsigned char data[10] {0}; size_t len_cp len sizeof(data) ? len : sizeof data; int size_cp copy_from_user(data, buf, len_cp); if(size_cp 0) return size_cp; if(!strcmp(buf, ledon)) led_on(); else if(!(strcmp(buf, ledoff))) led_off(); else return -EINVAL; printk(led write...\n); return size_cp; } static int close(struct inode * node, struct file * file) { led_off(); printk(led close...\n); return 0; } static struct file_operations fops { .owner THIS_MODULE, .open open, .read read, .write write, .release close }; static struct miscdevice misc { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops fops }; static const struct of_device_id match_table[] { [0] {.compatible pt-led} }; static struct platform_driver drv { .probe probe, .remove remove, .driver { .name DEV_NAME .of_match_table match_table } }; static int __init led1_init(void) { int ret platform_driver_register(drv); if(ret 0) goto err_reg; printk(platform_driver_register ...\n); return 0; err_reg: platform_driver_unregister(drv); printk(platform_driver_register failed\n); return ret; } static int probe(struct platform_device * pdev) { struct device_node * pnode; const char * pcom; const char * pname1; u32 led_array[8] {0}; int ret misc_register(misc); if(ret 0) goto err_misc; pnode of_find_node_by_path(/pt_led); if(pnode NULL) { printk(of_find_node_by_path err\n); return -1; } of_property_read_string(pnode, compatible, pcom); of_property_read_string(pnode, name1, pname1); printk(led compatible %s name1 %s\n, pcom, pname1); of_property_read_u32_array(pnode, reg, led_array, sizeof(led_array) / sizeof(led_array[0])); sw_mux ioremap(led_array[0], led_array[1]); sw_pad ioremap(led_array[2], led_array[3]); gpio1_gdir ioremap(led_array[4], led_array[5]); gpio1_dr ioremap(led_array[6], led_array[7]); printk(led_init ##############\n); return 0; err_misc: misc_deregister(misc); printk(led_init failed ret %d\n, ret); return ret; } static void __exit led1_exit(void) { iounmap(gpio1_gdir); iounmap(gpio1_dr); iounmap(sw_pad); iounmap(sw_mux); misc_deregister(misc); printk(led_exit ##############\n); } module_init(led1_init); module_exit(led1_exit); MODULE_LICENSE(GPL);与之前的区别1.相当于把driver.c和plat.c结合起来通过of_device_id函数和paltform来进行匹配2.在初始化中注册palt_driver在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)二、gpio子系统Linux 内核提供了GPIO 子系统用于统一管理各个 SoC 的 GPIO 控制器并为驱动开发者提供标准 API。这样我就不需要使用 ioremap函数转换地址了只需要调用 GPIO 子系统提供的标准 API 即可。GPIO 控制器驱动内部已经做了 ioremap。需要用到这两个头文件#include linux/of_gpio.h#include linux/gpio.h将上面的代码改写为#include linux/init.h #include linux/printk.h #include linux/kdev_t.h #include linux/types.h #include linux/fs.h #include linux/export.h #include asm/uaccess.h #include asm/string.h #include asm/io.h #include linux/miscdevice.h #include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/of_gpio.h #include linux/gpio.h #define DEV_NAME led static int led_gpio; #define LED_ON 0 #define LED_OFF 1 static void led_init(void) { gpio_direction_output(led_gpio, LED_OFF); } static void led_on(void) { gpio_set_value(led_gpio, LED_ON); } static void led_off(void) { gpio_set_value(led_gpio, LED_OFF); } static int open(struct inode * node, struct file * file) { led_init(); printk(led open...\n); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { //copy_to_user(); printk(led read...\n); return 0; } static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset) { // ledon on ledoff off unsigned char data[10] {0}; size_t len_cp len sizeof(data) ? len : sizeof data; int size_cp copy_from_user(data, buf, len_cp); if(size_cp 0) return size_cp; if(!strcmp(buf, ledon)) led_on(); else if(!(strcmp(buf, ledoff))) led_off(); else return -EINVAL; printk(led write...\n); return size_cp; } static int close(struct inode * node, struct file * file) { led_off(); printk(led close...\n); return 0; } static struct file_operations fops { .owner THIS_MODULE, .open open, .read read, .write write, .release close }; static struct miscdevice misc { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret misc_register(misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode of_find_node_by_path(/pt_gpioled); if(IS_ERR(pnode)) { ret PTR_ERR(pnode); goto err_find_node; } led_gpio of_get_named_gpio(pnode, led-gpio, 0); gpio_request(led_gpio, red_led); gpio_direction_output(led_gpio, LED_OFF); printk(probe led misc_register ##############\n); return 0; err_find_node: printk(of_find_node_by_path err\n); err_misc: printk(led probe failed ret %d\n, ret); misc_deregister(misc); return ret; } static int remove(struct platform_device * pdev) { gpio_free(led_gpio); misc_deregister(misc); printk(remove led misc_deregister ##############\n); return 0; } static const struct of_device_id match_table[] { [0] {.compatible pt-gpioled} }; static struct platform_driver drv { .probe probe, .remove remove, .driver { .name DEV_NAME, .of_match_table match_table } }; static int __init led1_init(void) { int ret platform_driver_register(drv); if(ret 0) goto err_reg; printk(platform_driver_register ...\n); return 0; err_reg: printk(platform_driver_register failed ret %d\n, ret); platform_driver_unregister(drv); return ret; } static void __exit led1_exit(void) { platform_driver_unregister(drv); printk(platform_driver_unregister ...\n); } module_init(led1_init); module_exit(led1_exit); MODULE_LICENSE(GPL);函数解释1led_gpio of_get_named_gpio(pnode, led-gpio, 0);//查找设备树的led信息获取gpio编号np设备树节点指针propname属性名如led-gpio这个必须和设备树的变量名一致index可以理解为数组下标索引因为属性可能包含多个 GPIO如gpios gpio1 3 0, gpio1 4 0;返回值GPIO 编号整型负数表示错误。2gpio_request(led_gpio, red_led); //获取gpio后面的是标签随意写3gpio_direction_output(led_gpio, LED_OFF);//设置为输出模式后面是输出1宏4gpio_direction_output(led_gpio)//设置为输入模式5gpio_free(led_gpio);//释放gpio设备树1.这里面是引脚配置和电气属性2.表示上电后的默认电平三、中断通过led的学习我们同样可以写一个按键的驱动代码#include linux/init.h #include linux/printk.h #include linux/kdev_t.h #include linux/types.h #include linux/fs.h #include linux/export.h #include asm/uaccess.h #include asm/string.h #include asm/io.h #include linux/miscdevice.h #include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/of_gpio.h #include linux/gpio.h #include linux/interrupt.h #include linux/irqreturn.h #define DEV_NAME key static int key_gpio; static unsigned int irq_key_num; #define key_ON 0 #define key_OFF 1 static int arg 100; static irqreturn_t key_irq_handler(int dev_num, void * dev) { printk(dev_num %d dev %d\n, dev_num, *(int *)dev); return IRQ_HANDLED; } static int open(struct inode * node, struct file * file) { printk(key open...\n); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { //copy_to_user(); printk(key read...\n); return 0; } static int close(struct inode * node, struct file * file) { printk(key close...\n); return 0; } static struct file_operations fops { .owner THIS_MODULE, .open open, .read read, .release close }; static struct miscdevice misc { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret misc_register(misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode of_find_node_by_path(/pt_key); if(IS_ERR(pnode)) { ret PTR_ERR(pnode); goto err_find_node; } key_gpio of_get_named_gpio(pnode, key-gpio, 0); if(key_gpio 0) { ret key_gpio; goto err_get_gpio; } ret gpio_request(key_gpio, pt_key); if(ret 0) goto err_gpio_request; irq_key_num gpio_to_irq(key_gpio); if(irq_key_num 0) { ret irq_key_num; goto err_gpio_to_irq; } ret request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, key_irq, arg); if(ret 0) goto err_request_irq; printk(probe key misc_register ##############\n); return 0; err_request_irq: disable_irq(irq_key_num); free_irq(irq_key_num, arg); printk(key err_request_irq\n); err_gpio_to_irq: printk(key err_gpio_to_irq\n); err_gpio_request: printk(key err_gpio_request\n); err_get_gpio: printk(key err_get_gpio\n); err_find_node: printk(of_find_node_by_path err\n); err_misc: printk(key probe faikey ret %d\n, ret); misc_deregister(misc); return ret; } static int remove(struct platform_device * pdev) { disable_irq(irq_key_num); free_irq(irq_key_num, arg); gpio_free(key_gpio); misc_deregister(misc); printk(remove key misc_deregister ##############\n); return 0; } static const struct of_device_id match_table[] { [0] {.compatible pt-key} }; static struct platform_driver drv { .probe probe, .remove remove, .driver { .name DEV_NAME, .of_match_table match_table } }; static int __init key1_init(void) { int ret platform_driver_register(drv); if(ret 0) goto err_reg; printk(platform_driver_register ...\n); return 0; err_reg: printk(platform_driver_register faikey ret %d\n, ret); platform_driver_unregister(drv); return ret; } static void __exit key1_exit(void) { platform_driver_unregister(drv); printk(platform_driver_unregister ...\n); } module_init(key1_init); module_exit(key1_exit); MODULE_LICENSE(GPL);函数解释1static irqreturn_t key_irq_handler(int dev_num, void * dev)中断触发后的句柄后面的void*dev是中断请求传的参数2irq_key_num gpio_to_irq(key_gpio);//获取中断号3ret request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, key_irq, arg);参数1中断号参数2中断句柄参数3触发方式参数4中断名参数5传入的参数代码这里写的全局变量static int arg 100;4结束中断disable_irq(irq_key_num); //关闭中断free_irq(irq_key_num, arg);//释放中断设备树在我们的dts中添加这两个信息睡眠与唤醒机制我们可以在原来的代码里面使用标志位然后用while来循环判断标志位来实现各种功能但这种方法非常浪费资源CPU一直空转这里我们加入一个wait_queue_head_t它是 Linux 内核中等待队列头的类型定义在linux/wait.h中。相关函数init_waitqueue_head(wq); //初始化等待队列wait_event_interruptible(wq, condition); //睡眠condition为我们的标志位为0则一直休眠为1则唤醒wake_up_interruptible(wq); //唤醒判断标志位是否为1不为1继续休眠加入机制后的中断代码为#include linux/init.h #include linux/printk.h #include linux/kdev_t.h #include linux/types.h #include linux/fs.h #include linux/export.h #include asm/uaccess.h #include asm/string.h #include asm/io.h #include linux/miscdevice.h #include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/of_gpio.h #include linux/gpio.h #include linux/interrupt.h #include linux/irqreturn.h #include linux/of_irq.h #include linux/wait.h #include linux/sched.h #define DEV_NAME key static unsigned int irq_key_num; static int arg 100; static wait_queue_head_t wq; static int condition; static irqreturn_t key_irq_handler(int dev_num, void * dev) { condition 1; wake_up_interruptible(wq); printk(dev_num %d dev %d\n, dev_num, *(int *)dev); return IRQ_HANDLED; } static int open(struct inode * node, struct file * file) { printk(key open...\n); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { condition 0; wait_event_interruptible(wq, condition); //copy_to_user(); printk(key read...\n); return 0; } static int close(struct inode * node, struct file * file) { printk(key close...\n); return 0; } static struct file_operations fops { .owner THIS_MODULE, .open open, .read read, .release close }; static struct miscdevice misc { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret misc_register(misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode of_find_node_by_path(/pt_key); if(IS_ERR(pnode)) { ret PTR_ERR(pnode); goto err_find_node; } irq_key_num irq_of_parse_and_map(pnode, 0); if(irq_key_num 0) { ret irq_key_num; goto err_of_parse; } ret request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, key_irq, arg); if(ret 0) goto err_request_irq; init_waitqueue_head(wq); printk(probe key misc_register ##############\n); return 0; err_request_irq: disable_irq(irq_key_num); free_irq(irq_key_num, arg); printk(key err_request_irq\n); err_of_parse: printk(key err_of_parse\n); err_find_node: printk(of_find_node_by_path err\n); err_misc: printk(key probe faikey ret %d\n, ret); misc_deregister(misc); return ret; } static int remove(struct platform_device * pdev) { disable_irq(irq_key_num); free_irq(irq_key_num, arg); misc_deregister(misc); printk(remove key misc_deregister ##############\n); return 0; } static const struct of_device_id match_table[] { [0] {.compatible pt-key} }; static struct platform_driver drv { .probe probe, .remove remove, .driver { .name DEV_NAME, .of_match_table match_table } }; static int __init key1_init(void) { int ret platform_driver_register(drv); if(ret 0) goto err_reg; printk(platform_driver_register ...\n); return 0; err_reg: printk(platform_driver_register faikey ret %d\n, ret); platform_driver_unregister(drv); return ret; } static void __exit key1_exit(void) { platform_driver_unregister(drv); printk(platform_driver_unregister ...\n); } module_init(key1_init); module_exit(key1_exit); MODULE_LICENSE(GPL);这样在我们应用层read的时候函数就会阻塞休眠当我们中断后就会继续执行了。四、错误处理内核里面的出错必须处理不处理系统会崩某些函数的返回值是指针这种怎么判断它的错误码这里需要用到我们的错误处理函数宏/函数输入类型输出/判断适用场景IS_ERR_VALUE(ret)unsigned long是否为错误值来自负数错误码整数返回值函数IS_ERR(pnode)void *是否为错误指针指针返回值函数PTR_ERR(pnode)void *错误码负数从错误指针提取错误码示例五、总结与补充遇到的问题1模块加载不进去返回错误码-16busy解决办法把默认设备树中的key和led相关的全部删掉使用我们自己写的即可2模块加载不进去返回错误码-2解决办法检查driver是否与设备节点匹配compatible、of_find_node_by_path和of_get_named_gpio3为什么按键不用of_get_named_gpio显式获取 GPIO我们的按键中断代码只关心中断事件并不需要直接读取按键的 GPIO 电平比如做消抖、轮询。补充1Vim替换字符串2IRQF_ONESHOT中断宏数据过来的时候会触发中断在没有把数据读空之前再有数据来就不触发中断六、扩展练习dht11传感器实现现在可以自己实现一个驱动程序了这里用dht11传感器举例步骤如下代码为#include linux/init.h #include linux/printk.h #include linux/kdev_t.h #include linux/types.h #include linux/fs.h #include linux/export.h #include asm/uaccess.h #include asm/string.h #include asm/io.h #include linux/miscdevice.h #include linux/module.h #include linux/platform_device.h #include linux/of.h #include linux/of_gpio.h #include linux/gpio.h #include asm/ioctl.h #include linux/delay.h #define DEV_NAME dht11 static int dht11_gpio; #define DHT11_PIN_HIGH 1 #define DHT11_PIN_DOWN 0 // 主机发送起始信号 static void master_start(void) { gpio_set_value(dht11_gpio, 0); msleep(20); gpio_set_value(dht11_gpio, 1); udelay(30); } static int dht11_response(void) { int time 0; gpio_direction_input(dht11_gpio); // 等待低电平到来 while (gpio_get_value(dht11_gpio) time 4) { udelay(30); time; } if (time 4) { printk(wait low_level failed,timeout!\n); return -1; } // 等待高电平到来 time 0; while(!gpio_get_value(dht11_gpio) time 4) { udelay(30); time; } if (time 4) { printk(wait high_level failed,timeout!\n); return -2; } // 等待低电平到来 while (gpio_get_value(dht11_gpio)); return 1; } static int open(struct inode * node, struct file * file) { gpio_direction_output(dht11_gpio, 1); msleep(5); printk(dht11 open...\n); return 0; } static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset) { int i 0; int j 0; unsigned char dht_dat[5] {0}; // 湿度整数 湿度小数 温度整数 温度小数 校验和 unsigned char checksum 0; master_start(); // 主机发送起始信号 if (dht11_response() ! 1) // 等待dht11回复响应信号 { printk(dht11 no response!\n); return -3; } // 接收dht11 40位数据 for (j 0; j 5; j) { for (i 0; i 8; i) { // 等待高电平到来 while (!gpio_get_value(dht11_gpio)); // 延时60us udelay(60); // 代表dht11发送了一个1 if (gpio_get_value(dht11_gpio)) { dht_dat[j] | (1 (7 - i)); } // 等待低电平到来接收下一个bit while (gpio_get_value(dht11_gpio)); } } // 判断校验是否通过 checksum dht_dat[0] dht_dat[1] dht_dat[2] dht_dat[3]; if (checksum ! dht_dat[4]) { printk(dht11 check failed!\r\n); return -4; } //copy_to_user(); /*unsigned char dht_dat[5] 0; 湿度整数 湿度小数 温度整数 温度小数 校验和*/ if (copy_to_user(buf,dht_dat,sizeof dht_dat)) return -EFAULT; gpio_direction_output(dht11_gpio, 1); printk(dht11 read...\n); return sizeof(dht_dat); } static int close(struct inode * node, struct file * file) { printk(dht11 close...\n); return 0; } static struct file_operations fops { .owner THIS_MODULE, .open open, .read read, .release close }; static struct miscdevice misc { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops fops }; static int probe(struct platform_device * pdev) { struct device_node * pnode; int ret misc_register(misc); if(IS_ERR_VALUE(ret)) goto err_misc; pnode of_find_node_by_path(/dht11); if(IS_ERR(pnode)) { ret PTR_ERR(pnode); goto err_find_node; } dht11_gpio of_get_named_gpio(pnode, dht11-gpio, 0); gpio_request(dht11_gpio, dht11); printk(probe dht11 misc_register ##############\n); return 0; err_find_node: printk(of_find_node_by_path err\n); err_misc: printk(dht11 probe failed ret %d\n, ret); misc_deregister(misc); return ret; } static int remove(struct platform_device * pdev) { gpio_free(dht11_gpio); misc_deregister(misc); printk(remove dht11 misc_deregister ##############\n); return 0; } static const struct of_device_id match_table[] { [0] {.compatible dht11} }; static struct platform_driver drv { .probe probe, .remove remove, .driver { .name DEV_NAME, .of_match_table match_table } }; static int __init dht11_init(void) { int ret platform_driver_register(drv); if(ret 0) goto err_reg; printk(platform_driver_register ...\n); return 0; err_reg: printk(platform_driver_register failed ret %d\n, ret); platform_driver_unregister(drv); return ret; } static void __exit dht11_exit(void) { platform_driver_unregister(drv); printk(platform_driver_unregister ...\n); } module_init(dht11_init); module_exit(dht11_exit); MODULE_LICENSE(GPL);遇到的问题问题1我在主机发送信号的时候直接把引脚拉低没有延迟引脚之前的电平并不清楚是否为高解决办法在主机发送信号的时候必须在open函数先置高引脚最好延迟 5ms然后每次读完数据都要把引脚拉高置为输出模式问题2第一次数据能读后续报错check failed解决办法全局变量的数组在重新读数据的时候没有清零导致后面校验不成功清零就行问题3设备树的引脚配置不一致引脚控制里面写的gpio1_io04 然后在gpio变量中我写的是gpio1 22号引脚。