当我们插入一个USB设备,系统如何感知到USB设备的接入,后续发生了哪些细节?系统如何区分这些USB设备?主机侧如何和这些从机设备进行数据的交互?
这里参考Linux kernel 4.9.xx的代码,部分异常和次要代码在这里没有体现。
usb_hub_init
在 4.9 的 Linux 内核中,通过subsys_initcall
,在系统启动时注册 USB 子系统的初始化函数,以确保在系统正常运行之前进行必要的 USB 初始化工作。
在usb_init
里,进行bus_register
以及usb_hub_init
。当hub_driver
完成了注册,通过hub probe
完成hub_event
的初始化工作。当有设备插入时,感知到硬件上的电平变化,后续的工作将在hub_event
里完成。
c
int usb_hub_init(void)
{
if (usb_register(&hub_driver) < 0) {
printk(KERN_ERR "%s: can't register hub driver\n",
usbcore_name);
return -1;
}
}
hub_driver结构体如下:
c
static struct usb_driver hub_driver = {
.name = "hub",
.probe = hub_probe,
.disconnect = hub_disconnect,
.suspend = hub_suspend,
.resume = hub_resume,
.reset_resume = hub_reset_resume,
.pre_reset = hub_pre_reset,
.post_reset = hub_post_reset,
.unlocked_ioctl = hub_ioctl,
.id_table = hub_id_table,
.supports_autosuspend = 1,
};
hub_event
在hub_event
,通过检测event_bits
,change_bits
,wakeup_bits
是否置位。当以上三种情况有一个发生时,就会发起port_event
。同时后续会对hub的状态进行状态管理。
c
static void hub_event(struct work_struct *work)
{
...
if (test_bit(i, hub->event_bits)
|| test_bit(i, hub->change_bits)
|| test_bit(i, hub->wakeup_bits)) {
port_event(hub, i);
}
/* deal with hub status changes */
...
}
port_event
c
static void port_event(struct usb_hub *hub, int port1)
__must_hold(&port_dev->status_lock)
{
if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange))
connect_change = 1;
if (connect_change)
hub_port_connect_change(hub, port1, portstatus, portchange);
}
hub_handle_remote_wakeup 和 hub_port_connect_change
当发生以下情况时调用hub_port_connect_change
:
- 端口连接状态发生变化;
- 端口使能状态发生变化(通常由电磁干扰引起);
usb_reset_and_verify_device()
遇到了变化的描述符(例如:固件下载)
调用此函数时,必须已经拿到hub的锁。
c
static void hub_port_connect_change(struct usb_hub *hub, int port1,
u16 portstatus, u16 portchange)
__must_hold(&port_dev->status_lock)
{
...
hub_port_connect(hub, port1, portstatus, portchange);
...
}
hub_port_connect
如果说前面都是检测"设备接入"状态的流程,那这里就是注册USB新的deivce的核心流程。当检测到新设备时,需要在总线上进行注册。注册前,需要填充udev结构,这个结构的内容分别由以下几个函数完成。
c
static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
u16 portchange)
{
...
udev = usb_alloc_dev(hdev, hdev->bus, port1);//注册一个usb device,然后会放在usb总线上
usb_set_device_state(udev, USB_STATE_POWERED);//设置注册的USB设备的状态标志
choose_devnum(udev);//给新的设备分配一个地址编号
status = hub_port_init(hub, udev, port1, i);//分配设备地址,获取详细的设备描述符
//前面都在填充udev结构内容,这里才将udev结构注册到总线上
status = usb_new_device(udev);//创建USB设备,与USB设备驱动连接
...
}
usb_alloc_dev
这是USB 设备构造函数,这里设置device的成员,每当创建一个USB设备时,总线都会调用 .match匹配的函数,使得USB driver和device绑定。
c
struct usb_device *usb_alloc_dev(struct usb_device *parent,
struct usb_bus *bus, unsigned port1)
{
dev = kzalloc(sizeof(*dev), GFP_KERNEL);//分配usb device设备结构体
device_initialize(&dev->dev);//初始化usb device
dev->dev.bus = &usb_bus_type;//设置绑定usb device的成员
dev->dev.type = &usb_device_type;
dev->dev.groups = usb_device_groups;
}
usb_bus_type 的结构如下:
c
struct bus_type usb_bus_type = {
.name = "usb", //总线名称,存在/sys/bus下
.match = usb_device_match, //匹配函数,匹配成功就会调用usb_driver驱动的probe函数成员
.uevent = usb_uevent, //事件函数(可选)
.suspend = usb_suspend, //休眠函数(可选)
.resume = usb_resume, //唤醒函数(可选)
};
choose_devnum
这里主要是在devnum_next
~128之间,寻找下一个非0(没有被设备占用)的编号。如果被占用则往后顺延。0地址是被用作初次接入未初始化USB设备所使用。
hub_port_init
hub_port_init
的作用是:重置设备,分配地址,获取设备描述符。
此时设备连接必须稳定,成功返回 USB_STATE_ADDRESS
状态的设备,除非出现错误。
如果这是为一个已经存在的设备调用( 比如在usb_reset_and_verify_device
时调用),调用者必须拥有设备的锁和端口的锁。
这里通过获取设备的speed来决定ep0最大数据包大小。因为不同的speed,包的大小并不相同。对于无线 USB 设备,端点 0 的最大数据包大小始终为 512。
此外也可以直接读取前8个字节,获得USB设备描述符,因为这8个每个设备都有,后面再根据设备的数据,通过usb_get_device_descriptor
再重新读一次目标设备的描述结构。
c
static int
hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
int retry_counter)
{
...
retval = hub_set_address(udev, devnum);//设置地址,告诉USB新的地址号
retval = usb_get_device_descriptor(udev, 8);//获得USB设备描述符的前8个字节
//还不确定对方支持的包容量,所以先读8个,这8个每个设备都有,后面再根据设备的数据,通过
//usb_get_device_descriptor再重新读一次目标设备的描述结构。
/*
struct usb_device_descriptor {
__u8 bLength; //本描述符的size
__u8 bDescriptorType; //描述符的类型,这里是设备描述符DEVICE
__u16 bcdUSB; //指明usb的版本,比如usb2.0
__u8 bDeviceClass; //类
__u8 bDeviceSubClass; //子类
__u8 bDeviceProtocol; //指定协议
__u8 bMaxPacketSize0; //端点0对应的最大包大小
__u16 idVendor; //厂家ID
__u16 idProduct; //产品ID
__u16 bcdDevice; //设备的发布号
__u8 iManufacturer; //字符串描述符中厂家ID的索引
__u8 iProduct; //字符串描述符中产品ID的索引
__u8 iSerialNumber; //字符串描述符中设备序列号的索引
__u8 bNumConfigurations; //可能的配置的数目
} __attribute__ ((packed));
*/
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);//重新获取设备描述符信息
...
}
为什么要以这种方式交错 GET_DESCRIPTOR
和 SET_ADDRESS
?
因为设备的硬件和固件在这个领域有时会有缺陷,而这是 Linux 多年来一直采用的方法。
usb_new_device
此函数用于检测但尚未完全枚举的设备。获取设备描述符信息,打印device的相关信息,然后将device添加到usb bus的链表中。
此调用是同步的,不能在中断上下文使用。返回值为设备是否被正确配置。如果接口已在驱动程序核心注册,则返回0。
c
int usb_new_device(struct usb_device *udev)
{
err = usb_enumerate_device(udev); /* Read descriptors */ //获取描述符信息
/* Tell the world! */
announce_device(udev);
err = device_add(&udev->dev);//注册device。将device放入bus的dev链表中,并寻找对应的设备驱动
}
总结
当我们插上USB设备,系统会根据电平信号的变化,检测到新设备的接入,接着获取USB设备、配置、接口、端点的数据,并创建新设备。