usb声卡驱动(一):USB描述符

usb声卡驱动(一)

前面看了内核的启动,接下来就是驱动的学习。

正好手边有一个USB声卡,就准备以此为基础,进行usb声卡驱动的学习。

因此,在学些usb声卡之前,先看看usb驱动。然后再是alsa驱动,然后再是两者的结合

usb的关键数据结构

任何usb设备,都有一段数据,用来描述自己。比如自己有什么功能,自己的厂商ID是多少等等

有个组织,定义了这段数据的组织形式和意义,这段数据称为USB描述符。这个组织叫USB-IF(USB Implementers Forum)

USB描述符的组织形式

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 总线后,这些信息该如何获取呢?

第一步:如何检测到硬件已经被插入

常规的usb接口,有四根线,gnd线,vcc线,D+线,D-线。在hub端D+,D-分别接了一个15k的下拉电阻到地。

而在设备端,D+或者D-端接了一个1.5k的上拉电阻。低速设备接在D-端。高速和全速设备接在D+端。

当设备插入hub时,hub能通过D+,D-上面的变化来区分设备的类型。

第二步:如何区分全速设备和高速设备

对于全速设备和高速设备而言,他们的上拉电阻都接在了D+端。为了进一步区分这两种设备。需要进行一定的通信。

为了简化通信的细节,现在大致描述如下:

  1. 当设备进入复位状态之后,设备持续一段时间的向hub发送信号。这个信号就是给D-持续输出17.87mA的电流。

  2. hub因为有自己的电阻,所以,它能检查到一定的电压,大概为800mV

  3. hub在检查到持续了一段时间的800mV的电压之后,就知道,哦,原来这是一个高速设备。

  4. hub在接下来100us内,进行响应。告诉设备,我已经知道你是高速设备了,并且我也切换到了高速模式下了。

  5. 设备在接受到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作为默认地址。

那么获取设备描述的过程,大致描述如下:

第一阶段

  1. 首先使用一个Get_Descriptor这个请求。这个请求使用的是SETUP事务,它由令牌包,数据包,握手包组成.

  2. 然后再次发一个数据输入的请求。这个请求使用的是IN事务,它同样由令牌包,数据包,握手包组成。

  3. 然后再次发送一个数据输出请求——用于通知设备,Get_Descriptor请求的状态。这次使用的是OUT事务,它同样有三个阶段

  4. 现在主机已经拥有了usb设备的描述符信息。

第二阶段

  1. 已经知道了部分数据之后,需要为该设备分配地址。

  2. 发出一个Set_Address请求。这个请求跟第一阶段的第一步几乎一样,使用的是SETUP事务

  3. 本次的数据为地址,但是地址已经放在了Set_Address请求中了。所以不需要传输数据。

  4. 为了获得Set_Address请求是否成功,需要接受设备的响应。因此发送一个数据输入请求,即跟第一阶段的第二步一样。

第三阶段

  1. 当一切正常之后,就使用新的地址,重新获取设备描述符。获取设备描述符的过程见第一阶段,长度稍有不同,不过不影响整个过程的理解

  2. 除了设备描述符以外,还需要获取其他的描述符。如配置描述符,接口描述符,端点描述符,字符串描述符等。

  3. 根据这些描述符的内容,选择不同的驱动程序

至此,整个设备才算是完完全全的能被使用了。

注意,注意:上面所有的步骤,建立在两个前提下。1.默认所有的操作都正常;2.描述过程时使用的是数据包中的更加抽象的语言。并没有引入具体的二进制数值

本篇相当于,usb设备的一个枚举过程。

接下来一篇,用于说明,音频设备描述符的相关细节

你可能感兴趣的:(usb声卡驱动,usb描述符,usb枚举,usb插入检测,usb数据包,usb描述字段)