尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Linux驱动08-input子系统框架和platform驱动框架

Linux驱动08-input子系统框架和platform驱动框架 一、input子系统1.1 input子系统介绍①input 子系统是 Linux 内核中专门管理输入设备的框架与 pinctrl 和 gpio 子系统类似都是针对特定设备类型设计的。常见的输入设备包括按键、键盘、鼠标和触摸屏等它们的功能特性各不相同按键和键盘主要用于传递按键信息而鼠标和触摸屏则负责传输坐标数据。这些差异导致应用层的处理方式也有所区别。对于驱动开发者而言无需关注应用层实现只需按照规范上报相应的输入事件即可。其对应的子系统框架如下所示② input 子系统采用分层架构包含驱动层、核心层和事件处理层最终为用户空间提供可访问的设备节点。所有输入设备都以文件形式呈现供用户应用程序调用。各层级功能分工如下驱动层实现具体输入设备的驱动程序如按键驱动负责向内核层上报输入事件。核心层承上启下为驱动层提供设备注册和操作接口并通知事件层处理输入事件。事件层主要负责与用户空间的交互操作。1.2 input 子系统相关函数接口函数名功能描述devm_input_allocate_device输入设备分配函数自动分配input_dev结构体内存并在设备注销时自动释放资源input_allocate_device输入设备分配函数需手动调用input_free_device()释放资源input_register_device将输入设备注册到Linux输入子系统input_unregister_device注销输入设备从输入子系统中移除设备input_mt_init_slots初始化多点触控的槽位设置设备支持的触摸点数量并配置MT协议类型input_mt_slot指定当前上报的触摸点槽位IDinput_mt_report_slot_state报告槽位状态触摸/抬起和工具类型手指、笔等input_mt_report_pointer_emulation处理指针模拟状态当追踪的触摸点多余上报数量时通知用户层优化触摸板多点手势处理input_report_abs上报绝对坐标事件如触摸点的X/Y坐标input_set_abs_params配置绝对坐标轴的参数范围、精度等设置轴的最小值、最大值、模糊值和平坦值input_sync上报同步事件标志一组事件上报结束input_set_capability声明输入设备支持的事件类型和具体事件码如按键、坐标等input_event向输入子系统核心层上报一个原始输入事件input_report_key封装input_event专用于上报按键事件按下/释放/长按__set_bit位图设置位操作非原子操作建议优先使用原子操作set_bit()__clear_bit位图清除位操作非原子操作1.3 input子系统实现按键驱动实现步骤① 设备树增添以下内容vi arch/arm/boot/dts/imx6ull-alientek-emmc.dts / putekey { #address-cells 1; #size-cells 1; compatible pute-key; status okay; pinctrl-names default; pinctrl-0 pinctrl_ptkey; gpio-key gpio1 18 GPIO_ACTIVE_LOW; interrupt-parent gpio1; interrupts 18 IRQ_TYPE_EDGE_FALLING; }; iomuxc { pinctrl_ptkey: ptkeygrp { fsl,pins MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0x10B0 ; }; } 编译设备树make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- dtbs 把设备树拷贝到tftpboot目录下cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ~/tftpboot/② drv 端1所有 input 子系统的设备主设备号统一为 13不再需要 misc_dev 和 fops 的配置。使用 input 子系统时只需注册一个 input 设备input_dev 结构体即代表 input 设备。struct input_dev { const char *name; const char *phys; const char *uniq; struct input_id id; unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */ unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */ unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */ unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */ unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */ unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */ unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* sound 有关的位图 */ unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */ unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */ .. bool devres_managed; };2使用 input_allocate_device 函数来申请一个 input_dev结构体变量struct input_dev *input_allocate_device(void) 参数无。 返回值 申请到的 input_dev。3初始化重要的成员 pdev-name key_input;4evbit 表示输入事件类型#define EV_SYN 0x00 /* 同步事件 */ #define EV_KEY 0x01 /* 按键事件 */ #define EV_REL 0x02 /* 相对坐标事件 */ #define EV_ABS 0x03 /* 绝对坐标事件 */ #define EV_MSC 0x04 /* 杂项(其他)事件 */ #define EV_SW 0x05 /* 开关事件 */ #define EV_LED 0x11 /* LED */ #define EV_SND 0x12 /* sound(声音) */ #define EV_REP 0x14 /* 重复事件 */ #define EV_FF 0x15 /* 压力事件 */ #define EV_PWR 0x16 /* 电源事件 */ #define EV_FF_STATUS 0x17 /* 压力状态事件 */keybit 表示按键事件使用的位图#define KEY_RESERVED 0 #define KEY_ESC 1 #define KEY_1 2 #define KEY_2 3 #define KEY_3 4 #define KEY_4 5 #define KEY_5 6 #define KEY_6 7 #define KEY_7 8 #define KEY_8 9 #define KEY_9 10 #define KEY_0 11 .. #define BTN_TRIGGER_HAPPY39 0x2e6 #define BTN_TRIGGER_HAPPY40 0x2e75input_set_capability(pdev, EV_KEY, KEY_0); 配置输入设备的按键能力指定按键类型和键值。6input_register_device(pdev); 将自定义的输入设备注册到内核中。7of_find_node_by_path(/putekey); 从设备树中查找指定路径的节点。8of_get_named_gpio(pnode, gpio-key, 0); 从设备树节点中获取指定名称的GPIO编号。9gpio_request(key_gpio, gpio_for_key); 向内核申请独占使用从设备树解析得到的key_gpio引脚资源防止多个驱动同时操作同一个GPIO引发冲突。10gpio_direction_input(key_gpio); 将GPIO引脚配置为输入模式。11of_irq_get(pnode, 0); 通过gpio 获得对应的中断号。12request_irq(key_irq_no, key_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, key_irq, NULL); 注册中断服务函数配置为上升沿和下降沿触发。13在中断服务函数中使用定时器进行消抖处理确保能检测按键的按下和释放事件若仅配置单边缘触发可能导致事件漏报。14在定时器回调函数中通过input_event()上报按键的按下和释放事件按键操作必然包含按下和释放两个动作。15input_sync()表示一组输入数据上报完成16资源释放流程del_timer()free_irq() gpio_free()of_node_put()最后 input_unregister_device()input_free_device()③ app端#include linux/input.h struct input_event { struct timeval time; // 事件的事件 __u16 type; // 事件的类型 __u16 code; // 对应的 键值 __s32 value; // 按键按下或释放 };④ 具体代码//drv.c #include asm-generic/errno-base.h #include asm-generic/gpio.h #include asm/atomic.h #include asm/delay.h #include asm/gpio.h #include asm/io.h #include asm/uaccess.h #include linux/device.h #include linux/export.h #include linux/fs.h #include linux/interrupt.h #include linux/input.h #include linux/irqreturn.h #include linux/jiffies.h #include linux/key.h #include linux/of_irq.h #include linux/printk.h #include linux/sysfs.h #include linux/types.h #include linux/wait.h #include linux/workqueue.h #include linux/init.h #include linux/module.h #include linux/miscdevice.h #include linux/of.h #include linux/of_address.h #include linux/of_gpio.h #include linux/sched.h #include linux/time.h #include linux/timer.h #include linux/delay.h #include linux/input.h static struct input_dev *pdev; static struct device_node *pnode; static int key_gpio; static int key_irq_no; // static wait_queue_head_t wait_que; static struct work_struct work_que; static struct timer_list mytimer; irqreturn_t key_handler(int num, void *arg) { //static struct work_struct work_que; schedule_work(work_que); return IRQ_HANDLED; } void work_handle(struct work_struct *work) { unsigned long expires jiffies msecs_to_jiffies(10); mod_timer(mytimer, expires); } void timer_handle(unsigned long arg) { if (0 gpio_get_value(key_gpio)) { // key press input_event(pdev, EV_KEY, KEY_0, 1); } else { // key relese input_event(pdev, EV_KEY, KEY_0, 0); } input_sync(pdev); } static int __init ptkey_init(void) { int ret 0; pdev input_allocate_device(); if (NULL pdev) { pr_err(input_allocate_device error\n); return -ENOMEM; } pdev-name key_input; // 设置 按键类型 set_bit(EV_KEY, pdev-evbit); // 设置 多次上报 set_bit(EV_REP, pdev-evbit); // 按键分配 key_value set_bit(KEY_0, pdev-keybit); input_set_capability(pdev, EV_KEY, KEY_0); ret input_register_device(pdev); if (ret 0) { pr_err(input_register_device error\n); goto del_dev; } pnode of_find_node_by_path(/putekey); if (NULL pnode) { pr_err(of_find_node_by_path error\n); ret -ENOMEM; goto un_reg_dev; } key_gpio of_get_named_gpio(pnode, gpio-key, 0); if (key_gpio 0) { pr_err(of_get_named_gpio error,errno:%d\n, key_gpio); ret key_gpio; goto del_node; } ret gpio_request(key_gpio, gpio_for_key); if (ret) { pr_err(gpio_request error,errno:%d\n, ret); goto del_node; } ret gpio_direction_input(key_gpio); if (ret) { pr_err(gpio_direction_input error,errno:%d\n, ret); goto del_gpio; } // 从设备树中取出 引进对应的中断号 key_irq_no of_irq_get(pnode, 0); if (key_irq_no 0) { ret key_irq_no; pr_err(of_irq_get error,errno:%d\n, ret); goto del_gpio; } ret request_irq(key_irq_no, key_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, key_irq, NULL); if (ret) { pr_err(request_irq error,errno:%d\n, ret); goto del_gpio; } // init_waitqueue_head(wait_que); INIT_WORK(work_que, work_handle); init_timer(mytimer); // mytimer.expires 0; mytimer.function timer_handle; return 0; del_gpio: gpio_free(key_gpio); del_node: of_node_put(pnode); un_reg_dev: input_unregister_device(pdev); del_dev: input_free_device(pdev); return ret; } static void __exit key_exit(void) { del_timer(mytimer); free_irq(key_irq_no, NULL); gpio_free(key_gpio); of_node_put(pnode); input_unregister_device(pdev); input_free_device(pdev); } module_exit(key_exit); module_init(ptkey_init); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(this drv is key test); MODULE_AUTHOR(YAS);main.c #include stdio.h #include stdlib.h #include unistd.h #include fcntl.h #include linux/input.h int main(int argc, char **argv) { int fd open(/dev/input/event1, O_RDWR); if (fd 0) { perror(open); return 1; } while (1) { struct input_event ev {0}; read(fd, ev, sizeof(ev)); printf(type:%d code:%d val:%d\n, ev.type, ev.code, ev.value); } close(fd); return 0; }二、platform 平台工程化驱动2.1 platform介绍Linux 驱动开发强调工程化与专业化内核需要对驱动进行统一管理这是驱动开发的最终形态。Linux 设备驱动特别注重软件的可重用性和跨平台能力。例如当多个平台都需要设备 A而 A 在不同硬件平台上的配置信息不同时传统做法是在驱动中编写大量条件判断来处理不同硬件平台的操作这种方式显然不够高效。为解决这个问题Linux 驱动模型逐渐发展出了以下架构设备存储硬件平台的寄存器信息及地址信息后来发展为设备树概念总线负责管理和匹配设备与驱动驱动通过总线与设备匹配成功后获取设备信息并执行设备操作对于 I2C、SPI 等具有物理总线的硬件这种模型很容易理解。但对于 ADC、网卡等没有物理总线连接的外设内核开发者设计了platform 总线架构也称为虚拟平台总线其组成如下设备端platform_device 类型通过设备树编写总线端platform_bus_type 类型由系统提供驱动端platform_driver 类型需要开发者编写在这种架构中设备端包含物理平台的寄存器信息及中断信息这些信息现在存储在设备树中内核会自动将其转换为 platform_device总线负责匹配设备与驱动匹配成功后执行驱动中 probe 函数指针指向的接口来加载驱动相比过去需要手动从设备树获取节点数据的方式这种总线模型能够自动完成匹配并获取节点数据大大提高了开发效率和代码的可维护性。2.2 platform相关函数接口函数名功能intplatform_driver_register(struct platform_driver *driver)向内核注册一个平台驱动将其绑定到platform虚拟总线上。注册时会触发设备与驱动的匹配流程基于ID表或设备树兼容性匹配成功后调用驱动的probe函数初始化设备。voidplatform_driver_unregister(struct platform_driver *drv)从内核注销平台驱动解除与所有绑定设备的关联。注销时会触发驱动的remove回调函数释放资源确保驱动模块可安全卸载。module_platform_driver宏定义用于自动生成模块的init和exit函数。内部调用platform_driver_register和platform_driver_unregister简化模块化驱动的代码编写。struct resource *platform_get_resource(struct platform_device *dev,unsigned int type,unsigned int num);从平台设备中获取硬件资源如内存、I/O地址。参数包括设备指针、资源类型IORESOURCE_MEM等和索引号。返回struct resource*指针失败时返回NULL。intplatform_get_irq(struct platform_device *dev,unsigned int num);获取平台设备的中断号。设备树中通过interrupts属性定义的中断会自动映射为Linux IRQ编号。非设备树需在资源中指定。voidplatform_set_drvdata(struct platform_device *pdev,void *data);将驱动私有数据指针存储到platform_device的dev.driver_data字段便于后续通过platform_get_drvdata读取。void *platform_get_drvdata(const struct platform_device *pdev);从platform_device中读取驱动私有数据指针通常用于probe或remove函数中访问设备特定信息。2.3 驱动和设备的匹配过程触发匹配的条件包括新增设备时自动触发匹配以及手动加载insmod驱动时同步触发匹配。2.3.1 新增设备时自动触发匹配过程内核启动解析设备树自动生成 platform_device1内核启动解压 dtbuboot 传递 dtb 基地址内核解析设备树所有节点从根节点开始遍历各级子节点。2 平台设备节点识别与创建对于普通节点具有compatible属性且不属于 I2C/SPI 等特殊总线内核会调用of_device_alloc和platform_device_add自动生成platform_device结构体填充dev.of_node指向设备树节点拷贝compatible字符串至设备结构解析reg、gpio、interrupt等资源并存入设备资源列表将该platform_device挂载至platform总线3总线自动触发匹配遍历总线维护两条链表总线驱动链表所有注册成功的驱动platform_driver总线设备链表所有设备platform_device匹配机制新增device时总线遍历所有已注册的driver进行匹配新增driver时总线遍历所有已存在的device进行匹配2.3.2手动加载insmod驱动时同步触发匹配手动加载 xxx.ko 驱动模块注册 platform_driver 流程1驱动模块加载阶段 (insmod)调用 platform_driver_register 完成注册初始化 driver 内嵌的 struct device_driver将驱动结构体加入 platform 总线的全局驱动链表int platform_driver_register (struct platform_driver *driver) struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; const struct platform_device_id *id_table; bool prevent_deferred_probe; }; struct platform_device_id { char name[PLATFORM_NAME_SIZE]; kernel_ulong_t driver_data; }; struct of_device_id { char name[32]; char type[32]; char compatible[128]; const void *data; };2总线匹配阶段总线获取新驱动后按优先级顺序遍历已注册的 platform_devicedriver_override 强制匹配设备指定需要匹配的驱动设备树中compatible 和驱动名匹配ACPI ID 匹配X86架构使用ARM不使用platform_driver id_table 匹配设备名与驱动名全等匹配兜底方案static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev to_platform_device(dev); struct platform_driver *pdrv to_platform_driver(drv); /* When driver_override is set, only bind to the matching driver */ if (pdev-driver_override) return !strcmp(pdev-driver_override, drv-name); /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; /* Then try to match against the id table */ if (pdrv-id_table) return platform_match_id(pdrv-id_table, pdev) ! NULL; /* fall-back to driver name match */ return (strcmp(pdev-name, drv-name) 0); }3匹配成功处理总线执行标准操作建立 dev-driver 与 drv-driver 的双向绑定调用驱动定义的 .probe 回调函数probe 函数执行硬件初始化解析设备树资源GPIO/IRQ/寄存器等申请硬件资源GPIO/注册中断/初始化定时器等注册设备input/misc 等分配私有设备结构体存入 dev-platform_data/driver_dataprobe 返回 0 表示成功负数则总线解除绑定4匹配失败处理若所有设备遍历完毕均未匹配驱动保留在总线链表等待后续新设备加载时会重新触发匹配如动态创建设备树节点2.4platform实现LED灯驱动① 修改设备树vi arch/arm/boot/dts/imx6ull-alientek-emmc.dts / puteled { #address-cells 1; #size-cells 1; compatible PT,pute-led; status okay; pinctrl-names default; pinctrl-0 pinctrl_ptled; gpio-led gpio1 3 GPIO_ACTIVE_LOW; }; iomuxc { pinctrl_ptled: ptledgrp { fsl,pins MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0X10B0 ; }; } 编译设备树make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- dtbs 把设备树拷贝到tftpboot目录下cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb ~/tftpboot/② 编写驱动#include asm-generic/errno-base.h #include asm-generic/gpio.h #include asm/gpio.h #include asm/uaccess.h #include linux/export.h #include linux/fs.h #include linux/gpio.h #include linux/gpio/consumer.h #include linux/leds.h #include linux/miscdevice.h #include linux/of.h #include linux/of_gpio.h #include linux/platform_device.h #include linux/init.h #include linux/module.h #define DEV_NAME ptled0 static int myprobe(struct platform_device *plat_dev); static int myremove(struct platform_device *plat_dev); static ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off); static ssize_t mywrite(struct file *f, const char __user *userdata, size_t size, loff_t *off); static int myopen(struct inode *inode, struct file *f); static int myrelease(struct inode *inode, struct file *f); struct device_node *pnode NULL; static int led_gpio 0; static struct of_device_id of_match_table[] {{.compatible PT,pute-led}, // 主要靠这个 匹配 驱动 {} }; static struct platform_device_id id_table[] {{ .name ptled, }, {} }; typedef enum { LED_STATE_OFF, LED_STATE_ON } LED_STATE; static LED_STATE led_states; static struct platform_driver plat_drv { .probe myprobe, .remove myremove, .driver { .name ptled, .owner THIS_MODULE, .of_match_table of_match_table, }, .id_table id_table, }; static struct file_operations fops { .owner THIS_MODULE, .open myopen, .release myrelease, .read myread, .write mywrite, }; static struct miscdevice misc_dev { .minor MISC_DYNAMIC_MINOR, .name DEV_NAME, .fops fops, }; ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off) { int ret 0; ret copy_to_user(userdata, led_states, sizeof(led_states)); if (ret) { pr_err( myread copy_to_user error, ret:%d\n, ret); return ret; } return sizeof(led_states); } ssize_t mywrite(struct file *f, const char __user *userdata, size_t size, loff_t *off) { int sw 0; int ret 0; ret copy_from_user(sw, userdata, size); if (ret) { pr_err( mywrite copy_from_user error, ret:%d\n, ret); return ret; } if (0 sw) // led off { gpio_set_value(led_gpio, 1); led_states LED_STATE_OFF; } else { gpio_set_value(led_gpio, 0); led_states LED_STATE_ON; } return size; } int myopen(struct inode *inode, struct file *f) { pr_info(open called\n); return 0; } int myrelease(struct inode *inode, struct file *f) { pr_info(close called\n); return 0; } //匹配成功 加载驱动 static int myprobe(struct platform_device *plat_dev) { int ret 0; ret misc_register(misc_dev); if (ret 0) { pr_err(misc_register error\n); return ret; } pnode of_find_node_by_path(/puteled); if (NULL pnode) { pr_err(of_find_node_by_path error\n); ret -ENODEV; goto del_misc; } led_gpio of_get_named_gpio(pnode, gpio-led, 0); if (led_gpio 0) { pr_err(of_get_named_gpio error\n); ret -ENODEV; goto del_pnode; } ret devm_gpio_request(misc_dev.this_device, led_gpio, led_gpio); if (ret 0) { pr_err(devm_gpio_request error,errno:%d\n, ret); goto del_pnode; } gpio_direction_output(led_gpio, 1); pr_info(pute led myprobe ok \n); return 0; del_pnode: of_node_put(pnode); del_misc: misc_deregister(misc_dev); return ret; } //卸载驱动 static int myremove(struct platform_device *plat_dev) { gpio_free(led_gpio); of_node_put(pnode); misc_deregister(misc_dev); return 0; } static int __init led_init(void) { int ret 0; ret platform_driver_register(plat_drv); if (ret 0) { pr_err(platform_driver_register error,errno :%d\n, ret); return ret; } return 0; } static void __exit led_exit(void) { platform_driver_unregister(plat_drv); return; } module_init(led_init); module_exit(led_exit); MODULE_LICENSE(GPL);③ 编写app#include stdio.h #include stdlib.h #include unistd.h #include fcntl.h int main(int argc, char **argv) { int fd open(/dev/ptled0, O_RDWR); if (fd 0) { perror(open); return 1; } char buf[10] {0}; int sw 0; int status 0; while (1) { sw 1; // led on write(fd, sw, sizeof(sw)); // read(fd, status, sizeof(status)); // printf( cmd led on, status :%d\n, status); sleep(1); sw 0; // led off write(fd, sw, sizeof(sw)); // read(fd, status, sizeof(status)); // printf( cmd led off, status :%d\n, status); sleep(1); } close(fd); return 0; }
返回列表