USB设备热插拔流程解析

本文详细介绍了设备热插拔技术在i2c、SPI、PCI、SDIO、MDIO和SIM卡中的实现机制,特别关注USB主机控制器如EHCI的初始化过程,包括`__usb_create_hcd`和`usb_add_hcd`函数,以及USB设备通过中断和轮询检测插拔的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设备热插拔

i2c,spi都是主机驱动初始化的时候,就会根据设备树来添加设备

pci的话,主机初始化的时候,会通过总线去遍历枚举card设备

sdio的话,主机驱动初始化的时候,会去枚举卡;插卡可能会触发主机中断,然后中断处理程序又去扫描枚举卡;也可能通过cd脚注册中断,来判断卡的在位情况

mdio的话,根据设备树,去枚举和创建phy设备,会给phy创建一个状态机,每秒去读phy口的网线连接状态

sim卡,有一个cd脚,注册一个中断,根据高低电平,判断是否插卡,拔卡

所以,对于能热插拔的设备,要么通过中断,要么通过轮询(定时器,延时队列等),去判断设备的插拔情况

注册主机

usb主机一般都是通过__usb_create_hcd和usb_add_hcd来注册一个usb主机

static int fsl_ehci_drv_probe(struct platform_device *pdev)
{
	....
	hcd = __usb_create_hcd(&fsl_ehci_hc_driver, pdev->dev.parent,
			       &pdev->dev, dev_name(&pdev->dev), NULL);
	
	....
	retval = usb_add_hcd(hcd, irq, IRQF_SHARED);
	....
}

__usb_create_hcd的流程如下

__usb_create_hcd
	rh_timer_func
		usb_hcd_poll_rh_status
			usb_hcd_giveback_urb
				urb->complete (urb);

usb_add_hcd的流程如下

usb_add_hcd
	usb_hcd_request_irqs
		request_irq(irqnum, &usb_hcd_irq, irqflags, hcd->irq_descr, hcd);
			hcd->driver->irq
				ehci_irq
					usb_hcd_poll_rh_status
						usb_hcd_giveback_urb
							urb->complete (urb);

usb_hcd_poll_rh_status函数中,调用主机控制器的hub_status_data函数获取端口状态。如果端口的状态有变化,那么length > 0,把获取到的端口状态的数组拷贝到urb->transfer_buffer中,就是前面的hub->buffer中,同时调用usb_hcd_giveback_urb函数

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
	struct urb	*urb;
	int		length;
	int		status;
	unsigned long	flags;
	char		buffer[6];	/* Any root hubs with > 31 ports? */

	if (unlikely(!hcd->rh_pollable))
		return;
	if (!hcd->uses_new_polling && !hcd->status_urb)
		return;

	length = hcd->driver->hub_status_data(hcd, buffer);
	if (length > 0) {

		/* try to complete the status urb */
		spin_lock_irqsave(&hcd_root_hub_lock, flags);
		urb = hcd->status_urb;
		if (urb) {
			clear_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
			hcd->status_urb = NULL;
			if (urb->transfer_buffer_length >= length) {
				status = 0;
			} else {
				status = -EOVERFLOW;
				length = urb->transfer_buffer_length;
			}
			urb->actual_length = length;
			memcpy(urb->transfer_buffer, buffer, length);

			usb_hcd_unlink_urb_from_ep(hcd, urb);
			usb_hcd_giveback_urb(hcd, urb, status);
		} else {
			length = 0;
			set_bit(HCD_FLAG_POLL_PENDING, &hcd->flags);
		}
		spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
	}

	/* The USB 2.0 spec says 256 ms.  This is close enough and won't
	 * exceed that limit if HZ is 100. The math is more clunky than
	 * maybe expected, this is to make sure that all timers for USB devices
	 * fire at the same time to give the CPU a break in between */
	if (hcd->uses_new_polling ? HCD_POLL_RH(hcd) :
			(length == 0 && hcd->status_urb != NULL))
		mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}

usb_hcd_giveback_urb函数中调用urb->complete (urb),而urb->complete = hub_irq;hub_irq会执行一次检查port状态的work;有设备插入就会去初始化port,注册新的usb设备

hub_probe
	INIT_WORK(&hub->events, hub_event);
	hub_configure
		usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval);
			urb->complete = hub_irq;

初始化HUB

在系统初始化的时候在usb_init函数中调用usb_hub_init函数,就进入了hub的初始化。

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

在usb_hub_init函数中完成了注册hub驱动,然后创建一个工作队列hub_wq,每当有设备连接到USB接口时;根据上面----USB主机在中断后半段会回调complete函数(hub_irq)

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 */
		dev_dbg(hub->intfdev, "transfer --> %d\n", status);
		if ((++hub->nerrors < 10) || hub->error)
			goto resubmit;
		hub->error = status;
		/* FALL THROUGH */

	/* 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;来执行一次work;work的工作内容为hub->events;

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->events去发现设备,初始化设备

INIT_WORK(&hub->events, hub_event);
	hub_event
		port_event
			hub_port_connect_change
				usb_alloc_dev
				hub_port_init
				usb_new_device

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值