0%

Linux驱动之USB子系统(五)UAC同步方式

同步问题原因

USB Isochronous传输(协议无问题)

Isochronous Transfer 同步问题

电脑播放器播放音乐时:是按一个固定的速率,比如44.1KHZ,电脑内有一个晶振,可分频出一个44.1KHZ,进行音乐播放,发给USB的数据流速率固定。USB声卡自己得有一个晶振才能工作,它也可分频出一个44.1KHZ,供给I2S信号或DAC。

问题来了,晶振是有误差的,这两个44.1KHZ不可能完全一模一样,电脑可能是44.100KHZ,USB声卡可能是44.098KHZ,误差约50ppm,很正常的情况。虽然声卡晶振分频出来是44.098KHZ,但声卡认为它就是工作在44.100KHZ下。好吧,如果二者时钟独立运行,那么1个小时会误差0.2秒,会出现不同步! 即电脑播了1个小时的数据,USB声卡实际是无法播完的,要多0.2秒才能播完。 如果声卡也要1小时播完,那这1小时就需要丢掉0.2秒的数据。

所以二者的时钟必须要同步一致才行,这就是UAC同步问题的原因,因此USB音频规定了一是采用“等时传输模式”,二是设备需要指定为3种同步方式之一:同步(synchronous),适应(adaptive),异步(asynchronous)。

以上摘自http://bbs.erji.net/forum.php?mod=viewthread&tid=2076674&page=1

USB音频声卡的时钟同步方式

USB的isochronous模式

USB音频声卡采用isochronous“等时传输模式”,能保证实时传输数据,但错误容忍,出错不重传。它是实时传输,所以USB电脑端发出多少速率的数据,USB接收端(USB声卡)就得接收多少速率。比如电脑发送44.1KHZ的,声卡就接收44.1KHZ。

Asynchronous

cannot synchronize to SOF or any other clock in the USB domain。

Source: Provides implicit feedforward(data stream),Free running Fs

Sink: Provides explicit feedback (isochronous pipe),Free running Fs

Asynchronous sink endpoints must provide explicit feedback information to an adaptive driver.

Asynchronous:要求Sink主动提供data rate信息,对信息如何使用没做规范要求,Source和Sink都各自按照自己的速率运行,音频质量是最好的

Synchronous

Source:Uses implicit feedback(SOF), Fs locked to SOF

Sink: Uses implicit feedback(SOF) Fs locked to SOF

Source: its sample clock from SOF and produces a fixed number of audio samples every USB (micro)frame,

Sink: its sample clock from SOF and consumes a fixed number of samples every USB (micro)frame.

Synchronous:Source和Sink 都与SOF同步,这样导致Source和Sink的clk都不够稳定,音频质量会受到影响,但是Source和Sink都无需提供feedback信息,兼容性最好

Adaptive

Source:Uses explicit feedback(isochronous pipe) ,Fs locked to sink

Sink: Uses implicitfeedforward (data stream), Fs locked to data flow

Source: Adaptive source endpoints produce data at a rate that is controlled by the data sink。

Sink:The sink provides feedback to the source,the data rate information is embedded in the data stream。The average number of samples received during a certain averaging time determines the instantaneous data rate

condition:Sink Device contains a fully adaptive sample rate converter (SRC)

Adaptive:使用Sink提供的feedback信息(明确要求Sink提供data rate信息),调整自己的clk对齐Sink

Feedback

ASYNC: An asynchronous sink must provide explicit feedback to the host by indicating accurately what its desired data rate (Ff) is, relative to the USB (micro)frame frequency。This allows the host to continuously adjust the number of samples sent to the sink so that neither underflow or overflow of the data buffer occurs

ADAPTIVE: An adaptive source must receive explicit feedback from the host so that it can accurately generate the number of samples required by the host.

Ff: the desired data rate

Fs: actual sampling rate,至少需要1s的测量时间Tmeas(单位是帧数)。单位是帧数,对于全速设备1ms一帧,那么1s就需要1000帧(Fs>=1000),而对于高速设备,125us一帧(Fs≥8000).

Fm: 通常Fs是由master clock Fm分频得到:

\[ F_m = F_s * 2^P \]

因此可以利用Fm来缩短测量时间Tmeas:

\[ T_{meas} = \frac{2^K}{2^P} = 2^{K-P} \]

这样测量Fs的时间就降低为 \(2^(K-P)\) 帧时间了。

对于全速设备,Fs需要使用定点数10.10(K=10)格式,需要3字节,调整为10.14定点数来表示

对于高速设备,Fs需要使用定点数12.13(K=13)格式,需要4字节,调整为16.16定点数来表示

对P的要求:

  • P must be in the range [0,K]
  • P 尽量大一些,这样可以减少Tmeas测试时间,也可以减少缓存大小
  • P 应该小于K,保证至少两帧取平均来减少抖动,测量更平滑
  • P 不要为0,保证在Ff丢失的情况下可以用更为近的Fs来代替,减小误差相邻数据相似性

Implicit Feedback

有些时候,可以避免explicit实现一个feedback endpoint,如果设备实现了一组isochronous data endpoints,且:

  • All the endpoints in the group are synchronized (i.e. use sample clocks that are derived from a common master clock)
  • The group contains one or more isochronous data endpoints in one direction that normally would need explicit feedback
  • The group contains at least one isochronous data endpoint in the opposite direction

在这些情况下,设备可以不explicit feedback endpoint.

有以下可能的情况:

  • One or more asynchronous sink endpoints are accompanied by an asynchronous source endpoint. The data rate on the source endpoint can be used as implicit feedback information to adjust the data rate on the sink endpoint(s).
  • One or more adaptive source endpoints are accompanied by an adaptive sink endpoint. The source endpoint can adjust its data rate based on the data rate received by the sink endpoint

解决5.x以前的内核在Windows10系统下无法识别UAC2的问题

UAC端点描述符

1
2
3
4
5
6
7
8
9
struct _ENDPOIN_DESCRIPTOR_STRUCT 
{
BYTE bLength; //设备描述符的字节数大小,为0x7
BYTE bDescriptorType; //描述符类型编号,为0x05
BYTE bEndpointAddress; //端点地址及输入输出属性
BYTE bmAttribute; //端点的传输类型属性
WORD wMaxPacketSize; //端点收、发的最大包的大小
BYTE bInterval; //主机查询端点的时间间隔
} ENDPOIN_DESCRIPTOR_STRUCT ;
  • bLength : 描述符大小.固定为0x07.

  • bDescriptorType : 端点描述符类型.固定为0x05.

  • bEndpointAddress : USB设备的端点地址.Bit7,方向,对于控制端点可以忽略,1/0:IN/OUT.Bit6-4,保留.BIt3-0:端点号.

  • bmAttributes : 端点属性.Bit7-2,保留(同步有定义).BIt1-0:00控制,01同步,02批量,03中断. 当为同步传输时,bEndpointType的bit3-2的值不同代表的含义不同:00:无同步,01:异步,10:适配,11:同步;BIT5:4含义00: 表示数据端点,01:表示反馈端点Feedback endpoint,10:表示隐式反馈数据端点 Implicit feedback Data endpoint,11:保留

  • wMaxPacketSize : 本端点接收或发送的最大信息包大小.

    • USB2.0时: 对于同步端点,此值用于指示主机在调度中保留的总线时间,这是每(微)帧数据有效负载所需的时间,有效负载时间就是发送一帧数据需要占用的总线时间,在实际数据传输过程中,管道实际使用的带宽可能比保留的带宽少,大家想想,如果实际使用的带宽比保留的还多,那就丢数了;

      对于其类型的端点,bit10~bit0指定最大数据包大小(以字节为单位);

      bit12bit11对于高速传输的同步和中断端点有效:bit12bit11可指定每个微帧的额外通信次数,这里大家一定要知道是在高速传输中,当一个事务超时时,在一个微帧时间内重传的次数,如果设置为00b(None),则表示在一个微帧内只传输一个事务,不进行额外的超时重传,如果设置为01b,则表示在一个微帧内可以传输两次事务,有一次额外的重传机会,从下面可以看出,一个微帧最多可以有两次重传事务的机会,如果微帧结束了还是失败,就需要等到下一个微帧继续发送该事务;

    • USB3.0时: wMaxPacketSize表示包的大小。对于bulk为1024,而对于同步传输,可以为0~1024或 1024。

  • bInterval : 轮询数据传送端点的时间间隔.对于批量传送和控制传送的端点忽略.对于同步传送的端点,必须为1,对于中断传送的端点,范围为1-255.

    • 对于全速/高速同步端点,此值必须在1到16之间。bInterval值用作2的指数,例如bInterval为4,表示周期为8个单位;
    • 对于全速/低速中断端点,该字段的值可以是1到255,也就是主机多少ms给设备发一次数据请求;
    • 对于高速中断端点,使用bInterval值作为2的指数,例如bInterval为4表示周期为8。这个值必须在1到16之间;
    • 对于高速批量/控制输出端点,bInterval必须指定端点的最大NAK速率。值0表示端点永不NAK。其它值表示每个微帧的bInterval*125us时间最多1个NAK。这个值的范围必须在0到255之间;
      • 00 = None (1 transaction per microframe)
      • 01 = 1 additional (2 per microframe)
      • 10 = 2 additional (3 per microframe)
      • 11 = Reserved
      • 其它位默认为0,
    • 对于全速/低速批量/控制输出端点,此值无意义,可以任意指定。

以fs_epout_desc为例描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* STD AS ISO OUT Endpoint */
static struct usb_endpoint_descriptor fs_epout_desc = {
.bLength = USB_DT_ENDPOINT_SIZE, //#define USB_DT_ENDPOINT_SIZE 7
.bDescriptorType = USB_DT_ENDPOINT, //#define USB_DT_ENDPOINT 0x05

.bEndpointAddress = USB_DIR_OUT, // #define USB_DIR_OUT 0 /* to device */
// #define USB_ENDPOINT_XFER_ISOC 1 /* 同步传输 */
// #define USB_ENDPOINT_SYNC_ADAPTIVE (2 << 2) /* 适配 */
.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
/* The USB limits the maximum data payload size to 1,023 bytes for each full-speed isochronous endpoint.
High-speed endpoints are allowed up to 1024-byte data payloads. */
.wMaxPacketSize = cpu_to_le16(1023),
.bInterval = 1,
};

patch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -273,7 +273,7 @@ static struct usb_endpoint_descriptor fs_epout_desc = {
.bDescriptorType = USB_DT_ENDPOINT,

.bEndpointAddress = USB_DIR_OUT,
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 1,
};
@@ -282,7 +282,7 @@ static struct usb_endpoint_descriptor hs_epout_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,

- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};
@@ -350,7 +350,7 @@ static struct usb_endpoint_descriptor fs_epin_desc = {
.bDescriptorType = USB_DT_ENDPOINT,

.bEndpointAddress = USB_DIR_IN,
- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_SYNC,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 1,
};
@@ -359,7 +359,7 @@ static struct usb_endpoint_descriptor hs_epin_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,

- .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_SYNC,
/* .wMaxPacketSize = DYNAMIC */
.bInterval = 4,
};

IN和OUT都是相对于USB Host设备来讲的:

对于OUT Endpoint不支持ASYNC做如下说明:

  • ASYNC模式下需要Sink设备Provides explicit feedback,而对于OUT Endpoint,Sink设备是我们的主机,这说明我们的设备不支持feedback

对于IN EndPoint,测试发现支持ASYNC做如下说明:

  • ASYNC模式下需要Sink设备Provides explicit feedback,而对于IN Endpoint,Sink设备是测试的Windows10系统,说明Windows10(基于我的测试版本)是支持feedback

结论:
Windows的UAC2是支持Feedback的,而我们的系统不支持,那么将OUT Endpoint改成ADAPTIVE模式符合USB isochronous 传输规范,但是因为我们设备没有实现feedback,有没有利用到Host提供的feedback信息待验证。同理OUT Endpoint改成ADAPTIVE模式,那么IN EndPoint就可以保持为ASYNC,因为这都要求Host端提供feedback。当然全部改成SYNC模式也可以获得很好的兼容性,但是会造成Source和Sink的clk抖动。 还有另外一种做法,我看到Linux6.x驱动里已经支持feedback了,移植过来Device端就支持三种同步方式了。

fs_epout_desc端点会由USB_ENDPOINT_SYNC_ASYNC改成USB_ENDPOINT_SYNC_ADAPTIVE,最新的6.x内核会根据是否支持feedback来决定用哪个同步模式,如果支持feedback就用USB_ENDPOINT_SYNC_ASYNC,如果不支持feedback就用USB_ENDPOINT_SYNC_ADAPTIVE。实际上用USB_ENDPOINT_SYNC_SYNC也能够在windows上识别。 fs_epin_desc端点:由USB_ENDPOINT_SYNC_ASYNC改成USB_ENDPOINT_SYNC_SYNC。

参考文献

《USB2.0协议规范》
《UAC2协议规范》