Linux内核驱动开发——gpio子系统
发布时间:2026/9/28 21:13:00来源:尧图网络
platform 平台驱动 设备树静态节点 动态加载驱动核心概念静态设备树设备节点写在 DTS 里内核启动阶段就解析动态加载驱动驱动编译成.koinsmod/rmmod手动加载 / 卸载不是内置进内核 zImage 匹配逻辑内核启动扫描 DTS发现test_gpioled静态节点保存等到 insmod 加载 platform 驱动模块驱动注册 platform_driver内核拿驱动里of_match_table和 DTS 节点compatible比对匹配成功 → 自动调用probermmod 卸载驱动调用remove。1.编写 DTS 静态设备节点compatible gpio-led1;核心匹配属性字符串用来和驱动里.compatible数组做匹配pinctrl-names default;引脚控制状态名字default代表默认上电状态pinctrl-0 pinctrl_gpioled;pinctrl-0对应上面pinctrl-names第 0 项default。pinctrl_gpioled是引用 pinctrl 子节点这个节点会配置 GPIO1_IO3 引脚功能复用为普通 GPIO、设置电气属性上下拉、速率。led-gpio gpio1 3 GPIO_ACTIVE_HIGH;自定义属性描述 LED 使用的 GPIO驱动代码中用of_get_named_gpio读取这三个参数gpio1引用 gpio1 控制器3gpio1 组里面第 3 号引脚GPIO1_IO03GPIO_ACTIVE_HIGH高电平点亮 LED低电平熄灭 如果是GPIO_ACTIVE_LOW就是低电平点亮。2.platform 驱动代码编写驱动匹配设备树内节点并注册设备和驱动程序static int probe(struct platform_device * pdev) { int ret 0; /*查找设备节点通过设备树节点完整路径找到 /test_gpioled 节点*/ struct device_node * dev_node of_find_node_by_path(/test_gpioled); if(NULL dev_node) { printk(of_find_node_by_path not found /test_gpioled\n); return -1; } /*从设备树节点属性led-gpio索引0获取GPIO编号*/ gpio_led of_get_named_gpio(dev_node,led-gpio,0); /*申请该GPIO名字为led1占用该GPIO资源防止其他驱动复用*/ gpio_request(gpio_led,led1); /*设置GPIO为输出模式默认输出高电平(LED熄灭低电平点亮)*/ gpio_direction_output(gpio_led,1); /*注册misc杂项设备生成/dev/xxx设备文件供用户空间访问*/ ret misc_register(misc); if(ret 0) goto err_register; printk(led_init ********************\n); return ret; err_register: /*注册失败反注册misc设备这里逻辑有隐患后面说明*/ misc_deregister(misc); printk(led_init failed********************\n); return ret; } /*设备树匹配列表用于设备树compatible属性和驱动匹配*/ const struct of_device_id match_table[] { [0] {.compatible gpio-led1}, //匹配dts中compatible gpio-led1的设备节点 [1] {.compatible pt-led2}, //匹配dts中compatible pt-led2的设备节点 [2] {.compatible pt-led3}, //匹配dts中compatible pt-led3的设备节点 {} //of_device_id数组必须以空结构体结尾作为结束标记 }; /*platform平台驱动结构体*/ static struct platform_driver drv { //设备和驱动成功匹配后内核自动调用probe探测函数 .probe probe, //设备卸载/驱动移除时内核调用remove函数 .remove remove, .driver { .name DEV_NAME, //驱动名字用于传统方式匹配非设备树匹配 .of_match_table match_table //指定设备树匹配表优先使用compatible匹配 } }; /*驱动入口函数模块加载时执行*/ static int __init led_driver_init(void) { /*向内核注册platform平台驱动*/ int ret platform_driver_register(drv); if(ret 0) { //注册失败反注册驱动 platform_driver_unregister(drv); return ret; } printk(platform_driver_register ...\n); return ret; }3.修改Makefile动态编译生成.ko文件修改当前目录下Kconfig使Makefile生效修改根目录下的menuconfig增加makefile编译依赖 obj-$(CONFIG_LED1_GPIO) led1_gpio.o 添加menuconfig选项 config LED1_GPIO tristate LED1_GPIO help this is a LED1 demo testGPIO 子系统是什么GPIO 子系统是 Linux 内核封装好的统一操作 GPIO 的框架屏蔽了芯片寄存器差异i.MX6ULL、STM32、RK3588 寄存器不一样。驱动不需要直接读写寄存器调用内核提供的 GPIO API 即可。配合设备树使用在 DTS 节点描述 GPIO 信息驱动通过 of 函数读取做到硬件信息和驱动代码分离换硬件不用改驱动源码。通过上面一部分已经完成了DTS设备树的修改所以这里只需要在驱动程序中修改就可以了主要使用到一下几个API函数1. of_get_named_gpio()gpio_led of_get_named_gpio(dev_node,led-gpio,0);功能从设备树节点读取 GPIO 编号参数dev_node设备树节点指针propnameDTS 里属性名led-gpioindex同一个属性多个 gpio 时的索引0 取第一个 返回大于等于 0 合法 gpio 号负数读取失败2. gpio_request()gpio_request(gpio_led,led1);功能向内核申请占用该 GPIO 资源防止多个驱动抢同一个引脚 参数gpio 编号gpio 名字用于/sys/kernel/debug/gpio查看 返回0 成功负数失败引脚被占用3. gpio_direction_output()gpio_direction_output(gpio_led,1);设置 GPIO 为输出模式第二个参数是初始电平1输出高0输出低4. gpio_direction_input(gpio)设置 GPIO 为输入模式5. gpio_set_value(gpio, value)动态修改 GPIO 电平value0/1。probe 里面只做初始化应用层 ioctl 调用这个来点灯灭灯6. gpio_get_value(gpio)读取 GPIO 当前电平输入引脚常用7. gpio_free(gpio_led)8.ioctl是字符设备 /misc 设备的控制接口用户空间传命令 (cmd) 参数内核驱动解析 cmd执行点灯、灭灯、读状态这类操作。open/write/read 适合数据流ioctl 适合控制命令LED 开关最常用。函数原型如下static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { // cmd用户传来的命令号 // arg用户传的参数可以是数值、指针 switch(cmd) { case LED_ON: gpio_set_value(gpio_led, 0); break; case LED_OFF: gpio_set_value(gpio_led, 1); break; default: return -EINVAL; // 无效命令 } return 0; }挂载到file_operationsstatic struct file_operations led_fops { .owner THIS_MODULE, .unlocked_ioctl led_ioctl, };其中需要用到cmd参数一般需要通过拼接得到为了避免cmd命令重叠以及误操作设备ioctl命令是由四个字段拼接而成的。bit31~bit30bit29~bit16bit15~bit8bit7~bit0方向数据长度魔数 (幻数)序号内核提供 4 个宏来自动拼装 cmd不要自己手动写数字// 无参数命令 #define _IO(type, nr) // 用户 → 内核写数据(IOW: Input to kernel) #define _IOW(type, nr, size) // 内核 → 用户读数据(IOR: Output to user) #define _IOR(type, nr, size) // 双向读写 #define _IOWR(type, nr, size)这里就是简单的操作LED所以只拼接设备类型和一个简单的命令/*定义设备类型*/ #define MAGIC_NUM X /*定义命令*/ #define LED_ON 0 #define LED_OFF 1 /*拼接命令*/ #define CMD_LED_ON _IO(MAGIC_NUM,LED_ON) #define CMD_LED_OFF _IO(MAGIC_NUM,LED_OFF)修改驱动匹配执行函数static int probe(struct platform_device * pdev) { int ret 0; /*查找设备节点硬编码路径查找/test_gpioled设备树节点*/ struct device_node * dev_node of_find_node_by_path(/test_gpioled); if(NULL dev_node) { printk(of_find_node_by_path not found /test_gpioled\n); return -1; } /*从设备树节点读取名为led-gpio属性中第0个gpio信息得到内核全局GPIO编号*/ gpio_led of_get_named_gpio(dev_node,led-gpio,0); /*向内核申请对应编号的GPIO资源命名为led1防止其他驱动占用该引脚*/ gpio_request(gpio_led,led1); /*设置GPIO为输出模式初始输出高电平LED默认熄灭假设低电平点亮*/ gpio_direction_output(gpio_led,1); /*注册misc杂项设备自动在/dev下创建设备文件供应用层ioctl操作*/ ret misc_register(misc); if(ret 0) goto err_register; printk(led_init ********************\n); return ret; err_register: /*misc注册失败注销杂项设备*/ misc_deregister(misc); printk(led_init failed********************\n); return ret; }最后修改用户应用层代码#include stdio.h #include fcntl.h #include unistd.h #include sys/ioctl.h #define MAGIC_NUM X #define LED_ON 0 #define LED_OFF 1 #define CMD_LED_ON _IO(MAGIC_NUM,LED_ON) #define CMD_LED_OFF _IO(MAGIC_NUM,LED_OFF) int main(int argc, const char *argv[]) { /*打开驱动生成的设备文件/dev/led1读写方式*/ int fd open(/dev/led1,O_RDWR); if(fd 0) { perror(open led1); return -1; // 打开失败退出 } while(1) { /*发送CMD_LED_ON命令驱动ioctl回调收到命令点亮LED第三个参数arg这里无用传0*/ ioctl(fd,CMD_LED_ON,0); sleep(1); //保持点亮1秒 /*发送CMD_LED_OFF命令驱动ioctl回调收到命令熄灭LED*/ ioctl(fd,CMD_LED_OFF,0); sleep(1); //保持熄灭1秒 } close(fd); return 0; }
网站建设高端定制企业官网