Linux USB host driver 枚举前的源码分析

当我们插入一个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里完成。

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结构体如下:

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_bitschange_bitswakeup_bits是否置位。当以上三种情况有一个发生时,就会发起port_event。同时后续会对hub的状态进行状态管理。

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

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的锁。

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结构,这个结构的内容分别由以下几个函数完成。

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绑定。

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 的结构如下:

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再重新读一次目标设备的描述结构。

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_DESCRIPTORSET_ADDRESS
因为设备的硬件和固件在这个领域有时会有缺陷,而这是 Linux 多年来一直采用的方法。

usb_new_device

此函数用于检测但尚未完全枚举的设备。获取设备描述符信息,打印device的相关信息,然后将device添加到usb bus的链表中。

此调用是同步的,不能在中断上下文使用。返回值为设备是否被正确配置。如果接口已在驱动程序核心注册,则返回0。

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设备、配置、接口、端点的数据,并创建新设备。

相关推荐

  1. Linux USB host driver 分析

    2024-04-08 08:32:04       14 阅读
  2. C++

    2024-04-08 08:32:04       9 阅读
  3. Swift中

    2024-04-08 08:32:04       11 阅读
  4. TypeScript中

    2024-04-08 08:32:04       7 阅读
  5. Kotlin 中密封类、类与密封接口对比分析

    2024-04-08 08:32:04       38 阅读
  6. flutter中使用

    2024-04-08 08:32:04       43 阅读
  7. Go中更安全

    2024-04-08 08:32:04       34 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-08 08:32:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-08 08:32:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-08 08:32:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-08 08:32:04       20 阅读

热门阅读

  1. 【Android】一文总结Android的init语言

    2024-04-08 08:32:04       12 阅读
  2. QWebApp http服务器笔记

    2024-04-08 08:32:04       11 阅读
  3. HashMap底层源码面试题

    2024-04-08 08:32:04       14 阅读
  4. 升级到springdoc的Swagger3

    2024-04-08 08:32:04       13 阅读
  5. 2024.4.7力扣刷题记录-数组篇刷题记录2

    2024-04-08 08:32:04       14 阅读
  6. 蓝桥杯常用模板

    2024-04-08 08:32:04       13 阅读