前面看了内核的启动,接下来就是驱动的学习。
正好手边有一个USB声卡,就准备以此为基础,进行usb声卡驱动的学习。
因此,在学些usb声卡之前,先看看usb驱动。然后再是alsa驱动,然后再是两者的结合
任何usb设备,都有一段数据,用来描述自己。比如自己有什么功能,自己的厂商ID是多少等等
有个组织,定义了这段数据的组织形式和意义,这段数据称为USB描述符。这个组织叫USB-IF(USB Implementers Forum)
usb描述符,逻辑上分成三个层级:配置,接口,端点
一个usb设备描述符,可能包含多个配置,一个配置可能包含多个接口,一个接口可能包含多个端点。
上述每一个逻辑体,在linux中都有一个数据结构与之对应。
配置描述符:
struct usb_config_descriptor {
__u8 bLength;//描述符的长度
__u8 bDescriptorType;//描述符的类型,有两种值:USB_DT_CONFIG,USB_DT_OTHER_SPEED_CONFIG (表示高速设备操作在低速或者全速模式时的配置信息)
__le16 wTotalLength;//所有描述符的总长度
__u8 bNumInterfaces;//配置包含的接口数目
__u8 bConfigurationValue;//表示这个配置的一个数字。使用该值,调用SET_CONFIGURATION请求来设置此配置为当前配置
__u8 iConfiguration;//描述配置信息的字符串描述符的索引值
__u8 bmAttributes;//表示配置的一些特点,如bit6为1表示self-power;bit5为1表示支持远程唤醒
__u8 bMaxPower;//设备正常运作时,从hub分得的最大电流,单位2mA。
//那么当设备请求的电流,大于hub能给予的最大值时,hub就会直接拒绝
//而保存hub当前能给出的最大电流保存在struct usb_device的bus_mA中。
} __attribute__ ((packed));
接口描述符:
struct usb_interface_descriptor {
__u8 bLength;//该描述符的长度
__u8 bDescriptorType;//描述符的类型
__u8 bInterfaceNumber;//每个配置里面可以包含多个接口,这个值可以表示对应的接口索引号
__u8 bAlternateSetting;//接口使用的可选设置号。默认为0
__u8 bNumEndpoints;//接口拥有的端点个数,不包括0端点(后续说明原因)
//下面三个,用于表示这个接口,具备的功能。由于各个功能其实有很多共同点,因此将其抽象出三个层级:class,subclass,protocol.
//同一个class下面,可以有很多subclass,同一个subclass下,也可以根据protocol的不同,分成很多的设备
__u8 bInterfaceClass;
__u8 bInterfaceSubClass;
__u8 bInterfaceProtocol;
__u8 iInterface;//接口对应的字符串描述符的索引值
} __attribute__ ((packed));
端点描述符:
struct usb_endpoint_descriptor {
__u8 bLength;//描述符的长度
__u8 bDescriptorType;//端点的类型
__u8 bEndpointAddress;//该字段含有,端点方向信息,地址信息,端点号信息
__u8 bmAttributes;//bit1,bit0 表示传输类型:00控制类型,01登时,10批量,11中断
__le16 wMaxPacketSize;//端点一次处理的最大字节数
__u8 bInterval;//希望主机轮询自己的时间间隔。
/* NOTE: these two are _only_ in audio endpoints. */
/* use USB_DT_ENDPOINT*_SIZE in bLength, not sizeof. */
//下面两个字段,用于在音频设备中,主要用于等时同步端点,会在后面介绍audio协议的时候,引入介绍
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
除了上面的三个描述以外,还有一个重要的描述,那就是设备描述符:
struct usb_device_descriptor {
__u8 bLength;//描述符的长度
__u8 bDescriptorType;//描述符的类型
__le16 bcdUSB;//USB spc的版本号
//和接口描述符中的class,subclass,protocol意义类似
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bMaxPacketSize0;//端点0一次可以处理的最大字节数.因为端点0没有对应的描述符,所以,将端点0的相关信息,放在了设备描述符中
__le16 idVendor;//厂商ID
__le16 idProduct;//产品ID
__le16 bcdDevice;//设备版本号
__u8 iManufacturer;//制造商对应的字符串描述符的索引值
__u8 iProduct;//产品对应的字符串描述符的索引值
__u8 iSerialNumber;//序列号对应的字符串描述符的索引值
__u8 bNumConfigurations;//该设备当前速度模式下的配置数量
} __attribute__ ((packed));
现在我们知道了usb设备大概有哪些信息,接下来查看一下,应该怎么才能获取到上面的这些
当一个新的设备,插入USB 总线后,这些信息该如何获取呢?
常规的usb接口,有四根线,gnd线,vcc线,D+线,D-线。在hub端D+,D-分别接了一个15k的下拉电阻到地。
而在设备端,D+或者D-端接了一个1.5k的上拉电阻。低速设备接在D-端。高速和全速设备接在D+端。
当设备插入hub时,hub能通过D+,D-上面的变化来区分设备的类型。
对于全速设备和高速设备而言,他们的上拉电阻都接在了D+端。为了进一步区分这两种设备。需要进行一定的通信。
为了简化通信的细节,现在大致描述如下:
当设备进入复位状态之后,设备持续一段时间的向hub发送信号。这个信号就是给D-持续输出17.87mA的电流。
hub因为有自己的电阻,所以,它能检查到一定的电压,大概为800mV
hub在检查到持续了一段时间的800mV的电压之后,就知道,哦,原来这是一个高速设备。
hub在接下来100us内,进行响应。告诉设备,我已经知道你是高速设备了,并且我也切换到了高速模式下了。
设备在接受到hub的响应之后,就将自己切换到高速设备的电路上。
上面所述的5个步骤,在电气信号上面的详细描述,可以参考:
https://blog.csdn.net/flydream0/article/details/71512852
现在,设备已经连上,接下来,就是获取设备的信息(设备描述符)。但是在此之前,需要明白一个东西:那就是usb的数据包,到底是怎么组织的。
usb数据包的组织
我们都知道电信号只能传递0和1的逻辑值。在usb的世界里,将这些0和1排列组合,组成7种基本的信息,称为域,分别叫做:同步域(SYNC),标识域(PID),地址域(ADDR),端点域(ENDP),帧号域(FRAME),数据域(DATA),校验域(CRC)
在这些域的上层,则定义4种包:令牌包,数据包,握手包,特殊包。这些包都是由上面介绍的域组合而成。
令牌包有四种,分别为:输入,输出,设置,帧起始。前面三种的域的组成情况一样,为SYNC+PID+ADDR+ENDP+CRC.第四种的域组成为,SYNC+PID+FRAME+CRC
数据包有两种,分别为:DATA0,DATA1。他们的域组成一样都为:SYNC+PID+DATA+CRC
握手包,只有一种,它的域组成为:SYNC+PID
现在有了这些包之后,我们使用这4种包来定义各种不同的事务。目前就定义了3种事务,称为:IN事务,OUT事务,SETUP事务。
每种事务,都由三个阶段组成:令牌包阶段,数据包阶段,握手包阶段
令牌包阶段:启动输入、输出、设置事务
数据包阶段:按照输入、输出发送相应的数据
握手包阶段:返回数据的接收情况
现在知道了3种事务之后,就可以使用这3中事务,进行传输了,传输也分成了4种:中断传输,批量传输,同步传输,控制传输
中断传输:由OUT事务和IN事务构成
批量传输:由OUT事务和IN事务构成
同步传输:由OUT事务和IN事务构成
控制传输:由SETUP事务,(OUT事务,IN事务)构成
现在知道了上面的东西,就可以进一步知道,怎么获取设备描述符了。
注意:上面只是介绍了各个数据包的组成成分,对于这些成分的具体二进制值没有介绍。因为我关心的是整个过程的理解,而不是具体的二进制是什么样子的。如果需要查看具体的二进制,可以参考对应的文章
在设备才连上hub时,此时设备还处于一种默认的状态,它没有地址,为了能够响应主机发出的请求。它将地址0作为默认地址。
那么获取设备描述的过程,大致描述如下:
第一阶段
首先使用一个Get_Descriptor这个请求。这个请求使用的是SETUP事务,它由令牌包,数据包,握手包组成.
然后再次发一个数据输入的请求。这个请求使用的是IN事务,它同样由令牌包,数据包,握手包组成。
然后再次发送一个数据输出请求——用于通知设备,Get_Descriptor请求的状态。这次使用的是OUT事务,它同样有三个阶段
现在主机已经拥有了usb设备的描述符信息。
第二阶段
已经知道了部分数据之后,需要为该设备分配地址。
发出一个Set_Address请求。这个请求跟第一阶段的第一步几乎一样,使用的是SETUP事务
本次的数据为地址,但是地址已经放在了Set_Address请求中了。所以不需要传输数据。
为了获得Set_Address请求是否成功,需要接受设备的响应。因此发送一个数据输入请求,即跟第一阶段的第二步一样。
第三阶段
当一切正常之后,就使用新的地址,重新获取设备描述符。获取设备描述符的过程见第一阶段,长度稍有不同,不过不影响整个过程的理解
除了设备描述符以外,还需要获取其他的描述符。如配置描述符,接口描述符,端点描述符,字符串描述符等。
根据这些描述符的内容,选择不同的驱动程序
至此,整个设备才算是完完全全的能被使用了。
注意,注意:上面所有的步骤,建立在两个前提下。1.默认所有的操作都正常;2.描述过程时使用的是数据包中的更加抽象的语言。并没有引入具体的二进制数值
本篇相当于,usb设备的一个枚举过程。
接下来一篇,用于说明,音频设备描述符的相关细节