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_epout_ep表示USB设备输入和输出端点,in_ep_maxpsizeout_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;  // 引用计数
};

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

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

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_CONFIGURATIONUSB_REQ_SET_INTERFACE请求时,需要调用uac2驱动的afunc_set_alt函数处理,当主机发送USB_REQ_GET_INTERFACE请求时,需要调用afunc_get_alt函数处理,其他USB类请求命令,调用afunc_setup处理。

UAC2设备被枚举的过程如下(这里只说明uac2驱动参与处理的部分):

  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时,开始播放。

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

下面是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驱动是生产者,用户是消费者,播放时则相反。

DMA缓冲区和USB设备控制器的数据交换都由u_audio_iso_complete函数完成。录音时,将DMA缓冲区中的数据拷贝到usb_request缓冲区,播放时,将usb_request缓冲区中的数据拷贝到DMA缓冲区中,最后将usb_request填充到端点的队列中。如此循环往复。

[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__);
}

参考资料

  1. Rockchip RK3399TRM V1.3 Part1
  2. Rockchip RK3399TRM V1.3 Part2
  3. Linux内核4.1版本源码
  4. UNIVERSAL SERIAL BUS DEVICE CLASS DEFINITION FOR AUDIO DEVICES Release 3.0-Errata

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

  1. USB总线-Linux内核USB3.0设备控制器之UDC驱动分析(六)

    1.概述 UDC驱动的接口都定义在drivers/usb/gadget/udc/core.c文件中.USB Function驱动通过调用这些接口匹配及访问USB设备控制器,而底层USB控制器驱动要实现 ...

  2. 【usb】linux内核USB键盘驱动解析--普通键值上报及转化

    一.概况 建议阅读前置文章[usb]linux内核USB键盘驱动解析–特殊键值上报及转化 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kernel. ...

  3. USB总线-Linux内核USB3.0设备控制器之dwc3 gadget驱动初始化过程分析(五)

    1.概述 USB设备控制器(UDC)驱动的框图如下图所示,由三部分组成.第一部分是UDC驱动核心层,在drivers/usb/gadget/udc/core.c文件中实现,该层是一个兼容层,将USB ...

  4. USB总线-Linux内核USB3.0设备控制器中断处理程序分析(九)

    1.概述 USB设备枚举.请求处理.数据交互都涉及USB设备控制器中断.当有事件发生时,USB设备控制器首先将事件信息通过DMA写入到事件缓冲区中,然后向CPU发出中断,随后CPU调用中断处理函数开始 ...

  5. USB总线-Linux内核USB3.0设备控制器复合设备之USB gadget configfs分析(七)

    1.简介 configfs是基于ram的文件系统,与sysfs的功能有所不同.sysfs是基于文件系统的kernel对象视图,虽然某些属性允许用户读写,但对象是在kernel中创建.注册.销毁,由ke ...

  6. 【usb】linux内核USB键盘驱动解析--特殊键值转化及上报

    文章目录 一.概况 二.探索 入口 usb_kbd_irq 三.总结 四.参考资料 一.概况 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kerne ...

  7. linux内核usb驱动框架,基于S3C2440平台的linux2.6.22内核版本的USB驱动框架分析

    基于S3C2440平台的linux2.6.22内核版本的USB驱动框架分析 发布时间:2014-07-18 16:47:31来源:红联作者:linux08071151 driver/usb/host/ ...

  8. linux内核中kset是什么意思,Linux内核之设备驱动-底层数据结构kobject/kset

    Linux内核之设备驱动-底层数据结构kobject/kset kobject kobject是组成device.driver.bus.class的基本结构.如果把前者看成基类,则后者均为它的派生产物 ...

  9. linux 内核 网卡驱动 移植,linux内核移植步骤添加dm9000网卡驱动(设备树).docx

    linux内核移植步骤添加dm9000网卡驱动(设备树).docx LINUX内核移植步骤2015年05月13日星期三上午1105往设备树中添加网卡驱动1.选平台,指定交叉编译工具链1.在MAKEFI ...

最新文章

  1. 脸书 AI 识别误将黑人标记为「灵长类动物」
  2. URAL 1009. K-based Numbers
  3. 关于异步IO与同步IO的写操作区别
  4. 支持断线重连、永久watcher、递归操作并且能跨平台(.NET Core)的ZooKeeper异步客户端...
  5. SQL Server 性能调优(方法论)
  6. 【传智播客】Javaweb程序设计任务教程 黑马程序员 第二章 课后答案
  7. word2003计算机考试题,[2018职称计算机Word2003考前练习题] 2018年职称计算机考试练习题库...
  8. java string == 比较,Java 基础 之 String 的比较
  9. c语言字符型运算符,C语言试题:数据类型、运算符与表达式
  10. Hivesql-高级进阶技巧
  11. java mavenpom_java-使用pom-packaging Maven项目作为依赖项
  12. debain服务器系统用什么,建站服务器系统用CentOS还是Debian 适合自己才好
  13. 11个好用的黑科技资源搜索网站
  14. UI进阶——XMPP即时通讯
  15. 兄弟dcp9020cdn手册_兄弟Brother DCP-9020CDN 驱动
  16. opencv Mat 16位unsigned数据显示为黑色
  17. 编译原理第三章 词法分析与有穷自动机
  18. PAP和CHAP认证是什么
  19. (10.2)【隐写实现】简介、流程图、具体步骤
  20. word修改标题编号

热门文章

  1. Oracle SQL 之 数学计算-开方根(咋个办呢 zgbn)
  2. 基于鸟群优化的BP神经网络(预测应用) - 附代码
  3. 不可不学的英语骂人句子
  4. 插入背景雨天html,【AE教程】雨天-波光粼粼效果制作
  5. PhotoShop中画圆角矩形最简单方法(图文并茂)!
  6. VC++6.0使用MATCOM矩阵库的方法
  7. 陈春花老师说「管理是管事不是管人」,而我想说这句漂亮话是经不起推敲的
  8. 计算机组成原理 俸远桢,计算机组成原理与汇编语言程序设计第5章.ppt
  9. Linux-更改终端字体大小
  10. fopen函数mode参数详解