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_inst
,alloc_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; // 引用计数
};
uac2驱动通过configfs的配置过程如下图所示,创建functions调用uac2驱动的afunc_alloc_inst
函数,关联functions和配置时调用uac2驱动的afunc_alloc
,使能gadget设备调用uac2驱动的afunc_bind
函数,下面分析这三个函数的执行过程。
afunc_alloc_inst
和afunc_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
用于设置描述符、端点、配置、注册声卡,主要的工作内容如下:
- 设置描述符的字符串索引值、初始化描述符中的配置参数。
- 设置接口描述符的编号,ac_intf=0,as_out_intf=1,as_in_intf=2。设置各个接口的alt值为0。
- 根据音频设备所需的带宽计算端点的最大包长。
- 根据端点描述符,匹配要使用的端点,同时再描述符中记录端点的地址。
- 处理描述符。
- 调用
g_audio_setup
函数创建音频设备。- 分配uac请求和USB请求缓冲区,请求默认分配2个,缓冲区长度为端点的最大包长
- 创建声卡(包含声卡控制设备),一个声卡只有一个控制设备。
- 创建PCM子流和PCM设备。子流包含两类,分别为capture和playback,每个类下面又包含多个子流,子流是PCM设备功能的实现。
- 设置子流的操作函数为
uac_pcm_ops
,应用层要访问音频设备,最终会调用到uac_pcm_ops
。 - 分配DMA缓冲区,底层最终通过调用
__get_free_pages
分配。 - 注册声卡。声卡中包含很多设备,如控制设备、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_STATUS
、USB_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
处理。
UAC2设备被枚举的过程如下(这里只说明uac2驱动参与处理的部分):
- 设置配置
主机发送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;
}
- 设置接口
主机发送USB_REQ_SET_INTERFACE
命令设置设备接口。调用afunc_set_alt
将as_in_intf
和as_out_intf
接口的alt值设置为0。 - 发送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__);
}
参考资料
- Rockchip RK3399TRM V1.3 Part1
- Rockchip RK3399TRM V1.3 Part2
- Linux内核4.1版本源码
- UNIVERSAL SERIAL BUS DEVICE CLASS DEFINITION FOR AUDIO DEVICES Release 3.0-Errata
USB总线-Linux内核USB设备驱动之UAC2驱动分析(十)相关推荐
- USB总线-Linux内核USB3.0设备控制器之UDC驱动分析(六)
1.概述 UDC驱动的接口都定义在drivers/usb/gadget/udc/core.c文件中.USB Function驱动通过调用这些接口匹配及访问USB设备控制器,而底层USB控制器驱动要实现 ...
- 【usb】linux内核USB键盘驱动解析--普通键值上报及转化
一.概况 建议阅读前置文章[usb]linux内核USB键盘驱动解析–特殊键值上报及转化 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kernel. ...
- USB总线-Linux内核USB3.0设备控制器之dwc3 gadget驱动初始化过程分析(五)
1.概述 USB设备控制器(UDC)驱动的框图如下图所示,由三部分组成.第一部分是UDC驱动核心层,在drivers/usb/gadget/udc/core.c文件中实现,该层是一个兼容层,将USB ...
- USB总线-Linux内核USB3.0设备控制器中断处理程序分析(九)
1.概述 USB设备枚举.请求处理.数据交互都涉及USB设备控制器中断.当有事件发生时,USB设备控制器首先将事件信息通过DMA写入到事件缓冲区中,然后向CPU发出中断,随后CPU调用中断处理函数开始 ...
- USB总线-Linux内核USB3.0设备控制器复合设备之USB gadget configfs分析(七)
1.简介 configfs是基于ram的文件系统,与sysfs的功能有所不同.sysfs是基于文件系统的kernel对象视图,虽然某些属性允许用户读写,但对象是在kernel中创建.注册.销毁,由ke ...
- 【usb】linux内核USB键盘驱动解析--特殊键值转化及上报
文章目录 一.概况 二.探索 入口 usb_kbd_irq 三.总结 四.参考资料 一.概况 以Linux5.10内核中USB键盘驱动为例进行解析:https://mirrors.edge.kerne ...
- linux内核usb驱动框架,基于S3C2440平台的linux2.6.22内核版本的USB驱动框架分析
基于S3C2440平台的linux2.6.22内核版本的USB驱动框架分析 发布时间:2014-07-18 16:47:31来源:红联作者:linux08071151 driver/usb/host/ ...
- linux内核中kset是什么意思,Linux内核之设备驱动-底层数据结构kobject/kset
Linux内核之设备驱动-底层数据结构kobject/kset kobject kobject是组成device.driver.bus.class的基本结构.如果把前者看成基类,则后者均为它的派生产物 ...
- linux 内核 网卡驱动 移植,linux内核移植步骤添加dm9000网卡驱动(设备树).docx
linux内核移植步骤添加dm9000网卡驱动(设备树).docx LINUX内核移植步骤2015年05月13日星期三上午1105往设备树中添加网卡驱动1.选平台,指定交叉编译工具链1.在MAKEFI ...
最新文章
- 脸书 AI 识别误将黑人标记为「灵长类动物」
- URAL 1009. K-based Numbers
- 关于异步IO与同步IO的写操作区别
- 支持断线重连、永久watcher、递归操作并且能跨平台(.NET Core)的ZooKeeper异步客户端...
- SQL Server 性能调优(方法论)
- 【传智播客】Javaweb程序设计任务教程 黑马程序员 第二章 课后答案
- word2003计算机考试题,[2018职称计算机Word2003考前练习题] 2018年职称计算机考试练习题库...
- java string == 比较,Java 基础 之 String 的比较
- c语言字符型运算符,C语言试题:数据类型、运算符与表达式
- Hivesql-高级进阶技巧
- java mavenpom_java-使用pom-packaging Maven项目作为依赖项
- debain服务器系统用什么,建站服务器系统用CentOS还是Debian 适合自己才好
- 11个好用的黑科技资源搜索网站
- UI进阶——XMPP即时通讯
- 兄弟dcp9020cdn手册_兄弟Brother DCP-9020CDN 驱动
- opencv Mat 16位unsigned数据显示为黑色
- 编译原理第三章 词法分析与有穷自动机
- PAP和CHAP认证是什么
- (10.2)【隐写实现】简介、流程图、具体步骤
- word修改标题编号