linux usb子系统

本文使用linux6.4内核版本代码,从以下几个方面来学习usb子系统
1、usb总线注册与通用驱动注册
2、usb总线match回调函数分析
3、usb host 控制器HCD驱动加载以ehci_hcd为例
4、hub设备驱动(通用设备驱动)的probe
5、hub interface驱动的probe
6、hub_event的触发

usb总线注册与通用驱动注册

源码位置:
kernel/driver/usb/core/usb.c
usb_init函数截图在这里插入图片描述
这个初始化完成了几个重要功能:
1、usb总线注册
2、hub interface驱动注册
3、usb通用设备驱动注册
其中后面两个驱动贯穿了usb子系统整个框架,把这两个驱动搞明白就差不对usb有个整体理解了。

usb总线match回调函数分析

下面来看看usb总线的match函数实现,其主要作用就是将usb device和driver进行匹配

const struct bus_type usb_bus_type = {
	.name =		"usb",
	.match =	usb_device_match,
	.uevent =	usb_uevent,
	.need_parent_lock =	true,
};

来看看match函数

static int usb_device_match(struct device *dev, struct device_driver *drv)
{
	/* devices and interfaces are handled separately */
	if (is_usb_device(dev)) {
		struct usb_device *udev;
		struct usb_device_driver *udrv;

//如果是接口驱动直接返回失败
		if (!is_usb_device_driver(drv))
			return 0;

		udev = to_usb_device(dev);
		udrv = to_usb_device_driver(drv);
		//如果没有table也没有定义match则一定会匹配上
		这个就跟早期版本没区别了
		if (!udrv->id_table && !udrv->match)
			return 1;
		//如果既定义了table也定义了match,则需两个都能
		匹配上才算匹配成功。如果只定义了其中一个,只需
		定义的那个匹配成功即可。
		return usb_driver_applicable(udev, udrv);

	} else if (is_usb_interface(dev)) {
		struct usb_interface *intf;
		struct usb_driver *usb_drv;
		const struct usb_device_id *id;
		/* device drivers never match interfaces */
		if (is_usb_device_driver(drv))
			return 0;
		intf = to_usb_interface(dev);
		usb_drv = to_usb_driver(drv);

		id = usb_match_id(intf, usb_drv->id_table);
		if (id)
			return 1;
		id = usb_match_dynamic_id(intf, usb_drv);
		if (id)
			return 1;
	}
	return 0;
}

usb总线的匹配分为设备驱动和接口驱动匹配,其驱动类型根据type来区分

static inline int is_usb_device(const struct device *dev)
{
	return dev->type == &usb_device_type;
}

static inline int is_usb_interface(const struct device *dev)
{
	return dev->type == &usb_if_device_type;
}

一、对于设备匹配,更清晰的理解可以看早期的内核版本源码比如4.14。早期版本只要是设备驱动则默认就是匹配上了,返回1。所以对于usb所有的设备device来说都默认走同一个设备驱动程序(即上面所说的usb通用设备驱动),因为如果有多个设备驱动那么都会走这样不合理。
二、最新的版本(当前6.14)增加部分代码,主要是增加了设备device与驱动的匹配规则,后面会分析通用驱动的匹配规则。为后续厂家实现自己的设备驱动提供了依据,但是一般情况下用不到这个。
三、由厂家提供的usb驱动基本都是interface类驱动,其匹配规则如下:

int usb_match_one_id_intf(struct usb_device *dev,
			  struct usb_host_interface *intf,
			  const struct usb_device_id *id)
{
	/* The interface class, subclass, protocol and number should never be
	 * checked for a match if the device class is Vendor Specific,
	 * unless the match record specifies the Vendor ID. */
	if (dev->descriptor.bDeviceClass == USB_CLASS_VENDOR_SPEC &&
			!(id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
			(id->match_flags & (USB_DEVICE_ID_MATCH_INT_CLASS |
				USB_DEVICE_ID_MATCH_INT_SUBCLASS |
				USB_DEVICE_ID_MATCH_INT_PROTOCOL |
				USB_DEVICE_ID_MATCH_INT_NUMBER)))
		return 0;

	if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
	    (id->bInterfaceClass != intf->desc.bInterfaceClass))
		return 0;

	if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_SUBCLASS) &&
	    (id->bInterfaceSubClass != intf->desc.bInterfaceSubClass))
		return 0;

	if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_PROTOCOL) &&
	    (id->bInterfaceProtocol != intf->desc.bInterfaceProtocol))
		return 0;

	if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_NUMBER) &&
	    (id->bInterfaceNumber != intf->desc.bInterfaceNumber))
		return 0;

	return 1;
}

usb host 控制器HCD驱动加载

USB HCD是主机控制器驱动(Host Controller Driver)的缩写,以ehci_hcd为例ehci-hcd是usb2.0控制器,下面是大体的调用流程图
在这里插入图片描述
各个总线的控制器一般都是platform总线加载的usb也不例外,这里只关注重点部分的代码。

1、ehci-hcd回调函数初始化

void ehci_init_driver(struct hc_driver *drv,
		const struct ehci_driver_overrides *over)
{
	/* Copy the generic table to drv and then apply the overrides */
	*drv = ehci_hc_driver;
	if (over) {
		drv->hcd_priv_size += over->extra_priv_size;
		if (over->reset)
			drv->reset = over->reset;
		if (over->port_power)
			drv->port_power = over->port_power;
	}
}

在这里插入图片描述

2、hcd设备的创建

	hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev,
			     dev_name(&dev->dev));

创建hcd比较简单,大体分几个点:
一、为hcd分配内存空间
二、数据成员初始化
三、初始化定时器,这个是比较重要的。使用定时器会直接触发hub-event的调用
四、绑定hcd的回调处理函数

3 hcd的添加

//注意传的参数,包括刚刚创建的hcd、中断号、中断标置:此中断号与其它设备共享
	err = usb_add_hcd(hcd, irq, IRQF_SHARED);

usb_add_hcd是一个核心函数,下面切片来分析该函数的各个功能
一、usb phy硬件的初始化、上电等这里不关注。
二、调用usb_alloc_dev函数生成一个设备device

	rhdev = usb_alloc_dev(NULL, &hcd->self, 0);

在这里插入图片描述
这里是该函数的部分截图,上面清晰的表明此次分配的usb dev是一个设备device
三、刚刚分配的dev将会用作root hub的设备device

	mutex_lock(&usb_port_peer_mutex);
	hcd->self.root_hub = rhdev;
	mutex_unlock(&usb_port_peer_mutex);

四、hcd的速度配置决定了hub的速度上限
五、初始下两个tasklet:高低优先级

	init_giveback_urb_bh(&hcd->high_prio_bh);
	hcd->high_prio_bh.high_prio = true;
	init_giveback_urb_bh(&hcd->low_prio_bh);

static void init_giveback_urb_bh(struct giveback_urb_bh *bh)
{
	spin_lock_init(&bh->lock);
	INIT_LIST_HEAD(&bh->head);
	tasklet_setup(&bh->bh, usb_giveback_urb_bh);
}

在这里插入图片描述
这里的tasklet就是将hcd产生的各种事件交由hub处理的关键中转环节
六、为hcd注册中断处理函数

	if (usb_hcd_is_primary_hcd(hcd) && irqnum) {
		retval = usb_hcd_request_irqs(hcd, irqnum, irqflags);
		if (retval)
			goto err_request_irq;
	}
static int usb_hcd_request_irqs(struct usb_hcd *hcd,
		unsigned int irqnum, unsigned long irqflags)
{
	int retval;
	if (hcd->driver->irq) {
		snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",
				hcd->driver->description, hcd->self.busnum);
		retval = request_irq(irqnum, &usb_hcd_irq, irqflags,
				hcd->irq_descr, hcd);
		……
	return 0;
}
irqreturn_t usb_hcd_irq (int irq, void *__hcd)
{
	struct usb_hcd		*hcd = __hcd;
	irqreturn_t		rc;

	if (unlikely(HCD_DEAD(hcd) || !HCD_HW_ACCESSIBLE(hcd)))
		rc = IRQ_NONE;
	else if (hcd->driver->irq(hcd) == IRQ_NONE)
		rc = IRQ_NONE;
	else
		rc = IRQ_HANDLED;

	return rc;
}

这里的中断号和中断回调处理函数都由ehci-hcd提供
七:hub 设备device的注册

	if (!HCD_DEFER_RH_REGISTER(hcd)) {
		retval = register_root_hub(hcd);
		if (retval != 0)
			goto err_register_root_hub;
		if (hcd->uses_new_polling && HCD_POLL_RH(hcd))
			usb_hcd_poll_rh_status(hcd);
	}

register_root_hub ->usb_new_device->device_add
此时就会将第二步生成的dev注册到usb bus的klist_devices链表上。到此时hcd的工作就结束了,剩下就是设备驱动、接口驱动和root hub的各种处理了。这里要注意的是由于此时此刻注册了usb设备device,所以会触发device和drvier的匹配。下面就来分析下设备device的匹配

hub设备驱动(usb通用设备驱动)的probe

这里从以下几个方面来学习通用设备驱动的加载过程
一、设备驱动和接口驱动的区分
二、设备驱动的match过程
三、通用设备驱动probe的学习
通用设备驱动以下代码进行注册

	retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);

在这里插入图片描述
通用设备驱动的定义:

struct usb_device_driver usb_generic_driver = {
	.name =	"usb",
	.match = usb_generic_driver_match,
	.probe = usb_generic_driver_probe,
	.disconnect = usb_generic_driver_disconnect,
#ifdef	CONFIG_PM
	.suspend = usb_generic_driver_suspend,
	.resume = usb_generic_driver_resume,
#endif
	.supports_autosuspend = 1,
};

先来分析其match规则:
在这里插入图片描述
通用设备驱动的probe:
上面已经说过hcd会主动生成第一个设备驱动即root hub设备驱动,根据匹配规则这里会直接与usb_generic_driver匹配上,调用其probe函数。现在借hub来分析通用设备驱动的probe函数

int usb_generic_driver_probe(struct usb_device *udev)
{
	int err, c;

	if (udev->authorized == 0)
		dev_err(&udev->dev, "Device is not authorized for usage\n");
	else {
		c = usb_choose_configuration(udev);
		if (c >= 0) {
			err = usb_set_configuration(udev, c);
			if (err && err != -ENODEV) {
				dev_err(&udev->dev, "can't set config #%d, error %d\n",
					c, err);

			}
		}
	}
	/* USB device state == configured ... usable */
	usb_notify_add_device(udev);

	return 0;
}

这个probe比较明白,有两点:
一、从设备的众多配置信息中找出最合适的个,返回其下标
二、根据找的设备配置信息,分配interface设备并添加
第二点是问题的关键,只有这里生成的usb interface device才可能与我们的接口驱动进行匹配。比如usb网卡有时候注册不上,查看interface驱动是存在的就是设备没有,问题就出在这上面。这里对usb_set_configuration函数进行切片分析

	if (dev->authorized == 0 || configuration == -1)
		configuration = 0;
	else {
		for (i = 0; i < dev->descriptor.bNumConfigurations; i++) {
			if (dev->config[i].desc.bConfigurationValue ==
					configuration) {
				cp = &dev->config[i];
				break;
			}
		}
	}

从众多配置中,将最合适的配置信息取出来

	if (cp) {
		nintf = cp->desc.bNumInterfaces;
		new_interfaces = kmalloc_array(nintf, sizeof(*new_interfaces),
					       GFP_NOIO);
		if (!new_interfaces)
			return -ENOMEM;

		for (; n < nintf; ++n) {
			new_interfaces[n] = kzalloc(
					sizeof(struct usb_interface),
					GFP_NOIO);
			if (!new_interfaces[n]) {
				ret = -ENOMEM;
free_interfaces:
				while (--n >= 0)
					kfree(new_interfaces[n]);
				kfree(new_interfaces);
				return ret;
			}
		}

		i = dev->bus_mA - usb_get_max_power(dev, cp);
		if (i < 0)
			dev_warn(&dev->dev, "new config #%d exceeds power "
					"limit by %dmA\n",
					configuration, -i);
	}

根据配置信息里告诉的接口数量分配同等数量的usb_interface设备

for (i = 0; i < nintf; ++i) {
		struct usb_interface_cache *intfc;
		struct usb_interface *intf;
		struct usb_host_interface *alt;
		u8 ifnum;

		cp->interface[i] = intf = new_interfaces[i];
		intfc = cp->intf_cache[i];
		intf->altsetting = intfc->altsetting;
		intf->num_altsetting = intfc->num_altsetting;
		intf->authorized = !!HCD_INTF_AUTHORIZED(hcd);
		kref_get(&intfc->ref);

		alt = usb_altnum_to_altsetting(intf, 0);
		if (!alt)
			alt = &intf->altsetting[0];

		ifnum = alt->desc.bInterfaceNumber;
		intf->intf_assoc = find_iad(dev, cp, ifnum);
		intf->cur_altsetting = alt;
		usb_enable_interface(dev, intf, true);
		intf->dev.parent = &dev->dev;
		if (usb_of_has_combined_node(dev)) {
			device_set_of_node_from_dev(&intf->dev, &dev->dev);
		} else {
			intf->dev.of_node = usb_of_get_interface_node(dev,
					configuration, ifnum);
		}
		ACPI_COMPANION_SET(&intf->dev, ACPI_COMPANION(&dev->dev));
		intf->dev.driver = NULL;
		intf->dev.bus = &usb_bus_type;
		intf->dev.type = &usb_if_device_type;
		intf->dev.groups = usb_interface_groups;
		INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
		INIT_WORK(&intf->wireless_status_work, __usb_wireless_status_intf);
		intf->minor = -1;
		device_initialize(&intf->dev);
		pm_runtime_no_callbacks(&intf->dev);
		dev_set_name(&intf->dev, "%d-%s:%d.%d", dev->bus->busnum,
				dev->devpath, configuration, ifnum);
		usb_get_dev(dev);
	}

对所有分配的usb_interface进行初始化,注意这里的device类型都是usb_if_device_type即接口device,要关注下of_node是如何赋值的。

	for (i = 0; i < nintf; ++i) {
		struct usb_interface *intf = cp->interface[i];

		if (intf->dev.of_node &&
		    !of_device_is_available(intf->dev.of_node)) {
			dev_info(&dev->dev, "skipping disabled interface %d\n",
				 intf->cur_altsetting->desc.bInterfaceNumber);
			continue;
		}

		dev_dbg(&dev->dev,
			"adding %s (config #%d, interface %d)\n",
			dev_name(&intf->dev), configuration,
			intf->cur_altsetting->desc.bInterfaceNumber);
		device_enable_async_suspend(&intf->dev);
		ret = device_add(&intf->dev);
		if (ret != 0) {
			dev_err(&dev->dev, "device_add(%s) --> %d\n",
				dev_name(&intf->dev), ret);
			continue;
		}
		create_intf_ep_devs(intf);
	}

将所有的usb_interface添加到设备中去,也就是接口设备。将会给所有的interface驱动提供匹配的机会,这里的hub设备驱动生成的自然就是其interface设备,自然会匹配上前面说到的hub驱动。

hub interface驱动的probe

hub interface驱动的注册

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

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_probe函数主要是初始化hub,其中非常重要的就是hub_event队列的初始化

	kref_init(&hub->kref);
	hub->intfdev = &intf->dev;
	hub->hdev = hdev;
	INIT_DELAYED_WORK(&hub->leds, led_work);
	INIT_DELAYED_WORK(&hub->init_work, NULL);
	INIT_WORK(&hub->events, hub_event);
	INIT_LIST_HEAD(&hub->onboard_hub_devs);
	spin_lock_init(&hub->irq_urb_lock);
	timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0);
	usb_get_intf(intf);
	usb_get_dev(hdev);

	usb_set_intfdata(intf, hub);
	intf->needs_remote_wakeup = 1;
	pm_suspend_ignore_children(&intf->dev, true);

	if (hdev->speed == USB_SPEED_HIGH)
		highspeed_hubs++;

	if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
		hub->quirk_check_port_auto_suspend = 1;

	if (id->driver_info & HUB_QUIRK_DISABLE_AUTOSUSPEND) {
		hub->quirk_disable_autosuspend = 1;
		usb_autopm_get_interface_no_resume(intf);
	}

	if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) {
		onboard_hub_create_pdevs(hdev, &hub->onboard_hub_devs);
		return 0;
	}

和hub_irq对hub->urb的填充,hub_irq主要作用就是触发hub_event调用
hub_configure函数太过庞大这里就不具体分析了,把谓主主要流程搞清楚即可,细节需要带着问题才能更好的搞明白。
在这里插入图片描述
如上所示当hub接口驱动起来后,其内部的hub_event函数将持续工作,处理所有usb的插拨,将调用流程后
hub_event->port_event->hub_port_connect_change->hub_port_connect->usb_alloc_dev
hub_event收到port变化后进行相关处理,最后条件满足所用usb_alloc_dev函数生成设备device,此device再与通用驱动匹配上然后生成接口device再与我们外设提供的接口驱动匹配上完成一个轮回。

hub_event的触发

最后就只剩下一件事了,既然hub_event是后续所有usb设备生成的核心起点,那么它是如何被触发调用的呢。对于ehci-hcd来说大体流程如下图
在这里插入图片描述
前面说过在hcd添加的时候会注册中断处理函数,其最终调用的就是ehci-hcd提供的ehci_irq回调函数,后续最终会由tasklet下半部进行处理。
在这里插入图片描述
而tasklet的初始下如下:

static void init_giveback_urb_bh(struct giveback_urb_bh *bh)
{
	spin_lock_init(&bh->lock);
	INIT_LIST_HEAD(&bh->head);
	tasklet_setup(&bh->bh, usb_giveback_urb_bh);
}

在这里插入图片描述
在这里插入图片描述
到这里基本上整个逻辑就闭环了。usb子系统过于复杂,后续需要带着问题仔细分析哪个环节出了问题,但是只要对整个流程有个大体的了解,不至于一出问题就一头雾水。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bruk_spp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值