linux驱动之输入子系统
对于驱动开发者来说,对按键 触摸屏 鼠标等设备分别进行文件操作显得很繁琐,他们具有一些相同的规律,即内核负责记录数据,应用负责读取数据,因此,内核开发者为了简化驱动开发者的工作,特地创造了输入子系统。
输入子系统分为两层,一个是驱动子系统,一个是文件操作子系统。驱动子系统依旧由驱动开发者完成,当发生一个事件时,驱动向子系统核心发送一个事件报告,子系统核心将这个报告交给文件操作子系统,由后者将具体的事件封装成一个input_event结构体,并传递给应用程序,这样应用程序就知道硬件发生了什么。
以触摸屏驱动程序为例,大致介绍输入子系统的使用。
触摸屏驱动程序
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/delay.h>struct input_dev *ts_dev;
struct timer_list ts_timer;struct adc_regs {unsigned long adccon;unsigned long adctsc;unsigned long adcdly;unsigned long adcdat0;unsigned long adcdat1;unsigned long adcupdn;unsigned long adcclrint;unsigned long reserved;unsigned long adcclrintpndnup;
};static struct adc_regs *adc_regs;//等待按下
static void wait_for_pen_down(void)
{adc_regs->adctsc = 0xd3;
}//等待抬起
static void wait_for_pen_up(void)
{adc_regs->adctsc = 0x1d3;
}//xy坐标自动转换
static void measure_xy_mode(void)
{adc_regs->adctsc = ((1<<7) | (1<<6) | (1<<4) | (1<<3) | (1<<2));
}//启动adc转换
static void start_adc(void)
{adc_regs->adccon |= (1<<0);
}//触摸中断
irqreturn_t ts_irq(int irq, void *dev_id)
{unsigned long data0, data1;int down;data0 = adc_regs->adcdat0;data1 = adc_regs->adcdat1;down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开if(!down){wait_for_pen_down();//等待按下input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);input_sync(ts_dev);//报告失败事件}else{measure_xy_mode();//xy自动转换start_adc();//开启adc转换}
// adc_regs->adcclrint = 0;//清除adc中断标志adc_regs->adcclrintpndnup = 0;//清除触摸中断标志return IRQ_HANDLED;
}
//adc中断
irqreturn_t adc_irq(int irq, void *dev_id)
{mod_timer(&ts_timer, jiffies + HZ / 100);//10mswait_for_pen_up();//等待抬起adc_regs->adcclrint = 0;//清除adc中断标志
// adc_regs->adcclrintpndnup = 0;//清除触摸中断标志return IRQ_HANDLED;
}
//定时器中断
void ts_timer_function(unsigned long data)
{int x, y;unsigned long data0, data1;int down;data0 = adc_regs->adcdat0;data1 = adc_regs->adcdat1;down = (!(data0 & (1 << 15))) && (!(data1 & (1 << 15)));//只有xy都没有值时down才会为0,表示松开if(!down){wait_for_pen_down();//等待按下input_event(ts_dev, EV_ABS, ABS_PRESSURE, 0);input_event(ts_dev, EV_KEY, BTN_TOUCH, 0);input_sync(ts_dev);//报告失败事件}else{x = data0 & 0xfff;y = data1 & 0xfff;input_event(ts_dev, EV_ABS, ABS_X, x);input_event(ts_dev, EV_ABS, ABS_Y, y);input_event(ts_dev, EV_ABS, ABS_PRESSURE, 1);input_event(ts_dev, EV_KEY, BTN_TOUCH, 1);input_sync(ts_dev);measure_xy_mode();//xy自动转换start_adc();//开启adc转换}
}int ts_init(void)
{struct clk *clk;ts_dev = input_allocate_device();//分配输入结构体set_bit(EV_KEY, ts_dev->evbit);//key事件set_bit(EV_ABS, ts_dev->evbit);//绝对地址set_bit(BTN_TOUCH, ts_dev->keybit);//触摸input_set_abs_params(ts_dev, ABS_X, 0, 0xfff, 0, 0);//设置x坐标的范围input_set_abs_params(ts_dev, ABS_Y, 0, 0xfff, 0, 0);input_set_abs_params(ts_dev, ABS_PRESSURE, 0, 1, 0, 0);input_register_device(ts_dev);//注册设备adc_regs = ioremap(0x7e00b000, sizeof(struct adc_regs));//虚拟地址映射clk = clk_get(NULL, "adc");clk_enable(clk);adc_regs->adccon = (1 << 16) | (1 << 14) | (65 << 6);adc_regs->adcdly = 0xffff;adc_regs->adcclrintpndnup = 0;//清除触摸中断标志,该标志为1时进入中断,必须软件清零request_irq(IRQ_TC, ts_irq, IRQF_SHARED, "ts", NULL);//申请触摸中断request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, "adc", NULL);//adc转换完成中断init_timer(&ts_timer);ts_timer.function = ts_timer_function;add_timer(&ts_timer);wait_for_pen_down();//等待按下return 0;
}void ts_exit(void)
{del_timer(&ts_timer);free_irq(IRQ_TC, NULL);free_irq(IRQ_ADC, NULL);iounmap(adc_regs);input_unregister_device(ts_dev);input_free_device(ts_dev);
}module_init(ts_init);
module_exit(ts_exit);
MODULE_LICENSE("GPL");
输入子系统分析
大致看一下一个输入设备结构体包含哪些成员,其余成员暂且不介绍
struct input_dev
{unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//所支持的事件类型,包括EV_KEY按键 EV_ABS绝对地址,触摸屏 EV_REL相对地址,鼠标unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//对于按键事件,有哪些选项struct device dev;//设备结构体struct list_head node;
};
首先,看一下设备分配函数
struct input_dev *input_allocate_device(void)
{struct input_dev *dev;dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);if (dev) {dev->dev.type = &input_dev_type;dev->dev.class = &input_class;device_initialize(&dev->dev);mutex_init(&dev->mutex);spin_lock_init(&dev->event_lock);INIT_LIST_HEAD(&dev->h_list);INIT_LIST_HEAD(&dev->node);__module_get(THIS_MODULE);}return dev;
}
一般情况下,都会采用设备嵌套的结构,而分配函数也就是对input_dev->dev的初始化以及自身相关的初始化
接下来,看看注册函数
int input_register_device(struct input_dev *dev)
{list_add_tail(&dev->node, &input_dev_list);//将结点添加到input_dev链表list_for_each_entry(handler, &input_handler_list, node)//遍历链表input_attach_handler(dev, handler);
}
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{const struct input_device_id *id;id = input_match_device(handler, dev);error = handler->connect(handler, dev, id);
}
在进行一番比较后,如果设备和处理函数匹配,则连接。input_handler结构体如下,该结构体负责处理对应input_dev的输入事件。
struct input_handler {void *private;void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);bool (*match)(struct input_handler *handler, struct input_dev *dev);int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);void (*disconnect)(struct input_handle *handle);void (*start)(struct input_handle *handle);const struct file_operations *fops;int minor;const char *name;const struct input_device_id *id_table;struct list_head h_list;struct list_head node;
}
接下来,介绍一下输入事件
事件结构体如下
struct input_event {struct timeval time;__u16 type;//事件类型__u16 code;//事件的具体参数类型__s32 value;//参数值
};
发送事件函数如下
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{if (is_event_supported(type, dev->evbit, EV_MAX)) {//测试是否设置了该事件,一般如下设置set_bit(EV_KEY, ts_dev->evbit);input_handle_event(dev, type, code, value);}
}
下面以input_event(ts_dev, EV_ABS, ABS_X, x)具体分析input_handle_event函数
static void input_handle_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{switch (type) //首先判断类型,为EV_ABS{case EV_ABS:if (is_event_supported(code, dev->absbit, ABS_MAX))//测试EV_ABS事件是否设置了该参数disposition = input_handle_abs_event(dev, code, &value);break;}
}
继续调用input_handle_abs_event函数
static int input_handle_abs_event(struct input_dev *dev,unsigned int code, int *pval)
{if (is_mt_event && dev->slot != input_abs_get_val(dev, ABS_MT_SLOT)) {input_abs_set_val(dev, ABS_MT_SLOT, dev->slot);input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);}
}
如下是关于ABS_X的定义以及结构体input_absinfo
#define ABS_MT_SLOT 0x2f /* MT slot being modified */
#define ABS_X 0x00
struct input_absinfo {__s32 value;__s32 minimum;__s32 maximum;__s32 fuzz;__s32 flat;__s32 resolution;
};
最后一步,在input_pass_event(dev, EV_ABS, ABS_MT_SLOT, dev->slot);中调用input_handler的event函数,将消息整合成input_event结构体的形式发送给用户,由用户读取信息。具体代码没有深入研究,猜测类似于read、write之类的函数。
触摸屏相关分析
如图所示,是电阻型触摸屏获取坐标值的原理。
对于触摸屏驱动来说,有两个中断,分别是IRQ_TC和IRQ_ADC,前者是触摸中断,后者是ADC转换完成中断。
当进入IRQ_TC中断时,启动adc转换,adc转换的结果x坐标、y坐标分别存放在ADCDAT0和ADCDAT1寄存器中,进入ADC中断后将事件上报给输入子系统。
linux驱动之输入子系统相关推荐
- 《Linux 驱动:输入子系统》
前言 输入子系统作为一个模块存在,向上,为用户层提供调用接口:向下,为驱动层程序提供统一的注册接口.这样,就能够使输入设备的事件通过输入子系统发送给用户层应用程序,用户层应用程序也可以通过输入子系统通 ...
- Linux的input输入子系统:设备驱动之按键驱动
环境:kernel-2.6.30.4,arm-linux-gcc-4.3.3,目标板TQ2440 一.设备层驱动程序: #include <linux/module.h> #include ...
- ARM Linux内核Input输入子系统浅解
--以触摸屏驱动为例 第一章.了解linux input子系统 Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些设备 ...
- 【linux驱动】USB子系统分析
本文针对Linux内核下USB子系统进行分析,主要会涉及一下几个方面: USB基础知识:介绍USB设备相关的基础知识 Linux USB子系统分析:分析USB系统框架,USB HCD/ROOT HUB ...
- Linux输入事件类型EV_SW,Linux的input输入子系统:总体框架
一.input输入子系统总体框架 Linux输入子系统将输入驱动抽象为三层:设备驱动层.核心层.事件处理层. 设备驱动层:将底层的硬件输入事件转化为统一事件形式,向输入核心(Input Core)汇报 ...
- 【Linux驱动】input子系统与按键驱动
input子系统架构总览 在网上能找到一些关于input子系统架构相关的示意图,大体表达的意思都差不多. linux输入子系统(linux input subsystem)从上到下由三层实现,分别为: ...
- linux按键驱动中的结构体,linux 驱动之input子系统(gpio-keys)实现
1.概述 Gpio-keys 是基于input子系统实现的一个通用按键驱动,该驱动也符合linux驱动实现模型,即driver和device分离模型.一般按键驱动,都是基于gpio-keys进行开发的 ...
- linux驱动之I2C子系统
目录 一.I2C基本原理 二.linux内核的I2C子系统详解 1.linux内核的I2C驱动框架总览 2.I2C子系统的4个关键结构体(kernel/include/linux/i2c.h) 3.关 ...
- Linux驱动分析——I2C子系统
stm32mp157 盘古开发板 Linux内核版本4.19 目录 1.朱有鹏老师视频笔记 2.I2C子系统的4个关键结构体 3.关键文件 4.i2c-core.c初步分析 4.1.smbus代 ...
- linux驱动之i2c子系统mpu6050设备驱动
以下是mpu6050简单的驱动实现,mpu6050是I2C接口的6轴传感器,可以作为字符设备注册到内核,本代码运行环境是3.4.2内核,4.3.2版本的编译链,12.04版本的Ubuntu,硬件环境是 ...
最新文章
- C++读取文件名,过滤文件路径及后缀
- 机器学习的优化目标、期望最大化(Expectation-Maximum, EM)算法、期望最大化(EM)和梯度下降对比
- 原创经验:微信小程序开发总结
- linux的开机网络设置
- 视频需求超平常数 10 倍,却节省了 60% 的 IT 成本投入是一种什么样的体验?
- 上交大计算机导师俞凯,WLA青科聊高考①|偶像剧“男主”、上海交大教授俞凯的学霸人生...
- 徐波 博士 计算机,徐波教授:医工联合促进智能肿瘤学发展——探秘肿瘤精准治疗中的AI技术...
- MSSQL 2008 企业管理器打开命令
- 【OS学习笔记】二十 保护模式六:保户模式下操作系统内核如何加载用户程序并运行 对应的汇编代码之主引导扇区程序
- JAVA能做flash游戏吗_FLASH+JAVA开发实时网络游戏 (转:闪客帝国)
- Web App 前端构建(纯净版)
- 数据库-如何快速创建连接字符串
- android apk下载 安装程序,Android之APK的下载安装
- 软件测试的重要性与必要性,软件测试的目的和意义
- python生成exe文件与exe文件的反编译
- python安装出错0x80072ee7_python3.6.4安装错误0x80072efd
- 翁恺慕课C语言作业 第三章时区换算
- qsnctf 哥哥打篮球 wp
- 华硕ASUS 笔记本 改WIN7 BIOS 设置详解
- 粒子物理中的粒子鉴别
热门文章
- Ubuntu18.04添加vim配置
- VSFTPD 上传文件 200 227 553错误
- dw怎么做html鼠标变化,dw鼠标经过单元格变色 DW 鼠标经过表格 背景变色
- C语言 枚举——熄灯问题
- 4.4 Recovery和Removal分析
- 解决esp8266无法连接手机和电脑热点的问题
- 论文阅读: (ECCV 2022) Content-Oriented Learned Image Compression
- 积分商城系统积分兑换运营开源架构
- 计算机用户中的配置文件,我的电脑的用户配置文件中多了一个未知的帐户 – 手机爱问...
- 三年前端路:分享我的工作经验与学习经历