USB原理以及对照hub.c的代码分析

声明:本文章是看了韦东山老师的视频所总结的学习到的东西,所以如果有与其他网友一样的地方,敬请原谅,如果对你有帮助,那是我的荣幸。

在介绍其他的知识之前,我先说一下USB的基础知识:

首先,USB分为主从结构,所有的USB传输都是从USB主机这方发起的,而USB从机没有主动通知USB主机的能力。

而USB主机的电路图为:

我们可以看到主机的D+和D-两个引脚通过15K的电阻接地。而USB从机的电路图为:

可以看到从机的D-接地,而D+通过1.5K电阻接到电源极。所以当从设备接到主设备时,通过从机D+的主机的D+获得电位差,从而在硬件上产生电位变化从而引起某些中断,使主机设备知道有USB设备接入。而当USB设备接入后,主机设备是怎样识别从机设备的那?

这就要说到标识符了,标识符就是USB设备之间通信的规范。当一个新的USB设备接入时,他的默认地址为0,此时主设备通过标识符识别从设备,并与其通信,来获得更多关于从设备的信息。并为从设备在1到127找到一个没有分配的地址。并将该地址赋给这个从设备,这样,这个从设备就可以使用新获得的地址和主设备通信了。

而一个USB驱动程序的框架为:

app:                                                                                                                                                                                     ........................................................................................................................................................

                              usb设备驱动程序:这个层知道设备间传输的数据的含义,根据数据做什么有他决定                                                                         内核                       ..........................................................................................................................

                             usb总线驱动程序:设备usb设备,查找并安装对应的设备驱动程序,提供usb读写函数(不知道数据含义)  ..................................................................................................................................................................................................                                                                  usb主机控制器                                                                                                                                                                            硬件                           ................................................................................................................................................                                                                           usb从机设备

 

上面就是usb的框架,而要写usb设备驱动之前,我们有必要了解下usb总线驱动程序都做了什么,而他做的对usb设备驱动有什么影响。

下面我们来分析drivers/usb/core/hub.c函数中当端口状态发生变化所引起的代码:

当主机的端口状态发生变化时,会触发usb的中断处理函数usb_irq,而usb_irq又会调用kick_khubd函数,而kick_khubd函数会唤醒khubd队列:wake_up(&khubd_wait);。那么是哪个函数使这个队列休眠的那:是hub_thread函数,该函数通过调用wait_event_interruptible(khubd_wait,!list_empty(&hub_event_list)来实现对队列的休眠,而当中断发生时唤醒这个队列中的 khubd而,又是哪个函数调用hub_thread,那就是hub_event,而最终是hub_port_connect_change做了这些事。那么我们将对hub_port_connect_change函数分析,来了解usb插入后usb总线驱动程序做了什么事。

 

static void hub_port_connect_change(struct usb_hub *hub, int port1,
					u16 portstatus, u16 portchange)

 

在与usb插入过程相联系前,我先将这个函数大致关系说一下,以方便大家对后面的分析有一个全面的了解:

 hub_port_connect_change

struct usb_device *udev;  //定义设备

udev = usb_alloc_dev(hdev, hdev->bus, port1);  //为这个 设备分配空间

choose_address(udev);    //为这个设备选择合适的地址,方便主机与其通信

hub_port_init(hub, udev, port1, i);  //初始化端口:把地址告诉从机,并 获得他的描述符

usb_new_device(udev);         //将所有的描述符读出并解析,最后将这个设备加到设备链表中。

上面就是这个函数的主要做的事情。下面我们细分一下:

首先他会定义一个设备结构体,并为其分配空间:

 

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

 

然后会为其分配一个未被占用的地址:

 

 

		/* set the address */
		choose_address(udev);

而在choose_address(udev)函数中又是通过那个函数找到的未被占用的地址那?我们打开choose_address(udev)函数,会发现:

 

 

	/* Try to allocate the next devnum beginning at bus->devnum_next. */
	devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
			bus->devnum_next);   //寻找从当前位置起,到128,看哪个位置为0
	if (devnum >= 128)                   //如果寻找的位置大于128,则从1开始继续往上寻找
		devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);

	bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);

	if (devnum < 128) {
		set_bit(devnum, bus->devmap.devicemap);  //将找到的值存到devnum中
		udev->devnum = devnum;
	}

从上面的代码我们可以看出,地址是从当前所占用的地址开始往上寻找,当寻找的地址超过128时,他会返回到1,继续向上寻找。我们说过usb设备刚接到usb主机上时,都是通过0地址通信的,而现在我们找到了一个未被占用的地址,那么我们将要把这个未被占用的地址分配给从机,而将地址分配给从机的函数在hub_port_init(hub, udev, port1, i)函数中。

 

下面我们分析这个函数:

 

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
		int retry_counter)

在这个函数下将地址告诉从设备的函数为:retval = hub_set_address(udev);

 

hub_port_init函数还有一个功能就是获得设备描述符的信息通过下面的函数:

 

 

retval = usb_get_device_descriptor(udev, 8);
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);

 

也许有人会问为什么要获取两次描述符,而且两次获取的长度还不相同。这就要说到设备描述符了:

 

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
	__u8  bLength;
	__u8  bDescriptorType;      //描述符类型
	__le16 bcdUSB;		//USB版本号
	__u8  bDeviceClass;   //设备类
							
	__u8  bDeviceSubClass;     //设备子类
	__u8  bDeviceProtocol;    //协议
	__u8  bMaxPacketSize0;     //端点0的最大包的大小,数据长度
	                         
	__le16 idVendor;           //厂家ID
	__le16 idProduct;         //产品ID
	__le16 bcdDevice;          
	__u8  iManufacturer;       //生产厂商
	__u8  iProduct;           
	__u8  iSerialNumber;
	__u8  bNumConfigurations;   //设备有多少种配置
} 

通过上边代码的描述我们知道,前8个字节的数据为

	__u8  bLength;
	__u8  bDescriptorType;      //描述符类型
	__le16 bcdUSB;					//USB版本号
	__u8  bDeviceClass;   //设备类
				
	__u8  bDeviceSubClass;     //设备子类
	__u8  bDeviceProtocol;    //协议
	__u8  bMaxPacketSize0;     //端点0的最大包的大小,数据长度 __u8  bLength;
	__u8  bDescriptorType;      //描述符类型
	__le16 bcdUSB;					//USB版本号
	__u8  bDeviceClass;   //设备类
				
	__u8  bDeviceSubClass;     //设备子类
	__u8  bDeviceProtocol;    //协议
	__u8  bMaxPacketSize0;     //端点0的最大包的大小,数据长度 

而最后一个字节的就是最大包数据的大小,所以第二次使用这个去获取描述符的长度。

 

通过上面获得的设备描述符,我们可以将配置描述符的信息读出来并解析它,最后将插入的这个设备的信息写入相应的配置项,并将这个设备添加到设备链表中。

而详细的代码在usb_new_device(udev);中。

而在说明上面代码之前,让我们先来了解下描述符:标识符就是USB设备之间通信的规范,而不同的阶层又有不同的描述符,下面这张图就展现了不同描述符之间的关系:



下面,我们就讲usb_new_device函数,

 

int usb_new_device(struct usb_device *udev)

而这个函数要做的就是把所有的描述符都不出来,并解析,这个功能主要由usb_get_configuration(udev);函数完成:详细代码为:

 

 

int usb_get_configuration(struct usb_device *dev)
	int ncfg = dev->descriptor.bNumConfigurations;  //获得配置描述符的个数int ncfg = dev->descriptor.bNumConfigurations;  //获得配置描述符的个数
	struct usb_config_descriptor *desc;
struct usb_config_descriptor *desc;
	length = ncfg * sizeof(struct usb_host_config);       //计算描述符所占空间大小
	dev->config = kzalloc(length, GFP_KERNEL);           //为描述符分配空间
length = ncfg * sizeof(struct usb_host_config);       //计算描述符所占空间大小
	dev->config = kzalloc(length, GFP_KERNEL);           //为描述符分配空间
	/* We grab just the first descriptor so we know how long
		 * the whole configuration is */
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
	    buffer, USB_DT_CONFIG_SIZE);                      //获得首个配置描述符		 * the whole configuration is */
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
	    buffer, USB_DT_CONFIG_SIZE);                      //获得首个配置描述符
	result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
		   	 bigbuffer, length);                 //获得所有的配置描述符

result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
		   	 bigbuffer, length);                 //获得所有的配置描述符

	result = usb_parse_configuration(&dev->dev, cfgno,
		   	 &dev->config[cfgno], bigbuffer, length); //解析所有的配置描述符
result = usb_parse_configuration(&dev->dev, cfgno,
		   	 &dev->config[cfgno], bigbuffer, length); //解析所有的配置描述符

 

 

 

 

 

而上面的所有工作做完后,我们将要通过device_add函数将这个设备加入到设备链表中,具体代码为:

 

		/* tie the class to the device */
		list_add_tail(&dev->node, &dev->class->devices);

		/* notify any interfaces that the device is here */
		list_for_each_entry(class_intf, &dev->class->interfaces, node)
			if (class_intf->add_dev)
				class_intf->add_dev(dev, class_intf);

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值