/*参考过韦东山老师的usb分析笔记*/
1. USB的传输类型:
a. 控制传输:可靠,时间有保证,比如:USB设备的识别过程
b. 批量传输: 可靠, 时间没有保证, 比如:U盘
c. 中断传输:可靠,实时,比如:USB鼠标
d. 实时传输:不可靠,实时,比如:USB摄像头
2.USB驱动程序框架:
app:
-------------------------------------------
USB设备驱动程序 // 知道数据含义
内核 --------------------------------------
USB总线驱动程序 // 1. 识别, 2. 找到匹配的设备驱动, 3. 提供USB读写函数 (它不知道数据含义)
-------------------------------------------
USB主机控制器
UHCI OHCI EHCI
硬件 -----------
USB设备
-------------------------------------------
3.查看现有usb设备相关信息的命令:lsusb -v -d [ID]
例如:
root@ubuntu:/home/jy# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 002: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
如果想查看上面第一个设备详细信息,命令就是:lsusb -v -d 1d6b:0002
4.当笔者插入一个usb摄像头时候,打印信息如下
[ 187.868194] usb 1-1: new high-speed USB device number 2 using musb-hdrc
[ 188.838409] usb 1-1: New USB device found, idVendor=1e4e, idProduct=0110
[ 188.845489] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 188.853027] usb 1-1: Product: USB2.0 Camera
[ 188.857421] usb 1-1: Manufacturer: Etron Technology, Inc.
追踪上面信息,是在 announce_device 中打印出来的,从这个函数入手分析
subsys_initcall(usb_init); //系统初始化时候调用
usb_hub_init();
kthread_run(hub_thread, NULL, "khubd"); //运行内核线程 hub_thread
hub_events();
hub_port_connect_change(hub, i, portstatus, portchange);
usb_alloc_dev(hdev, hdev->bus, port1);
choose_devnum(udev); //为usb设备分配一个设备号,寻找1-128之间第一个不为0的,后边会用
devnum = find_next_zero_bit(bus->devmap.devicemap, 128, bus->devnum_next);
hub_port_init(hub, udev, port1, i);
hub_set_address(udev, devnum); //把编号(地址)告诉USB设备
usb_get_device_descriptor(udev, 8);//获取设备描述符
usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); //为什么获取两次?
usb_new_device(struct usb_device *udev)
usb_enumerate_device(udev); /* Read descriptors */
usb_get_configuration(udev);//得到描述符
usb_parse_configuration(...); //解析描述符
udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); //主设备号是189,次设备号=总线号*128+上边分配的设备号
announce_device(udev); /* Tell the world! */ 到这里已经都解析完毕了,在这里打印信息解析的一些信息
device_add(&udev->dev);
bus_probe_device(dev); //从这里往下其实是每一种总线都有的匹配方式
device_attach(dev);
bus_for_each_drv(dev->bus, NULL, dev, __device_attach); //对usb总线上的每一个驱动都去调用匹配
__device_attach
driver_match_device(drv, dev);
return drv->bus->match ? drv->bus->match(dev, drv) : 1; //调用usb总线上的match函数
driver_probe_device(drv, dev);
really_probe(dev, drv); 调用usb总线上的probe函数
dev->bus->probe(dev);
上面得到描述符时候为什么为什么获取两次?这就需要从描述符获得手段来说了,usb协议通信是通过端点传输的,而识别usb过程是通过端点0来传输
描述符的信息,而对于低速设备,端点0最大数据包长度是8字节,而对于全速设备,就可能是16-64字节。而前八个字节的最后一个,正好是bMaxPacketSize0,
也就是端点0的最大传输速度,这样后边的传输就可以按照最大传输速度进行了。第二次就是按照最大速度重新读取全部的设备描述符的信息。
下面我们来看下usb总线的匹配函数
struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match,
.uevent = usb_uevent,
};
usb_device_match
intf = to_usb_interface(dev); //得到usb接口
usb_drv = to_usb_driver(drv); //得到usb驱动
id = usb_match_id(intf, usb_drv->id_table); //从这里可以看出,匹配的是设备接口的 id_table 和具体驱动的 id_table,从这里我们也可以看出usb设备的每一个接口都代表一种功能
for (; id->idVendor || id->idProduct || id->bDeviceClass || id->bInterfaceClass || id->driver_info; id++)
//对驱动中 id_table 数组中的每一项,如果有上述的标识,那么执行下面匹配
usb_match_one_id(interface, id)
usb_match_device(dev, id)
if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && id->idVendor != le16_to_cpu(dev->descriptor.idVendor))
...下面还有很多项
即根据驱动中 match_flags 来决定是否要进行比较设备的相应id,驱动要求匹配的选项必须一一匹配才能成功。上面语句意思
就是驱动加入要求匹配厂商id,那么必须驱动指定的厂商id和设备描述符的厂商id相同才会成功。同理,还可以要求设备id等匹配。
如果 id->match_flags 没有指定上面要求匹配的选项,那么就默认上面匹配规则通过,再进行匹配下面的规则
if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && (id->bInterfaceClass != intf->desc.bInterfaceClass))
... //还有好多,说白了就是再去匹配接口类是否支持,不再去详细分析了。
举个具体例子:
比如我们写了一个驱动,只支持我们的上面列出的usb摄像头设备(idVendor=1e4e,idProduct=0110),那么驱动的id_table就应该这么写:
static struct usb_device_id capture_usb_id_table [] = {
{USB_DEVICE(0x1e4e,0x0110)}, //这个宏会自动填充 match_flags = USB_DEVICE_ID_MATCH_DEVICE
{ } /* Terminating entry */
};
那么依照上面分析的,再匹配时候就必须匹配设备的 idVendor 和 idProduct 和驱动要求都一样才能通过。
我们来看下 usbmouse.c (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 */
};
就是接口类的匹配了,包含HID(人机接口)一些协议族的匹配规则,不再分析。linux已经实现了基类通用的usb设备驱动,比如音频、HID,
(具体参照:Linux设备驱动开发详解P519),所以就相应有这些设备类的匹配规则
5.好了,到这里我们基本就梳理清楚usb设备的识别流程了,总结下USB总线驱动程序的作用:
--识别USB设备
--分配地址
--并告诉USB设备(set address)
--发出命令获取描述符
--根据设备的描述符信息查找并安装对应的设备驱动程序
--提供USB读写函数
6.上面两点我们都已经分析了,第三点“提供USB读写函数”,基本所有总线类驱动都会提供本总线一些通用接口来供具体的设备驱动来使用,
比如I2C、SPI,当然,usb总线也不例外,它是通过一个核心数据结构 urb 来作为流淌在总线中的血液完成数据的传输。
这一块我们不再具体去分析,内核都已经封装好,不需要去了解具体的实现流程。至于提供的接口及使用要点,“Linux设备驱动开发详解”
usb部分讲的不错,可以参考。
7.那么应该怎么去写一个挂在usb总线上的设备呢?内核有些具体的例子可以参考(usb-skeleton.c--usb骨架程序、usbmouce.c鼠标、usbkbd.c键盘)
--分配/设置usb_driver结构体
.id_table
.probe
.disconnect
--注册
--通过usb总线提供的通用接口实现具体的设备逻辑
8.驱动中要注意问题:
--endpoint = &interface->endpoint[0].desc;//像这种写法,代表是除了端点0以外的第一个端点,而不是代表端点0
--驱动中确定一些方向属性要以主机端为主,比如usb鼠标,那么相对主机就是输入,方向就是in或者recv
9.人机接口类设备驱动配置在:
-> Device Drivers
-> HID Devices
<*> USB Human Interface Device (full HID) support
linux3.2.0-usb驱动框架分析
于 2015-01-26 20:05:07 首次发布