输入子系统:从input_event到多点触控的驱动层实现

发布时间:2026/7/25 5:47:05
输入子系统:从input_event到多点触控的驱动层实现 输入子系统从input_event到多点触控的驱动层实现一、场景痛点与技术挑战Linux输入子系统是用户与设备交互的桥梁。键盘、鼠标、触摸屏、游戏手柄都依赖它。但驱动开发者在实践中遇到大量痛点。多点触控协议理解困难。内核定义了Type A和Type B两种上报协议。Type A每次触摸独立上报完整数据包。Type B用slot机制追踪持续触摸点。两者切换逻辑不同开发者常混淆。input_event结构体字段含义模糊。type、code、value三字段的组合规则复杂。EV_ABS的code从ABS_X到ABS_MT_SLOT多达数十种。触摸屏上报时ABS和EV_SYN的顺序有严格要求。顺序错误导致上层收到残缺数据包。触摸坐标映射和校准容易出错。硬件坐标与屏幕坐标方向可能不一致。旋转90度时X/Y轴需要互换。多触点ID分配和回收逻辑容易出Bug。触点离开后ID未回收上层读到幽灵触点。二、核心原理与架构设计输入子系统分三层架构。驱动层负责硬件交互。从I2C或SPI总线读取触摸数据。解析硬件寄存器中的坐标、压力、触点数。将原始数据转换为input_event上报。核心层负责事件路由。input_core维护设备链表和handler链表。设备注册时匹配对应handler。事件从设备流经核心层到达handler。Handler层负责事件分发。evdev handler将事件写入字符设备节点。mousedev handler模拟鼠标协议。keyboard handler处理键盘事件到终端。用户空间通过read()从/dev/input/eventX获取数据。多点触控上报协议分两种。Type A协议无追踪。每次触摸完整上报所有触点。SYN_REPORT之前连续上报多个ABS_MT_PACKET。触点离开不做标记靠缺失判断。适合硬件不追踪触点ID的低端触摸屏。Type B协议有追踪。用ABS_MT_SLOT标记当前操作触点。ABS_MT_TRACKING_ID分配和回收触点。触点进入时分配新ID离开时ID设-1。剩余数据只在触点状态变化时上报。适合硬件追踪触点的高端触摸屏。三、生产级代码实现多点触控驱动Type B协议/* 基于Linux内核 drivers/input/touchscreen 简化实现 */ #include linux/module.h #include linux/input.h #include linux/input/mt.h #include linux/i2c.h #include linux/interrupt.h #include linux/delay.h #define MAX_TOUCH_POINTS 10 #define TOUCH_CHIP_REGS 0x00 struct ts_data { u16 x; u16 y; u8 pressure; u8 id; bool valid; }; struct mt_touch_dev { struct i2c_client *client; struct input_dev *input; struct ts_data points[MAX_TOUCH_POINTS]; int num_points; int max_slots; /* 触点ID到slot的映射表 */ int id_to_slot[MAX_TOUCH_POINTS]; /* slot使用状态跟踪 */ bool slot_used[MAX_TOUCH_POINTS]; }; /* 读取触摸芯片数据 */ static int mt_touch_read_data(struct mt_touch_dev *dev) { struct i2c_client *client dev-client; u8 buf[MAX_TOUCH_POINTS * 6 2]; int ret; ret i2c_smbus_read_i2c_block_data(client, TOUCH_CHIP_REGS, sizeof(buf), buf); if (ret 0) { dev_err(client-dev, I2C read failed: %d\n, ret); return ret; } dev-num_points buf[0] 0x0F; if (dev-num_points MAX_TOUCH_POINTS) dev-num_points MAX_TOUCH_POINTS; for (int i 0; i dev-num_points; i) { int offset 2 i * 6; dev-points[i].x (buf[offset] 8) | buf[offset 1]; dev-points[i].y (buf[offset 2] 8) | buf[offset 3]; dev-points[i].pressure buf[offset 4]; dev-points[i].id buf[offset 5] 0x0F; dev-points[i].valid true; } return 0; } /* 查找或分配slot */ static int mt_touch_assign_slot(struct mt_touch_dev *dev, int tracking_id) { /* 已有映射则直接返回 */ if (dev-id_to_slot[tracking_id] 0) return dev-id_to_slot[tracking_id]; /* 查找空闲slot */ for (int slot 0; slot dev-max_slots; slot) { if (!dev-slot_used[slot]) { dev-id_to_slot[tracking_id] slot; dev-slot_used[slot] true; return slot; } } /* 无空闲slot丢弃该触点 */ dev_warn(dev-client-dev, No free slot for id %d\n, tracking_id); return -1; } /* 释放slot */ static void mt_touch_release_slot(struct mt_touch_dev *dev, int tracking_id) { int slot dev-id_to_slot[tracking_id]; if (slot 0) { dev-slot_used[slot] false; dev-id_to_slot[tracking_id] -1; } } /* Type B协议上报触点数据 */ static void mt_touch_report_type_b(struct mt_touch_dev *dev) { struct input_dev *input dev-input; int slot; /* 上报当前有效触点 */ for (int i 0; i dev-num_points; i) { struct ts_data *pt dev-points[i]; slot mt_touch_assign_slot(dev, pt-id); if (slot 0) continue; input_mt_slot(input, slot); input_mt_report_slot_state(input, MT_TOOL_FINGER, true); input_report_abs(input, ABS_MT_POSITION_X, pt-x); input_report_abs(input, ABS_MT_POSITION_Y, pt-y); input_report_abs(input, ABS_MT_PRESSURE, pt-pressure); } /* 标记已离开的触点 */ for (int id 0; id MAX_TOUCH_POINTS; id) { bool still_present false; for (int i 0; i dev-num_points; i) { if (dev-points[i].id id) { still_present true; break; } } if (!still_present dev-id_to_slot[id] 0) { slot dev-id_to_slot[id]; input_mt_slot(input, slot); input_mt_report_slot_state(input, MT_TOOL_FINGER, false); mt_touch_release_slot(dev, id); } } input_mt_sync_frame(input); input_sync(input); } /* 中断处理函数 */ static irqreturn_t mt_touch_irq(int irq, void *data) { struct mt_touch_dev *dev data; if (mt_touch_read_data(dev) 0) return IRQ_HANDLED; mt_touch_report_type_b(dev); return IRQ_HANDLED; } /* 设备初始化 */ static int mt_touch_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct mt_touch_dev *dev; struct input_dev *input; int ret; dev devm_kzalloc(client-dev, sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; dev-client client; dev-max_slots MAX_TOUCH_POINTS; /* 初始化ID映射表 */ for (int i 0; i MAX_TOUCH_POINTS; i) { dev-id_to_slot[i] -1; dev-slot_used[i] false; } input devm_input_allocate_device(client-dev); if (!input) return -ENOMEM; input-name multi-touch-device; input-id.bustype BUS_I2C; /* 设置设备支持的事件类型 */ __set_bit(EV_ABS, input-evbit); __set_bit(EV_KEY, input-evbit); __set_bit(BTN_TOUCH, input-keybit); /* 设置ABS参数 */ input_set_abs_params(input, ABS_X, 0, 4096, 0, 0); input_set_abs_params(input, ABS_Y, 0, 4096, 0, 0); input_set_abs_params(input, ABS_MT_POSITION_X, 0, 4096, 0, 0); input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 4096, 0, 0); input_set_abs_params(input, ABS_MT_PRESSURE, 0, 255, 0, 0); input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, MAX_TOUCH_POINTS - 1, 0, 0); /* 初始化多点触控slot */ ret input_mt_init_slots(input, MAX_TOUCH_POINTS, INPUT_MT_DIRECT); if (ret) return ret; dev-input input; ret input_register_device(input); if (ret) return ret; /* 注册中断 */ ret devm_request_threaded_irq(client-dev, client-irq, NULL, mt_touch_irq, IRQF_ONESHOT | IRQF_TRIGGER_FALLING, mt-touch, dev); if (ret) { dev_err(client-dev, IRQ request failed: %d\n, ret); return ret; } i2c_set_clientdata(client, dev); return 0; } static const struct i2c_device_id mt_touch_id[] { { mt-touch-chip, 0 }, { } }; MODULE_DEVICE_TABLE(i2c, mt_touch_id); static struct i2c_driver mt_touch_driver { .driver { .name mt-touch, }, .probe mt_touch_probe, .id_table mt_touch_id, }; module_i2c_driver(mt_touch_driver); MODULE_AUTHOR(zhongyiren); MODULE_DESCRIPTION(Multi-touch driver with Type B protocol); MODULE_LICENSE(GPL);触摸坐标校准与映射触摸坐标校准工具 import numpy as np class TouchCalibrator: 四点校准算法 def __init__(self, screen_width: int, screen_height: int): self.screen_w screen_width self.screen_h screen_height def calibrate(self, raw_points: list, ref_points: list) - dict: raw_points: 4个硬件坐标 [(x,y), ...] ref_points: 4个屏幕坐标 [(x,y), ...] 返回: 仿射变换矩阵参数 # 构建方程组 Ax B # x a*x b*y c # y d*x e*y f A [] B [] for raw, ref in zip(raw_points, ref_points): rx, ry raw sx, sy ref A.append([rx, ry, 1, 0, 0, 0]) A.append([0, 0, 0, rx, ry, 1]) B.append(sx) B.append(sy) A np.array(A, dtypenp.float64) B np.array(B, dtypenp.float64) # 最小二乘求解 result, _, _, _ np.linalg.lstsq(A, B, rcondNone) return { a: round(result[0], 6), b: round(result[1], 6), c: round(result[2], 6), d: round(result[3], 6), e: round(result[4], 6), f: round(result[5], 6), } def transform(self, raw_x: int, raw_y: int, params: dict) - tuple: 将硬件坐标转换为屏幕坐标 sx params[a] * raw_x params[b] * raw_y params[c] sy params[d] * raw_x params[e] * raw_y params[f] sx max(0, min(self.screen_w, int(round(sx)))) sy max(0, min(self.screen_h, int(round(sy)))) return (sx, sy)四、性能优化与工程实践中断处理是触摸驱动的性能瓶颈。触摸中断频率可达100-200Hz。每帧数据包含5-10个触点的坐标。中断处理耗时过长导致数据丢帧。优化策略一使用threaded_irq。中断上半部仅唤醒线程。数据解析和上报在下半部线程执行。避免中断上下文中的长时间操作。IRQF_ONESHOT标志保证中断线程完成前不重新触发。优化策略二I2C读取批量优化。一次性读取整帧数据而非逐字节。i2c_smbus_read_i2c_block_data一次传输60字节。比逐寄存器读取减少90%的总线事务。I2C时钟从100kHz提升到400kHzFast Mode。优化策略三减少不必要的input_report。Type B协议只在触点状态变化时上报。稳定的触点只上报坐标变化。大幅减少input_sync的调用频率。触摸坐标校准实践。四点校准算法是工业标准。屏幕四角放置参考点。硬件坐标与屏幕坐标的仿射变换矩阵。校准参数存储在设备树或NVRAM中。旋转0/90/180/270度各有独立校准参数。Ghost触点防护。触摸芯片的固件质量参差不齐。偶发的虚假触点需要驱动层过滤。连续3帧以上出现的触点才上报。单帧触点直接丢弃避免误触。五、总结与技术提炼输入子系统三层架构职责清晰。驱动层读硬件数据核心层路由事件handler层分发到用户空间。Type B协议用slot追踪持续触点。ABS_MT_SLOT标记操作触点ABS_MT_TRACKING_ID分配和回收。触点离开ID设-1释放slot供新触点使用。id_to_slot映射表管理触点生命周期。新触点分配空闲slot离开时释放slot。映射表避免ID和slot的错配。input_mt_init_slots初始化slot结构。INPUT_MT_DIRECT模式直接映射坐标到屏幕。input_mt_sync_frame替代旧的input_mt_sync。I2C批量读取优化数据传输。一次block_read替代多次byte_read。threaded_irq避免中断上下文长时间操作。四点仿射校准解决坐标映射。硬件坐标与屏幕坐标的非线性偏差。旋转场景需要独立校准参数集。Ghost触点用帧数阈值过滤。