Linux ALSA驱动框架分析之(一):架构介绍
Linux ALSA驱动框架分析之(二):pcm逻辑设备的创建
Linux ALSA驱动框架分析之(三):Control逻辑设备的创建
pcm设备的主要功能就是:
- playback,把用户空间的应用程序发过来的PCM数据进行D/A转换,转化为人耳可以辨别的模拟音频。
- capture,把麦克风或者其他输入源的模拟信号进行A/D转换,把模拟的声音信号转变CPU能够处理的数字信号。
声卡可以包含多个pcm设备。一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又分别有一个或多个substreams组成。
snd_pcm结构体描述一个pcm设备,定义如下:
struct snd_pcm {
struct snd_card *card; //所属声卡
struct list_head list;
int device; /* pcm设备的编号 */
......
char id[64];
char name[80];
//streams[0]为playback(播放),streams[1]为capture(录音)
struct snd_pcm_str streams[2];
......
};
struct snd_pcm_str {
int stream; /* 0为playback,1为capture */
struct snd_pcm *pcm;
/* -- substreams -- */
unsigned int substream_count; //substreams的个数
unsigned int substream_opened;
struct snd_pcm_substream *substream; //子流
......
struct device dev;
};
snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是它的ops(snd_pcm_ops)字段,许多user空间的应用程序通过alsa-lib对驱动程序的请求都是由该结构中的函数处理,它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运行环境和参数。
struct snd_pcm_substream {
struct snd_pcm *pcm;
struct snd_pcm_str *pstr;
void *private_data; /* copied from pcm->private_data */
int number; /* 子流编号 */
char name[32]; /* substream name */
int stream; /* stream (direction) */
size_t buffer_bytes_max; /* limit ring buffer size */
struct snd_dma_buffer dma_buffer;
size_t dma_max;
/* 由具体驱动设置ops */
const struct snd_pcm_ops *ops;
/* pcm设备运行时的信息 */
struct snd_pcm_runtime *runtime;
......
};
驱动程序可调用snd_pcm_set_ops函数来设置snd_pcm_substream里的ops字段。
调用snd_pcm_new函数创建声卡的控制逻辑设备:
/* card,声卡实例
* id,pcm设备的标识符
* device,表示目前创建的是该声卡下的第几个pcm,第一个pcm设备从0开始
* playback_count,表示该pcm将会有几个playback substream
* capture_count,表示该pcm将会有几个capture substream
*/
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, struct snd_pcm **rpcm)
{
return _snd_pcm_new(card, id, device, playback_count, capture_count,
false, rpcm);
}
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool ,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
//这个snd_device_ops不同类型的逻辑设备,其回调函数是不一样的
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
......
//创建一个snd_pcm
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
if (!pcm)
return -ENOMEM;
//设置snd_pcm
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
mutex_init(&pcm->open_mutex);
init_waitqueue_head(&pcm->open_wait);
INIT_LIST_HEAD(&pcm->list);
if (id)
strlcpy(pcm->id, id, sizeof(pcm->id));
//创建playback的substreams
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
//创建capture的substreams
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
//创建snd_device,并挂入snd_card的devices链表
if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
snd_pcm_free(pcm);
return err;
}
if (rpcm)
*rpcm = pcm;
return 0;
}
snd_device_new函数:
int snd_device_new(struct snd_card *card, enum snd_device_type type,
void *device_data, struct snd_device_ops *ops)
{
struct snd_device *dev;
struct list_head *p;
......
//创建一个snd_device
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
//设置snd_device
INIT_LIST_HEAD(&dev->list);
dev->card = card;
dev->type = type;
//snd_device的state设置为SNDRV_DEV_BUILD,表示还未注册
dev->state = SNDRV_DEV_BUILD;
dev->device_data = device_data;
dev->ops = ops;
/* 把snd_device挂入snd_card的devices链表 */
list_for_each_prev(p, &card->devices) {
struct snd_device *pdev = list_entry(p, struct snd_device, list);
if ((unsigned int)pdev->type <= (unsigned int)type)
break;
}
list_add(&dev->list, p);
return 0;
}
snd_pcm是挂在snd_card下面的一个snd_device,来个图:
在声卡的注册阶段( snd_card_register),会调用snd_device_register_all函数注册声卡的所有逻辑设备,最终会调用到snd_device->snd_device_pos的dev_register回调,对于pcm逻辑设备该回调设置为snd_pcm_dev_register。
来个时序图:
snd_pcm_dev_register函数定义如下:
static int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
struct snd_pcm_substream *substream;
struct snd_pcm_notify *notify;
struct snd_pcm *pcm;
......
mutex_lock(®ister_mutex);
err = snd_pcm_add(pcm);
if (err)
goto unlock;
for (cidx = 0; cidx < 2; cidx++) {
int devtype = -1;
if (pcm->streams[cidx].substream == NULL)
continue;
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
/* 注册snd_device */
err = snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm,
&pcm->streams[cidx].dev);
......
}
......
}
......
}
调用snd_register_device函数注册snd_device:
/* 参数说明:
* type,逻辑设备的类型
* card,逻辑设备所属的snd_card
* f_ops,文件操作集
* private_data,私有数据
* device,逻辑设备对应的struct device
*/
int snd_register_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data, struct device *device)
{
int minor;
int err = 0;
struct snd_minor *preg;
......
//创建一个snd_minor
preg = kmalloc(sizeof *preg, GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
//设置snd_minor
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops;
preg->private_data = private_data;
preg->card_ptr = card;
mutex_lock(&sound_mutex);
//找到一个次设备号
minor = snd_find_free_minor(type, card, dev);
......
preg->dev = device;
//主设备号为116
device->devt = MKDEV(major, minor);
//会在/dev/snd/目录下创建pcm逻辑设备的设备文件,如pcmC0D0c...
err = device_add(device);
......
/* snd_minor为struct snd_minor *类型的全局数组,用于保存系统中的snd_minor
之后可以次设备号为下标在该数组中找到对应的snd_minor
*/
snd_minors[minor] = preg;
......
}
在sound/sound.c中定义了一个snd_minor指针的全局数组:
#define SNDRV_OS_MINORS 256
static struct snd_minor *snd_minors[SNDRV_OS_MINORS]; /* sound/core/sound.c*/
snd_minor结构体定义如下:
struct snd_minor {
int type; /* 逻辑设备的类型*/
int card; /* 声卡的编号 */
int device; /* device number */
const struct file_operations *f_ops; /* 文件操作集 */
void *private_data;
struct device *dev; /* 逻辑设备的device */
struct snd_card *card_ptr; /* 声卡实例 */
};
来个图:
声卡设备的主设备号都是116,声卡的不同逻辑设备,其次设备号是不同的。注册一个逻辑设备会创建一个对应的snd_minor,里面有file_operations,应用层open、read、write等最终会调用到snd_minor里file_operations对应的回调函数。
对于一个pcm设备,snd_pcm_dev_register函数执行后,会生成两个设备文件,一个用于playback,一个用于capture,对应的file_operations:
const struct file_operations snd_pcm_f_ops[2] = {
{
.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,
},
{
.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,
}
};
应用层open、read、write最终是怎么样调用到snd_minor里file_operations对应的回调函数的呢?
在sound/core/sound.c文件中会注册字符设备:
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
//注册字符设备,对应的file_operations为snd_fops
if (register_chrdev(major, "alsa", &snd_fops)) {
pr_err("ALSA core: unable to register native major device number %d\n", major);
return -EIO;
}
......
return 0;
}
来看看snd_open函数:
static int snd_open(struct inode *inode, struct file *file)
{
//得到次设备
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *new_fops;
int err = 0;
if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
mutex_lock(&sound_mutex);
//以次设备号为下标在snd_minors数组中找到对应的snd_minor
mptr = snd_minors[minor];
......
//从snd_minor得到file_operations
new_fops = fops_get(mptr->f_ops);
mutex_unlock(&sound_mutex);
......
//替换原来的file_operations
replace_fops(file, new_fops);
if (file->f_op->open)
err = file->f_op->open(inode, file);
return err;
}
应用层打开一个pcm设备:
进行录音或播放,ioctl("/dev/snd/pcmC0D0",…),最终会调用到:
snd_pcm_substream->snd_pcm_ops->ioctl,进行硬件上的操作。snd_pcm_substream的snd_pcm_ops由驱动程序调用snd_pcm_set_ops函数进行设置:
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
const struct snd_pcm_ops *ops)
{
struct snd_pcm_str *stream = &pcm->streams[direction];
struct snd_pcm_substream *substream;
for (substream = stream->substream; substream != NULL; substream = substream->next)
substream->ops = ops;
}