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

2019-07-12 20:38发布

声明:本文章是看了韦东山老师的视频所总结的学习到的东西,所以如果有与其他网友一样的地方,敬请原谅,如果对你有帮助,那是我的荣幸。 在介绍其他的知识之前,我先说一下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);