几个重要的结构

struct--接口

[plain]  view plain copy
  1. struct usb_interface
  2. {
  3. /* array of alternate settings for this interface,
  4. * stored in no particular order */
  5. struct usb_host_interface *altsetting;
  6. struct usb_host_interface *cur_altsetting;      /* the currently
  7. * active alternate setting */
  8. unsigned num_altsetting;        /* number of alternate settings */
  9. int minor;                      /* minor number this interface is
  10. * bound to */
  11. enum usb_interface_condition condition;         /* state of binding */
  12. unsigned is_active:1;           /* the interface is not suspended */
  13. unsigned needs_remote_wakeup:1; /* driver requires remote wakeup */
  14. struct device dev;              /* interface specific device info */
  15. struct device *usb_dev;         /* pointer to the usb class's device, if any */
  16. int pm_usage_cnt;               /* usage counter for autosuspend */
  17. };

结构体struct usb_host_interface就代表一个设置

struct usb_interface中的struct usb_host_interface *cur_altsetting成员,表示当前正在使用的设置

struct--设置

[plain]  view plain copy
  1. struct usb_host_interface
  2. {
  3. struct usb_interface_descriptor desc;//usb描述符,主要有四种usb描述符,设备描述符,配置描述符,接口描述符和端点描述符,协议里规定一个usb设备是必须支持这四大描述符的。
  4. //usb描述符放在usb设备的eeprom里边
  5. /* array of desc.bNumEndpoint endpoints associated with this
  6. * interface setting. these will be in no particular order.
  7. */
  8. struct usb_host_endpoint *endpoint;//这个设置所使用的端点
  9. char *string;           /* iInterface string, if present */
  10. unsigned char *extra;   /* Extra descriptors */关于额外描述符
  11. int extralen;
  12. };

具体到接口描述符,它当然就是描述接口本身的信息的。一个接口可以有多个设置,使用不同的设置,描述接口的信息会有些不同,所以接口描述符并没有放在struct usb_interface结构里,而是放在表示接口设置的struct usb_host_interface结构里。

struct--接口描述符

[plain]  view plain copy
  1. struct usb_interface_descriptor
  2. {
  3. __u8  bLength;//接口描述符长度
  4. __u8 bDescriptorType;//接口描述符类型
  5. __u8 bInterfaceNumber;//接口号。每个配置可以包含多个接口,这个值就是它们的索引值。
  6. __u8 bAlternateSetting;//接口使用的是哪个可选设置。协议里规定,接口默认使用的设置总为0号设置。
  7. __u8 bNumEndpoints;//接口拥有的端点数量。这里并不包括端点0,因为端点0是控制传输,是所有的设备都必须提供的,所以这里就没必要多此一举的包括它了。对于hub,因为它的传输是中断传输,所以此值为1(不包括端点0)
  8. __u8 bInterfaceClass;
  9. __u8 bInterfaceSubClass;//对于hub,这个值是零
  10. __u8 bInterfaceProtocol;
  11. __u8 iInterface;
  12. } __attribute__ ((packed));

struct--端点

[plain]  view plain copy
  1. struct usb_host_endpoint
  2. {
  3. struct usb_endpoint_descriptor desc;
  4. struct list_head                urb_list;//端点要处理的urb队列.urb是usb通信的主角,设备中的每个端点都可以处理一个urb队列.要想和你的usb通信,就得创建一个urb,并且为它赋好值,
  5. //交给咱们的usb core,它会找到合适的host controller,从而进行具体的数据传输
  6. void                            *hcpriv;//这是提供给HCD(host controller driver)用的
  7. struct ep_device                *ep_dev;        /* For sysfs info */
  8. unsigned char *extra;   /* Extra descriptors */
  9. int extralen;
  10. };

struct--端点描述符

[plain]  view plain copy
  1. struct usb_endpoint_descriptor {
  2. __u8 bLength;
  3. __u8 bDescriptorType;//接口类型
  4. __u8 bEndpointAddress;//它的bits 0~3表示的就是端点号,USB_ENDPOINT_NUMBER_MASK;bit7是方向,USB_ENDPOINT_DIR_MASK
  5. __u8 bmAttributes;//bmAttributes,属性,总共8位,其中bit1和bit0 共同称为Transfer Type,即传输类型, 00 表示控制,01 表示等时,10 表示批量,11 表示中断。USB_ENDPOINT_XFERTYPE_MASK
  6. __le16 wMaxPacketSize;//端点一次可以处理的最大字节数,如果你发送的数据量大于端点的这个值,也会分成多次一次一次来传输
  7. __u8 bInterval;//USB是轮询式的总线,这个值表达了端点一种美好的期待,希望主机轮询自己的时间间隔,但实际上批准不批准就是host的事了
  8. /* NOTE: these two are _only_ in audio endpoints. */
  9. /* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
  10. __u8 bRefresh;
  11. __u8 bSynchAddress;
  12. } __attribute__ ((packed));

注:0号端点没有自己的端点描述符

struct--设备

[plain]  view plain copy
  1. struct usb_device {
  2. int             devnum;         //devnum只是usb设备在一条usb总线上的编号.一条usb_bus_type类型的总线上最多可以连上128个设备
  3. char            devpath [16];   /* Use in messages: /port/port/*/  //对于root hub.会将dev->devpath[0]=’0’
  4. enum usb_device_state   state;  //设备的状态Attached,Powered,Default,Address,Configured,Suspended;
  5. //Attached表示设备已经连接到usb接口上了,是hub检测到设备时的初始状态。那么这里所谓的USB_STATE_NOTATTACHED就是表示设备并没有Attached。
  6. //Address状态表示主机分配了一个唯一的地址给设备,此时设备可以使用缺省管道响应主机的请求
  7. //Configured状态表示设备已经被主机配置过了,也就是协议里说的处理了一个带有非0值的SetConfiguration()请求,此时主机可以使用设备提供的所有功能
  8. //Suspended挂起状态,为了省电,设备在指定的时间内,3ms吧,如果没有发生总线传输,就要进入挂起状态。此时,usb设备要自己维护包括地址、配置在内的信息
  9. enum usb_device_speed   speed;  /* high/full/low (or error) */
  10. struct usb_tt   *tt;            //如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。
  11. int             ttport;         //如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。
  12. unsigned int toggle[2];         /* one bit for each endpoint     //他实际上就是一个位图.IN方向的是toggle[0].OUT方向的是toggle[1].其实,这个数组中的每一位表示ep的toggle值
  13. * ([0] = IN, [1] = OUT) */它里面的每一位表示的就是每个端点当前发送或接收的数据包是DATA0还是DATA1
  14. struct usb_device *parent;      /* our hub, unless we're the root */
  15. //USB设备是从Root Hub开始,一个一个往外面连的,比如Root Hub有4个口,每个口连一个USB设备,比如其中有一个是Hub,那么这个Hub有可以继续有多个口,于是一级一级的往下连,
  16. //最终连成了一棵树。
  17. struct usb_bus *bus;            /* Bus we're part of */设备所在的总线
  18. struct usb_host_endpoint ep0;   //端点0的特殊地位决定了她必将受到特殊的待遇,在struct usb_device对象产生的时候它就要初始化
  19. struct device dev;              /* Generic device interface */嵌入到struct usb_device结构里的struct device结构
  20. struct usb_device_descriptor descriptor;/* Descriptor */设备描述符,此结构体的bMaxPacketSize0 filed保存了端点0的maximum packet size
  21. struct usb_host_config *config; //设备拥有的所有配置
  22. struct usb_host_config *actconfig;//设备正在使用的配置
  23. struct usb_host_endpoint *ep_in[16];//ep_in[16],359行,ep_out[16],除了端点0,一个设备即使在高速模式下也最多只能再有15个IN端点和15个OUT端点,端点0太特殊了,
  24. struct usb_host_endpoint *ep_out[16];//对应的管道是Message管道,又能进又能出特能屈能伸的那种,所以这里的ep_in和ep_out数组都有16个值
  25. char **rawdescriptors;          /* Raw descriptors for each config */
  26. unsigned short bus_mA;          /* Current available from the bus */这个值是在host controller的驱动程序中设置的,通常来讲,计算机的usb端口可以提供500mA的电流
  27. u8 portnum;                     //不管是root hub还是一般的hub,你的USB设备总归要插在一个hub的端口上才能用,portnum就是那个端口号。
  28. u8 level;                       //层次,也可以说是级别,表征usb设备树的级连关系。Root Hub的level当然就是0,其下面一层就是level 1,再下面一层就是level 2,依此类推
  29. unsigned discon_suspended:1;    /* Disconnected while suspended */
  30. unsigned have_langid:1;         /* whether string_langid is valid */
  31. int string_langid;              /* language ID for strings */
  32. /* static strings from the device */
  33. char *product;                  /* iProduct string, if present */
  34. char *manufacturer;             /* iManufacturer string, if present */
  35. char *serial;                   /* iSerialNumber string, if present */
  36. //分别用来保存产品、厂商和序列号对应的字符串描述符信息
  37. struct list_head filelist;
  38. #ifdef CONFIG_USB_DEVICE_CLASS
  39. struct device *usb_classdev;
  40. #endif
  41. #ifdef CONFIG_USB_DEVICEFS
  42. struct dentry *usbfs_dentry;    /* usbfs dentry entry for the device */
  43. #endif
  44. /*
  45. * Child devices - these can be either new devices
  46. * (if this is a hub device), or different instances
  47. * of this same device.
  48. *
  49. * Each instance needs its own set of data structures.
  50. */
  51. int maxchild;                   /* Number of ports if hub */
  52. struct usb_device *children[USB_MAXCHILDREN];
  53. int pm_usage_cnt;               /* usage counter for autosuspend */
  54. u32 quirks;                     //quirk就是用来判断这些有毛病的产品啥毛病的
  55. #ifdef CONFIG_PM
  56. struct delayed_work autosuspend; /* for delayed autosuspends */
  57. struct mutex pm_mutex;          /* protects PM operations */
  58. unsigned long last_busy;        /* time of last use */
  59. int autosuspend_delay;          /* in jiffies */
  60. unsigned auto_pm:1;             /* autosuspend/resume in progress */
  61. unsigned do_remote_wakeup:1;    /* remote wakeup should be enabled */
  62. unsigned autosuspend_disabled:1; /* autosuspend and autoresume */
  63. unsigned autoresume_disabled:1;  /*  disabled by the user */
  64. #endif
  65. };

struct--设备描述符

[plain]  view plain copy
  1. struct usb_device_descriptor {
  2. __u8  bLength;
  3. __u8  bDescriptorType;
  4. __le16 bcdUSB;            //USB spec的版本号,一个设备如果能够进行高速传输,那么它设备描述符里的bcdUSB这一项就应该为0200H。
  5. __u8  bDeviceClass;            //
  6. __u8  bDeviceSubClass;
  7. __u8  bDeviceProtocol;        //为了设置tt。full/low speed的hub的bDeviceProtocol是0;对于high speed的hub,其bDeviceProtocol为1表示是single tt,为2表示是multiple tt;
  8. __u8  bMaxPacketSize0;        //端点0一次可以处理的最大字节数
  9. __le16 idVendor;            //厂商id
  10. __le16 idProduct;            //产品id
  11. __le16 bcdDevice;            //设备版本号
  12. __u8  iManufacturer;
  13. __u8  iProduct;
  14. __u8  iSerialNumber;
  15. __u8  bNumConfigurations;    //设备当前速度模式下支持的配置数量。有的设备可以在多个速度模式下操作,这里包括的只是当前速度模式下的配置数目,不是总的配置数目
  16. } __attribute__ ((packed));
  17. #define USB_DT_DEVICE_SIZE

18
注:为什么端点0的属性bMaxPacketSize0要放到设备描述符里边呢 ?
首先表明了这是一个共性的东西。
前面说端点的时候说了端点0并没有一个专门的端点描述符,因为不需要,基本上它所有的特性都在spec里规定好了的,然而,别忘了这里说的是“基本上”,有一个特性则是不一样的,这叫做maximum packet size,每个端点都有这么一个特性,即告诉你该端点能够发送或者接收的包的最大值。对于通常的端点来说,这个值被保存在该端点描述符中的wMaxPacketSize这一个field,而对于端点0就不一样了,由于它自己没有一个描述符,而每个设备又都有这么一个端点,所以这个信息被保存在了设备描述符里,所以我们在设备描述符里可以看到这么一项,bMaxPacketSize0。而且spec还规定了,这个值只能是8,16,32或者64这四者之一,如果一个设备工作在高速模式,这个值还只能是64,如果是工作在低速模式,则只能是8,取别的值都不行。

struct--配置

[plain]  view plain copy
  1. struct usb_config_descriptor {
  2. __u8  bLength;
  3. __u8  bDescriptorType;        //这里的值并不仅仅可以为USB_DT_CONFIG,还可以为USB_DT_OTHER_SPEED_CONFIG
  4. __le16 wTotalLength;        //使用GET_DESCRIPTOR请求从设备里获得配置描述符信息时,返回的数据长度
  5. __u8  bNumInterfaces;        //这个配置包含的接口数量
  6. __u8  bConfigurationValue;    //对于拥有多个配置的幸运设备来说,可以拿这个值为参数,使用SET_CONFIGURATION请求来改变正在被使用的 USB配置,bConfigurationValue就指明了将要激活哪个配置。
  7. //咱们的设备虽然可以有多个配置,但同一时间却也只能有一个配置被激活。捎带着提一下,SET_CONFIGURATION请求也是标准的设备请求之一,专门用来设置设备的配置。
  8. __u8  iConfiguration;        //描述配置信息的字符串描述符的索引值
  9. __u8  bmAttributes;        //这个字段表征了配置的一些特点,比如bit 6为1表示self-powered,bit 5为1表示这个配置支持远程唤醒。另外,它的bit 7必须为1
  10. __u8  bMaxPower;            //设备正常运转时,从总线那里分得的最大电流值,以2mA为单位。设备可以使用这个字段向hub表明自己需要的的电流,但如果设备需求过于旺盛,请求的超出了hub所能给予的,hub就会直接拒绝
  11. //还记得struct usb_device结构里的bus_mA吗?它就表示hub所能够给予的。计算机的usb端口可以提供最多500mA的电流
  12. } __attribute__ ((packed));

struct--配置描述符

[plain]  view plain copy
  1. struct usb_config_descriptor {
  2. __u8  bLength;
  3. __u8  bDescriptorType;        //这里的值并不仅仅可以为USB_DT_CONFIG,还可以为USB_DT_OTHER_SPEED_CONFIG
  4. __le16 wTotalLength;        //使用GET_DESCRIPTOR请求从设备里获得配置描述符信息时,返回的数据长度
  5. __u8  bNumInterfaces;        //这个配置包含的接口数量
  6. __u8  bConfigurationValue;    //对于拥有多个配置的幸运设备来说,可以拿这个值为参数,使用SET_CONFIGURATION请求来改变正在被使用的 USB配置,bConfigurationValue就指明了将要激活哪个配置。
  7. //咱们的设备虽然可以有多个配置,但同一时间却也只能有一个配置被激活。捎带着提一下,SET_CONFIGURATION请求也是标准的设备请求之一,专门用来设置设备的配置。
  8. __u8  iConfiguration;        //描述配置信息的字符串描述符的索引值
  9. __u8  bmAttributes;        //这个字段表征了配置的一些特点,比如bit 6为1表示self-powered,bit 5为1表示这个配置支持远程唤醒。另外,它的bit 7必须为1
  10. __u8  bMaxPower;            //设备正常运转时,从总线那里分得的最大电流值,以2mA为单位。设备可以使用这个字段向hub表明自己需要的的电流,但如果设备需求过于旺盛,请求的超出了hub所能给予的,hub就会直接拒绝
  11. //还记得struct usb_device结构里的bus_mA吗?它就表示hub所能够给予的。计算机的usb端口可以提供最多500mA的电流
  12. } __attribute__ ((packed));

struct--usb接口的缓存

[plain]  view plain copy
  1. struct usb_interface_cache {
  2. unsigned num_altsetting;        /* number of alternate settings */
  3. struct kref ref;                /* reference counter */
  4. /* variable-length array of alternate settings for this interface,
  5. * stored in no particular order */
  6. struct usb_host_interface altsetting[0];
  7. };

struct--hub描述符

[plain]  view plain copy
  1. struct usb_hub_descriptor {
  2. __u8  bDescLength;
  3. __u8  bDescriptorType;
  4. __u8  bNbrPorts;        //Number of downstream facing ports that this hub supports,就是说这个hub所支持的下行端口,这个值不能比31大
  5. __le16 wHubCharacteristics;    //它记录了很多信息
  6. __u8  bPwrOn2PwrGood;
  7. __u8  bHubContrCurrent;        //bHubContrCurrent是Hub控制器的最大电流需求
  8. /* add 1 bit for hub status change; round to bytes */
  9. __u8  DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];//用来判断这个端口连接的设备是否是可以移除的,每一个bit代表一个端口,如果该bit为0,则说明可以被移除,为1,就说明不可以移除
  10. __u8  PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];
  11. } __attribute__ ((packed));

struct--hub结构体

[plain]  view plain copy
  1. struct usb_hub {
  2. struct device        *intfdev;    /* the "interface" device */
  3. struct usb_device    *hdev;
  4. struct kref        kref;
  5. struct urb        *urb;        /* for interrupt polling pipe */
  6. /* buffer for urb  with extra space in case of babble */
  7. char            (*buffer)[8];
  8. dma_addr_t        buffer_dma;    /* DMA address for buffer */
  9. union {
  10. struct usb_hub_status    hub;
  11. struct usb_port_status    port;
  12. }            *status;    /* buffer for status reports */
  13. struct mutex        status_mutex;    /* for the status buffer */
  14. int            error;        /* last reported error */
  15. int            nerrors;    /* track consecutive errors */
  16. struct list_head    event_list;    /* hubs w/data or errs ready */
  17. unsigned long        event_bits[1];    /* status change bitmask */
  18. unsigned long        change_bits[1];    /* ports with logical connect status change */
  19. unsigned long        busy_bits[1];    /* ports being reset or
  20. //resumed */这个flag只有在reset和resume的函数内部才会设置,所以可以通过测试busy_bits的相应位来检测某个端口是否在执行reset或resume操作。
  21. #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
  22. #error event_bits[] is too short!
  23. #endif
  24. struct usb_hub_descriptor *descriptor;    /* class descriptor */
  25. struct usb_tt        tt;        /* Transaction Translator */
  26. unsigned        mA_per_port;     //提供给每一个port的电流,一般为500mA,但可能会因为hub提供不了这么大的电流或者host controller那边提供不了这么大的电流,而导致不足500mA
  27. unsigned        limited_power:1; //对于host controller那边限制了电流的情况下,这个值被设置为1
  28. unsigned        quiescing:1;    //quiescing是停止的意思,在reset的时候我们会设置它为1,在suspend的时候我们也会把它设置为1,一旦把它设置成了1,那么hub驱动程序就不会再提交任何URB
  29. unsigned        activating:1;    //如果activating为1,那么hub驱动程序就会给每个端口发送一个叫做Get Port Status的请求,通常情况下,hub驱动只有在一个端口发生了状态变化的情况下才会去发送
  30. //Get Port Status从而去获得端口的状态。正常情况下,quiescing和activating都应该为0.
  31. unsigned        disconnected:1;
  32. unsigned        has_indicators:1;
  33. u8            indicator[USB_MAXCHILDREN];
  34. struct delayed_work    leds;
  35. };

struct--usb_bus结构体

[plain]  view plain copy
  1. struct usb_bus {
  2. struct device *controller;    /* host/master side hardware */
  3. int busnum;            /* Bus number (in order of reg) */
  4. char *bus_name;            /* stable id (PCI slot_name etc) */
  5. u8 uses_dma;            /* Does the host controller use DMA? */
  6. u8 otg_port;            /* 0, or number of OTG/HNP port */
  7. unsigned is_b_host:1;        /* true during some HNP roleswitches */
  8. unsigned b_hnp_enable:1;    /* OTG: did A-Host enable HNP? */
  9. int devnum_next;        /* Next open device number in //          在总线初始化的时候,其devnum_next被设置为1
  10. * round-robin allocation */
  11. struct usb_devmap devmap;    /* device address allocation map */ 每条总线设有一个地址映射表
  12. struct usb_device *root_hub;    /* Root hub */
  13. struct list_head bus_list;    /* list of busses */
  14. int bandwidth_allocated;    /* on this bus: how much of the time
  15. * reserved for periodic (intr/iso)
  16. * requests is used, on average?
  17. * Units: microseconds/frame.
  18. * Limits: Full/low speed reserve 90%,
  19. * while high speed reserves 80%.
  20. */
  21. int bandwidth_int_reqs;        /* number of Interrupt requests */
  22. int bandwidth_isoc_reqs;    /* number of Isoc. requests */
  23. #ifdef CONFIG_USB_DEVICEFS
  24. struct dentry *usbfs_dentry;    /* usbfs dentry entry for the bus */
  25. #endif
  26. struct device *dev;        /* device for this bus */
  27. #if defined(CONFIG_USB_MON)
  28. struct mon_bus *mon_bus;    /* non-null when associated */
  29. int monitored;            /* non-zero when monitored */
  30. #endif
  31. };
  32. struct--usb_devmap
  33. struct usb_devmap {
  34. unsigned long devicemap[128 / (8*sizeof(unsigned long))];
  35. };

几个重要的函数分析usb_hub_init所做的工作:
代码原型:

[plain]  view plain copy
  1. khubd_task = kthread_run(hub_thread, NULL, "khubd");

建立一个内核级线程:hub_threadhub_thread所做的工作:
代码原型:

[plain]  view plain copy
  1. do {
  2. hub_events();
  3. wait_event_freezable(khubd_wait,
  4. !list_empty(&hub_event_list) ||
  5. kthread_should_stop());    //一般来说,只要hub_event_list不为空,就会一直调用hub_events()函数,hub_event_list将所有的usb_hub的list_head类型的event_list对象连在一起
  6. } while (!kthread_should_stop() || !list_empty(&hub_event_list));

调用规则:
hub_events函数所做的工作:
对每个端口号(共计bNbrPorts个端口,bNbrPorts这个值从hub描述符里边得到,因为此值描述了hub所用用的端口的情况),假如满足下列条件则调用hub_port_connect_change:
1.连接有变化
2.端口本身重新使能,即所谓的enable,这种情况通常就是为了对付电磁干扰的,正如我们前面的判断中所说的那样
3.在复位一个设备的时候发现其描述符变了,这通常对应的是硬件本身有了升级.很显然,第一种情况是真正的物理变化,后两者就算是逻辑变化
代码模型如下:

[plain]  view plain copy
  1. for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {
  2. if (connect_change) //表示满足如上条件之一
  3. hub_port_connect_change(hub, i,
  4. portstatus, portchange);

个人感觉,此处的i没有什么实际意义,只是为了保证有“几个端口,才能接几个设备”这样的一个逻辑。虽然说usb_bus最多可以接128个设备,hub最多可以接31个设备,但是,假如hub没有那么多的端口,你怎么接设备?hub_port_connect_change的调用规则:
代码模型:

[plain]  view plain copy
  1. for (i = 0; i < SET_CONFIG_TRIES; i++)            //在此驱动里边,SET_CONFIG_TRIES=4,新策略尝试两次,旧策略尝试两次
  2. {
  3. udev = usb_alloc_dev(hdev, hdev->bus, port1);
  4. udev->speed = USB_SPEED_UNKNOWN;
  5. //进行一些必要的设置
  6. choose_address(udev);                        //设置了udev->devnum
  7. //错误判断
  8. status = hub_port_init(hub, udev, port1, i);        //先进行两次新的策略(i=0和=1时),如果不行就再进行两次旧的策略(i=2和i=3时).所有这一切只有一个目的,就是为了获得设备的描述符
  9. //设置了udev->tt、udev->ttport和udev->ep0.desc.wMaxPacketSize,设置udev->status= USB_STATE_ADDRESS

hub_port_init功能:
    hub_port_init里边为udev->tt和udev->ttport和udev->ep0.desc.wMaxPacketSize赋了值,设置udev->status= USB_STATE_ADDRESS.
    它的长篇大论只是为了讨论如何确定赋给udev->ep0.desc.wMaxPacketSize的值,这个值是由设备描述符的bMaxPacketSize0字段确定的.每调用一次hub_port_init,使用一种策略。
hub_port_init的原型:

[plain]  view plain copy
  1. static int  hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)
  2. retval = hub_port_reset(hub, port1, udev, delay);            //将hub hub上的udev设备重启,并设置udev->speed的值
  3. //根据udev->speed的值设置udev->ep0.desc.wMaxPacketSize的值,但此处的值仅仅是个猜测,最终端点0的maxinum packet size的值要根据设备描述符的bMaxPacketSize0字段得到
  4. //设置udev->tt和udev->ttport的值
  5. for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100)))        //GET_DESCRIPTOR_TRIES=2,是为了多执行几次保证正确
  6. {
  7. if (USE_NEW_SCHEME(retry_counter))                     //USE_NEW_SCHEME判断使用什么策略,USB_NEW_SCHEME(i)将在i为0和1的时候为1,在i为2和3的时候为0
  8. {                                        //当retry_counter=0,1时,USE_NEW_SCHEME(retry_counter)=1,表示使用新策略;
  9. //当retry_counter=2,3时,USE_NEW_SCHEME(retry_counter)=0,表示使用旧策略
  10. #define GET_DESCRIPTOR_BUFSIZE    64
  11. buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);    //首先定义一个struct usb_device_descriptor的指针buf,然后申请64个字节的空间
  12. for (j = 0; j < 3; ++j) {
  13. buf->bMaxPacketSize0 = 0;
  14. r = usb_control_msg(udev, usb_rcvaddr0pipe(),    //发送一个64字节的控制传输的请求
  15. USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
  16. USB_DT_DEVICE << 8, 0,
  17. buf, GET_DESCRIPTOR_BUFSIZE,
  18. USB_CTRL_GET_TIMEOUT);
  19. switch (buf->bMaxPacketSize0) {
  20. case 8: case 16: case 32: case 64: case 255:
  21. if (buf->bDescriptorType ==
  22. USB_DT_DEVICE) {
  23. r = 0;
  24. break;
  25. }
  26. /* FALL THROUGH */
  27. default:
  28. if (r == 0)
  29. r = -EPROTO;
  30. break;
  31. }
  32. if (r == 0)
  33. break;
  34. }
  35. udev->descriptor.bMaxPacketSize0 = buf->bMaxPacketSize0;//得到udev->descriptor.bMaxPacketSize0
  36. kfree(buf);
  37. retval = hub_port_reset(hub, port1, udev, delay);    //将hub hub上的udev设备重启,并设置udev->speed的值
  38. #undef GET_DESCRIPTOR_BUFSIZE
  39. }
  40. for (j = 0; j < SET_ADDRESS_TRIES; ++j) {                //SET_ADDRESS_TRIES=2,多试几次
  41. retval = hub_set_address(udev, devnum);            //告知设备它的总线地址,设置udev->status= USB_STATE_ADDRESS
  42. if (retval >= 0)                            //表示正确,退出循环
  43. break;
  44. msleep(200);
  45. }
  46. if (retval < 0) {
  47. dev_err(&udev->dev,
  48. "device not accepting address %d, error %d\n",    //我的驱动打印出了这个信息,表示hub_set_address函数出错了
  49. devnum, retval);
  50. goto fail;
  51. }
  52. if (USE_NEW_SCHEME(retry_counter))                //假如这是新策略,执行到这里,表示得到了正确的设备描述符,退出大循环
  53. break;
  54. retval = usb_get_device_descriptor(udev, 8);            //执行到这里,说明使用的是旧策略,先一次读取8个字节
  55. //假如返回出错,那么go to fail吧,两种策略都失败了
  56. }                                            //到此为止,应该成功读取到了设备描述符,并可以从中得到bMaxPacketSize0的值
  57. //根据bMaxPacketSize0的值重新设置udev->ep0.desc.wMaxPacketSize的值
  58. retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);     //再一次读取设备描述符来测试可不可以正确读取,注意这次读取的长度为USB_DT_DEVICE_SIZE
  59. //由于知道了endpoint 0的max packet size,所以可以进行正常的控制传输了
  60. //假如retval == sizeof(udev->descriptor),那么说明控制传输ok了,也验证了端点0的maxinum packet size的值是正确的,那么happy的返回把
  61. }

hub_port_reset功能:

hub_port_reset的原型:

[plain]  view plain copy
  1. static int hub_port_reset(struct usb_hub *hub, int port1,
  2. struct usb_device *udev, unsigned int delay)
  3. {
  4. int i, status;
  5. /* Block EHCI CF initialization during the port reset.
  6. * Some companion controllers don't like it when they mix.
  7. */
  8. down_read(&ehci_cf_port_reset_rwsem);
  9. /* Reset the port */
  10. for (i = 0; i < PORT_RESET_TRIES; i++) {                         //PORT_RESET_TRIES=5
  11. status = set_port_feature(hub->hdev,                    //发送一个hub类型的控制请求,设置一个feature,传递进来的feature是USB_PORT_FEAT_RESET,即对应于usb spec中的reset
  12. port1, USB_PORT_FEAT_RESET);
  13. if (status)
  14. dev_err(hub->intfdev,
  15. "cannot reset port %d (err = %d)\n",
  16. port1, status);
  17. else {
  18. status = hub_port_wait_reset(hub, port1, udev, delay);    //取得端口的状态,然后判断reset是否成功,如果成功,则根据端口的状态字设置udev->speed的值,否则,出错
  19. if (status && status != -ENOTCONN)
  20. dev_dbg(hub->intfdev,
  21. "port_wait_reset: err = %d\n",
  22. status);
  23. }
  24. //在我的例子理,hub_port_wait_reset返回了-110
  25. /* return on disconnect or reset */
  26. switch (status) {
  27. case 0:
  28. /* TRSTRCY = 10 ms; plus some extra */
  29. msleep(10 + 40);
  30. udev->devnum = 0;    /* Device now at address 0 */
  31. /* FALL THROUGH */
  32. case -ENOTCONN:       &n,bsp;                            //ENOTCONN=107,transport endpoint is not connected
  33. case -ENODEV:                                    //ENODEV=19,no such device
  34. clear_port_feature(hub->hdev,                        //status的值为0,-107,-19的时候才会执行clear_port_feature和usb_set_device
  35. port1, USB_PORT_FEAT_C_RESET);
  36. /* FIXME need disconnect() for NOTATTACHED device */
  37. usb_set_device_state(udev, status                    //status为0的情况,属于正常情况,从这时候开始,struct usb_device结构体的状态就将记录为USB_STATE_DEFAULT
  38. ? USB_STATE_NOTATTACHED
  39. : USB_STATE_DEFAULT);
  40. goto done;
  41. }
  42. dev_dbg (hub->intfdev,                                //status的值不为0,-107,-19的时候才会执行到这里,比如,我的驱动里为-110
  43. "port %d not enabled, trying reset again\n",
  44. port1);
  45. delay = HUB_LONG_RESET_TIME;
  46. }
  47. dev_err (hub->intfdev,                                    //假如执行到这里,说明几次尝试重启都失败了
  48. "Cannot enable port %i.  Maybe the USB cable is bad?\n",
  49. port1);
  50. done:
  51. up_read(&ehci_cf_port_reset_rwsem);
  52. return status;
  53. }

hub_port_wait_reset功能:
取得端口的状态,然后判断reset是否成功,如果成功,则根据端口的状态字设置udev->speed的值,否则,出错
hub_port_wait_reset函数原型:

[plain]  view plain copy
  1. static int hub_port_wait_reset(struct usb_hub *hub, int port1,
  2. struct usb_device *udev, unsigned int delay)
  3. {
  4. //超时重试控制
  5. ret = hub_port_status(hub, port1, &portstatus, &portchange);    //得到端口的状态
  6. ..
  7. if (!(portstatus & USB_PORT_STAT_CONNECTION))                //如果在reset期间设备都被撤掉了,那就返回吧
  8. return -ENOTCONN;
  9. if ((portchange & USB_PORT_STAT_C_CONNECTION))                //如果又一次汇报说有设备插入,那就是见鬼了.返回错误
  10. return -ENOTCONN;
  11. if (!(portstatus & USB_PORT_STAT_RESET) &&
  12. (portstatus & USB_PORT_STAT_ENABLE)) {                //reset真正完成以后,status就应该是enabled,所以if如果满足就说明reset好了
  13. if (hub_is_wusb(hub))
  14. udev->speed = USB_SPEED_VARIABLE;
  15. else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
  16. udev->speed = USB_SPEED_HIGH;
  17. else if (portstatus & USB_PORT_STAT_LOW_SPEED)
  18. udev->speed = USB_SPEED_LOW;
  19. else
  20. udev->speed = USB_SPEED_FULL;
  21. return 0;                                    //reset好了以后,设置udev->speed字段,然后返回
  22. //问题:怎么能从端口的状态里边得到设备的speed字段呢?有点疑惑
  23. }
  24. //超时重试控制
  25. }
  26. hub_port_status其实就是调用了get_port_status(hub->hdev, port1, &hub->status->port);
  27. get_port_status的实现如下:
  28. static int get_port_status(struct usb_device *hdev, int port1,
  29. struct usb_port_status *data)
  30. {
  31. int i, status = -ETIMEDOUT;         //ETIMEDOUT=110
  32. for (i = 0; i < USB_STS_RETRIES && status == -ETIMEDOUT; i++) {
  33. status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
  34. USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1,
  35. data, sizeof(*data), USB_STS_TIMEOUT);
  36. }
  37. return status;
  38. }

在我的驱动里边,这个函数出错,返回值是-110,

关于usb驱动的若干基本知识:

1.什么是tt电路?
知道tt干嘛的吗?tt叫做transaction translator.你可以把它想成一块特殊的电路,是hub里面的电路,确切的说是高速hub中的电路,我们知道usb设备有三种速度的,分别是low speed,full speed,high speed.即所谓的低速/全速/高速,抗日战争那会儿,这个世界上只有low speed/full speed的设备,没有high speed的设备,后来解放后,国民生产力的大幅度提升催生了一种high speed的设备,包括主机控制器,以前只有两种接口的,OHCI/UHCI,这都是在usb spec 1.0的时候,后来2.0推出了EHCI,高速设备应运而生.Hub也有高速hub和过去的hub,但是这里就有一个兼容性问题了,高速的hub是否能够支持低速/全速的设备呢?一般来说是不支持的,于是有了一个叫做TT的电路,它就负责高速和低速/全速的数据转换,于是,如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里。tt有两种,一种是single tt,一种是multi tt.前者表示整个hub就是一个TT,而multi tt表示每个端口都配了一个TT.大多数hub是single TT,因为一个hub一个TT就够了,国家资源那么紧张,何必铺张浪费.使用single TT就是支持国家反腐倡廉!
2.
我们知道usb-storage里面最常见的传输方式就是control/bulk传输,而对于hub,它的传输方式就是control/interrupt,而最有特色的正是它的中断传输.
3.
USB_PORT_STAT_CONNECTION的意思的确是表征是否有设备连接在这个端口上,我们不妨假设有,那么portstatus和它相与的结果就是1,在usb spec里面,这一位叫做Current Connect Status位,于是这里我们会看到connect_change被设置成了1.而接下来,USB_PORT_STAT_C_CONNECTION则是表征这个端口的Current Connect Status位是否有变化,如果有变化,那么portchange和USB_PORT_STAT_C_CONNECTION相与的结果就是1,对于这种情况,我们需要发送另一个请求以清除这个flag,并且将connect_change也设置为1.这个请求叫做Clear Port Feature.这个请求也是Hub的标准请求。
usb的接口驱动的数据结构为:struct usb_drive,它和usb接口对等;
usb设备驱动的数据结构为:struct usb_device_driver,它和整个usb设备对等

4.配置和设置的区别:
配置还有设置有什么区别,起码我平时即使是再无聊也不会去想这个,但老外不一样,他们不知道老子也不知道郑板桥,所以说他们挺较真儿这个,还分了两个词,配置是configuration,设置是setting。先说配置,一个手机可以有多种配置,比如可以摄像,可以接在电脑里当做一个U盘,那么这两种情况就属于不同的配置,在手机里面有相应的选择菜单,你选择了哪种它就按哪种配置进行工作,供你选择的这个就叫做配置。很显然,当你摄像的时候你不可以访问这块U盘,当你访问这块U盘的时候你不可以摄像,因为你做了选择。第二,既然一个配置代表一种不同的功能,那么很显然,不同的配置可能需要的接口就不一样,我假设你的手机里从硬件上来说一共有5个接口,那么可能当你配置成U盘的时候它只需要用到某一个接口,当你配置成摄像的时候,它可能只需要用到另外两个接口,可能你还有别的配置,然后你可能就会用到剩下那两个接口,也就是说一个配置可能包括到若干个接口
再说说设置,一个手机可能各种配置都确定了,是振动还是铃声已经确定了,各种功能都确定了,但是声音的大小还可以变吧,通常手机的音量是一格一格的变动,大概也就5、6格,那么这个可以算一个setting吧。

5.端点、接口、设置、配置
•  设备通常有一个或多个配置.
•  配置常常有一个或多个接口
•  接口常常有一个或多个设置.
•  接口有零或多个端点.

6.一般来说,主设备号表明了设备的种类,也表明了设备对应着哪个驱动程序,而次设备号则是因为一个驱动程序要支持多个设备而为了让驱动程序区分它们而设置的。也就是说,主设备号用来帮你找到对应的驱动程序,次设备号给你的驱动用来决定对哪个设备进行操作

7.
每个端点都有一个叫做maximum packet size的filed,即告诉你该端点能够发送或者接收的包的最大值.对于通常的端点来说,这个值被保存在该端点描述符中的wMaxPacketSize这一个field,而对于端点0就不一样了,由于它自己没有一个描述符,而每个设备又都有这么一个端点,所以这个信息被保存在了设备描述符里,所以我们在设备描述符里可以看到这么一项,bMaxPacketSize0,而且spec还规定了,这个值只能是8,16,32或者64这四者之一,而且,如果一个设备工作在高速模式,这个值还只能是64,取别的值都不行

8.
在usb_hcd_submit_urt()中,Root Hub, rh_urb_enqueue会被执行,对于非Root Hub,即一般的Hub,driver->urb_enqueue会被执行,对于uhci来说,就是uhci_urb_enqueue会被执行.
对于一般的hub,会执行usb/host/ixp4xx_ehci_hcd.c中的ehci_urb_enqueue(),而ehci_urb_enqueue()则会调用submit_async()(对于设备请求,调用到了这里)
对于一般的hub
ehci_watchdog->    ehci_work()出现ehci_work(),就开始返回了
给出一个请求,会执行如下过程:rh_timer_func->    usb_hcd_poll_rh_status->    usb_hcd_giveback_urb->    urb->complete

9.对于root hub的情况,为什么不用对传输缓存区进行DMA映射呢?
在后面的处理中我们可以看到,其实对于root hub ,它不需要进行实际的物理传输,linux按照spec上的规定,将它静态放置在内存中,在进行相关操作的时候,只要直接copy过去就可以了.
usb_hcd_giveback_urb函数里边会调用urb->complete(urb);

10.urb发送处理过程 
(1).假如一个usb_control_msg函数被调用 
(2).usb_control_msg会调用usb_internal_control_msg函数
(3).usb_internal_control_msg会调用usb_alloc_urb分配一个urb结构,然后调用usb_fill_control_urb函数来填充这个urb结构,然后调用usb_start_wait_urb函数
(4).usb_start_wait_urb会调用usb_submit_urb(urb,GFP_NOIP)将这个urb发送给usb core,完成过程如下:
(4.1).usb_submit_urb对urb进行一些设置,然后交给usb主控制器驱动来出作进一步的处理,调用的函数为usb_hcd_submit_urb
(4.2).在usb_hcd_submit_urb函数中,判断urb要发送到的设备,假如此设备为 root hub,那么调用rh_urb_enqueue(hcd, urb),否则,调用hcd->driver->urb_enqueue(hcd, urb, mem_flags);
在我的主控制器驱动里注册的urb_enqueue函数为ehci_urb_enqueue

[plain]  view plain copy
  1. if (is_root_hub(urb->dev))
  2. status = rh_urb_enqueue(hcd, urb);
  3. else
  4. //如果是一般的设备
  5. status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);

因为我分析的是一个普通的usb设备,所以调用ehci_urb_enqueue
(4.3).ehci_urb_enqueue将来自usbcore层的urb的传输请求转换成ehci 可识别的传输描述结构(iTD,siTD,qTD等),然后安排到echi的periodic schedule list或者asynchronous schedule list的合适位置。
对于urb 所要发送到endpoint的类型的control的情况下,此函数将依次调用两个函数:qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)和submit_async (ehci, ep, urb, &qtd_list, mem_flags)
(4.4).调用qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)从urb生成一系列qTD结构,并将这些结构连接到qtd_list;
(4.5).调用submit_async (ehci, ep, urb, &qtd_list, mem_flags)将qtd_list链接的qTD结构分配到ep对应的QH, 将该QH安排到ehci asynchronous schedule list中;
(5)usb_start_wait_urb调用usb_submit_urb将urb最终发送给主控制器驱动之后,它开始满怀信息的等待usb core给它的结果。而它呢,在usb core给它结果以前,它没什么事情可干了把。于是,它调用wait_for_completion_timeout进行非中断的等待(休眠了自己)。

11.urb的完成
(1).在usb_create_hcd函数内有一句:

[plain]  view plain copy
  1. hcd->rh_timer.function = rh_timer_func;

(2).rh_timer_func函数实现如下:

[plain]  view plain copy
  1. static void rh_timer_func (unsigned long _hcd)
  2. {
  3. usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
  4. }

(3).usb_hcd_poll_rh_status函数实现如下:
root hub的中断传输使用polling方式(使用一个定时器),当驱动请求它的时候。也就是说,当有事件发生的时候,驱动负责调用usb_hcd_poll_rh_status

[plain]  view plain copy
  1. void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
  2. {
  3. struct urb *urb;
  4. int length;
  5. unsigned long flags;
  6. char buffer[4]; /* Any root hubs with > 31 ports? */
  7. //检测主机控制器驱动是否已经注册
  8. if (unlikely(!hcd->rh_registered))
  9. return;
  10. if (!hcd->uses_new_polling && !hcd->status_urb)
  11. return;
  12. //负责检测端口和td队列的状态
  13. length = hcd->driver->hub_status_data(hcd, buffer);
  14. //端口有设备
  15. if (length > 0)
  16. {
  17. /* try to complete the status urb */
  18. spin_lock_irqsave(&hcd_root_hub_lock, flags);
  19. urb = hcd->status_urb;
  20. //检测urb是否存在
  21. if (urb)
  22. {
  23. hcd->poll_pending = 0;
  24. //清除hcd的状态urb
  25. hcd->status_urb = NULL;
  26. //置实际传输长度为1
  27. urb->actual_length = length;
  28. //拷贝端口状态描述组到urb中
  29. memcpy(urb->transfer_buffer, buffer, length);
  30. //卸载urb与节点的连接
  31. usb_hcd_unlink_urb_from_ep(hcd, urb);
  32. spin_unlock(&hcd_root_hub_lock);
  33. //返回urb给驱动程序
  34. usb_hcd_giveback_urb(hcd, urb, 0);
  35. spin_lock(&hcd_root_hub_lock);
  36. }
  37. else
  38. {
  39. length = 0;
  40. hcd->poll_pending = 1;
  41. }
  42. spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
  43. }
  44. /* The USB 2.0 spec says 256 ms. This is close enough and won't
  45. * exceed that limit if HZ is 100. The math is more clunky than
  46. * maybe expected, this is to make sure that all timers for USB devices
  47. * fire at the same time to give the CPU a break inbetween */
  48. //每间隔HZ/4就执行一次hcd->rh_timer中指定的函数
  49. if (hcd->uses_new_polling ? hcd->poll_rh :(length == 0 && hcd->status_urb != NULL))
  50. mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
  51. }

usb_hcd_poll_rh_status首先调用hcd->driver->hub_status_data(hcd, buffer);在我的ehci中为ehci_hub_status_data
ehci_hub_status_data负责检测端口和td队列的状态。假如ehci_hub_status_data返回值大于0,表明存在已经完成的urb,判断hcd->status_urb是否为空,
假如非空,那么设置hcd结构,然后调用usb_hcd_giveback_urb.
(4).usb_hcd_giveback_urb设置urb->status,然后调用urb->complete,这将导致urb的完成处理函数被执行。
(5).在urb的完成处理函数中会调用complete(&ctx->done)或类似的函数。
(6).complete(&ctx->done)将会唤醒usb_start_wait_urb函数.从usb_start_wait_urb函数的wait_for_completion_timeout处继续执行。
如果超时,也会返回usb_start_wait_urb,然后进行错误处理;如果一切正常,那么恭喜,此次通信圆满成功。

12.
ehci_irq
ehci_work
scan_saync
qh_completions
在qh_competions函数里,urb->status的值在此函数里发生了变化,
ehci_urb_done
usb_hcd_giveback_urb

13.
系统中断入口:
asm_do_IRQ
handle_level_irq
handle_IRQ_event
usb控制器中断
usb_hcd_irq
ehci_irq
ehci_wrok
scan_async
qh_completions
qtd_copy_status
consistent_asny

usb驱动的基本结构和函数简介相关推荐

  1. usb驱动的基本结构和函数简介【转】

    转自: http://blog.csdn.net/jeffade/article/details/7698404 几个重要的结构 struct--接口 [plain]  view plain copy ...

  2. WinCE USB驱动CDevice::EnterOperationalState函数相关

    wince usb驱动中的CHub::AttachDevice函数 函数CHub::AttachDevice在HubStatusChangeThread中被调用.当有设备插入的时候,该函数被调用.函数 ...

  3. Wince下usb驱动详细总结(史无前例的详细)

    0,前言: 1, 本篇文章只讲wince下的usb host驱动,并深入解析HID驱动.本博客的目的并不是只是为了讲怎么写驱动, 更重要的是: 1,了解wince驱动的架构. 2,学习微软的写作方法, ...

  4. arm-linux下usb驱动的结构和相关函数

    几个重要的结构 struct--接口 struct usb_interface {          /* array of alternate settings for this interface ...

  5. STM32 之三 标准外设版USB驱动库详解(架构+文件+函数+使用说明+示例程序)

    写在前面 目前,ST的USB驱动有两套,一套是早期的独立版USB驱动,官方培训文档中称为Legacy library:一套为针对其Cube 系列的驱动,根据芯片不同可能有区别,具体见对应芯片的Cube ...

  6. USB驱动详解(主从对比)

    参考资料: <圈圈教你玩USB> <STM32 USB-FS-Device development kit> 沁雪微电子-USB开发视频教程 零声教育-Linux内核-USB系 ...

  7. USB 驱动架构浅析

    USB 驱动架构浅析 之前一直概念模糊,最近简单总结了一下 1.USB简介 USB,即Universal Serial Bus(通用串行总线)的缩写,是一个外部总线标准,用于规范电脑与外部设备的连接和 ...

  8. 基于RK3399Pro的USB驱动-鼠标键值获取

    目录 USB协议简介 USB 设备基础概念 设备描述符 配置描述符 接口描述符 端点描述符 字符串描述符 管道 USB 端点分类 USB 总线驱动概念 驱动程序讲解 代码编写: 函数入口 probe函 ...

  9. linux-2.6.14下USB驱动移植心得

    linux-2.6.14下USB驱动移植心得 USB 驱动移植心得 一.代码修改 主要是按照这个贴来做: http://www.hfrk.net/S3C2410/kaifa/0631522024832 ...

最新文章

  1. 那些学校计算机招不满,那些招不满人的985院校,请留意!
  2. SAP CRM category search的实现
  3. 多迪技术总监揭秘:PHP为什么是世界上最好的语言?
  4. python画图灰白_python 站点资料插值画图及白化
  5. [AngularJS]Chapter 1 AnjularJS简介
  6. 是时候展现真正的技术了!4道程序员智力题你能对几道| IT巨能唠
  7. CSS Grid 网格布局教程
  8. 你知道他们的输出结果吗?
  9. 程序员必备工具包(实物)
  10. mysql 匹配所有记录_如何记录mysql中所有的查询
  11. 关于C语言中一些常用函数的说明
  12. java对象 引用 原理,java对象引用和对象值得行为
  13. Mysql常见的引擎
  14. IM在线聊天-微聊即时通讯完整源码
  15. 谷歌浏览器Chrome离线安装包下载地址
  16. oracle入门教程+视频教程
  17. matlab恶狼追兔问题,饿狼追兔问题-数学建模.doc
  18. 蓝桥杯 试题 B: 纪念日
  19. 校园招聘的秋招和春招有什么区别?
  20. E280 P0410故障修复

热门文章

  1. 【Mysql学习之旅-2】经典sql面试题及答案分析
  2. pyinstaller 打包的exe在某些win7上面报错 faild to execute script pyi_rth_multiprocessing
  3. 解决了:微信小程序使用canvas绘制倒计时圆圈和数字居中的实现
  4. 学习OpenMV(一)详细参数及简单介绍
  5. 易语言利用Vs2017Linker编译器
  6. ElasticJob‐Lite:HTTP作业
  7. Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Could not
  8. ios激活锁_如何检查iOS设备的激活锁状态
  9. 从Devcon5大会看以太坊生态的发展
  10. 毕业设计-基于微信小程序的居民社区论坛系统