当前位置: 首页 > article >正文

USB枚举过程及Linux下U盘识别流程

USB枚举过程及Linux下U盘识别流程

  • USB枚举过程
  • Linux 内核U盘识别流程
    • 打开内核动态输出时候的U盘插拔log
    • 内核工作队列检测HUB端口变化
    • hub_event() 函数
    • hub_port_connect
      • USB连接去抖
      • 枚举USB设备
      • hub_port_init
    • USB接口与USB设备匹配
    • USB接口驱动
    • USB-STORAGE 接口驱动中USB 识别并添加设备
    • SCSI驱动中获取U盘信息的内容
      • 调用sd_probe函数
      • sd_probe_async
      • sd_revalidate_disk

USB枚举过程

USB协议定义了设备的6种状态,仅在枚举过程种,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两种是连接状态和挂起状态(Suspend))。
在这里插入图片描述
USB枚举基本的流程为:

  1. 用户把USB设备插入USB端口或给系统启动时设备上电
    USB端口指的是主机下的根hub或主机下行端口上的hub端口。Hub给端口供电,连接着的设备处于上电状态。

  2. Hub检测它各个端口数据线上(D+/D-)的电压
    在hub端,数据线D+和D-都有一个阻值在14.25k到24.8k的下拉电阻Rpd,而在设备端,D+(全速,高速)和D-(低速)上有一个1.5k的上拉电阻Rpu。当设备插入到hub端口时,有上拉电阻的一根数据线被拉高到幅值的90%的电压(大致是3V)。hub检测到它的一根数据线是高电平,就认为是有设备插入,并能根据是D+还是D-被拉高来判断到底是什么设备(全速/低速)插入端口。
    在这里插入图片描述
    检测到设备后,hub继续给设备供电,但并不急于与设备进行USB传输。

  3. Host了解连接的设备
    每个hub利用它自己的中断端点向主机报告它的各个端口的状态(对于这个过程,设备是看不到的,也不必关 心),报告的内容只是hub端口的设备连接/断开的事件。如果有连接/断开事件发生,那么host会发送一个 Get_Port_Status请求(request)以了解更多hub上的信息。Get_Port_Status等请求属于所有hub都要 求支持的hub类标准请求(standard hub-class requests)。

  4. Hub检测所插入的设备是高速还是低速设备
    hub通过检测USB总线空闲(Idle)时差分线的高低电压来判断所连接设备的速度类型,当host发来 Get_Port_Status请求时,hub就可以将此设备的速度类型信息回复给host。(USB 2.0规范要求速度检 测要先于复位(Reset)操作)。

  5. hub复位设备
    当主机获悉一个新的设备后,主机控制器就向hub发出一个 Set_Port_Feature请求让hub复位其管理的端 口。hub通过驱动数据线到复位状态(D+和D-全为低电平),并持续至少10ms。当然,hub不会把这样的复位 信号发送给其他已有设备连接的端口,所以其他连在该hub上的设备自然看不到复位信号,不受影响。

  6. Host检测所连接的全速设备是否支持高速模式
    因为根据USB 2.0协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,所以 对于一个支持USB 2.0的高速hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前 这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。 同样的,从设备的角度来看,如果是一个高速设备,在刚连接bub或上电时只能用全速信号模式运行(根据 USB 2.0协议,高速设备必须向下兼容USB 1.1的全速模式)。随后hub会进行高速检测,之后这个设备才会 切换到高速模式下工作。假如所连接的hub不支持USB 2.0,即不是高速hub,不能进行高速检测,设备将一 直以全速工作。

  7. Hub建立设备和主机之间的信息通道
    主机不停得向hub发送 Get_Port_Status请求,以查询设备是否复位成功。Hub返回的报告信息中有专门的 一位用来标志设备的复位状态。 当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备着主机发来的请求。设备和 主机之间的通信通过控制传输,默认地址0,端点号0进行。在此时,设备能从总线上得到的最大电流是 100mA。

  8. 主机发送Get_Descriptor请求获取默认管道的最大包长度
    默认管道(Default Pipe)在设备一端来看就是端点0。主机此时发送的请求是默认地址0,端点0,虽然所 有位分配地址的设备都是通过地址0来获取主机发来的信息,但由于枚举过程不是多个设备并行处理,而是一 次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。 设备描述符的第8字节代表设备端点0的最大包大小,Get_Descriptor请求中的wLength一项都会设为64, 虽然说设备所返回的设备描述符(Device Descriptor)长度只有18字节(但获取的最大包长至少8字 节),但系统也不在乎,此时,描述符的长度信息对它来说是最重要的,其他的瞄一眼就过了。当完成第一次 的控制传输后,也就是完成控制传输的状态阶段,系统会要求hub对设备进行再一次的复位操作(USB规范里 面可没这要求)。再次复位的目的是使设备进入一个确定的状态。
    在这里插入图片描述

  9. 主机给设备分配一个地址
    主机控制器通过Set_Address请求向设备分配一个唯一的地址。在完成这次传输之后,设备进入地址状态 (Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终身制的,设备在,地 址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是 上次那个了。

在这里插入图片描述

  1. 主机再次获取设备描述符
    主机发送 Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚 心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置 (Configuration)个数,设备类型,VID(Vendor ID,由USB-IF分配), PID(Product ID,由厂 商自己定制)等信息。Get_Descriptor请求(Device type)和设备描述符(已抹去VID,PID等信息)。
    在这里插入图片描述

  2. 主机获取配置描述符
    主机发送Get_Descriptor请求,读取配置描述符(Configuration Descriptor),字符串等,逐一了 解设备更详细的信息。事实上,对于配置描述符的标准请求中,有时wLength一项会大于实际配置描述符的长 度(9字节),比如255。这样的效果便是:主机发送了一个Get_Descriptor_Configuration 的请求, 设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具 体是何种描述符。 如果有自己穿描述符,还要获取字符串描述符。另外像HID设备还有报告描述符,他们是单独获取的。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

Linux 内核U盘识别流程

打开内核动态输出时候的U盘插拔log

打开动态输出:

echo -n "file drivers/usb/core/hub.c +p" > /sys/kernel/debug/dynamic_debug/control 
echo -n "file drivers/usb/storage/usb.c +p" > /sys/kernel/debug/dynamic_debug/control

U盘插入:

[80525.540466] hub 1-5.2:1.0: state 7 ports 4 chg 0000 evt 0002
[80525.540583] usb 1-5.2-port1: status 0101, change 0001, 12 Mb/s 
[80525.683717] usb 1-5.2-port1: debounce total 100ms stable 100ms status 0x101 [80525.763651] usb 1-5.2.1: new high-speed USB device number 17 using xhci_hcd [80525.890057] usb 1-5.2.1: udev 17, busnum 1, minor = 16 
[80525.890067] usb 1-5.2.1: New USB device found, idVendor=1f75, idProduct=0918, bcdDevice= 2.10 
[80525.890073] usb 1-5.2.1: New USB device strings: Mfr=2, Product=3, SerialNumber=4 
[80525.890078] usb 1-5.2.1: Product: MiniKing 
[80525.890082] usb 1-5.2.1: Manufacturer: aigo 
[80525.890087] usb 1-5.2.1: SerialNumber: C0003889 
[80525.892239] usb-storage 1-5.2.1:1.0: USB Mass Storage device detected [80525.897026] scsi host2: usb-storage 1-5.2.1:1.0 
[80525.897304] usb-storage 1-5.2.1:1.0: waiting for device to settle before scanning 
[80526.907541] usb-storage 1-5.2.1:1.0: starting scan
[80526.907735] usb-storage 1-5.2.1:1.0: scan complete 
[80526.911022] scsi 2:0:0:0: Direct-Access aigo U330 0009 PQ: 0 ANSI: 4 [80526.911709] sd 2:0:0:0: Attached scsi generic sg1 type 0 
[80526.912511] sd 2:0:0:0: [sdb] 61440000 512-byte logical blocks: (31.5 GB/29.3 GiB) 
[80526.913106] sd 2:0:0:0: [sdb] Write Protect is off 
[80526.913111] sd 2:0:0:0: [sdb] Mode Sense: 23 00 00 00 
[80526.913725] sd 2:0:0:0: [sdb] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA 
[80526.919820] sdb: sdb4 
[80526.922673] sd 2:0:0:0: [sdb] Attached SCSI removable disk 
[80528.374262] FAT-fs (sdb4): utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive! 
[80528.376852] FAT-fs (sdb4): Volume was not properly unmounted. Some data may be corrupt. Please run fsck

U盘拔出:

[80578.788424] hub 1-5.2:1.0: state 7 ports 4 chg 0000 evt 0002 
[80578.788530] usb 1-5.2-port1: status 0100, change 0001, 12 Mb/s 
[80578.788539] usb 1-5.2.1: USB disconnect, device number 17 
[80578.788545] usb 1-5.2.1: unregistering device 
[80578.795859] blk_update_request: I/O error, dev sdb, sector 257 op 0x1:(WRITE) flags 0x0 phys_seg 1 prio class 0 
[80578.806028] Buffer I/O error on dev sdb4, logical block 1, lost async page write [80579.007738] usb 1-5.2-port1: debounce total 100ms stable 100ms status 0x100

内核工作队列检测HUB端口变化

在USB 子系统初始化时(usb.c 中的usb_init()),会去做usb hub的初始化 usb_hub_init(),在其中会分配一个工作队列 usb_hub_wq:

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;
	}

	/*
	 * The workqueue needs to be freezable to avoid interfering with
	 * USB-PERSIST port handover. Otherwise it might see that a full-speed
	 * device was gone before the EHCI controller had handed its port
	 * over to the companion full-speed controller.
	 */
	hub_wq = alloc_workqueue("usb_hub_wq", WQ_FREEZABLE, 0);
	if (hub_wq)
		return 0;

	/* Fall through if kernel_thread failed */
	usb_deregister(&hub_driver);
	pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name);

	return -1;
}

INIT_WORK(&hub->events, hub_event);
并在hubi_irq中唤醒hub_event的工作队列kick_hub_wq(hub);:

/* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
{
   
	struct usb_hub *hub = urb->context;
	int status = urb->status;
	unsigned i;
	unsigned long bits;

	switch (status) {
   
	case -ENOENT:		/* synchronous unlink */
	case -ECONNRESET:	/* async unlink */
	case -ESHUTDOWN:	/* hardware going away */
		return;

	default:		/* presumably an error */
		/* Cause a hub reset after 10 consecutive errors */
		//连续十次错误导致hub reset
		dev_dbg(hub->intfdev, "transfer --> %d\n", status);
		if ((++hub->nerrors < 10) || hub->error)	
			goto resubmit;
		hub->error = status;
		fallthrough;

	/* let hub_wq handle things */
	case 0:			/* we got data:  port status changed */
		bits = 0;
		for (i = 0; i < urb->actual_length; ++i)
			bits |= ((unsigned long) ((*hub->buffer)[i]))
					<< (i*8);
		hub->event_bits[0] = bits;
		break;
	}

	hub->nerrors = 0;

	/* Something happened, let hub_wq figure it out */
	kick_hub_wq(hub);

resubmit:
	hub_resubmit_irq_urb(hub);
}

其中 kick_hub_wq 中:

static void kick_hub_wq(struct usb_hub *hub)
{
   
	struct usb_interface *intf;

	if (hub->disconnected || work_pending(&hub->events))
		return;

	/*
	 * Suppress autosuspend until the event is proceed.
	 *
	 * Be careful and make sure that the symmetric operation is
	 * always called. We are here only when there is no pending
	 * work for this hub. Therefore put the interface either when
	 * the new work is called or when it is canceled.
	 */
	intf = to_usb_interface(hub->intfdev);
	usb_autopm_get_interface_no_resume(intf);
	kref_get(&hub->kref);

	//唤醒工作队列
	if (queue_work(hub_wq, &hub->events))
		return;

	/* the work has already been scheduled */
	usb_autopm_put_interface_async(intf);
	kref_put(&hub->kref, hub_release);
}

hub_event() 函数

所有有关hub端口的变化最终都会在hub_event()中执行,以此来做相应的动作。比如枚举连接上来的USB设备,在 hub_event() 中通过调用 port_event来获取端口变化,后续在port_event中调用hub_hub_status() 来获得 portchange 和 portstatus 的状态并设置 connect_change变量。connect_change 为1表示Hub上的端口状态有变化(设备插入或拔出),进而执行hub_port_connect_change() 的内容。
hub_port_connect_change()中调用hub_port_connect,hub_port_connect中调用
usb_alloc_dev()为新的USB设备申请资源,并进行一些初始化。设置设备的状态为
USB_STATE_POWERED,接下来会枚举USB设备(复位、握手、获取描述符),当USB设备枚举完成后就会触发加载与之对应的USB接口驱动。
hub_event:

static void hub_event(struct work_struct *work)
{
   

......
	/* deal with port status changes */
	for (i = 1; i <= hdev->maxchild; i++) {
   
		struct usb_port *port_dev = hub->ports[i - 1];

		if (test_bit(i, hub->event_bits)
				|| test_bit(i, hub->change_bits)
				|| test_bit(i, hub->wakeup_bits)) {
   
			/*
			 * The get_noresume and barrier ensure that if
			 * the port was in the process of resuming, we
			 * flush that work and keep the port active for
			 * the duration of the port_event().  However,
			 * if the port is runtime pm suspended
			 * (powered-off), we leave it in that state, run
			 * an abbreviated port_event(), and move on.
			 */
			pm_runtime_get_noresume(&port_dev->dev);
			pm_runtime_barrier(&port_dev->dev);
			usb_lock_port(port_dev);
			port_event(hub, i);
			usb_unlock_port(port_dev);
			pm_runtime_put_sync(&port_dev->dev);
		}
	}

	/* deal with hub status changes */
	if (test_and_clear_bit(0, hub->event_bits) == 0)
		;	/* do nothing */
	else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
		dev_err(hub_dev, "get_hub_status failed\n");
	else {
   
		if (hubchange & HUB_CHANGE_LOCAL_POWER) {
   
			dev_dbg(hub_dev, "power change\n");
			clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
			if (hubstatus & HUB_STATUS_LOCAL_POWER)
				/* FIXME: Is this always true? */
				hub->limited_power = 1;
			else
				hub->limited_power = 0;
		}
		if (hubchange & HUB_CHANGE_OVERCURRENT) {
   
			u16 status = 0;
			u16 unused;

			dev_dbg(hub_dev, "over-current change\n");
			clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
			msleep(500);	/* Cool down */
			hub_power_on(hub, true);
			hub_hub_status(hub, &status, &unused);
			if (status & HUB_STATUS_OVERCURRENT)
				dev_err(hub_dev, "over-current condition\n");
		}
	}

out_autopm:
	/* Balance the usb_autopm_get_interface() above */
	usb_autopm_put_interface_no_suspend(intf);
out_hdev_lock:
	usb_unlock_device(hdev);

	/* Balance the stuff in kick_hub_wq() and allow autosuspend */
	usb_autopm_put_interface(intf);
	kref_put(&hub->kref, hub_release);

	kcov_remote_stop();
}

port_event:

static void port_event(struct usb_hub *hub, int port1)
		__must_hold(&port_dev->status_lock)
{
   
	int connect_change;
	struct usb_port *port_dev = hub->ports[port1 - 1];
	struct usb_device *udev 

http://www.kler.cn/a/538494.html

相关文章:

  • 【电机控制器】STC8H1K芯片——低功耗
  • Android Studio超级详细讲解下载、安装配置教程(建议收藏)
  • ssti学习笔记(服务器端模板注入)
  • Centos执行yum命令报错
  • mybatis 是否支持延迟加载?延迟加载的原理是什么?
  • 基于DeepSeek API和VSCode的自动化网页生成流程
  • DeepSeek-V3 论文解读:大语言模型领域的创新先锋与性能强者
  • ubuntu 22.04 cuda12.x 上 cutensor 1.6.2 版本环境搭建
  • 机器人调度系统交通管制算法
  • DeepSeek在FPGA/IC开发中的创新应用与未来潜力
  • 如何查看Ubuntu24.04系统,显卡是什么型号,适配的驱动是什么?
  • 音频进阶学习十二——Z变换一(Z变换、收敛域、性质与定理)
  • Win10环境使用ChatBox集成Deep Seek解锁更多玩法
  • MFC 基础
  • ubuntu 22.04 安装 cuda sdk 11.8
  • Go 语言环境安装指南
  • directx12 3d开发过程中出现的报错 五
  • Kotlin 2.1.0 入门教程(十)if、when
  • 基于生成式语言模型的就业指导
  • 常见string库中的函数(C语言超详细)
  • ProxySQL实现mysql8主从同步读写分离
  • 全面支持DeepSeek接入,聚铭网络以「AI+安全」重新定义网络防御新范式
  • 【ffmpeg 到Qt的图片格式转换】精彩的像素:深入解析 AVFrame 到 QImage 的转换
  • C++:类和对象初识
  • 没有 DevOps 团队
  • MYSQL索引与视图