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