\qquad 输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机制是:
\qquad 根据这个过程,可以认为只是中断、读键值等是与设备有关,输入事件的缓冲区管理及字符设备驱动的ifle_operations接口则对输入设备是通用的。因此,内核设计了输入子系统。由核心层处理公共的工作。
Linux内核 输入子系统的运行框架如图:
\qquad 上方的图从上到下有应用层,内核层,硬件层。对于应用层而言,用户主要操作的是设备文件如图上所示有
\qquad 对于内核空间,整个input驱动系统的框架,又分为三个功能层。如下:
struct input_handler
)去处理。内核维护着多个事件处理器对象,每个input_handler对象专门处理一类事件,所有产生同类事件的设备驱动共用同一个handler。struct input_dev
对象。input_register_device
),为事件处理层提供输入事件驱动的接口;通知事件处理层对事件进行处理。\qquad 驱动框架,把所有来自硬件的数据统一成struct input_event。这样,从设备驱动开始,只要把数据整成event向上传递给了input核心层。在这个标准化的基础上,事件驱动层以及核心层都由系统编写完成,开发者只需要针对某个输入设备编写对应的input设备驱动程序就可以了,这样极大简化了驱动程序的编写工作量。
init或probe函数中:
解释:
\qquad 如果在传统硬编码的驱动程序,则在init()函数里写下这三个步骤。如果是在platform等总线式驱动编程里,则在probe()函数里实现这三个步骤。
解释:
\qquad 同样,如果在硬编码的驱动程序里,在exit()函数中完成这两个步骤。如果是在platform等总线式驱动编程里,则在probe()函数里实现这两个操作。
两种事件上报方式:
事件上报的主要函数如下:
\qquad 这部分可以在阅读完其它部分后,再来看这个总结,会有更清晰的认识。
1、input子系统是一个针对输入类型硬件的高度标准化的驱动框架。
2、在充分的归纳驱动行为的基础上,把驱动由3层1事件来实现。 这3层从上到下就是(事件处理层、核心层、设备驱动层),而一个事件指的就是struct input_event 。
3、开发者只需要编写设备驱动层对应的驱动程序即可。事件处理层以及核心层的处理,由内核实现。而串起这些内核 处理的核心又是事件类型(EVENT TYPE)与事件码(EVENT CODE)。这个的指定,由开发者用input_event()函数去指定。
4、所以开发者在驱动中只做了简单的几件事:
5、使用者在应用层用文件标准IO操作,读出struct input_event事件数据。
主要的接口API介绍:
初始化时涉及到的主要函数,写在init()或probe()函数中:
struct input_dev *input_allocate_device(void)//创建对象
static inline void set_bit(unsigned long nr , volatile unsigned long *m)//设置事件类型
void input_set_abs_params(struct input_dev *dev,unsigned int axis,int min,int max,int fuzz,int flat)
int input_register_device(struct input_dev *dev)//注册input设备到内核
退出时涉及到的主要函数,写在exit()或release()函数中:
void input_unregister_device(struct input_dev *dev)
void input_free_device(struct input_dev *dev)
上报事件:
/*上报事件*/
void input_event(struct input_dev *,unsigned int t,unsigned int c,int v)
void input_report_key(struct input_dev *,unsigned int c,int v) //上报按键事件
void input_report_abs(struct input_dev *,unsigned int c,int v)//上报绝对坐标事件
//上面这些函数只是把数据准备好,每调一次只准备一个值,一个分量。多次调用后才能完成准备。
void input_sync(struct input_dev *)//在上述这些函数完成数据分量的整理后,统一由本函数完成最后的完整上报。
应用层获得数据类型:
struct input_event {
struct timeval time; // 时间戳
__u16 type; // 事件类型
__u16 code; // 哪个分值
__s32 value; // 具体值
};
\qquad 由于前面软件的分层概念已说过,linux把输入设备的数据都标准化到struct input_event中了。所以驱动把该对象传递给input核心,核心再上传,这样一步涉向上。最后应用层接收数据,也是接收该对像。
头文件:include/linux/input.h
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)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
#define to_input_dev(d) container_of(d, struct input_dev, dev)
这个结构体 input_dev 表示 Linux 输入子系统中的一个输入设备。它定义在 include/linux/input.h 头文件中。
input_dev 包含的主要成员有:
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];语句的含义:
解释位图的操作
input_dev对象中的evbit成员是一个位图(bitmap),用于标记该输入设备支持的事件类型。
当驱动调用set_bit(EV_KEY, input_dev->evbit);时,就是在evbit位图中设置EV_KEY类型事件对应的位。
例如,EV_KEY事件类型对应的位可能是第3位,那么set_bit(EV_KEY, input_dev->evbit);就相当于:
input_dev->evbit[0] |= (1 << 3); 设置input_dev->evbit的第3位
这表示该输入设备支持EV_KEY类型的事件。
之后,输入子系统在解析该输入设备的事件时,会检查evbit位图中是否设置了对应事件类型的位,如果设置了,则确认设备支持该事件类型,并继续解析事件代码来判断具体事件。
例如,收到输入事件(EV_KEY, KEY_A, 1)时,输入子系统会先检查evbit位图是否设置了EV_KEY类型事件的位,如果设置了,则确认设备支持键盘事件,然后再根据KEY_A代码解析该事件为KEY_A按下。
如果evbit位图中没有设置EV_KEY事件类型对应的位,那么输入子系统将无法解析该事件,因为设备自身并不支持该类型事件。
头文件: /include/uapi/linux/input.h
#include
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
input_event 包含的主要成员有:
举个简单例子:
当有一个按键按下时,会产生一个 input_event 事件,其中:
当按键弹起时,会产生一个新的 input_event 事件,type和code与上一个事件相同,但value设为0,表示弹起。
此外,输入子系统还会根据时间戳time来判断事件的先后顺序和处理事件的频率等。
所以,input_event 结构体对描述一个输入事件来说,已包含了所有必须的信息。它是Linux输入子系统识别和处理各种输入事件的关键。
总之,input_event 和 input_dev 这两个结构体,是输入子系统的两大基石,前者描述输入事件,后者描述输入设备,二者共同实现了Linux下的输入处理机制。
这些宏定义,会用在input_event函数的type参数 ,以及struct input_event的type成员
定义在/include/uapi/linux/input.h头文件里。
/*
* Event types
*/
#define EV_SYN 0x00 同步事件,用于同步输入设备的时间轴。
#define EV_KEY 0x01 按键事件, comme PC键盘、触摸屏上的按键等。
#define EV_REL 0x02 相对运动事件,如鼠标移动。
#define EV_ABS 0x03 绝对坐标事件,如触摸屏、手写板等。
#define EV_MSC 0x04 杂项事件,不适合其他类型的事件。
#define EV_SW 0x05 开关事件,如笔记本电脑的盖子开关事件。
#define EV_LED 0x11 示灯事件,如Num Lock 指示灯状态变化。
#define EV_SND 0x12 声音事件,如蜂鸣器发出的声音信号。
#define EV_REP 0x14 重复事件,某事件连续多次产生,用来减少事件数量。
#define EV_FF 0x15 力回馈事件,如游戏手柄产生的振动事件。
#define EV_PWR 0x16 电源事件,如AC/Battery 状态变化事件。
#define EV_FF_STATUS 0x17 力回馈设备状态事件。
#define EV_MAX 0x1f 定义事件类型的最大值。
#define EV_CNT (EV_MAX+1)
这些宏定义在 include/uapi/linux/input.h 头文件里。
主要用在input_event()函数的code 参数,以及struct input_event的code成员。以及关联着struct input_dev中的成员(如:evbit成员、keybit成员、relbit成员、absbit等)。这里只列出部分,详细的直接参考头文件里的详细定义。
Key 及 Button的事件码定义
#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 KEY_MINUS 12
#define KEY_EQUAL 13
#define KEY_BACKSPACE 14
#define KEY_TAB 15
#define KEY_Q 16
#define KEY_W 17
#define KEY_E 18
#define KEY_R 19
#define KEY_T 20
#define KEY_Y 21
#define KEY_U 22
#define KEY_I 23
#define KEY_O 24
#define KEY_P 25
#define KEY_LEFTBRACE 26
#define KEY_RIGHTBRACE 27
#define KEY_ENTER 28
#define KEY_LEFTCTRL 29
#define KEY_A 30
#define KEY_S 31
#define KEY_D 32
...............
/* We avoid low common keys in module aliases so they don't get huge. */
#define KEY_MIN_INTERESTING KEY_MUTE
#define KEY_MAX 0x2ff
#define KEY_CNT (KEY_MAX+1)
相对轴的事件码定义
*
* Relative axes
*/
#define REL_X 0x00
#define REL_Y 0x01
#define REL_Z 0x02
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)
绝对轴的事件码定义
/*
* Absolute axes
*/
#define ABS_X 0x00 X轴坐标值
#define ABS_Y 0x01 Y轴坐标值
#define ABS_Z 0x02 Z轴坐标值
#define ABS_RX 0x03 X轴相对坐标(增量)值
#define ABS_RY 0x04 Y轴相对坐标(增量)值
#define ABS_RZ 0x05 Z轴相对坐标(增量)值
#define ABS_THROTTLE 0x06 油门位置值
#define ABS_RUDDER 0x07 方向舵位置值
#define ABS_WHEEL 0x08 轮坐标值
#define ABS_GAS 0x09 加速度值
#define ABS_BRAKE 0x0a 刹车值
#define ABS_HAT0X 0x10 帽子开关0的X轴值
#define ABS_HAT0Y 0x11 帽子开关0的Y轴值
#define ABS_HAT1X 0x12 帽子开关1的X轴值
#define ABS_HAT1Y 0x13 帽子开关1的Y轴值
#define ABS_HAT2X 0x14
#define ABS_HAT2Y 0x15
#define ABS_HAT3X 0x16
#define ABS_HAT3Y 0x17
#define ABS_PRESSURE 0x18 压力值
#define ABS_DISTANCE 0x19 距离值
#define ABS_TILT_X 0x1a X轴倾斜值
#define ABS_TILT_Y 0x1b Y轴倾斜值
#define ABS_TOOL_WIDTH 0x1c 工具宽度值
#define ABS_VOLUME 0x20 音量值
#define ABS_MISC 0x28 其它杂项值
#define ABS_MT_SLOT 0x2f /* MT slot being modified */修改的多点触摸事件的插槽索引
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */椭圆触摸区域的长轴
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */椭圆触摸区域的短轴
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */逼近的椭圆触摸区域的长轴
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */逼近的椭圆触摸区域的短轴
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */椭圆触摸区域的方向
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */触摸中心的X坐标
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */ 触摸中心的Y坐标
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */触摸设备的类型
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */把一组数据包分组为一个整体
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */初始化触摸点的唯一ID
#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */触摸区域的压力值
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */触摸悬停的距离
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */工具中心的X坐标
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */工具中心的Y坐标
#define ABS_MAX 0x3f 绝对坐标事件代码的最大值
#define ABS_CNT (ABS_MAX+1) 绝对坐标事件代码的数量
这些定义代表Linux内核输入子系统中绝对坐标事件类型(EV_ABS)对应的事件代码。
这些绝对坐标事件代码用来表示各种输入设备的坐标数据或类似数据,例如:
#include
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;
struct list_head h_node;
};
这个结构体表示input子系统中的一个input handle。它包含以下几个字段:
#include
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
void (*events)(struct input_handle *handle,
const struct input_value *vals, unsigned int count);
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);
bool legacy_minors;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
};
它包含以下几个字段:
include/linux/mod_devicetable.h
struct input_device_id {
kernel_ulong_t flags;
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
kernel_ulong_t driver_info;
};
这个结构体表示input设备的ID信息,用来匹配input handler和input device。它包含以下几个字段:
static const struct input_device_id my_input_table[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_BUS,
.bustype = BUS_USB,
.vendor = 0x1234,
.product = 0x5678,
},
}
这样这个handler就只会匹配厂商ID为0x1234、产品ID为0x5678的USB输入设备。
所以通过将各个input handler的id_table设计得错落不重叠,可以使不同的input device匹配到不同的handler进行处理。
input_allocate_device函数是input子系统提供的函数,用于分配一个新的input device对象。
原型:
#include < >
struct input_dev *input_allocate_device(void);
它只有一个void类型的形参,并返回一个新的struct input_dev对象。
用法:
当一个驱动程序检测到一个输入设备时,它需要先通过input_allocate_device()函数分配一个input_dev对象,然后设置这个对象的各个字段来描述这个输入设备,最后通过input_register_device()函数向input子系统注册这个设备。
举个例子:
struct input_dev *input_dev;
input_dev = input_allocate_device();
if (!input_dev)
return -ENOMEM;
input_dev->name = "Foo"; // 设置name为"Foo"
input_dev->phys = "foo/input0"; // 设置物理ID
input_dev->id.bustype = BUS_HOST; // 设置总线类型
...
input_register_device(input_dev); // 注册该设备
这个例子展示了如何分配一个新input device对象,设置它的字段,然后注册该设备到input子系统。
另外需要注意,调用input_register_device()成功注册设备后,内核会采取input_dev对象的所有权,驱动不需要也不应该再释放这个对象。所以驱动在调用input_allocate_device()后需要牢记这个对象,而在调用input_register_device()成功注册后就可以忘记这个对象了。
input_register_device()函数用来向input子系统注册一个input device对象。
#include
int __must_check input_register_device(struct input_dev *);
用法:
当一个驱动程序通过input_allocate_device()函数分配一个新的input_dev对象并设置完毕后,需要调用input_register_device()函数来注册该设备,否则input子系统不会知道该设备的存在,也就不会对其产生的输入事件进行处理。
registration的工作主要是将该input_dev对象添加到input_dev_list链表中,并为其分配一个input minor number,以便应用程序可以通过该minor number来访问该输入设备。
举个例子:
struct input_dev *input_dev;
input_dev = input_allocate_device();
/* 设置input_dev各个字段 ... */
if (input_register_device(input_dev)) {
/* 注册失败 */
input_free_device(input_dev);
/* 错误处理 */
}
如代码所示,如果注册失败,需要通过input_free_device()释放input_dev对象;如果注册成功,则无需释放该对象,内核会自动释放。
需要注意,注册成功后,驱动程序应该保留input_dev->dev.parent指向的设备对象的引用,以免该对象过早被销毁。
另外,一个驱动至多只能注册一个input device对象。如果需要注册多个,需要为每个设备分别编写一个独立的驱动。
input_free_device()函数用来释放一个input device对象。
#include
void input_free_device(struct input_dev *dev);
参数
用法:
当注册一个input device对象失败时,需要通过input_free_device()函数释放该对象以防内存泄露。
示例:*
struct input_dev *input_dev;
input_dev = input_allocate_device();
/* 设置input_dev各个字段 ... */
if (input_register_device(input_dev)) {
/* 注册失败 */
input_free_device(input_dev);
/* 错误处理 */
}
如代码所示,如果注册失败需要调用input_free_device()释放input_dev对象。
需要注意,如果registration成功,则不需要也不应该调用input_free_device(),因为内核会自己管理这个input_dev对象的生命周期。
只有在对象分配后注册before未成功的情况下,才需要手动调用input_free_device()释放input device对象。
input_unregister_device()函数用来从input子系统注销一个已经注册的input device对象。
#include
void input_unregister_device(struct input_dev *);
参数
用法:
当一个输入设备被移除时,驱动程序需要调用input_unregister_device()函数来注销对应的input device对象,以通知input子系统 stop 处理该设备产生的输入事件。
示例:
if (!input_dev->dev.parent) { /* 检查设备对象是否仍存在 */
input_unregister_device(input_dev);
}
如代码所示,在设备对象被删除之前,驱动需要调用input_unregister_device()注销对应的input_dev对象。
成功调用input_unregister_device()后,input子系统会从其管理的input_dev_list中移除该对象,并释放相关资源。
input_event()函数用来通过已注册的input device对象向input子系统提交一个输入事件。
#include
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
参数:
用法:
当检测到输入设备产生一个输入事件时,驱动程序需要通过input_event()函数提交该事件到input子系统,以便让相关的应用程序获得该事件的通知。
示例:
if (get_usb_keycode(&usb_dev, &scancode)) {
input_event(input_dev, EV_KEY, scancode, 1);
input_sync(input_dev);
}
如代码所示,当检测到USB键盘产生一个键盘事件时,驱动通过input_event()提交一个EV_KEY类型的事件,键码为scancode,值为1(表示按下), 然后调用input_sync()同步事件。
需要注意,仅调用input_event()是不够的,还需要及时调用input_sync()来同步事件,否则事件不会被处理。
另外,input_event()可以被调用在中断上下文,因此需要确保其执行时间尽量短,避免延迟中断响应。
set_bit()函数用来给一个长整型变量中的某一位设置1。
原型:
static inline void set_bit(unsigned long nr, volatile unsigned long *m)
参数:
该函数定义在
返回值: 无
用法:
set_bit()函数通常用来标记某种状态。它通过设置一个长整型变量中的某一位来表示一个布尔值。
示例:
unsigned long flags;
set_bit(0, &flags); /* 设置flags的第0位 */
set_bit(3, &flags); /* 设置flags的第3位 */
如上代码所示,设置了flags变量的第0和第3位。
此后,可以使用test_bit()函数来测试某位是否置位,即检查对应状态是否置TRUE。
if (test_bit(0, &flags))
/* flags的第0位已置位,值为TRUE */
if (!test_bit(2, &flags))
/* flags的第2位未置位,值为FALSE */
常见的使用场景有:
test_bit()
可以方便地检查状态。input_set_abs_params()函数用于为输入设备的一个绝对坐标轴设置参数。
#include
void input_set_abs_params(struct input_dev *dev, unsigned int axis,
int min, int max, int fuzz, int flat);
参数:
用法:
input_set_abs_params()函数通常在输入设备的probe()函数中调用,用于为设备的一个绝对坐标轴设置相关参数,包括:
设置这些参数后,内核输入子系统会根据它们来判断和处理该轴的输入事件:
举例来说,对于一触摸屏,我们可以设置:
input_set_abs_params(tp_dev, ABS_X, 0, 1024, 10, 40);
input_set_abs_params(tp_dev, ABS_Y, 0, 768, 10, 40);
这会设置x轴和y轴的范围(0-1024, 0-768),x/y轴的噪声过滤值为10,死区范围为40。
内核会据此来判断触摸屏的x/y坐标输入事件,实现对噪点和震动的过滤,提高触摸精度。
所以,input_set_abs_params()函数为Linux输入子系统提供了定制绝对坐标轴参数的方法。通过设置坐标范围、噪声过滤和死区值等,我们可以定制内核如何判断和处理该轴的输入事件,这在输入设备的开发中是非常有用的功能。
input_sync()函数用来同步先前通过input_event()提交的输入事件。
#include
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
参数
用法:
驱动程序在通过input_event()提交一系列输入事件后,需要调用input_sync()来同步这些事件,否则事件不会被送往应用程序。
input_sync()的实现很简单,它只是提交了一个EV_SYN类型的SYN_REPORT事件,值为0。但这个事件起到同步前面事件的作用。
示例:
input_event(input_dev, EV_KEY, KEY_A, 1);
input_event(input_dev, EV_KEY, KEY_B, 1);
input_sync(input_dev); /* 同步事件 */
如代码所示,两次事件提交后调用input_sync()来同步,否则这两个事件不会被处理。
所以,input_sync()函数的使用时机是在提交一系列输入事件后,在事件序列结束处调用,用来同步整个事件序列,使其被送往应用程序处理。
如果不调用input_sync(),先前提交的事件不会产生任何效果,这点开发者需要注意。
另外,input_sync()可以在中断上下文调用,但也应该保证尽量短时间执行,避免影响中断响应。
input_event()函数用来向input子系统报告一个输入事件。
原型:
#include
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
**参数 **
input_event(input_dev, EV_KEY, KEY_A, 1); /* A键按下 */
input_event(input_dev, EV_KEY, KEY_A, 0); /* A键弹起 */
input_event(input_dev, EV_REL, REL_X, -5); /* X轴左移5个单位 */
如上代码所示:
报告了一个A键按下和弹起事件,以及X轴左移5的相对移动事件。
输入子系统会解析这3个事件,并将EV_KEY类型的A键事件映射为KEY_A相应事件,从而通知上层软件A键的变化;将REL_X事件交由鼠标驱动处理,达到鼠标左移5像素的效果。
所以,input_event()函数是输入驱动与输入子系统沟通的桥梁,驱动通过调用该函数将外设产生的各种输入事件报告给输入子系统,让其将这些事件转化为系统事件后交由上层软件进行处理。
这是实现一个输入驱动的关键,没有input_event()函数,驱动就无法将输入事件传达给操作系统。
另外,需要注意的是,在调用input_event()前,必须已通过input_register_device()函数注册对应的输入设备input_dev,否则会引起内核异常。
input_report_key()函数用来方便地提交一个键盘事件。
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
参数
input_report_key(input_dev, KEY_A, 1); /* A键按下 */
...
input_report_key(input_dev, KEY_A, 0); /* A键释放 */
input_sync(input_dev); /* 同步事件 */
如代码所示,按下A键时value为1,释放A键时value为0,然后调用input_sync()同步事件。
所以,input_report_key()函数的使用时机是在检测到键盘按键事件时,用来方便地提交一个EV_KEY类型的键盘事件到input子系统。使用后同样需要调用input_sync()来同步事件。
相比直接调用input_event(),input_report_key()可以更清晰地表示键盘事件的按下和释放,使用更方便,所以是首选的输入键盘事件的接口。
input_report_rel()函数用来方便地提交一个相对轴事件。
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_REL, code, value);
}
参数
input_report_rel(input_dev, REL_X, -5); /* X轴减少5 */
input_report_rel(input_dev, REL_Y, 10); /* Y轴增加10 */
input_sync(input_dev); /* 同步事件 */
如代码所示,检测到X轴减少5、Y轴增加10时,调用input_report_rel()提交两个EV_REL事件,然后input_sync()同步。
所以,input_report_rel()函数的使用时机是在检测到相对轴输入设备的相对轴值变化时,用来方便地提交EV_REL类型的相对轴事件到input子系统。使用后同样需要调用input_sync()来同步事件。
input_report_abs()函数用来方便地提交一个绝对轴事件。
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_ABS, code, value);
}
参数
input_report_abs(input_dev, ABS_X, 100); /* X轴位置为100 */
input_report_abs(input_dev, ABS_Y, 200); /* Y轴位置为200 */
input_report_abs(input_dev, ABS_PRESSURE, 50); /* pressure为50 */
input_sync(input_dev); /* 同步事件 */
如代码所示,检测到X轴为100、Y轴为200、pressure为50时,调用input_report_abs()提交三个EV_ABS事件,然后input_sync()同步。
所以,input_report_abs()函数的使用时机是在检测到绝对轴输入设备的绝对轴值变化时,用来方便地提交EV_ABS类型的绝对轴事件到input子系统。使用后同样需要调用input_sync()来同步事件。
input_set_abs_params()函数用来设置一个绝对轴的范围参数。
#include
void input_set_abs_params(struct input_dev *dev, unsigned int axis,
int min, int max, int fuzz, int flat);
参数
用法:
在注册一个绝对轴输入设备前,需要通过input_set_abs_params()函数设置每个绝对轴的参数,如最小值、最大值、误差范围等,这些参数对标定绝对轴输入非常重要。
示例:
input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 767, 0, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, 255, 0, 0);
input_register_device(input_dev);
如代码所示,在注册input_dev之前设置了ABS_X、ABS_Y和ABS_PRESSURE三个绝对轴的参数,然后再注册该设备。
如果不设置这些参数,input子系统将使用默认值,这可能导致无法正确处理输入事件。
所以,input_set_abs_params()函数的使用时机是在注册绝对轴输入设备前,用来设置每个绝对轴的重要参数,如最小值、最大值、误差范围等。
正确设置这些参数对于标定绝对轴输入非常重要,否则无法识别和处理精确的输入事件。
另外,这些参数可以在注册后 runtime 重新调整,以实现标定、校准等功能。
input_alloc_absinfo()函数用来为一个input_dev对象分配absinfo数组。
原型:
#include
void input_alloc_absinfo(struct input_dev *dev);
参数
用法:
对于支持绝对轴的输入设备,input子系统会为其维护一个absinfo数组,每个元素对应一个绝对轴,用来保存相关参数信息。
调用input_alloc_absinfo()函数可以为一个input_dev对象分配这个absinfo数组。如果该对象已经拥有一个absinfo数组,则此函数不会执行任何操作。
一般来说,驱动不需要直接调用input_alloc_absinfo(),因为:
struct input_dev *input_dev;
input_dev = input_allocate_device(); /* 会自动分配absinfo数组 */
input_set_abs_params(input_dev, ABS_X, 0, 1024, 0, 0);
input_set_abs_params(input_dev, ABS_Y, 0, 768, 0, 0);
input_register_device(input_dev); /* 也会分配absinfo数组 */
如上代码所示,input_allocate_device()和input_register_device()都会自动分配absinfo数组,所以驱动不需要显式调用input_alloc_absinfo()。
根据input子系统的框架,对比硬编码方式所编写的按键驱动(见前几章),input框架的驱动结构将会变得简洁很多。
1、首先,硬编码方式下的file_operations结构体所关联的所有open、read、write等操作函数都不需要了,系统已帮我们完成。
2、不需要创建设备号,不需要创建设备文件。
/*************************************************************************
> File Name: key-input.c
> Author: maohm
> Created Time: Sun 11 Jun 2023 09:37:19 AM CST
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct key_dev_t{
struct input_dev *pidev;
int gpio;
int irqno;
};
struct key_dev_t *pgdev = NULL;
irqreturn_t key_irq_handle(int no , void *arg){
struct key_dev_t *pdev =(struct key_dev_t *)arg;
int status1 = gpio_get_value(pdev->gpio);
printk("driver:gpio -> %d , value = %d \n", pdev->gpio, status1);
/*中断处理程序里面实现对事件类型及事件代码和值的提交。*/
if (status1){
input_event(pdev->pidev , EV_KEY , KEY_2 , 0);
}else{
input_event(pdev->pidev, EV_KEY , KEY_2 ,1);
}
/*完成事件的同步,发送事件给input核心*/
input_sync(pdev->pidev);
return IRQ_HANDLED;
}
int __init key2_init(void){
int ret = 0;
/*读取设备树节点信息*/
struct device_node *pnode = NULL ; //设备树结点变量
pnode = of_find_node_by_path("/key2_node");
if (!pnode){
printk("driver:fine node key2_node failed\n");
return -1;
}
/*创建全局的数据结构变量*/
pgdev = (struct key_dev_t*)kmalloc(sizeof(struct key_dev_t),GFP_KERNEL);
if (!pgdev){
printk("driver: kmalloc for struct key_dev_t failed\n");
return -1;
}
/*读取设备树属性*/
pgdev->gpio = of_get_named_gpio(pnode , "key2_gpio", 0);
pgdev->irqno = irq_of_parse_and_map(pnode,0);
/*创建设备对象input_dev,并注册*/
pgdev->pidev = input_allocate_device();
set_bit(EV_KEY , pgdev->pidev->evbit); //设置事件类型
set_bit(KEY_2 , pgdev->pidev->keybit); //设置事件码
ret = input_register_device(pgdev->pidev);
ret = request_irq(pgdev->irqno, key_irq_handle , IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING , "key2" , pgdev);
if (ret){
printk("driver:request_irq failed\n");
input_unregister_device(pgdev->pidev);
input_free_device(pgdev->pidev);
kfree(pgdev);
pgdev = NULL;
return -1;
}
return 0;
}
void __exit key2_exit(void){
free_irq(pgdev->irqno , pgdev);
input_unregister_device(pgdev->pidev);
input_free_device(pgdev->pidev);
kfree(pgdev);
pgdev = NULL;
}
module_init(key2_init);
module_exit(key2_exit);
MODULE_LICENSE("GPL");
/*************************************************************************
> File Name: read-key.c
> Author: maohm
> Created Time: Sun 11 Jun 2023 04:28:48 PM CST
************************************************************************/
#include
#include
#include
#include
#include
#include
int main(int argc , char **argv){
int fd = -1;
struct input_event evt;
if (argc < 2){
printf("app:Argument is too few . \n");
return -1;
}
//打开设备文件,该文件需到/dev/input/目录中去查找
fd = open(argv[1] , O_RDONLY);
if (fd<0){
printf("app:open %s failed.\n",argv[1]);
return -1;
}
while (1){
read(fd, &evt , sizeof(evt));
printf("app : event type is %d\n", (int)evt.type);
printf("app : event code is %d\n", (int )evt.code);
printf("app : event value is %d \n ",(int)evt.value);
}
close(fd);
fd = -1;
return 0;
}
CUR_DIR := $(shell pwd)
ifeq ($(filename),)
ifeq ($(KERNELRELEASE), )
ifeq ($(ARCH),arm)
KERNEL_DIR := /home/mao/linux/linux-3.14
ROOTFS_DIR := /opt/4412/rootfs
else
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
endif
all :
$(MAKE) -C $(KERNEL_DIR) M=$(CUR_DIR) modules
install:
#$(MAKE) -C $(KERNEL_DIR) M=$(CUR_DIR) INSTALL_MOD_PATH=$(ROOTFS_DIR) modules_install
cp *.ko $(ROOTFS_DIR)/drv -rf
clean :
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
else
obj-m += key-input.o
endif
else
ifeq ($(ARCH),arm)
GCC_DIR := ~/linux/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi-
ROOTFSDIR := /opt/4412/rootfs/work
else
GCC_DIR = /usr/bin/
ROOTFSDIR = $(shell pwd)/work/
endif
all:
$(GCC_DIR)gcc $(filename).c -o $(filename).elf
sudo mv -f $(filename).elf $(ROOTFSDIR)
cp -rf ./load.sh $(ROOTFSDIR)
endif
struct delayed_work结构体用来表示一个延迟工作(delayed work),它用于Linux内核定时执行某个工作的场景。
定义在
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
它包含两个成员:
主要操作函数有:
struct delayed_work my_work;
INIT_DELAYED_WORK(&my_work, my_work_func);
queue_delayed_work(wq, &my_work, msecs_to_jiffies(2000));
示例:
void my_work_func(struct work_struct *work)
{
pr_info("工作函数被调用!\n");
}
/* 延迟2秒执行my_work_func() */
queue_delayed_work(wq, &my_work, msecs_to_jiffies(2000));
/* 1秒后取消该延迟工作 */
schedule_timeout_interruptible(msecs_to_jiffies(1000));
cancel_delayed_work(&my_work);
delayed_work机制使内核可以在指定的延迟后自动执行某段工作,这在需要定时操作或延迟触发的场景下非常有用。
所以,struct delayed_work用于表示一个需要延迟后执行的工作,包含定时器和实际工作两个部分;通过queue_delayed_work()入队并等待定时器超时后自动执行工作,cancel_delayed_work()可以取消一个延迟工作。
它实现了Linux内核的延迟作业机制,在需要定时或延迟执行某个操作的场景下非常实用。
work_struct结构体用来表示一个工作(work)。它包含一个工作函数和相关的数据,用于 Linux 内核实现工作队列(workqueue)机制。
#include
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
typedef void (*work_func_t)(struct work_struct *work);
主要成员:
typedef void (*work_func_t)(struct work_struct *work);
工作队列机制的主要步骤:
struct work_struct my_work;
INIT_WORK(&my_work, my_work_func);
queue_work(my_wq, &my_work); /* 入用户态工作队列 */
queue_work(system_wq, &my_work); /* 入内核工作队列 */
示例:
void my_work_func(struct work_struct *work)
{
pr_info("工作函数被调用!\n");
}
struct work_struct my_work;
INIT_WORK(&my_work, my_work_func);
/* 将work入queue,之后会自动执行my_work_func() */
queue_work(my_wq, &my_work);
work_struct实现了异步工作执行的机制:可以将需要运行的工作函数入队,稍后由内核自动提取并执行。
这避免了直接在调用线程中运行该函数可能带来的睡眠或调度延迟等问题。
#include
#define INIT_DELAYED_WORK(_work, _func) __INIT_DELAYED_WORK(_work, _func, 0)
功能伪代码为:
#define INIT_DELAYED_WORK(_work, _func) \
do { \
INIT_WORK(&(_work)->work, (_func)); \
init_timer(&(_work)->timer); \
(_work)->timer.function = delayed_work_timer_fn; \
(_work)->timer.data = (_work); \
} while (0)
它接受两个参数:
void (*_func)(struct work_struct *work);
它指向的函数接受一个struct work_struct *work参数,返回类型为void。该宏的作用是:
之后,可以调用queue_delayed_work()将该delayed_work入队,等待定时器超时后work对象会自动执行_func工作函数。
举例:
void my_work_func(struct work_struct *work)
{
pr_info("工作函数!\n");
}
struct delayed_work my_work;
INIT_DELAYED_WORK(&my_work, my_work_func); /* 初始化my_work */
queue_delayed_work(wq, &my_work, msecs_to_jiffies(2000)); /* 延迟2秒入队 */
如上,我们定义了一个delayed_work对象my_work,然后调用INIT_DELAYED_WORK宏初始化它,指定工作函数my_work_func。
之后,通过queue_delayed_work()将其入队,2秒后my_work_func()函数会被自动调用。
#include
static inline bool queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay)
{
return queue_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}
参数:
返回值:
用法:
queue_delayed_work()函数将一个延迟工作dwork入队,指定要延迟的时间delay后自动执行。步骤如下:
void my_work_func(struct work_struct *work)
{
pr_info("工作函数被调用!\n");
}
struct delayed_work my_work;
INIT_DELAYED_WORK(&my_work, my_work_func);
queue_delayed_work(wq, &my_work, msecs_to_jiffies(2000)); /* 延迟2秒入队 */
上述代码初始化了一个延迟工作my_work,指定其工作函数为my_work_func,然后通过queue_delayed_work()将其入队,延迟2秒后my_work_func()会被调用。
schedule_delayed_work()函数用来为一个延迟工作(delayed_work)安排延迟执行时间。
#include
static inline bool schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay)
{
return queue_delayed_work(system_wq, dwork, delay);
}
参数:
返回值:
用法:
schedule_delayed_work()函数用于将一个延迟工作dwork入队system_wq内核工作队列,指定delay时间后被执行。
具体步骤如下:
示例:
void my_work_func(struct work_struct *work)
{
pr_info("工作函数被调用!\n");
}
struct delayed_work my_work;
INIT_DELAYED_WORK(&my_work, my_work_func);
schedule_delayed_work(&my_work, msecs_to_jiffies(2000));
/* 延迟2秒将my_work添加到system_wq并执行 */
如上,我们初始化了一个延迟工作my_work,并通过schedule_delayed_work()函数将其添加到system_wq内核工作队列,2秒后my_work_func()会被调用。
schedule_timeout_interruptible()函数用来让出CPU,进入睡眠状态一定时间或直到被中断为止。
#include
signed long schedule_timeout_interruptible(signed long timeout);
参数:
返回值:
机制
schedule_timeout_interruptible()函数用于让出CPU,使调用线程进入睡眠状态一定时间。用法如下:
该函数用于主动让出CPU,进入睡眠一段时间。常见用途有:
示例:
timeout = schedule_timeout_interruptible(msecs_to_jiffies(2000));
/* 睡眠2秒,或者被信号中断唤醒 */
if (timeout > 0)
printk("提前被中断唤醒,剩余时间:%ld jiffies\n", timeout);
else
printk("2秒时间已到或被非信号中断唤醒\n");
如上,我们让调用线程睡眠2秒,如果在这期间收到信号并被中断唤醒,就打印剩余时间并提前返回,否则就打印睡眠已完成的提示。
cancel_delayed_work()函数用来取消一个延迟工作(delayed_work)。
#include
bool cancel_delayed_work(struct delayed_work *dwork);
参数:
返回值:
用法:
当一个延迟工作入队后,会在指定的延迟时间后被自动执行。如果在这段延迟时间内需要取消该工作,可以调用cancel_delayed_work()函数。
步骤如下:
示例:
void my_work_func(struct work_struct *work)
{
pr_info("工作函数!\n");
}
struct delayed_work my_work;
INIT_DELAYED_WORK(&my_work, my_work_func);
queue_delayed_work(wq, &my_work, msecs_to_jiffies(2000));
/* 1秒后取消该延迟工作 */
schedule_timeout_interruptible(msecs_to_jiffies(1000));
if (cancel_delayed_work(&my_work))
pr_info("取消成功!\n");
else
pr_info("无法取消!\n");
如上,我们初始化了一个延迟工作my_work, 2秒后会执行工作函数my_work_func。
但我们在1秒后调用cancel_delayed_work取消该工作,此时返回true表示成功取消。
如果我们的取消函数调用时间 > 2秒,就会返回false,表示my_work已超时执行,无法取消。
\qquad 利用上一章的【嵌入式环境下linux内核及驱动学习笔记-(15-1)例程】的第3.2节的实例。采用input框架来改写这个实例。
\qquad 由于这个实例是读取mpu6050输出的6个值。分别是X、Y、Z加整度和角速度,以及温度值。由于这些值是一次性输出,因此在INPUT框架中考虑的事件类型与事件码时,就不能用键事件EV_KEY了。正好可以用EV_ABS绝对值事件。绝对值事件。
\qquad 由于mpu6050的数值是一直在产生,如果读的频率远小于数据产生的速度,则可以不用中断方式触发事件处理,而是采用定时任务的方式。
\qquad 另外,从框架的结构上看,这个驱动同时使用了i2c驱动框架–用于mpu6050数据的读取;使用了input驱动框架–用于将数据事件提交。因此这个程序实际结合了这两个驱动框架的优势。即实现了总线式框架的driver与设备的分离,又不用开发者自已实现file_operations的操作函数。所以整个驱动实现变得很简洁,高效。
/*************************************************************************
> File Name: show-mpu.c
> Author: maohm
> Created Time: Sun 11 Jun 2023 05:08:04 PM CST
************************************************************************/
#include
#include
#include
#include
#include
#include
int main(int argc , char **argv){
int fd = -1;
struct input_event evt;
if(argc < 2){
printf("app : Argument is too few .\n");
return -1;
}
/*open device file */
fd = open(argv[1] , O_RDONLY );
if (fd < 0){
printf("app : open %s failed.\n",argv[1]);
return -1;
}
/*read mpu6050*/
while (1){
read(fd,&evt,sizeof(evt));
if (evt.type == EV_ABS){
switch(evt.code){
case ABS_X:
printf("app : Accel-x: %d \n" , evt.value);
break;
case ABS_Y:
printf("app : Accel-y: %d \n" , evt.value);
break;
case ABS_Z:
printf("app : Accel-z: %d \n" , evt.value);
break;
case ABS_RX:
printf("app : Gyro-x: %d \n" , evt.value);
break;
case ABS_RY:
printf("app : Gyro-y: %d \n" , evt.value);
break;
case ABS_RZ:
printf("app : Gyro-z: %d \n" , evt.value);
break;
case ABS_MISC:
printf("app : Temp: %d \n" , evt.value);
break;
}
}
}
close(fd);
fd = -1;
return 0;
}
./show-mpu.elf /dev/input/event1
/*mpu6050-input.c*/
/*本驱动程序,实际是由两个框架组成的,一个是i2c设备驱动框架,一个是input驱动框架。
* 由于是i2c驱动框架,则__init() __exit() XX_probe() XX_remove()的型式就确定了,并进行了
* 设备树的匹配。
* 又由于使用了input驱动框架,所以在probe里就无需去创建设备号dev_t,创建struct cdev,
* create_deivce()创建设备文件节点等事项。而是直接在probe()创建input_dev,并注册后就可以了。*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**以下为mpu6050中的各个寄存器地址标号*/
#define SMPLRT_DIV 0x19 //采样率分配器配置寄存器
#define CONFIG 0x1A //配置FSYNC 和 DLPF 寄存器
#define GYRO_CONFIG 0x1B //配置陀螺仪的寄存器
#define ACCEL_CONFIG 0x1C //配置加速度计的寄存器
#define ACCEL_XOUT_H 0x3B //X向加速度值的高8位
#define ACCEL_XOUT_L 0x3C //X向加速度值的低8位
#define ACCEL_YOUT_H 0x3D //Y向加速度值的高8位
#define ACCEL_YOUT_L 0x3E //Y向加速度值的低8位
#define ACCEL_ZOUT_H 0x3F //Z向加速度值的高8位
#define ACCEL_ZOUT_L 0x40 //Z向加速度值的低8位
#define TEMP_OUT_H 0x41 //温度的高8位
#define TEMP_OUT_L 0x42 //温度的氏8位
#define GYRO_XOUT_H 0x43 //x轴角速度的高8位
#define GYRO_XOUT_L 0x44 //x轴角速度的低8位
#define GYRO_YOUT_H 0x45 //Y轴角速度的高8位
#define GYRO_YOUT_L 0x46 //Y轴角速度的低8位
#define GYRO_ZOUT_H 0x47 //Z轴角速度的高8位
#define GYRO_ZOUT_L 0x48 //Z轴角速度的低8位
#define PWR_MGMT_1 0x6B //电源管理寄存器
struct mpu6050_dev //自定义数据结构,
{
struct input_dev *pinput; //代表一个input设备对象,用于input驱动框架
struct i2c_client *pclient; //clinet设备结构体
struct delayed_work work; //延时工作结构体
};
struct mpu6050_dev *pgmydev = NULL;
/****************************************************/
/*直接驱动i2c实现读一个字节的操作函数*/
int mpu6050_read_byte(struct i2c_client *pclt , unsigned char reg){
int ret = 0;
char txbuf[1] = {reg};
struct i2c_msg msg[2] ={
{pclt->addr , 0 , 1, txbuf}, //发送i2c地址,发送寄存器地址标号
{pclt->addr ,I2C_M_RD , 1, txbuf}, //发送地址及读标志,读数据到rxbuf中
};
ret = i2c_transfer(pclt->adapter , msg , ARRAY_SIZE(msg));
if (ret < 0){
printk("driver: ret = %d, in mpu6050_read_byte\n",ret);
return ret;
}
return txbuf[0];
}
/*直接驱动i2c实现写一个字节的操作函数*/
int mpu6050_write_byte(struct i2c_client *pclt , unsigned char reg , unsigned char val){
int ret = 0;
char txbuf[2] = {reg , val} ; //寄存器标号,寄存器值
struct i2c_msg msg[1] = {
{pclt->addr , 0 , 2 , txbuf},
};
ret = i2c_transfer(pclt->adapter , msg , ARRAY_SIZE(msg));
if (ret <0){
printk("driver: ret= %d , in mpu6050_write_byte\n",ret);
return ret;
}
return 0;
}
/*定时器的回调函数,在这里去上报input_event事件数据*/
void mpu6050_work_func(struct work_struct *pwk){
/*这里函数的参数struct work_struct *pwk是在mpu6050_work_func做为回调被调用时传递进来的。
* 而这个pwk指向的结构体又是在INIT_DELAYED_WORK()时创建的,这个pwk指针指向的结构体变量
* 是struct delayed_work work对象 中的第一个成员。
* 这也就意味着,pwk的指针就是我们创建pgmydev结构体成员work的指针,所以才有下面这个
* container_of函数第一个参数,用(struct delayed_work*)对pwk进行强转*/
struct mpu6050_dev *pmydev = container_of((struct delayed_work *)pwk , struct mpu6050_dev, work);
unsigned short ax = 0;
unsigned short ay = 0;
unsigned short az = 0;
unsigned short gx = 0;
unsigned short gy = 0;
unsigned short gz = 0;
unsigned short temp = 0;
//以下为从mpu6050中读取值,并把值合成事件提交给内核层
ax = mpu6050_read_byte(pmydev->pclient,ACCEL_XOUT_L);
ax += mpu6050_read_byte(pmydev->pclient,ACCEL_XOUT_H) << 8;
input_report_abs(pmydev->pinput , ABS_X , ax);
ay = mpu6050_read_byte(pmydev->pclient,ACCEL_YOUT_L);
ay += mpu6050_read_byte(pmydev->pclient,ACCEL_YOUT_H) << 8;
input_report_abs(pmydev->pinput , ABS_Y , ay);
az = mpu6050_read_byte(pmydev->pclient,ACCEL_ZOUT_L);
az += mpu6050_read_byte(pmydev->pclient,ACCEL_ZOUT_H) << 8;
input_report_abs(pmydev->pinput , ABS_Z , az);
gx = mpu6050_read_byte(pmydev->pclient, GYRO_XOUT_L);
gx += mpu6050_read_byte(pmydev->pclient, GYRO_XOUT_H) << 8;
input_report_abs(pmydev->pinput , ABS_RX, gx);
gy = mpu6050_read_byte(pmydev->pclient, GYRO_YOUT_L);
gy += mpu6050_read_byte(pmydev->pclient, GYRO_YOUT_H) << 8;
input_report_abs(pmydev->pinput , ABS_RY, gy);
gz = mpu6050_read_byte(pmydev->pclient, GYRO_ZOUT_L);
gz += mpu6050_read_byte(pmydev->pclient, GYRO_ZOUT_H) << 8;
input_report_abs(pmydev->pinput , ABS_RZ, gz);
temp = mpu6050_read_byte(pmydev->pclient , TEMP_OUT_L);
temp += mpu6050_read_byte(pmydev->pclient , TEMP_OUT_H) << 8;
input_report_abs(pmydev->pinput , ABS_MISC, temp);
//提交上述所有事件。
input_sync(pmydev->pinput);
//再次让延时工作在1秒后启动
schedule_delayed_work(&pgmydev->work , msecs_to_jiffies(1000));
}
/*mpu6050的初始化设置函数*/
void init_mpu6050(struct i2c_client *pclt){
mpu6050_write_byte(pclt ,PWR_MGMT_1,0x00);
mpu6050_write_byte(pclt,SMPLRT_DIV,0X07);
mpu6050_write_byte(pclt,CONFIG,0x06);
mpu6050_write_byte(pclt,GYRO_CONFIG,0xF8);
mpu6050_write_byte(pclt,ACCEL_CONFIG,0x19);
}
/*重要的驱动设备构造函数与驱动初始化函数*/
int mpu6050_probe(struct i2c_client *pclt , const struct i2c_device_id *pid){
int ret = 0;
pgmydev = (struct mpu6050_dev*)kmalloc(sizeof(struct mpu6050_dev), GFP_KERNEL);
if (!pgmydev){
printk("driver:kmalloc failed\n");
return -1;
}
memset(pgmydev , 0 , sizeof(struct mpu6050_dev));
pgmydev->pclient = pclt; //获得匹配好的i2c设备指针。
init_mpu6050(pgmydev->pclient);
/*创建一个struct input_dev结构体*/
pgmydev->pinput = input_allocate_device();
/*设定input_dev的事件类型为EV_ABS绝对值事件*/
set_bit(EV_ABS , pgmydev->pinput->evbit);
/*分别把7个绝对值事件码及最大最小等参数写入input_dev中的成员absinfo数组里*/
input_set_abs_params(pgmydev->pinput , ABS_X , -32768 , 32768 , 0, 0);
input_set_abs_params(pgmydev->pinput , ABS_Y , -32768 , 32768 , 0, 0);
input_set_abs_params(pgmydev->pinput , ABS_Z , -32768 , 32768 , 0, 0);
input_set_abs_params(pgmydev->pinput , ABS_RX , -32768 , 32768 , 0, 0);
input_set_abs_params(pgmydev->pinput , ABS_RY , -32768 , 32768 , 0, 0);
input_set_abs_params(pgmydev->pinput , ABS_RZ , -32768 , 32768 , 0, 0);
input_set_abs_params(pgmydev->pinput , ABS_MISC , -32768 , 32768 , 0, 0);
/*注册input_dev设备到内核中*/
ret = input_register_device(pgmydev->pinput);
if(ret){
printk("driver: input_register_device failed\n");
input_free_device(pgmydev->pinput);
pgmydev->pinput = NULL;
kfree(pgmydev);
pgmydev = NULL;
return -1;
}
/*初始化延时工作对象,并启动延时工作*/
INIT_DELAYED_WORK(&pgmydev->work , mpu6050_work_func);
schedule_delayed_work(&pgmydev->work , msecs_to_jiffies(1000));
return 0;
}
/*驱动的移除函数*/
int mpu6050_remove(struct i2c_client *plct){
cancel_delayed_work(&pgmydev->work);
input_unregister_device(pgmydev->pinput);
input_free_device(pgmydev->pinput);
pgmydev->pinput = NULL;
kfree(pgmydev);
pgmydev = NULL;
return 0;
}
struct i2c_device_id mpu6050_ids[]={
{"mpu6050-1" , 0},
{},
};
struct of_device_id mpu6050_dts[]={
{.compatible = "invensense,mpu6050"},
{},
};
/*驱动对象的数据结构体,代表了驱动对象,挂接到驱动链表中*/
struct i2c_driver mpu6050_driver = {
.driver ={
.name = "mpu6050-1",
.owner = THIS_MODULE,
.of_match_table = mpu6050_dts,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = mpu6050_ids,
};
#if 0
int __intit mpu6050_driver_init(void){
i2c_add_driver(mpu6050_driver);
}
void __exit mpu6050_driver_exit(void){
i2c_del_driver(mpu6050_driver);
}
module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);
#else
module_i2c_driver(mpu6050_driver);
#endif
MODULE_LICENSE("GPL");
insmod /dev/mpu6050-input.ko
CUR_DIR := $(shell pwd)
ifeq ($(filename),)
ifeq ($(KERNELRELEASE), )
ifeq ($(ARCH),arm)
KERNEL_DIR := /home/mao/linux/linux-3.14
ROOTFS_DIR := /opt/4412/rootfs
else
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
endif
all :
$(MAKE) -C $(KERNEL_DIR) M=$(CUR_DIR) modules
install:
#$(MAKE) -C $(KERNEL_DIR) M=$(CUR_DIR) INSTALL_MOD_PATH=$(ROOTFS_DIR) modules_install
cp *.ko $(ROOTFS_DIR)/drv -rf
clean :
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
else
obj-m += mpu6050-input.o
endif
else
ifeq ($(ARCH),arm)
GCC_DIR := ~/linux/toolchain/gcc-4.6.4/bin/arm-none-linux-gnueabi-
ROOTFSDIR := /opt/4412/rootfs/work
else
GCC_DIR = /usr/bin/
ROOTFSDIR = $(shell pwd)/work/
endif
all:
$(GCC_DIR)gcc $(filename).c -o $(filename).elf
sudo mv -f $(filename).elf $(ROOTFSDIR)
cp -rf ./load.sh $(ROOTFSDIR)
endif