Linux之 USB驱动框架-USB鼠标驱动源码分析(5)

一、usbmouse.c

下面我们分析下USB鼠标驱动,鼠标输入HID类型,其数据传输采用中断URB,鼠标端点类型为IN。好了,我们先看看这个驱动的模块加载部分。

路径:drivers/hid/usbhid/usbmouse.c

static const 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 */

};

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);

//注册usb 接口驱动

再细细看看USB_INTERFACE_INFO宏的定义

/**

* USB_INTERFACE_INFO - macro used to describe a class of usb interfaces

* @cl: bInterfaceClass value

* @sc: bInterfaceSubClass value

* @pr: bInterfaceProtocol value

*

* This macro is used to create a struct usb_device_id that matches a

* specific class of interfaces.

*/

#define USB_INTERFACE_INFO(cl, sc, pr) \

.match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \

.bInterfaceClass = (cl), \

.bInterfaceSubClass = (sc), \

.bInterfaceProtocol = (pr)

根据宏,我们知道,我们设置的支持项包括接口类,接口子类,接口协议三个匹配项。

1.2 USB接口设备的创建

当一个USB 鼠标设备插入后,主机USB控制器检测到后,触发USB设备集线器中的"中断"处理函数hub_irq。在hub_irq中会获取USB鼠标设备的设备描述符,根据设备描述符创建USB接口设备,从而和这边的USB接口驱动匹配,调用其probe函数,通过USB总线驱动程序(USB Core和USB HCD)和USB鼠标设备建立联系,进而操作(读写控制)该设备。

hub_irq->

kick_hub_wq->// 把hub work压入 hub_wq

hub_event ->// 处理USB设备插入事件

port_event->

hub_port_connect_change->

hub_port_connect->

hub_port_init->

usb_get_device_descriptor// 获取设备描述符

usb_new_device(udev)->

usb_enumerate_device// 把所有的描述符都读出来,并解析

device_add-> // 把device放入usb_bus_type的dev链表,

把device放入usb_bus_type的dev链表, 从usb_bus_type的driver链表里取出usb_driver,把usb_interface和usb_driver的id_table比较,如果能匹配,调用usb_driver的probe

1.3 USB接口驱动和USB接口设备的匹配

USB设备插入后根据获取到的设备描述符所创建的USB 接口设备和开发的USB接口驱动匹配:

对于设备:

将获取到的USB设备描述符信息保存在其id_table中。

对于驱动:

驱动的id_table中存放,期望该驱动适用的USB设备。

static const 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 */

};

匹配 HID 设备

USB 设备中有一大类就是 HID 设备,即 Human Interface Devices,人机接口设备。

这类设备包括鼠标、键盘等,主要用于人与计算机进行交互。

它是 USB 协议最早支持的一种设备类。

HID 设备可以作为低速、全速、高速设备用。

由于 HID 设备要求用户输入能得到及时响应,故其传输方式通常采用中断方式。

匹配成功后调用该驱动的probe函数;

static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)

{

struct usb_device *dev = interface_to_usbdev(intf);//由接口获取 usb_device

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;//获取设置

if (interface->desc.bNumEndpoints != 1) //鼠标端点只有 1个

return -ENODEV;

endpoint = &interface->endpoint0.desc; //获得端点描述符

if (!usb_endpoint_is_int_in(endpoint)) //检查该端点是否是中断输入端点

return -ENODEV;

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); //建立中断输入端点

maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));//返回端点能传输的最大的数据包,鼠标的返回的最大数据包为 4个字节

mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);//分配 mouse结构体

input_dev = input_allocate_device(); //分配 input设备空间

if (!mouse || !input_dev)

goto fail1;

mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);//分配缓冲区

if (!mouse->data)

goto fail1;

mouse->irq = usb_alloc_urb(0, GFP_KERNEL); //分配 urb

if (!mouse->irq)

goto fail2;

mouse->usbdev = dev; //填充 mouse的 usb_device结构体

mouse->dev = input_dev; //填充 mouse的 input结构体

if (dev->manufacturer)//拷贝厂商 ID

strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));

if (dev->product) {//拷贝产品 ID

if (dev->manufacturer)

strlcat(mouse->name, " ", sizeof(mouse->name));

strlcat(mouse->name, dev->product, sizeof(mouse->name));

}

if (!strlen(mouse->name))//拷贝产品 ID

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结构体

input_dev->phys = mouse->phys;//将鼠标设备节点名赋给内嵌 input结构体

usb_to_input_id(dev, &input_dev->id); //将 usb设备信息拷贝给 input_id

input_dev->dev.parent = &intf->dev; //设置父节点

//evbit表明支持按键事件 (EV_KEY)和相对坐标事件 (EV_REL)

input_dev->evbit0 = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);

//keybit表明按键值包括左键、右键和中键

input_dev->keybitBIT_WORD(BTN_MOUSE) = BIT_MASK(BTN_LEFT) |

BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);

//relbit表明相对坐标事件值包括 X坐标和 Y坐标

input_dev->relbit0 = BIT_MASK(REL_X) | BIT_MASK(REL_Y);

//keybit表明除了左键、右键和中键,还支持其他按键

input_dev->keybitBIT_WORD(BTN_MOUSE) |= BIT_MASK(BTN_SIDE) |

BIT_MASK(BTN_EXTRA);

//relbit表明除了 X坐标和 Y坐标,还支持中键滚轮的滚动值

input_dev->relbit0 |= BIT_MASK(REL_WHEEL);

input_set_drvdata(input_dev, mouse);//将 mouse设置为 input的私有数据

input_dev->open = usb_mouse_open;//input设备的 open

input_dev->close = usb_mouse_close;

usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,

(maxp > 8 ? 8 : maxp),

usb_mouse_irq, mouse, endpoint->bInterval); //填充 urb

mouse->irq->transfer_dma = mouse->data_dma;

mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /使用 transfer_dma

error = input_register_device(mouse->dev); //注册 input设备

if (error)

goto fail3;

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;

}

其实上面这个probe主要是初始化usb设备和input设备,终极目标是为了完成urb的提交和input设备的注册。由于注册为input设备类型,那么当用户层open打开设备时候,最终会调用input中的open实现打开,我们看看input中open的实现。

usb_mouse_open

static int usb_mouse_open(struct input_dev *dev)

{

struct usb_mouse *mouse = input_get_drvdata(dev);

mouse->irq->dev = mouse->usbdev;

if (usb_submit_urb(mouse->irq, GFP_KERNEL))

return -EIO;

return 0;

}

usb_submit_urb 的调用流程

usb_submit_urb->

usb_hcd_submit_urb->

①root hub -> rh_urb_enqueue

②非root hub -> urb_enqueue

urb_enqueue 的函数实现,以OHCI举例:

urb_enqueue ->

ohci_urb_enqueue

当用户层open打开这个USB鼠标后,我们就已经将urb提交给了USB核心,那么根据USB数据处理流程知道,当处理完毕后,USB核心会通知USB设备驱动程序,这里我们是响应中断服务程序,这就相当于该URB的回调函数。我们在提交urb时候定义了中断服务程序usb_mouse_irq,我们跟踪看看

usb_mouse_irq:

static void usb_mouse_irq(struct urb *urb)

{

struct usb_mouse *mouse = urb->context;

signed char *data = mouse->data;

struct input_dev *dev = mouse->dev;

int status;

switch (urb->status) {

case 0: /* success */

break;

case -ECONNRESET: /* unlink */

case -ENOENT:

case -ESHUTDOWN:

return;

/* -EPIPE: should clear the halt */

default: /* error */

goto resubmit;

}

input_report_key(dev, BTN_LEFT, data0 & 0x01);//鼠标左键

input_report_key(dev, BTN_RIGHT, data0 & 0x02);

input_report_key(dev, BTN_MIDDLE, data0 & 0x04);

input_report_key(dev, BTN_SIDE, data0 & 0x08);

input_report_key(dev, BTN_EXTRA, data0 & 0x10);

input_report_rel(dev, REL_X, data1);

input_report_rel(dev, REL_Y, data2);

input_report_rel(dev, REL_WHEEL, data3);

input_sync(dev);

resubmit:

status = usb_submit_urb (urb, GFP_ATOMIC);//再次提交urb,等待下次响应

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);

}

根据上面的中断服务程序,我们应该知道,系统是周期性地获取鼠标的事件信息,因此在URB回调函数的末尾再次提交URB请求块,这样又会调用新的回调函数,周而复始。在回调函数中提交URB只能是GFP_ATOMIC优先级,因为URB回调函数运行于中断上下文中禁止导致睡眠的行为。而在提交URB过程中可能会需要申请内存、保持信号量,这些操作或许会导致USB内核睡眠。

看一下 usb_mouse 私有的设备结构体

struct usb_mouse {

char name128;//名字

char phys64; //设备节点

struct usb_device *usbdev; //内嵌usb_device设备

struct input_dev *dev; //内嵌input_dev设备

struct urb *irq; //urb结构体指针

signed char *data; //transfer_buffer缓冲区

dma_addr_t data_dma; // transfer _dma缓冲区

};

在上面这个结构体中,每一个成员的作用都应该很清楚了,上面使用了内嵌的内核子系统的结构体,这是继承的开发思想。

相关推荐
骑上单车去旅行4 小时前
MD5校验对比脚本
linux·服务器·windows
平生幻4 小时前
Linux 常用命令
linux
期待着20134 小时前
docker 安装 ,在centos7.9
运维·docker·容器
踏月的造梦星球4 小时前
DM8 DSC 单机双实例部署
运维·开发语言·数据库
MXsoft6185 小时前
平台化是智能运维发展的必然趋势吗?
运维
ShirleyWang0125 小时前
让headlamp控制台能访问
linux·服务器·python·k8s·k3s
liuccn5 小时前
Linux 存储系统:LVM 与直接分区
linux·运维
xexpertS7 小时前
CI/CD 熔断机制:如何通过编排级熔断器提升开发者效率
java·linux·运维
带娃的IT创业者8 小时前
突破内存瓶颈:利用 Nvidia GPU 显存作为 Linux Swap 空间的深度实践
linux·运维·服务器·nvidia·swap·gpu显存
zy happy8 小时前
VMware虚拟机添加新的硬盘
java·linux·jvm·docker