-
了解usbmouse driver
-
drivers/hid/usbhid/usbmouse.c
1.struct 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;
};
2.通过宏module_usb_driver将usb 鼠标设备usb_mouse_driver注册下去。
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);
3.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。
4.USB mouse 设备探测
驱动和设备匹配成功之后就会调用driver 的probe探测函数,主要任务有:
- 获取接口; // 判断端点是否为中断模式
- 申请一个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;
}
urb 函数:
- mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
- usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data, (maxp > 8 ? 8 : maxp), usb_mouse_irq, mouse, endpoint->bInterval);
5.usb mouse 回调事件处理
在usb mouse 产生事件时,urb完成时被调用的完成处理函数complete :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_submit_urb 提交urb