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

STM32 HOST USB代码 u8 USH_User_App(void) { u32 total,free; u8 res=0; Show_Str(30,140,200,16,"É豸Á¬½Ó³É¹¦!.",16,0); res=exf_getfree("2:",&total,&free); if(res==0) { POINT_COLOR=BLUE;//ÉèÖÃ×ÖÌåΪÀ¶É« LCD_ShowString(30,160,200,16,16,"FATFS OK!"); LCD_ShowString(30,180,200,16,16,"U Disk Total Size: MB"); LCD_ShowString(30,200,200,16,16,"U Disk Free Size: MB"); LCD_ShowNum(174,180,total>>10,5,16); //ÏÔʾUÅÌ×ÜÈÝÁ¿ MB LCD_ShowNum(174,200,free>>10,5,16); } while(HCD_IsDeviceConnected(&USB_OTG_Core))//É豸Á¬½Ó³É¹¦ { LED1=!LED1; delay_ms(200); } POINT_COLOR=RED;//ÉèÖÃ×ÖÌåΪºìÉ« Show_Str(30,140,200,16,"É豸Á¬½ÓÖÐ...",16,0); LCD_Fill(30,160,239,220,WHITE); return res; } int main(void) { u8 t; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//ÉèÖÃϵͳÖжÏÓÅÏȼ¶·Ö×é2 delay_init(168); //³õʼ»¯ÑÓʱº¯Êý uart_init(115200); //³õʼ»¯´®¿Ú²¨ÌØÂÊΪ115200 LED_Init(); //³õʼ»¯ÓëLEDÁ¬½ÓµÄÓ²¼þ½Ó¿Ú KEY_Init(); //°´¼ü LCD_Init(); //³õʼ»¯LCD W25QXX_Init(); //SPI FLASH³õʼ»¯ usmart_dev.init(84); //³õʼ»¯USMART my_mem_init(SRAMIN); //³õʼ»¯ÄÚ²¿ÄÚ´æ³Ø exfuns_init(); //ΪfatfsÏà¹Ø±äÁ¿ÉêÇëÄÚ´æ piclib_init(); //³õʼ»¯»­Í¼ f_mount(fs[0],"0:",1); //¹ÒÔØSD¿¨ f_mount(fs[1],"1:",1); //¹ÒÔØSD¿¨ f_mount(fs[2],"2:",1); //¹ÒÔØUÅÌ POINT_COLOR=RED; while(font_init()) //¼ì²é×Ö¿â { LCD_ShowString(60,50,200,16,16,"Font Error!"); delay_ms(200); LCD_Fill(60,50,240,66,WHITE);//Çå³ýÏÔʾ delay_ms(200); } Show_Str(30,50,200,16,"̽Ë÷ÕßSTM32F407¿ª·¢°å",16,0); Show_Str(30,70,200,16,"USB UÅÌʵÑé",16,0); Show_Str(30,90,200,16,"2014Äê7ÔÂ22ÈÕ",16,0); Show_Str(30,110,200,16,"ÕýµãÔ­×Ó@ALIENTEK",16,0); Show_Str(30,140,200,16,"É豸Á¬½ÓÖÐ...",16,0); //³õʼ»¯USBÖ÷»ú USBH_Init(&USB_OTG_Core,USB_OTG_FS_CORE_ID,&USB_Host,&USBH_MSC_cb,&USR_Callbacks); while(1) { USBH_Process(&USB_OTG_Core, &USB_Host); delay_ms(1); t++; if(t==200) { LED0=!LED0; t=0; } } }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值