usb声卡驱动(五)
我手上,刚好有高通msm8996的android 8.1源码。那么就直接进入它的kernel分析。
usb声卡驱动的源码位于sound/usb下面。查看对应的Makefile文件。可知相应的源码c文件。总共就那么几个文件,慢慢看来。
现在将部分makefile文件摘录如下:
snd-usb-audio-objs := card.o \
clock.o \
endpoint.o \
format.o \
helper.o \
mixer.o \
mixer_quirks.o \
pcm.o \
proc.o \
quirks.o \
stream.o
snd-usbmidi-lib-objs := midi.o
# Toplevel Module Dependency
# obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usbmidi-lib.o
# separate midi and audio
obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o
obj-$(CONFIG_SND_USB_MIDI) += snd-usbmidi-lib.o
从经验上面来讲,这个声卡驱动一定得先是USB驱动,因此定位USB驱动的地方,全局搜素usb_driver.可以在card.c中找到
如下:
static struct usb_device_id usb_audio_ids [] = {
#include "quirks-table.h"
{ .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
.bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
{ } /* Terminating entry */
};
/*
* entry point for linux usb interface
*/
static struct usb_driver usb_audio_driver = {
.name = "snd-usb-audio",
.probe = usb_audio_probe,
.disconnect = usb_audio_disconnect,
.suspend = usb_audio_suspend,
.resume = usb_audio_resume,
.reset_resume = usb_audio_reset_resume,
.id_table = usb_audio_ids,
.supports_autosuspend = 1,
};
module_usb_driver(usb_audio_driver);
因此,当一个USB设备是Audio设备,且该设备含有控制接口(见id_table)时,就会调用probe回调。
可得,usb_audio_probe是整个驱动的起始点。从上一篇笔记中,可知,要创建audio驱动,需要分别创建对应的card对象,和component对象。再再从上上一篇笔记可知,还需要解析对应的usb audio的各种描述符。
整个usb 声卡驱动,使用了struct snd_usb_audio来代替上一篇我们提及的chip对象。如下:
struct snd_usb_audio {
int index;
struct usb_device *dev;
struct snd_card *card;
struct usb_interface *pm_intf;
u32 usb_id;
struct mutex mutex;
struct rw_semaphore shutdown_rwsem;
unsigned int shutdown:1;
unsigned int probing:1;
unsigned int in_pm:1;
unsigned int autosuspended:1;
unsigned int txfr_quirk:1; /* Subframe boundaries on transfers */
int num_interfaces;
int num_suspended_intf;
struct list_head pcm_list; /* list of pcm streams */
struct list_head ep_list; /* list of audio-related endpoints */
int pcm_devs;
struct list_head midi_list; /* list of midi interfaces */
struct list_head mixer_list; /* list of mixer interfaces */
int setup; /* from the 'device_setup' module param */
bool autoclock; /* from the 'autoclock' module param */
struct usb_host_interface *ctrl_intf; /* the audio control interface */
};
- index:驱动内部使用一个数组来保存所有的chip对象,index是这个数组的索引
- dev:usb_device
- card:这就是真正的card对象
- pm_intf:当前接口
- usb_id:usb 设备id
- mutex,shutdown_rwsem:各种锁
- shutdown:处于shutdown状态
- probing:处于probing状态
- in_pm:从suspend状态到resume状态
- autosuspended:自动suspend状态
- txfr_quirk:不懂这个的用法
- num_interfaces:使用这个驱动的usb接口数,如果为0,则会调用snd_card_free_when_closed释放card对象
- num_suspended_intf:进入suspend状态的次数
- pcm_list:pcm这个componet的链表,因为一个声卡,可能有多个pcm对象,将多个pcm对象链接在一起,放入此处
- ep_list:audio相关的端点的链表,可见后面的端点操作
- midi_list:midi这个component的链表,同pcm_list类似。
- mixer_list:mixer链表,同pcm_list
- setup:不懂
- autoclock:不懂
- ctrl_intf:audio control对应的usb接口
snd_usb_audio对象的创建
snd_usb_audio对象的创建通过
static struct snd_usb_audio *
snd_usb_audio_probe(struct usb_device *dev,
struct usb_interface *intf,
const struct usb_device_id *usb_id);
在前面的笔记中,提到,Audio Control ,Audio Streaming都分别对应USB的Interface而这个驱动的匹配条件为Audio Control.因此,每检查到一个Audio Control这个snd_usb_audio_probe会被调用一次,所以需要注意,不要过多的创建card对象。
static struct snd_usb_audio *
snd_usb_audio_probe(struct usb_device *dev,
struct usb_interface *intf,
const struct usb_device_id *usb_id)
{
//....
//首先选取可选设置0.注意注意,在某些设备上,可选设置0,表示的是一种0带宽的设置
alts = &intf->altsetting[0];
ifnum = get_iface_desc(alts)->bInterfaceNumber;
id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
//接下来就是读取配置,然后创建alsa世界需要的对象
/* check whether it's already registered */
chip = NULL;
mutex_lock(®ister_mutex);
//防止多个Audio Control情况下重复创建chip对象
for (i = 0; i < SNDRV_CARDS; i++) {
if (usb_chip[i] && usb_chip[i]->dev == dev) {
if (usb_chip[i]->shutdown) {
dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
goto __error;
}
chip = usb_chip[i];
chip->probing = 1;
break;
}
}
if (! chip) {
/* it's a fresh one.
* now look for an empty slot and create a new card instance
*/
//下面的逻辑,创建一个新的chip对象,创建对象的函数,为snd_usb_audio_create函数
for (i = 0; i < SNDRV_CARDS; i++)
if (enable[i] && ! usb_chip[i] &&
(vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
(pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
if (snd_usb_audio_create(intf, dev, i, quirk,
&chip) < 0) {
goto __error;
}
chip->pm_intf = intf;
break;
}
if (!chip) {
dev_err(&dev->dev, "no available usb audio device\n");
goto __error;
}
}
//...
//下面的代码,负责调用snd_usb_create_streams创建audio stream,它包括midi stream
//调用snd_usb_create_mixer,创建mixer
if (err > 0) {
/* create normal USB audio interfaces */
if (snd_usb_create_streams(chip, ifnum) < 0 ||
snd_usb_create_mixer(chip, ifnum, ignore_ctl_error) < 0) {
goto __error;
}
}
//创建完成之后,将card对象注册到alsa世界中,至此,可以从外部访问这些usb声卡设备
/* we are allowed to call snd_card_register() many times */
if (snd_card_register(chip->card) < 0) {
goto __error;
}
usb_chip[chip->index] = chip;
chip->num_interfaces++;
chip->probing = 0;
intf->needs_remote_wakeup = 1;
mutex_unlock(®ister_mutex);
return chip;
//...
}
上面的函数很简单,主要分成了三个步骤:
- 判断是不是已经创建了chip对象,这是因为某些声卡,具有多个Audio Control接口
- 如果没有chip对象,则调用snd_usb_audio_create,创建
- 然后调用snd_usb_create_streams,snd_usb_create_mixer创建对应的audio stream和audio mixer
- 将创建的card对象,注册到alsa世界中。至此,alsa驱动注册完毕,可以被外部的访问了
接下来看看snd_usb_audio_create函数。
static int snd_usb_audio_create(struct usb_interface *intf,
struct usb_device *dev, int idx,
const struct snd_usb_audio_quirk *quirk,
struct snd_usb_audio **rchip)
{
struct snd_card *card;
struct snd_usb_audio *chip;
int err, len;
char component[14];
//当释放card对象时,card对象里面的component也会被释放掉,为了能够释放为component分配的资源,所以传递下面的回调操作。
static struct snd_device_ops ops = {
.dev_free = snd_usb_audio_dev_free,
};
*rchip = NULL;
//速度判断
switch (snd_usb_get_speed(dev)) {
case USB_SPEED_LOW:
case USB_SPEED_FULL:
case USB_SPEED_HIGH:
case USB_SPEED_WIRELESS:
case USB_SPEED_SUPER:
break;
default:
dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
return -ENXIO;
}
//这里直接调用snd_card_new来创建一个card对象,注意此处的第五个参数0
//表示card对象的私有数据不需要这个函数分配。
//在usb声卡驱动中,为了管理chip对象,使用了《usb声卡驱动(四)》中的"chip对象的管理通常有两种方法"第二种方法
//即创建一个虚拟的component对象。并为其传递snd_device_ops
err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
0, &card);
if (err < 0) {
dev_err(&dev->dev, "cannot create card instance %d\n", idx);
return err;
}
//分配chip对象的空间
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
if (! chip) {
snd_card_free(card);
return -ENOMEM;
}
//初始化chip对象
mutex_init(&chip->mutex);
init_rwsem(&chip->shutdown_rwsem);
chip->index = idx;
chip->dev = dev;
chip->card = card;
chip->setup = device_setup[idx];
chip->autoclock = autoclock;
chip->probing = 1;
chip->usb_id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
INIT_LIST_HEAD(&chip->pcm_list);
INIT_LIST_HEAD(&chip->ep_list);
INIT_LIST_HEAD(&chip->midi_list);
INIT_LIST_HEAD(&chip->mixer_list);
//将这个chip对象,当做一个虚拟的component对象进行管理
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
snd_usb_audio_free(chip);
snd_card_free(card);
return err;
}
strcpy(card->driver, "USB-Audio");
sprintf(component, "USB%04x:%04x",
USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id));
//将这个虚拟的component对象附着在card对象上
snd_component_add(card, component);
*rchip = chip;
return 0;
}
上面的函数,也只做了那么几个简单的事情,大致流程如下:
- 调用标准的alsa api创建对应的card对象
- 对于chip对象的管理,这里使用了上一篇笔记介绍的第二种方法————创建一个虚拟的component对象
- 然后初始化chip对象中剩下的字段。并返回
解析音频设备的描述符
上面的步骤,创建了card对象,接下要做的事:根据描述符中的内容,创建不同的audio stream。
又因为篇幅原因,本篇笔记只会记录pcm对象的创建,对于各种control,mixer,midi等不在做过多的介绍,因为它和pcm几乎大同小异
使用snd_usb_create_streams函数,进入创建流程。
static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
{
//首先第一步是,确认使用的版本
//参照《usb声卡驱动二》的描述符层级图,可以知道,版本信息保存在header中
//版本1,有bInCollection个audiostream,各个stream保存在baInterfaceNr数组中
//版本2,不清楚,没有翻过相应的规范
control_header = snd_usb_find_csint_desc(host_iface->extra,
host_iface->extralen,
NULL, UAC_HEADER);
protocol = altsd->bInterfaceProtocol;
switch (protocol) {
case UAC_VERSION_1: {
struct uac1_ac_header_descriptor *h1 = control_header;
for (i = 0; i < h1->bInCollection; i++)
snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
break;
}
case UAC_VERSION_2: {
//母鸡呀···
}
}
return 0;
}
上面的函数,超级简单,提取header描述符,然后判断版本,根据stream个数,调用
snd_usb_create_stream函数,来创建相应的对象。
接下来进入snd_usb_create_stream函数。
//注意第二参数,是控制接口,第三个参数是streaming接口
static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
{
//判断这个是不是midiStreaming 接口,如果是,则调用snd_usbmidi_create创建midi
if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
int err = snd_usbmidi_create(chip->card, iface,
&chip->midi_list, NULL);
if (err < 0) {
dev_err(&dev->dev,
"%u:%d: cannot create sequencer device\n",
ctrlif, interface);
return -EINVAL;
}
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
return 0;
}
//判断是不是audio streaming 如果不是则返回错误
if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) {
dev_dbg(&dev->dev,
"%u:%d: skipping non-supported interface %d\n",
ctrlif, interface, altsd->bInterfaceClass);
/* skip non-supported classes */
return -EINVAL;
}
//如果是audio streaming ,则调用snd_usb_parse_audio_interface
if (! snd_usb_parse_audio_interface(chip, interface)) {
usb_set_interface(dev, interface, 0); /* reset the current interface */
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
return -EINVAL;
}
return 0;
}
上面的函数,也很简单,根据描述符里面的信息,判断是不是midi,或者audio。如果是midi则调用snd_usbmidi_create函数。
如果是audio streaming,则将后续逻辑转交给snd_usb_parse_audio_interface函数。
int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
{
/* parse the interface's altsettings */
iface = usb_ifnum_to_if(dev, iface_no);
//获取可选设置数
num = iface->num_altsetting;
//遍历所有可选设置
for (i = 0; i < num; i++) {
alts = &iface->altsetting[i];
altsd = get_iface_desc(alts);
protocol = altsd->bInterfaceProtocol;
//跳过跟音频无关的设置
if (((altsd->bInterfaceClass != USB_CLASS_AUDIO ||
(altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
altsd->bInterfaceSubClass != USB_SUBCLASS_VENDOR_SPEC)) &&
altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
altsd->bNumEndpoints < 1 ||
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) == 0)
continue;
//0端点必须是,等时端点(规范要求),见《usb声卡驱动(二)》
if ((get_endpoint(alts, 0)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
USB_ENDPOINT_XFER_ISOC)
continue;
//判断端点方向
stream = (get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN) ?
SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
altno = altsd->bAlternateSetting;
chconfig = 0;
/* get audio formats */
switch (protocol) {
case UAC_VERSION_1: {
//接下来获取音频相关的AS接口描述符
struct uac1_as_header_descriptor *as =
snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
struct uac_input_terminal_descriptor *iterm;
if (as->bLength < sizeof(*as)) {
//传输使用的格式
format = le16_to_cpu(as->wFormatTag); /* remember the format value */
//bTerminalLink保存了跟这个接口相关的Terminal ID
iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
as->bTerminalLink);
if (iterm) {
num_channels = iterm->bNrChannels;
chconfig = le16_to_cpu(iterm->wChannelConfig);
}
break;
}
case UAC_VERSION_2: {
//省略,不懂
}
}
//FORMAT_TYPE描述符
fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_FORMAT_TYPE);
fp = kzalloc(sizeof(*fp), GFP_KERNEL);
if (! fp) {
dev_err(&dev->dev, "cannot malloc\n");
return -ENOMEM;
}
//初始化fp对象,该对象专门用于保存格式信息。
fp->iface = iface_no;
fp->altsetting = altno;
fp->altset_idx = i;
fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress;
fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
fp->datainterval = snd_usb_parse_datainterval(chip, alts);
fp->protocol = protocol;
fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
fp->channels = num_channels;
if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
* (fp->maxpacksize & 0x7ff);
fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no);
fp->clock = clock;
INIT_LIST_HEAD(&fp->list);
//调用snd_usb_parse_audio_format进一步解析,它的功效依然是对fp对象的进一步填充,看过《usb声卡驱动(二)》的这个就没有什么可叙述的了
if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream) < 0) {
kfree(fp->rate_table);
kfree(fp);
fp = NULL;
continue;
}
/* Create chmap */
if (fp->channels != num_channels)
chconfig = 0;
fp->chmap = convert_chmap(fp->channels, chconfig, protocol);
dev_dbg(&dev->dev, "%u:%d: add audio endpoint %#x\n", iface_no, altno, fp->endpoint);
//调用snd_usb_add_audio_stream创建各种对象,并初始化,并添加好
err = snd_usb_add_audio_stream(chip, stream, fp);
//一切OK之后,将当前的可选设置选中
//然后初始化pitch
//然后初始化采样率
usb_set_interface(chip->dev, iface_no, altno);
snd_usb_init_pitch(chip, iface_no, alts, fp);
snd_usb_init_sample_rate(chip, iface_no, alts, fp, fp->rate_max);
}
return 0;
}
上面的函数,主要功能是解析描述符,它遍历所有的可选设置,选中audio 流的可选设置
分如下几步
- 首先跳过,不是我们关心的一些可选设置。
- 然后,填充audioformat对象,进行进一步的解析
- 当解析完成,调用snd_usb_add_audio_stream将各种创建好的对象放入规定的位置
- 一切,就进行一些初始化,包括接口的设置,pitch的初始化,采样率的初始化。
到现在为止,依然,还没有创建pcm对象。我们继续snd_usb_add_audio_stream函数
int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
int stream,
struct audioformat *fp)
{
struct snd_usb_stream *as;
struct snd_usb_substream *subs;
struct snd_pcm *pcm;
int err;
//遍历chip对象的pcm链表,如果存在一个空的stream,则使用这个stream
list_for_each_entry(as, &chip->pcm_list, list) {
if (as->fmt_type != fp->fmt_type)
continue;
subs = &as->substream[stream];
if (subs->ep_num)
continue;
//创建stream
err = snd_pcm_new_stream(as->pcm, stream, 1);
if (err < 0)
return err;
//初始化
snd_usb_init_substream(as, stream, fp);
return add_chmap(as->pcm, stream, subs);
}
//遍历之后没有找到,则创建新的,并加入链表中
as = kzalloc(sizeof(*as), GFP_KERNEL);
if (!as)
return -ENOMEM;
as->pcm_index = chip->pcm_devs;
as->chip = chip;
as->fmt_type = fp->fmt_type;
//啦啦啦,终于找到,我们辛辛苦苦期待的创建pcm对象啦
err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
&pcm);
if (err < 0) {
kfree(as);
return err;
}
as->pcm = pcm;
pcm->private_data = as;
pcm->private_free = snd_usb_audio_pcm_free;
pcm->info_flags = 0;
if (chip->pcm_devs > 0)
sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
else
strcpy(pcm->name, "USB Audio");
//然后初始化substream
snd_usb_init_substream(as, stream, fp);
list_add(&as->list, &chip->pcm_list);
chip->pcm_devs++;
snd_usb_proc_pcm_format_add(as);
return add_chmap(pcm, stream, &as->substream[stream]);
}
上面的函数,也太不复杂了吧。看看它的主要逻辑:
- 先遍历chip对象,看看有没有空闲的stream,如果有,就用它
- 如果没有就重新创建一个,stream对象,包含有alsa对象中的pcm对象(一种特殊的component)。
- 然后初始化这个stream对象。
接下来,看看这个stream对象的初始化
static void snd_usb_init_substream(struct snd_usb_stream *as,
int stream,
struct audioformat *fp)
{
struct snd_usb_substream *subs = &as->substream[stream];
INIT_LIST_HEAD(&subs->fmt_list);
spin_lock_init(&subs->lock);
subs->stream = as;
subs->direction = stream;
subs->dev = as->chip->dev;
subs->txfr_quirk = as->chip->txfr_quirk;
subs->speed = snd_usb_get_speed(subs->dev);
subs->pkt_offset_adj = 0;
snd_usb_set_pcm_ops(as->pcm, stream);
list_add_tail(&fp->list, &subs->fmt_list);
subs->formats |= fp->formats;
subs->num_formats++;
subs->fmt_type = fp->fmt_type;
subs->ep_num = fp->endpoint;
if (fp->channels > subs->channels_max)
subs->channels_max = fp->channels;
}
上面的代码那么的显眼,snd_usb_set_pcm_ops(as->pcm, stream);不用看也知道,是为stream对象里面的pcm对象,设置相应的回调接口。来看看
void snd_usb_set_pcm_ops(struct snd_pcm *pcm, int stream)
{
snd_pcm_set_ops(pcm, stream,
stream == SNDRV_PCM_STREAM_PLAYBACK ?
&snd_usb_playback_ops : &snd_usb_capture_ops);
}
果然,使用标准的alsa函数来注册相应的回调接口。
至此,整个alsa驱动都创建完成了。
现在来总结一下整个usb 音频驱动的数据结构:
-
chip对象为:struct snd_usb_audio.
它持有,snd_card对象,这个对象是alsa世界里面的声卡对象。 -
chip对象有一个链表pcm_list,它是所有snd_usb_stream对象的一个链表。
-
而snd_usb_stream对象,持有一个alsa世界的snd_pcm对象,即表示alsa世界里面的pcm
-
snd_usb_stream对象,还有一个数组substream[2]。它的类型为snd_usb_substream。表示的是打开的子流。在打开的时候,进行赋值,详见后面的pcm的打开和关闭。
在《usb声卡驱动二》中可以知道,一个流对应一个接口(输出一个interface,输入一个interface)。而在snd_usb_add_audio_stream函数中,已经将相同格式的接口统一在了一个snd_usb_stream下面了。
现在已经知道了,整个usb声卡驱动的主要数据结构,以及alsa世界里面的card对象,pcm对象的创建和赋值。接下来就是看它支持的各种功能啦。
下一篇见