USB驱动开发——认识USB驱动

本篇博客将聚焦于USB驱动框架上,下一篇博客将介绍USB驱动开发
在网上找到了一个图
在这里插入图片描述
开发的重点是USB设备驱动程序,不过我们可以从USB总线驱动(也就是图中的USB核心)开始分析
当把USB设备插入到开发板,串口便有打印信息如下

usb 1-1: new full speed USB device using s3c2410-ohci and address 2
usb 1-1: configuration #1 chosen from 1 choice
scsi0 : SCSI emulation for USB Mass Storage devices
scsi 0:0:0:0: Direct-Access     HTC      Android Phone    0100 PQ: 0 ANSI: 2
sd 0:0:0:0: [sda] Attached SCSI removable disk

显然这些信息中前面部分应该是host的内核中相关的驱动打印出来的,我们在内核/driver目录中查询“USB device using”到底是哪个文件中的内容,当然为什么要选中这个呢?这个需要一点点小技巧,因为需要选择尽量只有我们需要找的文件里才有的信息;同时这个信息不是因为%而打印出来的,向这咯full speed极有可能是%打印出来的,因为USB协议中有多种传输速率,full speed只是其中一种,所以这是一个可选项.
查询结果

usb/core/hub.c:2186:		  "%s %s speed %sUSB device using %s and address %d\n",
Binary file usb/core/hub.o matches
Binary file usb/core/usbcore.o matches
Binary file usb/core/built-in.o matches
Binary file usb/built-in.o matches
Binary file built-in.o matches

我们知道当插上USB设备后,内核会调用hub_port_init函数

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)
{
	static DEFINE_MUTEX(usb_address0_mutex);

	struct usb_device	*hdev = hub->hdev;
	int			i, j, retval;
	unsigned		delay = HUB_SHORT_RESET_TIME;
	enum usb_device_speed	oldspeed = udev->speed;
	char 			*speed, *type;

	/* root hub ports have a slightly longer reset period
	 * (from USB 2.0 spec, section 7.1.7.5)
	 */
	if (!hdev->parent) {
		delay = HUB_ROOT_RESET_TIME;
		if (port1 == hdev->bus->otg_port)
			hdev->bus->b_hnp_enable = 0;
	}

	mutex_lock(&usb_address0_mutex);
	/*首先是获取USB设备的speed类型*/
	/* Reset the device; full speed may morph to high speed */
	retval = hub_port_reset(hub, port1, udev, delay);
	if (retval < 0)		/* error or disconnect */
		goto fail;
				/* success, speed is known */
	retval = -ENODEV;

	if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed) {
		dev_dbg(&udev->dev, "device reset changed speed!\n");
		goto fail;
	}
	oldspeed = udev->speed;
  
	/* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
	 * it's fixed size except for full speed devices.
	 * For Wireless USB devices, ep0 max packet is always 512 (tho
	 * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
	 */
	 /*这里的英文介绍值得看一下*/
	 /*根据不同的速度来初始化udev->ep0.desc.wMaxPacketSize*/
	switch (udev->speed) {
	case USB_SPEED_VARIABLE:	/* fixed at 512 */
		udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(512);
		break;
	case USB_SPEED_HIGH:		/* fixed at 64 */
		udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64);
		break;
	case USB_SPEED_FULL:		/* 8, 16, 32, or 64 */
		/* to determine the ep0 maxpacket size, try to read
		 * the device descriptor to get bMaxPacketSize0 and
		 * then correct our initial guess.
		 */
		udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64);
		break;
	case USB_SPEED_LOW:		/* fixed at 8 */
		udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(8);
		break;
	default:
		goto fail;
	}
 
	type = "";
	switch (udev->speed) {
	case USB_SPEED_LOW:	speed = "low";	break;
	case USB_SPEED_FULL:	speed = "full";	break;
	case USB_SPEED_HIGH:	speed = "high";	break;
	case USB_SPEED_VARIABLE:
				speed = "variable";
				type = "Wireless ";
				break;
	default: 		speed = "?";	break;
	}
	/*在获取了speed和type以后打印如下内容*/
	dev_info (&udev->dev,
		  "%s %s speed %sUSB device using %s and address %d\n",
		  (udev->config) ? "reset" : "new", speed, type,
		  udev->bus->controller->driver->name, udev->devnum);

	/* Set up TT records, if needed  */
	if (hdev->tt) {
		udev->tt = hdev->tt;
		udev->ttport = hdev->ttport;
	} else if (udev->speed != USB_SPEED_HIGH
			&& hdev->speed == USB_SPEED_HIGH) {
		udev->tt = &hub->tt;
		udev->ttport = port1;
	}
 
	/* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
	 * Because device hardware and firmware is sometimes buggy in
	 * this area, and this is how Linux has done it for ages.
	 * Change it cautiously.
	 *
	 * NOTE:  If USE_NEW_SCHEME() is true we will start by issuing
	 * a 64-byte GET_DESCRIPTOR request.  This is what Windows does,
	 * so it may help with some non-standards-compliant devices.
	 * Otherwise we start with SET_ADDRESS and then try to read the
	 * first 8 bytes of the device descriptor to get the ep0 maxpacket
	 * value.

		/*usb_get_device_descriptor函数很重要,我们提前留意一下*/
		retval = usb_get_device_descriptor(udev, 8);
		if (retval < 8) {
			dev_err(&udev->dev, "device descriptor "
					"read/%s, error %d\n",
					"8", retval);
			if (retval >= 0)
				retval = -EMSGSIZE;
		} else {
			retval = 0;
			break;
		}
	}
	if (retval)
		goto fail;

	i = udev->descriptor.bMaxPacketSize0 == 0xff?
	    512 : udev->descriptor.bMaxPacketSize0;
	if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) {
		if (udev->speed != USB_SPEED_FULL ||
				!(i == 8 || i == 16 || i == 32 || i == 64)) {
			dev_err(&udev->dev, "ep0 maxpacket = %d\n", i);
			retval = -EMSGSIZE;
			goto fail;
		}
		dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
		udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
		ep0_reinit(udev);
	}
  
	retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);

	retval = 0;
}

我们可以这个函数主要是识别usb设备的speed,然后对udev->ep0.desc.wMaxPacketSize进行初始化
那是谁调用了hub_port_init 函数呢?
hub_port_connect_change和usb_reset_device这两个函数都有调用,这是两种不同情况,后面就会理解了
我们目前只看hub_port_connect_change


/* Handle physical or logical connection change events.
在如下的情况该函数会被调用
 * This routine is called when:
 * 	a port connection-change occurs;
 *	a port enable-change occurs (often caused by EMI);
 *	usb_reset_device() encounters changed descriptors (as from
 *		a firmware download)
 * caller already locked the hub
 */
static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)
{
	struct usb_device *hdev = hub->hdev;
	struct device *hub_dev = hub->intfdev;
	u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
	int status, i;
 
	dev_dbg (hub_dev,
		"port %d, status %04x, change %04x, %s\n",
		port1, portstatus, portchange, portspeed (portstatus));

	if (hub->has_indicators) {
		set_port_led(hub, port1, HUB_LED_AUTO);
		hub->indicator[port1-1] = INDICATOR_AUTO;
	}
 
	/* Disconnect any existing devices under this port */
	if (hdev->children[port1-1])
		usb_disconnect(&hdev->children[port1-1]);
	clear_bit(port1, hub->change_bits);


	
	if (portchange & USB_PORT_STAT_C_CONNECTION) {
		status = hub_port_debounce(hub, port1);
		if (status < 0) {
			if (printk_ratelimit())
				dev_err (hub_dev, "connect-debounce failed, "
						"port %d disabled\n", port1);
			goto done;
		}
		portstatus = status;
	}

	/* Return now if nothing is connected */
	if (!(portstatus & USB_PORT_STAT_CONNECTION)) {

		/* maybe switch power back on (e.g. root hub was reset) */
		if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
				&& !(portstatus & (1 << USB_PORT_FEAT_POWER)))
			set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
 
		if (portstatus & USB_PORT_STAT_ENABLE)
  			goto done;
		return;
	}


	for (i = 0; i < SET_CONFIG_TRIES; i++) {
		struct usb_device *udev;

		/* reallocate for each attempt, since references
		 * to the previous one can escape in various ways
		 */
		udev = usb_alloc_dev(hdev, hdev->bus, port1);
		if (!udev) {
			dev_err (hub_dev,
				"couldn't allocate port %d usb_device\n",
				port1);
			goto done;
		}

		usb_set_device_state(udev, USB_STATE_POWERED);
		udev->speed = USB_SPEED_UNKNOWN;
 		udev->bus_mA = hub->mA_per_port;
		udev->level = hdev->level + 1;

		/* set the address */
		/*给接入的设备分配地址号*/
		choose_address(udev);
		if (udev->devnum <= 0) {
			status = -ENOTCONN;	/* Don't retry */
			goto loop;
		}

		/* reset and get descriptor */
		status = hub_port_init(hub, udev, port1, i);
		if (status < 0)
			goto loop;

		/* consecutive bus-powered hubs aren't reliable; they can
		 * violate the voltage drop budget.  if the new child has
		 * a "powered" LED, users should notice we didn't enable it
		 * (without reading syslog), even without per-port LEDs
		 * on the parent.
		 */
		...

我们继续追代码,看谁调用了hub_port_connect_change函数
我们发现是hub_events这个函数调用了,这个函数非常复杂,没有必要深入研究
继续追踪发现,是在内核线程hub_thread一直运行这hub_events

static int hub_thread(void *__unused)
{
	do {
		hub_events();
		wait_event_interruptible(khubd_wait,
				!list_empty(&hub_event_list) ||
				kthread_should_stop());
		try_to_freeze();
	} while (!kthread_should_stop() || !list_empty(&hub_event_list));

	pr_debug("%s: khubd exiting\n", usbcore_name);
	return 0;
}

在do_while循环里,首先调用hub_events,然后wait_event_interruptible(khubd_wait, !list_empty(&hub_event_list) | kthread_should_stop());
我们需要解决两个疑问
1、hub_thread这个内核线程被谁注册到内核中?
2、khubd_wait是在哪里被唤醒?

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

	khubd_task = kthread_run(hub_thread, NULL, "khubd");
	if (!IS_ERR(khubd_task))
		return 0;

	/* Fall through if kernel_thread failed */
	usb_deregister(&hub_driver);
	printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);

	return -1;
}

在usb_hub_init创建并且运行hub_thread这个线程,而且也可以从这里佐证一点,内核线程会一直运行,如果返回,那说明出问题了
而usb_hub_init这个函数是被usb_init函数所调用,usb_init正是usb的核心层入口函数,所以当这个模块被编译到内核并被加载,就会有usb_hub_init函数的执行,因而也就有了内核线程hub_thread的运行
第二个问题

static void kick_khubd(struct usb_hub *hub)
{
	unsigned long	flags;

	/* Suppress autosuspend until khubd runs */
	to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;

	spin_lock_irqsave(&hub_event_lock, flags);
	if (list_empty(&hub->event_list)) {
		list_add_tail(&hub->event_list, &hub_event_list);
		wake_up(&khubd_wait);
	}
	spin_unlock_irqrestore(&hub_event_lock, flags);
}

进一步追踪发现是hub_irq函数调用了kick_khubd
hub_irq有一段英文注释 /* completion function, fires on port status changes and various faults */
翻译:完成函数,在端口状态更改和各种故障时触发。当USB设备连接到主机时,硬件设计上会使得产生一个电压变化,也就是这里的端口状态变更。而hub_irq则是由hub_probe函数调用的hub_configure注册到内核中,如是我们就可以和总线构建联系。
现在我们从下往上来分析USB核心层(主要是hub层)代码

/*usb核心层入口函数*/																		/*当hub的device链表中的元素和driver链表中的元素匹配成功后会调用probe函数*/
static int __init usb_init(void);													hub_probe;
	int usb_hub_init(void)														hub_configure(hub, endpoint);
		/*注册hub_driver注册到usb_bus_type总线上*/									usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq,hub, endpoint->bInterval);
																		kick_khubd(hub);								
		if (usb_register(&hub_driver) < 0) {						wake_up(&khubd_wait);
			/*向内核注册hub_thread内核线程*/
			khubd_task = kthread_run(hub_thread, NULL, "khubd");
				static int hub_thread(void *__unused)
					{
						/*内核线程循环运行hub_events函数,然后khubd_wait加入到等待队列,当有新的usb设备接入并成功触发hub_irq,就可以唤醒khubd_wait*/
						do {
							hub_events();
							wait_event_interruptible(khubd_wait,
									!list_empty(&hub_event_list) ||
									kthread_should_stop());
							try_to_freeze();
						} while (!kthread_should_stop() || !list_empty(&hub_event_list));
					}
					hub_events;
						hub_port_connect_change;
							/*构建一个usb_device,总线类型为usb_bus_type*/
							udev = usb_alloc_dev(hdev, hdev->bus, port1);
							/*选择一个地址,1-127之间第一个还没有被占用的*/
							choose_address(udev);
							/*usb设备接入时,正是这个函数打印了输出信息*/
							/* reset and get descriptor */
							hub_port_init(hub, udev, port1, i);
								/*将分配到的地址信号告诉给usb设备*/
								hub_set_address(udev);
								usb_get_device_descriptor;/*获取设备描述符*/

							status = usb_new_device(udev);
								err = usb_get_configuration(udev); // 把所有的描述符都读出来,并解析
								usb_parse_configuration
								device_add  // 把device放入usb_bus_type的dev链表, 
								            // 从usb_bus_type的driver链表里取出usb_driver,
								            // 把usb_interface和usb_driver的id_table比较
								            // 如果能匹配,调用usb_driver的probe							
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值