本帖最后由 robe.zhang 于 2021-5-29 14:28 编辑
【ALINXAXU2CGB试用】GPIO 按键视频 + linux 驱动源码分析
以下是笔者最终的测试电路:包含了以往两篇文章提到的 EMIO GPIO 按键,AXI GPIO按键 key1,key2 用的是 EMIO GPIO 按键 key3,key4 用的是 AXI GPIO 按键
设备树也做了调整,包含了以往两篇文章提到的三种GPIO按键配置: Key1 配置为中断方式 EMIO GPIO按键 Key2 配置为轮询方式 EMIO GPIO按键 Key3,key4 配置为轮询方式AXI GPIO 按键
重新编译后重启生效,运行视频: https://v.youku.com/v_show/id_XNTE2MTM1NDc0MA==.html 按键1,对应键盘 r 键 按键2,对应键盘 o 键 按键3,对应键盘 t 键 按键4,对应键盘 enter 键 笔者按 1,2,2,3,4,输入ubuntu 用户名:root,并回车 再次按 1,2,2,3,4,输入ubuntu 密码:root,并回车 正常登录进系统了。 按键支持 repeat,按住按键不送,会重复输入键值
Linux 源码分析: 驱动代码位于内核 drivers\input 目录中: 核心代码位于一下文件中: input.c #input 子系统核心代码 input-compat.c # 32位兼容代码 input-mt.c #多点触控代码 input-poller.c # 轮询代码 ff-core.c #力反馈代码
看 input.c 代码:
这个是 input 子系统核心层,在驱动初始化之前已经运行了。 2491行:注册 input class类
2497行:初始化 proc 文件系统
Input devices 对应的fops
Open 函数,使用了一个 seq_operations,使用序列文件的方法操作文件
Input handlers 对应的fops,也是用序列文件方法操作
以上是文件系统的内容,再具体点是 procfs 文件系统的内容,自己详细了解一下
2501行:注册字符设备,一个循环注册字符设备
注册时候找到 chrdevs 中的第 13项,就是input 系统的对应的字符设备,填充 structchar_device_struct 结构体,保存在 chrdevs 中
Chrdevs 是个机构体数组,第 13项用于 input 子系统,next 成员下保存每一个 input 字符设备指针
从这里能看出来,所有的字符设备,主设备号相同就全部存在于 Chrdevs 的第n项(n是主设备号),不同此设备号的字符设备结构体指针保存在 next 指针中,形成一个链表。 Input 设备注册之前,chrdevs[13]结构体要构建好,以便后续注册 input 设备。此处就是构建chrdev[13] 结构体,此代码先于设备驱动运行。
核心层还提供了一下 API EXPORT_SYMBOL(input_free_minor); EXPORT_SYMBOL(input_get_new_minor); # 申请释放 minor 次设备号 EXPORT_SYMBOL(input_register_handle); EXPORT_SYMBOL(input_unregister_handle); # 注册,注销 handle
注册handle结尾,运行handler->start
EXPORT_SYMBOL(input_register_handler); EXPORT_SYMBOL(input_unregister_handler); # 注册,注销handler EXPORT_SYMBOL(input_handler_for_each_handle);
EXPORT_SYMBOL(input_register_device); EXPORT_SYMBOL(input_unregister_device); # 注册,注销input device
注册input device 函数 2180行,计算 packet 大小 2199,2202 行,设置获取键值,设置键值的函数 2207行,添加 dev 设备 2224行,attach handler input device 注册函数就这么多内容
EXPORT_SYMBOL(input_enable_softrepeat); # 使能重复输入
EXPORT_SYMBOL(input_set_capability); # 设置位 EXPORT_SYMBOL(input_set_timestamp); EXPORT_SYMBOL(input_set_capability); # 设置,获取时间戳
EXPORT_SYMBOL(devm_input_allocate_device); EXPORT_SYMBOL(input_free_device); # 分配释放input_dev 结构体
devm_input_device_match 函数好简单,直接比较两个指针是不是相等
分配 input device,dev类是刚才的 input_class,类型是
EXPORT_SYMBOL(input_open_device); EXPORT_SYMBOL(input_close_device); EXPORT_SYMBOL(input_flush_device); # 打开,关闭,冲刷input_device 有 open 运行 open函数,有 poller运行 poller并开启轮询工作队列
Close ,flush 更简单
EXPORT_SYMBOL(input_release_device); # 释放 input_device EXPORT_SYMBOL(input_grab_device); EXPORT_SYMBOL(input_set_abs_params); EXPORT_SYMBOL(input_alloc_absinfo); # abs 设备的设置,比如触控板等
EXPORT_SYMBOL(input_inject_event); EXPORT_SYMBOL(input_event); # 发送事件 两个发送事件函数都调用了 input_handle_event
添加时间戳,调用 input_pass_values 发送
调用 input_to_handler
看 drivers\input\evdev.c 中的events 函数的实现
最终是传递事件,发送信号,唤醒进程去处理的。异步处理
核心层也实现了 autorepeat,使用时钟实现的
input_register_device 函数中 2196行,input_enable_softrepeat 初始化的
上报事件,设置定时器,过一会继续上报事件,实现 softrepeat
核心层就这么多,类似一个库,做了一些 input 基础的初始化,提供了一些接口给其他驱动调用
看gpio-keys驱动,这个是中断方式的gpio-keys驱动 :驱动代码位于 drivers\input\keyboard\gpio_keys.c文件中
774 行:如果 pdata 不存在就去解析设备树 780行,分配 ddata 787行,分配 keymap 793行,分配 input dev,下面是初始化input 825 行,是个循环,设置 gpio 按键,主要是分析子节点,设置gpio,中断,delay work 851行,注册 input dev 858行,休眠唤醒初始化
按键工作在中断模式,来了中断就上报事件,延迟一会就释放按键,释放也会上报
还有一个轮询模式的Gpio 按键驱动:代码位于drivers\input\keyboard\gpio_keys_polled.c 文件中
代码基本类似,360行,input_register_polled_device注册 polled_device 函数中 307 初始化 delay work 为 input_polled_device_work
input_polled_device_work 调用了 dev->poll后继续加入工作队列,继续轮询
dev->poll 在驱动 probe时候被初始化为 gpio_keys_polled_poll,
gpio_keys_polled_poll 的功能就是轮询所有按键,上报事件
轮询方式的 gpio 驱动工作方式就是轮训一编所有按键,加入工作队列,延迟一会,再次轮询再次延迟,不停循环
input_register_polled_device 函数第 314行,input_open_polled_device
一旦打开设备,就开始上报事件,加入工作队列,开始轮询了
关闭设备,就取消工作队列,不再轮询
按键驱动,核心是input子系统,也涉及到了gpio,中断,timer,work,文件系统等等,看驱动需要点内核基础,操作系统基础,需要一定的内核代码阅读量,内核各种api都要混个脸熟,需要一定的积累,需要分析大的框架和项目代码的能力,要求还是挺高的 有人总想投机,手把手教,包教包会,不存在的,没有速成法哈。驱动是内核级别的编程,在子系统框架下编程,学内核学框架就会了。Input 子系统看懂,是用好 gpio 按键驱动的基本功之一。
|