一.USB子系统
在介绍设备端驱动前,我们先来看看 Linux USB子系统。这里的子系统是相对于整个Linux kernel 来说的,而非单一设备。从整体概括了USB主机端和设备端的通信框架。
Linux kernel 中早已集成了较为完善的USB协议栈,由于其规模庞大,包含多个类别的设备驱动,所以Linux系统中的USB协议栈也被称为USB子系统。
1.1 主机端
主机端,简化抽象三层:
-
各种类设备驱动:mass sotrage, CDC, HID等
-
USB 设备驱动:USB 核心处理
-
主机控制器驱动:不同的USB主机控制器(OHCI/EHCI/UHCI),抽象为HDC。
1.2 设备端
设备端,也抽象为三层:
-
设备功能驱动:mass sotage , CDC, HID 等,对应主机端的类设备驱动
-
Gadget 设备驱动:中间层,向下直接和UDC通信,建立链接;向上提供通用接口,屏蔽USB请求以及传输细节。
-
设备控制器驱动:UDC驱动,直接处理USB设备控制器。
USB驱动整体框架:
二.USB描述符
USB设备描述符(usb_device_descriptor)
USB配置描述符(usb_config_descriptor)
USB接口描述符(usb_interface_descriptor)
USB端点描述符(usb_endpoint_descriptor)
一个USB设备描述符中可以有多个配置描述符,即USB设备可以有多种配置;一个配置描述符中可以有多个接口描述符,即USB设备可以支持多种功能(接口);一个接口描述符中可以有多个端点描述符。
设备描述符:
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
__u8 bLength; //该结构体大小
__u8 bDescriptorType; //描述符类型 (此处应为0x01,即设备描述符)
__le16 bcdUSB; //usb版本号 200 -> USB2.0
__u8 bDeviceClass; //设备类
__u8 bDeviceSubClass; //设备类子类
__u8 bDeviceProtocol; //设备协议,以上三点都是USB官方定义
__u8 bMaxPacketSize0; //端点0最大包大小 (只能为8,16,32,64)
__le16 idVendor; //厂家id
__le16 idProduct; //产品id
__le16 bcdDevice; //设备出厂编号
__u8 iManufacturer; //描述厂商信息的字符串描述符的索引值
__u8 iProduct; //描述产品信息的字串描述符的索引值
__u8 iSerialNumber; //描述设备序列号信息的字串描述符的索引值
__u8 bNumConfigurations; //可能的配置描述符的数目
} __attribute__ ((packed));
配置描述符:
struct usb_config_descriptor {
__u8 bLength; //该结构体大小
__u8 bDescriptorType;//描述符类型(本结构体中固定为0x02)
__le16 wTotalLength; //该配置下,信息的总长度(包括配置,接口,端点和设备类及厂商定义的描述符)
__u8 bNumInterfaces; //接口的个数
__u8 bConfigurationValue; //Set_Configuration命令所需要的参数值,用来选定此配置
__u8 iConfiguration; //描述该配置的字符串描述的索引值
__u8 bmAttributes;//供电模式的选择
__u8 bMaxPower;//设备从总线提取的最大电流
} __attribute__ ((packed));
接口描述符:
配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为“功能”更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。
struct usb_interface_descriptor {
__u8 bLength; //该结构体大小
__u8 bDescriptorType;//接口描述符的类型编号(0x04)
__u8 bInterfaceNumber; //该接口的编号
__u8 bAlternateSetting; //备用的接口描述符编号
__u8 bNumEndpoints; //该接口使用的端点数,不包括端点0
__u8 bInterfaceClass; //接口类
__u8 bInterfaceSubClass; //子类
__u8 bInterfaceProtocol; //协议
__u8 iInterface;//描述此接口的字串描述表的索引值
} __attribute__ ((packed));
端点描述符:
端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。
除了描述符中描述的端点外,每个设备必须要有一个默认的控制型端点,地址为0,它的数据传输为双向,而且没有专门的描述符,只是在设备描述符中定义了它的最大包长度。主机通过此端点向设备发送命令,获得设备的各种描述符的信息,并通过它来配置设备。
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
__u8 bLength; //端点描述符字节数大小(7个字节)
__u8 bDescriptorType;//端点描述符类型编号(0x05)
__u8 bEndpointAddress; //此描述表所描述的端点的地址、方向 :
// bit3~bit0:端点号,bit6~bit4:保留,
// bit7:方向,如果是控制端点则忽略,0-输出端点(主机到设备)1-输入端点(设备到主机)
__u8 bmAttributes; // 端点特性,bit1~bit0 表示传输类型,其他位保留
// 00-控制传输 01-实时传输 10-批量传输 11-中断传输
__le16 wMaxPacketSize; //端点收、发的最大包大小
__u8 bInterval; // 中断传输模式中主机查询端点的时间间隔。
// 对于实时传输的端点此域必需为1,表示周期为1ms。对于中断传输的端点此域值的范围为1ms到255ms
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
三.USB总线驱动如何识别设备
当USB设备连接到集线器,集线器状态将发生相应的变化,并将状态变化信息传递给USB主机。USB主机通过根集线器向USB设备发送命令,获取USB设备的各种信息,包含USB设备传输类型、ID号、Product、USB速度等信息。
USB匹配识别的框架:
四.USB设备驱动
USB 总线驱动程序,在接入 USB 设备时,会帮我们构造一个新的 usb_dev 注册到“usb_bus_type”里去。这部分是内核做好的。我们要做的是,构造一个 usb_driver 结构体,注册到“usb_bus_type”中去。在“usb_driver”结构体中有“id_table”表示他能支持哪些设备,当 USB 设备能匹配 id_table 中某一个设备时,就会调用“usb_driver”结构体中的“.probe”(自已确定在 probe 中做的事情)等函数,如当拔掉USB 设备时,就会调用其中的“.disconnect”函数。
简单的USB驱动程序示例:
/* drivers\hid\usbhid\usbmouse.c */
static struct input_dev *uk_dev;
static char *usb_buf;
static dma_addr_t usb_buf_phys;
static int len;
static struct urb *uk_urb;
static struct usb_device_id usbmouse_as_key_id_table [] = { //id_table支持的设备
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
//{USB_DEVICE(0x1234,0x5678)}, 直接指定厂家ID和设备ID
{ } /* Terminating entry */
};
static void usbmouse_as_key_irq(struct urb *urb){
static unsigned char pre_val;
/* USB鼠标数据含义
* data[0]: bit0-左键, 1-按下, 0-松开
* bit1-右键, 1-按下, 0-松开
* bit2-中键, 1-按下, 0-松开
*/
if ((pre_val & (1<<0)) != (usb_buf[0] & (1<<0))){
/* 左键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_L, (usb_buf[0] & (1<<0)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<1)) != (usb_buf[0] & (1<<1))){
/* 右键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_S, (usb_buf[0] & (1<<1)) ? 1 : 0);
input_sync(uk_dev);
}
if ((pre_val & (1<<2)) != (usb_buf[0] & (1<<2))){
/* 中键发生了变化 */
input_event(uk_dev, EV_KEY, KEY_ENTER, (usb_buf[0] & (1<<2)) ? 1 : 0);
input_sync(uk_dev);
}
pre_val = usb_buf[0];
/* 重新提交urb */
usb_submit_urb(uk_urb, GFP_KERNEL);
}
static int usbmouse_as_key_probe(struct usb_interface *intf, const struct usb_device_id *id){
struct usb_device *dev = interface_to_usbdev(intf);
//通过USB接口得到usb_device结构体,里面有描述符
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
int pipe;
interface = intf->cur_altsetting;
endpoint = &interface->endpoint[0].desc;
/* a. 分配一个input_dev */
uk_dev = input_allocate_device();
/* b. 设置 */
/* b.1 能产生哪类事件 */
set_bit(EV_KEY, uk_dev->evbit); //按键类
set_bit(EV_REP, uk_dev->evbit); //重复类
/* b.2 能产生哪些事件 */
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/* c. 注册 */
input_register_device(uk_dev);
/* d. 硬件相关操作 */
/* 数据传输3要素: 源,目的,长度 */
/* 源: USB设备的某个端点 */
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/* 长度: */
len = endpoint->wMaxPacketSize; //端点描述符最大包大小
/* 目的: */
usb_buf = usb_buffer_alloc(dev, len, GFP_ATOMIC, &usb_buf_phys); //分配缓冲区
/* 使用"3要素" */
/* 分配usb request block */
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/* 使用"3要素设置urb" */
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 使用URB */
usb_submit_urb(uk_urb, GFP_KERNEL);
return 0;
}
static void usbmouse_as_key_disconnect(struct usb_interface *intf){
struct usb_device *dev = interface_to_usbdev(intf);
usb_kill_urb(uk_urb);
usb_free_urb(uk_urb);
usb_buffer_free(dev, len, usb_buf, usb_buf_phys);
input_unregister_device(uk_dev);
input_free_device(uk_dev);
}
/* 1. 分配/设置usb_driver */
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse_as_key_",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
static int usbmouse_as_key_init(void){
usb_register(&usbmouse_as_key_driver); // 2. 注册 usb_driver
return 0;
}
static void usbmouse_as_key_exit(void){
usb_deregister(&usbmouse_as_key_driver); // 卸载usb_driver
}
module_init(usbmouse_as_key_init);
module_exit(usbmouse_as_key_exit);
MODULE_LICENSE("GPL");