Fork me on GitHub
侧边栏

USB总线-Linux内核USB设备驱动之UAC2驱动分析(十)

1.概述

UVC(USB Audio Class)定义了使用USB协议播放或采集音频数据的设备应当遵循的规范。目前,UAC协议有UAC1.0和UAC2.0。

UAC2.0协议相比UAC1.0协议,提供了更多的功能,支持更高的带宽,拥有更低的延迟。Linux内核中包含了UAC1.0和UAC2.0驱动,分别在f_uac1.c和f_uac2.c文件中实现。下面将以UAC2驱动为例,具体分析USB设备驱动的初始化、描述符配置、数据传输过程等。

2.初始化

2.1.定义

下面是UAC2.0的Gadget Function驱动的定义,驱动名称为uac2。alloc_inst被设置为afunc_alloc_installoc_func被设置为afunc_alloc,这两个函数在Gadget Function API层被回调。该宏将定义一个usb_function_driver数据结构,使用usb_function_register函数注册到function API层。

[drivers/usb/gadget/function/f_uac2.c]
// 定义UAC设备驱动
DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc);  

2.2.配置

uac2驱动的数据结构关系如下图所示。struct f_uac2表示uac2设备,由afunc_alloc分配,包含了具体音频设备和USB配置信息。如g_audio表示音频设备,包含了音频运行时参数、声卡、PCM设备等信息,uac_pcm_ops表示音频PCM设备流的操作方法,in_ep和out_ep表示USB设备输入和输出端点,in_ep_maxpsize和out_ep_maxpsize表示输入端点和输出端点数据包最大长度,params表示音频设备参数。比较重要的是func,描述了USB设备功能,uac2设备驱动需要填充该数据结构。struct f_uac2_opts表示uac2设备的选项,由afunc_alloc_inst动态分配,内部嵌入了struct usb_function_instance数据结构,表示一个USB Function实例,内部的fd指针指向DECLARE_USB_FUNCTION_INIT宏定义的uac2设备驱动结构体。

[drivers/usb/gadget/function/f_uac2.c]
struct f_uac2 {
    struct g_audio g_audio;  // uac2音频设备数据结构

    /* ac_intf - audio control interface,接口描述符编号为0
     * as_in_intf - audio streaming in interface,接口描述符编号为2
     * as_out_intf - audio streaming out interface,接口描述符编号为1
     */
    u8 ac_intf, as_in_intf, as_out_intf;

    /* 上述三个接口描述符当前的alternate settings值,有两个值,0表示关闭,1表示使能,
     * 主机可使用USB_REQ_GET_INTERFACE命令获取接口描述符当前的alternate settings值 */
    u8 ac_alt, as_in_alt, as_out_alt;    /* needed for get_alt() */
};
[drivers/usb/gadget/function/u_uac2.h]
struct f_uac2_opts {
    struct usb_function_instance    func_inst;
    int                p_chmask;  // 录音通道掩码
    int                p_srate;   // 录音采样率
    int                p_ssize;   // 录音一帧数据占多少字节
    int                c_chmask;  // 播放通道掩码
    int                c_srate;   // 播放采样率
    int                c_ssize;   // 播放一帧数据占多少字节
    int                req_number;  // usb_request的数量
    bool            bound;
    struct mutex    lock;
    int                refcnt;  // 引用计数
};    

image

uac2驱动通过configfs的配置过程如下图所示,创建functions调用uac2驱动的afunc_alloc_inst函数,关联functions和配置时调用uac2驱动的afunc_alloc,使能gadget设备调用uac2驱动的afunc_bind函数,下面分析这三个函数的执行过程。

image

afunc_alloc_instafunc_alloc是实现Gadget Function驱动的基础。afunc_alloc_inst创建usb_function_instance实例,设置uac2.0协议相关内容。afunc_alloc创建usb_function,设置uac2.0驱动的回调函数。具体的执行过程参考USB总线-Linux内核USB3.0设备控制器复合设备之USB gadget configfs分析(七)文章3.2节。

afunc_bind用于设置描述符、端点、配置、注册声卡,主要的工作内容如下:

  1. 设置描述符的字符串索引值、初始化描述符中的配置参数。
  2. 设置接口描述符的编号,ac_intf=0,as_out_intf=1,as_in_intf=2。设置各个接口的alt值为0。
  3. 根据音频设备所需的带宽计算端点的最大包长。
  4. 根据端点描述符,匹配要使用的端点,同时再描述符中记录端点的地址。
  5. 处理描述符。
  6. 调用g_audio_setup函数创建音频设备。
    1. 分配uac请求和USB请求缓冲区,请求默认分配2个,缓冲区长度为端点的最大包长
    2. 创建声卡(包含声卡控制设备),一个声卡只有一个控制设备。
    3. 创建PCM子流和PCM设备。子流包含两类,分别为capture和playback,每个类下面又包含多个子流,子流是PCM设备功能的实现。
    4. 设置子流的操作函数为uac_pcm_ops,应用层要访问音频设备,最终会调用到uac_pcm_ops
    5. 分配DMA缓冲区,底层最终通过调用__get_free_pages分配。
    6. 注册声卡。声卡中包含很多设备,如控制设备、PCM设备、混音设备等,内核将不同的设备统一抽象成snd_device,最终通过snd_register_device注册。控制设备操作函数集合为snd_ctl_f_ops,PCM设备操作函数集合为snd_pcm_f_ops

image

afunc_bind函数注册的PCM子流的操作函数集合uac_pcm_ops、控制设备操作函数集合为snd_ctl_f_ops,PCM设备操作函数集合为snd_pcm_f_ops如下所示。snd_pcm_f_ops函数最终会调用到uac_pcm_ops,具体调用过程后面分析。

[drivers/usb/gadget/function/u_audio.c]
// pcm流的操作函数
static const struct snd_pcm_ops uac_pcm_ops = {
    .open = uac_pcm_open,
    .close = uac_pcm_null,
    .ioctl = snd_pcm_lib_ioctl,
    .hw_params = uac_pcm_hw_params,
    .hw_free = uac_pcm_hw_free,
    .trigger = uac_pcm_trigger,
    .pointer = uac_pcm_pointer,
    .prepare = uac_pcm_null,
};

[sound/core/control.c]
static const struct file_operations snd_ctl_f_ops =
{    // SNDRV_DEVICE_TYPE_CONTROL设备操作函数
    .owner =    THIS_MODULE,
    .read =        snd_ctl_read,
    .open =        snd_ctl_open,
    .release =    snd_ctl_release,
    .llseek =    no_llseek,
    .poll =        snd_ctl_poll,
    .unlocked_ioctl =    snd_ctl_ioctl,
    .compat_ioctl =    snd_ctl_ioctl_compat,
    .fasync =    snd_ctl_fasync,
};

[sound/core/pcm_native.c]
const struct file_operations snd_pcm_f_ops[2] = {
    {   // SNDRV_PCM_STREAM_PLAYBACK操作函数
        .owner =        THIS_MODULE,
        .write =        snd_pcm_write,
        .write_iter =    snd_pcm_writev,
        .open =            snd_pcm_playback_open,
        .release =        snd_pcm_release,
        .llseek =        no_llseek,
        .poll =            snd_pcm_playback_poll,
        .unlocked_ioctl =    snd_pcm_playback_ioctl,
        .compat_ioctl = snd_pcm_ioctl_compat,
        .mmap =            snd_pcm_mmap,
        .fasync =        snd_pcm_fasync,
        .get_unmapped_area =    snd_pcm_get_unmapped_area,
    },
    {   // SNDRV_PCM_STREAM_CAPTURE操作函数
        .owner =        THIS_MODULE,
        .read =            snd_pcm_read,
        .read_iter =    snd_pcm_readv,
        .open =            snd_pcm_capture_open,
        .release =        snd_pcm_release,
        .llseek =        no_llseek,
        .poll =            snd_pcm_capture_poll,
        .unlocked_ioctl =    snd_pcm_capture_ioctl,
        .compat_ioctl = snd_pcm_ioctl_compat,
        .mmap =            snd_pcm_mmap,
        .fasync =        snd_pcm_fasync,
        .get_unmapped_area =    snd_pcm_get_unmapped_area,
    }
};

3.枚举

USB设备的枚举过程在第9章已经介绍过了,主要涉及USB设备端点0控制传输的3个过程。USB设备的枚举实质上是响应USB主机发送请求的过程。对于一些标准的USB请求,如USB_REQ_GET_STATUSUSB_REQ_CLEAR_FEATURE等,USB设备控制器驱动就可以处理,但有一些标准的USB请求,如USB_REQ_GET_DESCRIPTOR,需要USB gadget驱动参与处理,还有一些USB请求,需要function驱动参与处理。如下图所示,当主机发送USB_REQ_GET_CONFIGURATION或USB_REQ_SET_INTERFACE请求时,需要调用uac2驱动的afunc_set_alt函数处理,当主机发送USB_REQ_GET_INTERFACE请求时,需要调用afunc_get_alt函数处理,其他USB类请求命令,调用afunc_setup处理。

image

  1. 设置配置

主机发送USB_REQ_GET_CONFIGURATION命令设置设备当前使用的配置。uac2驱动只有一个配置,因此只需要调用afunc_set_alt将配置下面所有接口的alt值设置为0。afunc_set_alt函数的执行流程如下图所示。若是音频控制接口,alt=0时,直接返回0,其他值直接报错;若是音频流输出接口,alt=0时,停止录音,alt=1时,开始录音;若是音频流输入接口,alt=0时,停止播放,alt=1时,开始播放。

image

uac2设备枚举的使用没有发送USB_REQ_GET_INTERFACE命令,获取当前接口的alt值时。但还是介绍下afunc_get_alt函数,该函数直接返回当前接口的alt值。

[drivers/usb/gadget/function/f_uac2.c]
static int afunc_get_alt(struct usb_function *fn, unsigned intf)
{
    struct f_uac2 *uac2 = func_to_uac2(fn);
    struct g_audio *agdev = func_to_g_audio(fn);

    if (intf == uac2->ac_intf)
        return uac2->ac_alt;
    else if (intf == uac2->as_out_intf)
        return uac2->as_out_alt;
    else if (intf == uac2->as_in_intf)
        return uac2->as_in_alt;
    else
        ......
    return -EINVAL;
}
  1. 设置接口

主机发送USB_REQ_SET_INTERFACE命令设置设备接口。调用afunc_set_altas_in_intfas_out_intf接口的alt值设置为0。

  1. 发送USB类请求命令

USB类请求命令需要调用afunc_setup处理。该函数的执行流程如下图所示。实际工作过程中,主机通过该函数获取录音或播放的采样频率,而录音或播放的通道数已经包含在描述符中,不需要额外获取。

image

下面是UAC2协议中的名词,记录一下。

  • Current setting attribute (CUR)

  • Range attribute (RANGE)

  • Interrupt Enable attribute (INTEN)

  • Control Selector (CS)

  • Channel Number (CN)

  • Mixer Control Number (MCN)

4.工作过程分析

USB主机发送USB_REQ_SET_INTERFACE命令时,uac2驱动将会调用afunc_set_alt函数,若intf=2,alt=1,则开始录音,若intf=1,alt=1,则开始播放。

下图是USB音频设备工作时数据流的传输过程。录音(capture)时,USB主机控制器向USB设备控制器发送音频数据,USB设备控制器收到以后通过DMA将其写入到usb_request的缓冲区中,随后再拷贝到DMA缓冲区中,用户可使用arecord、tinycap等工具从DMA缓冲区中读取音频数据,DMA缓冲区是一个FIFO,uac2驱动往里面填充数据,用户从里面读取数据。播放(playback)时,用户通过aplay、tinyplay等工具将音频数据写道DMA缓冲区中,uac2驱动从DMA缓冲区中读取数据,然后构造成usb_request,送到USB设备控制器,USB设备控制器再将音频数据发送到USB主机控制器。可以看出录音和播放的音频数据流方向相反,用户和uac2驱动构造了一个生产者和消费者模型,录音时,uac2驱动是生产者,用户是消费者,播放时则相反。

image

[drivers/usb/gadget/function/u_audio.c]

static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req)
{
	......
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		/*
		 * For each IN packet, take the quotient of the current data
		 * rate and the endpoint's interval as the base packet size.
		 * If there is a residue from this division, add it to the
		 * residue accumulator.
		 */
		req->length = uac->p_pktsize;
		uac->p_residue += uac->p_pktsize_residue;
		/*
		 * Whenever there are more bytes in the accumulator than we
		 * need to add one more sample frame, increase this packet's
		 * size and decrease the accumulator.
		 */
		if (uac->p_residue / uac->p_interval >= uac->p_framesize) {
			req->length += uac->p_framesize;
			uac->p_residue -= uac->p_framesize * uac->p_interval;
		}
		req->actual = req->length;
	}
	......
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		// 录音时,将DMA缓冲区中的数据拷贝到usb_request缓冲区中
		if (unlikely(pending < req->actual)) {  // 处理DMA缓冲区回绕
			memcpy(req->buf, runtime->dma_area + hw_ptr, pending);
			memcpy(req->buf + pending, runtime->dma_area,
			       req->actual - pending);
		} else {
			memcpy(req->buf, runtime->dma_area + hw_ptr, req->actual);
		}
	} else {
		// 播放时,将usb_request缓冲区中的数据拷贝到DMA缓冲区中
		if (unlikely(pending < req->actual)) {  // 处理DMA缓冲区回绕
			memcpy(runtime->dma_area + hw_ptr, req->buf, pending);
			memcpy(runtime->dma_area, req->buf + pending, req->actual - pending);
		} else {
			memcpy(runtime->dma_area + hw_ptr, req->buf, req->actual);
		}
	}
	......
	if ((hw_ptr % snd_pcm_lib_period_bytes(substream)) < req->actual)
		snd_pcm_period_elapsed(substream);  // 更新PCM设备信息,如DMA缓冲区状态

exit:
	// 将usb_request重新填充到端点队列中,重复利用
	if (usb_ep_queue(ep, req, GFP_ATOMIC))
		dev_err(uac->card->dev, "%d Error!\n", __LINE__);
}
posted @ 2024-09-12 15:26  yooooooo  阅读(1137)  评论(0)    收藏  举报