linux 下usb 鼠标的驱动基本上属于USB 协议中HID 设备的中断通信的应用。代码vendor\mstar\kernel\linaro\drivers\hid\usbhid\usbmouse.c,下面一起学习usb 鼠标驱动,usb 键盘vendor\mstar\kernel\linaro\drivers\hid\usbhid\usbkbd.c 后续在学习。
USB mouse 设备结构
struct usb_mouse {
char name[128]; // 名称,一般存储制造商名称
char phys[64];
struct usb_device *usbdev; // usb 设备模型
struct input_dev *dev; // 输入设备
struct urb *irq; // 用于usb 设备通信的urb 模块
signed char *data; // USB 鼠标事件的buffer,存储鼠标的左键,右键,滑轮,坐标事件
dma_addr_t data_dma;
};
USB mouse 初始化
static struct usb_driver usb_mouse_driver = {
.name = "usbmouse",
.probe = usb_mouse_probe,
.disconnect = usb_mouse_disconnect,
.id_table = usb_mouse_id_table,
};
module_usb_driver(usb_mouse_driver);
通过宏module_usb_driver将usb 鼠标设备usb_mouse_driver注册下去
#define module_usb_driver(__usb_driver) \
module_driver(__usb_driver, usb_register, \
usb_deregister)
打开宏module_usb_driver可以看到module_init和module_exit 分别安装,卸载usb_mouse_driver 驱动
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
就这样,usb mouse 驱动就被注册到usb 总线上去了。
USB 设备的匹配
static struct usb_device_id usb_mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
上一步完成了驱动的配置,传入了id_table,此处也即usb_mouse_id_table,里面记载了支持的设备列表,USB_INTERFACE_INFO 来定义一类USB鼠标设备。通过这个信息就可以完成驱动和设备的匹配,成功之后就会调用usb_mouse_driver 里面的probe。
USB mouse 设备探测
驱动和设备匹配成功之后就会调用driver 的探测函数,主要任务有:
- 获取接口; // 判断端点是否为中断模式
- 申请一个input 设备并填充;
- 创建管道,设置大小,再申请缓存区;
- 申请一个urb 用于与usb 设备通信;
- 注册input 设备;
- 设置接口数据;
static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
// USB 接口描述符被当成参数传入(USB 的一个接口表示一个功能)
// 获取USB 设备描述
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct input_dev *input_dev;
int pipe, maxp;
int error = -ENOMEM;
// 获取当前接口描述
interface = intf->cur_altsetting;
// 判断接口是否合法,根据HID规范,鼠标只有一个端点(且不包含端点0)
if (interface->desc.bNumEndpoints != 1)
return -ENODEV;
// 获取端点0的描述符
endpoint = &interface->endpoint[0].desc;
// 判断端点类型是否合法,HID 规范,鼠标唯一的端点为中断端点,因为鼠标是中断控制
if (!usb_endpoint_is_int_in(endpoint))
return -ENODEV;
// 创建中断管道(in),鼠标属于中断控制
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 返回该端点能够传输的最大的包长度,鼠标的返回的最大数据包为4个字节。*/
//初始化URB的时候会用到这个长度,缓冲区的长度要依照maxp来决定,最大不能超过8
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
// 为mouse 申请内存
//mouse结构的主要作用是赋值给usb_interface中的一个属性
//以便于触发其它函数的时候通过usb_interface中的这个属性就可以知道相关信息
//usb_interface中的这个属性是专门为了储存用户需要的数据的
mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
// 创建input设备
input_dev = input_allocate_device();
if (!mouse || !input_dev)
goto fail1;
// 为urb 传输申请内存,data 指向该地址空间,初始化urb 缓存区,第四个参数为dma相关
mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
if (!mouse->data)
goto fail1;
// 申请urb
mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
if (!mouse->irq)
goto fail2;
// 为mouse 的usbdev,dev 赋值
mouse->usbdev = dev;
mouse->dev = input_dev;
// 为mouse 那么赋值制造商名称
if (dev->manufacturer)
strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
if (dev->product) {
if (dev->manufacturer)
strlcat(mouse->name, " ", sizeof(mouse->name));
strlcat(mouse->name, dev->product, sizeof(mouse->name));
}
if (!strlen(mouse->name))
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
input_dev->name = mouse->name;
input_dev->phys = mouse->phys;
// 从dev 设备中获取总线类型,设备id,厂商id,版本号,设置父设备
usb_to_input_id(dev, &input_dev->id);
input_dev->dev.parent = &intf->dev;
// 设置输入设备所支持的事件信息
// 支持相对坐标和事件
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
// 记录支持的按键值
input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
// 支持的相对坐标为鼠标移动坐标和滑轮坐标
input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
BIT_MASK(BTN_EXTRA);
input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);
// 将mouse 传入input_dev,方便通过input_dev 获取全部的mouse 信息
input_set_drvdata(input_dev, mouse);
// 设置输入设备的open,close函数
input_dev->open = usb_mouse_open;
input_dev->close = usb_mouse_close;
// 填充urb 模块,mouse 作为上下文被设置下去,另外usb_mouse_irq函数为回调
// 当usb mouse 有事件产生时,回调被调用
usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
(maxp > 8 ? 8 : maxp),
usb_mouse_irq, mouse, endpoint->bInterval);
// mouse->irq 就是urb,如下设置DMA传输相关,当flag为URB_NO_TRANSFER_DMA_MAP时
// 表示优先使用transfer_dma,而不是transfer buffer
mouse->irq->transfer_dma = mouse->data_dma;
mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
// 注册input 设备
error = input_register_device(mouse->dev);
if (error)
goto fail3;
// 设置mouse 到 usb interface 中
usb_set_intfdata(intf, mouse);
return 0;
fail3:
usb_free_urb(mouse->irq);
fail2:
usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
fail1:
input_free_device(input_dev);
kfree(mouse);
return error;
}
usb mouse 设备断开
static void usb_mouse_disconnect(struct usb_interface *intf)
{
struct usb_mouse *mouse = usb_get_intfdata (intf);
// 断开mouse 和usb interface
usb_set_intfdata(intf, NULL);
if (mouse) {
usb_kill_urb(mouse->irq); // kill urb 模块
input_unregister_device(mouse->dev); // 卸载input设备
usb_free_urb(mouse->irq); // 释放urb 模块
// 释放申请的buffer
usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);
// 释放mouse
kfree(mouse);
}
}
usb mouse 设备打开
static int usb_mouse_open(struct input_dev *dev)
{
struct usb_mouse *mouse = input_get_drvdata(dev);
// 设置urb->dev ,urb submit 时需要使用该usb 设备
mouse->irq->dev = mouse->usbdev;
// urb 提交,之后urb 的通信通道完成启动
if (usb_submit_urb(mouse->irq, GFP_KERNEL))
return -EIO;
return 0;
}
usb mouse 设备关闭
static void usb_mouse_close(struct input_dev *dev)
{
struct usb_mouse *mouse = input_get_drvdata(dev);
// 断开urb 通信
usb_kill_urb(mouse->irq);
}
usb mouse 回调事件处理
在usb mouse 产生事件时,usb_mouse_irq回调会被触发
static void usb_mouse_irq(struct urb *urb)
{
struct usb_mouse *mouse = urb->context; // mouse 为上下文
signed char *data = mouse->data; // buffer 中存储的事件信息
struct input_dev *dev = mouse->dev; //
int status;
// 判断urb 通信是否成功
switch (urb->status) {
case 0: /* success */
break;
case -ECONNRESET: /* unlink */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE: should clear the halt */
default: /* error */
goto resubmit; // 产生错误,重新提交urb
}
// data 第一个字节代表左右按键,中间按键,都会触发
input_report_key(dev, BTN_LEFT, data[0] & 0x01);
input_report_key(dev, BTN_RIGHT, data[0] & 0x02);
input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
input_report_key(dev, BTN_SIDE, data[0] & 0x08);
input_report_key(dev, BTN_EXTRA, data[0] & 0x10);
// 第二个字节代表X的坐标
input_report_rel(dev, REL_X, data[1]);
// 第三个字节代码Y的坐标
input_report_rel(dev, REL_Y, data[2]);
// 第四个字节代表滑轮的当前值
input_report_rel(dev, REL_WHEEL, data[3]);
input_sync(dev);
resubmit:
status = usb_submit_urb (urb, GFP_ATOMIC);
if (status)
dev_err(&mouse->usbdev->dev,
"can't resubmit intr, %s-%s/input0, status %d\n",
mouse->usbdev->bus->bus_name,
mouse->usbdev->devpath, status);
}
usb 键盘/鼠标协议说明
https://blog.csdn.net/peakguy/article/details/49476099
https://www.cnblogs.com/vonly/p/7403823.html
https://blog.csdn.net/jiujiujiuqiuqiuqiu/article/details/47277685
鼠标的协议
鼠标发送给PC的数据每次4个字节
BYTE1 BYTE2 BYTE3 BYTE4
定义分别是:
BYTE1 –
|–bit7: 1 表示 Y 坐标的变化量超出-256 ~ 255的范围,0表示没有溢出
|–bit6: 1 表示 X 坐标的变化量超出-256 ~ 255的范围,0表示没有溢出
|–bit5: Y 坐标变化的符号位,1表示负数,即鼠标向下移动
|–bit4: X 坐标变化的符号位,1表示负数,即鼠标向左移动
|–bit3: 恒为1
|–bit2: 1表示中键按下
|–bit1: 1表示右键按下
|–bit0: 1表示左键按下
BYTE2 – X坐标变化量,与byte的bit4组成9位符号数,负数表示向左移,正数表右移。用补码表示变化量
BYTE3 – Y坐标变化量,与byte的bit5组成9位符号数,负数表示向下移,正数表上移。用补码表示变化量
BYTE4 – 滚轮变化。
由于手上没有USB鼠标,对BYTE1的4-7位没有测试,对于BYTE2 BYTE3做个测试,BYTE1的4-7全为0的时候,BYTE2 BYTE3的正负表示鼠标移动方向
键盘的协议
键盘发送给PC的数据每次8个字节
BYTE1 BYTE2 BYTE3 BYTE4 BYTE5 BYTE6 BYTE7 BYTE8
定义分别是:
BYTE1 –
|–bit0: Left Control是否按下,按下为1
|–bit1: Left Shift 是否按下,按下为1
|–bit2: Left Alt 是否按下,按下为1
|–bit3: Left GUI 是否按下,按下为1
|–bit4: Right Control是否按下,按下为1
|–bit5: Right Shift 是否按下,按下为1
|–bit6: Right Alt 是否按下,按下为1
|–bit7: Right GUI 是否按下,按下为1
BYTE2 – 暂不清楚,有的地方说是保留位
BYTE3–BYTE8 – 这六个为普通按键
键盘经过测试。
例如:键盘发送一帧数据 02 00 0x04 0x05 00 00 00 00
表示同时按下了Left Shift + ‘a’+‘b’三个键
Event 事件上抛
回调里面解析完 usb mouse 事件之后,通过input_report_key丢给linux 的input 系统来处理。至此usb mouse 完成了事件的获取没解析和转发。另外input子系统还有以下input event 转发接口,在其它输入设备中可以用到,例如usb keyboard。
void input_report_key(struct input_dev *dev, unsigned int code, int value)
void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_sync(struct input_dev *dev)
void input_mt_sync(struct input_dev *dev)
深入分析linux input 子系统,
https://blog.csdn.net/yueqian_scut/article/details/48792939