LInux usb mouse(鼠标)驱动分析

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 的探测函数,主要任务有:

  1. 获取接口; // 判断端点是否为中断模式
  2. 申请一个input 设备并填充;
  3. 创建管道,设置大小,再申请缓存区;
  4. 申请一个urb 用于与usb 设备通信;
  5. 注册input 设备;
  6. 设置接口数据;
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

发布了101 篇原创文章 · 获赞 19 · 访问量 33万+

猜你喜欢

转载自blog.csdn.net/kehyuanyu/article/details/100983179