USB总线设备驱动分析(使用)
USB驱动程序框架:
app:
-------------------------------------------
USB设备驱动程序 // 知道数据含义
内核 --------------------------------------
USB总线驱动程序 // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义)
-------------------------------------------
USB主机控制器
UHCI OHCI EHCI
硬件 -----------
USB设备
UHCI: intel, 低速(1.5Mbps)/全速(12Mbps)
OHCI: microsoft 低速/全速
EHCI: 高速(480Mbps)
接上USB设备,内核打印信息
接上usbU盘后内核打印:
/*使用的是USB高速,148MB/s,设备号为3,使用的是echi*/
<6>[ 51.848206] usb 1-1.1: new high speed USB device number 3 using s5p-ehci
/*usb设备类型是:storage 存储设备 ,通过USB设备描述符得到的*/
<6>[ 51.962050] scsi0 : usb-storage 1-1.1:1.0
<5>[ 52.966223] scsi 0:0:0:0: Direct-Access Generic STORAGE DEVICE 0819 PQ: 0 ANSI: 6
<5>[ 52.993741] sd 0:0:0:0: Attached scsi generic sg0 type 0
<5>[ 53.237914] sd 0:0:0:0: [sda] 31116288 512-byte logical blocks: (15.9 GB/14.8 GiB)
<5>[ 53.239028] sd 0:0:0:0: [sda] Write Protect is off
<7>[ 53.239077] sd 0:0:0:0: [sda] Mode Sense: 23 00 00 00
<5>[ 53.240191] sd 0:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
/* 映射到内核的分区的名字 */
<6>[ 53.255055] sda: sda1
<5>[ 53.269893] sd 0:0:0:0: [sda] Attached SCSI removable disk
拔掉:
<6>[ 98.756713] usb 1-1.1: USB disconnect, device number 3
再插:
<6>[ 118.918368] usb 1-1.1: new high speed USB device number 4 using s5p-ehci
<6>[ 119.013751] scsi1 : usb-storage 1-1.1:1.0
<5>[ 120.016908] scsi 1:0:0:0: Direct-Access Generic STORAGE DEVICE 0819 PQ: 0 ANSI: 6
<5>[ 120.022879] sd 1:0:0:0: Attached scsi generic sg0 type 0
<5>[ 120.273979] sd 1:0:0:0: [sda] 31116288 512-byte logical blocks: (15.9 GB/14.8 GiB)
<5>[ 120.274958] sd 1:0:0:0: [sda] Write Protect is off
<7>[ 120.275006] sd 1:0:0:0: [sda] Mode Sense: 23 00 00 00
<5>[ 120.275959] sd 1:0:0:0: [sda] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
<6>[ 120.285008] sda: sda1
<5>[ 120.291078] sd 1:0:0:0: [sda] Attached SCSI removable disk
再拔:
<6>[ 134.596681] usb 1-1.1: USB disconnect, device number 4
根据打印设想USB设备挂接逻辑
根据正常逻辑分析 USB总线驱动程序作用:
1、识别USB设备
1.1 分配地址 (0-127)
1.2 设置USB设备地址(set address)
1.3 发出命令获取USB描述符。
2、查找并安装对应的设备驱动程序
3、提供USB读写函数
其中1、是被USB的过程就是USB设备枚举的过程。
根据打印信息,追踪源码,逆推USB设备挂接过程
查找:USB device number 去源码中定位到发现usb设备的源码:
drivers/usb/core/hub.c:2878: “%s %s speed %sUSB device number %d using %s\n”,
hub_port_init
由此函数逆推函数调用过程:
从硬件中断开始:(drivers/usb/core/hub.c)
hub_irq
kick_khubd
hub_thread //usb_hub_init中创建的线程,此函数在drivers/usb/core/usb.c中调用,usb总线驱动注册时建立的subsys_initcall(usb_init);
hub_events
hub_port_connect_change
usb_alloc_dev(hdev, hdev->bus, port1);
dev->dev.bus = &usb_bus_type; //总线设备模型,usb总线。
hub_port_init
hub_set_address //为usb分配一个设备地址
usb_get_device_descriptor(udev, 8); /*获取设备描述符,这里获取前8个。由USB设备描述符可知,前8个字节所有usb设备都是固定并且bMaxPacketSize0描述了总包的最大size。*/
usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
usb_new_device(udev);
usb_enumerate_device(udev); // 读设备描述符
device_add(&udev->dev); //总线驱动设备模型,把设备挂到USB udev设备链表上。然后从总线driver链表里一一取出和device进行匹配,匹配成功后调用dirver的probe函数
USB主节点入口在/drivers/usb/core/usb.c中
subsys_initcall(usb_init);//系统总线
module_exit(usb_exit);
usb_init
usb_hub_init
usb_deregister(&hub_driver);
kthread_run(hub_thread, NULL, "khubd");
hub_probe
hub_configure(hub, endpoint)
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval);
USB设备描述符简要说明
这里还要说一下USB设备的描述符:
USB设备描述符包含多个配置描述符,配置描述符包含多个接口描述符,接口描述符包含多个端点描述符。
接口:逻辑上的设备,比如usb声卡 有两个接口分别是:播放、录音。每一个接口对应一个驱动程序。
端点:描述输入输出方向、数据类型、等。usb数据的交互都是端点和端点之间的交互。
附录:USB设备描述符。( include/linux/usb/ch9.h )
/* USB_DT_DEVICE: Device descriptor*/
struct usb_device_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__le16 bcdUSB;
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bMaxPacketSize0;
__le16 idVendor;
__le16 idProduct;
__le16 bcdDevice;
__u8 iManufacturer;
__u8 iProduct;
__u8 iSerialNumber;
__u8 bNumConfigurations;
} __attribute__ ((packed));
/* USB_DT_INTERFACE: Interface descriptor */
struct usb_interface_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bInterfaceNumber;
__u8 bAlternateSetting;
__u8 bNumEndpoints;
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
__u8 iInterface;
} __attribute__ ((packed));
/* USB_DT_ENDPOINT: Endpoint descriptor */
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
/* 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是实体的总线设备驱动,platform模型。总线设备驱动模型把驱动分成两个部分:
1、设备部分(device)
通过usb_new_device建立usb_interface。每当有usb硬件中断来的时候,usb通过访问端点0设置usb地址和获取设备描述符,然后通过设备描述符描述的usb类型,实例化对应类型的usb_device挂到usb_bus的设备链表上。同时usb核心层会根据usb总线协议为usb_device数据读写功能。
注:“usb通过访问端点0设置usb地址和获取设备描述符”,此部分即为usb设备的枚举过程。细节复杂,有兴趣可以详细分析。
2、驱动部分(driver)
driver部分,此部分我会实例化一个代码来说明。
目标:
usb鼠标用作按键。
1.首先注册usb驱动模型
USB设备驱动程序步骤:
1、分配/设置usb_driver结构体
.id_table
.probe
.disconnect
//填写id_table,
static struct usb_device_id usbmouse_as_key_id_table[] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
{ } /* Terminating entry */
};
static int usbmouse_as_key_probe (struct usb_interface *intf, const struct usb_device_id *id)
{
//input模型
//从usb主机控制器获取usb数据
}
static void usbmouse_as_key_disconnect (struct usb_interface *intf)
{
//释放probe中申请的内存
//卸载probe中注册的设备
}
注意usb_device_id,此结构体中描述的描述符会和usb设备枚举出来的interface通过usb总线驱动的mach函数进行匹配。
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) },
若匹配成功则调用,usb_driver结构体中的probe函数。
然后再去根据USB总线驱动读写函数的数据进行逻辑操作,这些逻辑操作都是在usb设备驱动中实现。比如我们的input_event来上报鼠标传来的按键值。
2、注册
//实现usb_driver
static struct usb_driver usbmouse_as_key_driver = {
.name = "usbmouse",
.probe = usbmouse_as_key_probe,
.disconnect = usbmouse_as_key_disconnect,
.id_table = usbmouse_as_key_id_table,
};
//module入口函数
static int usbmouse_as_key_init(void)
{
//注册usb_driver
int retval = usb_register(&usbmouse_as_key_driver);
if (retval != 0)
printk("register usbmouse_as_key driver failed! \n");
return retval;
}
//出口函数
static void usbmouse_as_key_exit(void)
{
usb_deregister(&usbmouse_as_key_driver);
}
注册部分,调用usb_register函数来注册实例化的usb_dirver。
当销毁此driver时,调用usb_deregister函数来销毁。
2.作为按键,即是input设备,所以搞个input模型
input设备主要步骤:
1、分配input_dev结构体
2、设置
3、注册
4、硬件操作
此部分内容在probe中实现
//全局变量uk_dev
static struct input_dev *uk_dev;
/*1、分配一个input_dev*/
uk_dev = input_allocate_device();
/*2、设置*/
/*2.1、能产生那类事件*/
set_bit(EV_KEY, uk_dev->evbit);
set_bit(EV_PWR, uk_dev->evbit);
/*2.1、能产生哪些事件*/
set_bit(KEY_L, uk_dev->keybit);
set_bit(KEY_S, uk_dev->keybit);
set_bit(KEY_ENTER, uk_dev->keybit);
/*3、注册*/
input_register_device(uk_dev);
/*4、硬件相关操作*/
//硬件操作部分用于从usb总线驱动获取
说明:此部分为注册一个input device,类型为ev_key,注册后会通过input子系统与evdev handler进行绑定。从而注册输入设备。
3.然后再input模型中的硬件操作中填入usb获取数据的方法
//interface描述符
interface = intf->cur_altsetting;
//获取端点描述符
endpoint = &interface->endpoint[0].desc;
/*数据传输3要素*/
/*源:USB设备的某个端点*/
//usb设备每一个端点和端点之前都有一个通道,此通道用于两端点之间的通讯。
pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
/*长度*/
//端点最大包长度
len = endpoint->wMaxPacketSize;
/*目的 */
//我们后面会对usb_buf里面包含的数据,进行解析
usb_buf = usb_alloc_coherent(dev, len, GFP_ATOMIC, &usb_buf_phys);
/*使用3要素*/
/*分配usb request block*/
uk_urb = usb_alloc_urb(0, GFP_KERNEL);
/*使用3要素设置urb usb请求块,设备,源,目的, 长度 完成函数 usb控制器查询频率 */
//usb设备没有中断能力,但是usb主机控制器有中断能力,当usb主机查询到usb设备有数据时会中断主机,从而调用usbmouse_as_key_irq
usb_fill_int_urb(uk_urb, dev, pipe, usb_buf, len, usbmouse_as_key_irq, NULL, endpoint->bInterval);
//usb请求块的物理地址
uk_urb->transfer_dma = usb_buf_phys;
uk_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/*使用urb*/
//告诉usb总线驱动,此urb可以使用
usb_submit_urb(uk_urb, GFP_KERNEL);
此部分主要是通过usb总线驱动获取usb设备的数据。通过数据传输三要素,进行设置数据流向,然后通过申请并填充urb来获取数据。
到此就结束了,本文只是粗略的介绍了usb控制器驱动在插上usb之后的过程,没有具体介绍usb数据传输过程,此过程可以通过usb抓包工具,抓取usb总线数据包并结合驱动分析。
总的来看,usb设备驱动仍是总线驱动模型的一种实例化。device部分由usb核心层(usb主机控制器驱动)通过枚举usb设备的接口描述符来实现。driver部分就需要由开发者实现,此部分主要涉及逻辑功能
本文涉及的usb driver部分的源码:
https://github.com/cshang983255766/friendlyArm-linux3.0.8/tree/master/drivers/usb_driver