在Linux内核中,对于声卡驱动,其本质上也是一个字符设备驱动程序,主设备号为116
//ls -al dev/snd/
crw-rw---- 1 system audio 116, 3 1970-01-01 08:00 controlC0//0号声卡的控制节点
crw-rw---- 1 system audio 116, 6 1970-01-01 08:00 controlC1
crw-rw---- 1 system audio 116, 9 1970-01-01 08:00 controlC2
crw-rw---- 1 system audio 116, 2 1970-01-01 08:00 pcmC0D0p//播放节点
crw-rw---- 1 system audio 116, 5 1970-01-01 08:00 pcmC1D0c//录音节点
crw-rw---- 1 system audio 116, 4 1970-01-01 08:00 pcmC1D0p
crw-rw---- 1 system audio 116, 8 1970-01-01 08:00 pcmC2D0c
crw-rw---- 1 system audio 116, 7 1970-01-01 08:00 pcmC2D0p
crw-rw---- 1 system audio 116, 33 1970-01-01 08:00 timer
对于字符设备驱动,在Linux内核中都有一个file_operations结构体来管理open/read/write 等等函数,主设备号为116的设备驱动,其file_operations为snd_fops,源码路径:kernel\sound\core\sound.c
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
snd_fops 只有一个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;
mutex_lock(&sound_mutex);
mptr = snd_minors[minor];//根据次设备号,从snd_minors数组中取出一项
new_fops = fops_get(mptr->f_ops);//得到一个新的file_operations结构体
mutex_unlock(&sound_mutex);
if (!new_fops)
return -ENODEV;
replace_fops(file, new_fops);//用新的结构体替换
if (file->f_op->open)
err = file->f_op->open(inode, file);//调用新结构体的open函数
return err;
}
可以看出,snd_open起到了一个中转的作用,用户空间调用open/read/write函数,实际上是调用了new_fops中对应的函数,而new_fops是根据次设备号,从snd_minors数组中取出的。接下来看下这个snd_minors数组中的元素是在哪里添加的
snd_minors元素的添加,有两个调用链,分别是snd_card_new和snd_pcm_new
snd_card_new
源码路径:kernel\sound\core\init.c
//kernel\sound\core\init.c
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
struct snd_card *card;
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
if (xid)
strlcpy(card->id, xid, sizeof(card->id));
//省略
if (idx < 0) /* first check the matching module-name slot */
idx = get_slot_from_bitmask(idx, module_slot_match, module);
if (idx < 0) /* if not matched, assign an empty slot */
idx = get_slot_from_bitmask(idx, check_empty_slot, module);
//省略
card->dev = parent;
card->number = idx;//number为idx
card->module = module;
//省略
card->card_dev.parent = parent;
card->card_dev.class = sound_class;
card->card_dev.release = release_card_device;
card->card_dev.groups = card->dev_groups;
err = snd_ctl_create(card);
//省略
}
snd_card_new函数主要是创建了一个snd_card结构体,并对其成员进行初始化,最后调用snd_ctl_create
//kernel\sound\core\control.c
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
int err;
//省略
snd_device_initialize(&card->ctl_dev, card);
dev_set_name(&card->ctl_dev, "controlC%d", card->number);//控制节点的名字为controlC+ number
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);//1
if (err < 0)
put_device(&card->ctl_dev);
return err;
}
注释1处,在snd_device_new函数里面,会创建一个snd_device结构体,并将ops保存到snd_device结构体的ops成员中,最后将其加入到list链表中。注册声卡时(调用snd_card_register),会从list链表中取出snd_device,并调用其ops的dev_register 函数,也就是执行snd_ctl_dev_register函数
//kernel\sound\core\control.c
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;//取出snd_card
return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, &card->ctl_dev);
}
snd_register_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;
if (snd_BUG_ON(!device))
return -EINVAL;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
if (preg == NULL)
return -ENOMEM;
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops;//f_ops为传入进来的snd_ctl_f_ops
preg->private_data = private_data;
preg->card_ptr = card;
mutex_lock(&sound_mutex);
minor = snd_find_free_minor(type, card, dev);
//省略
snd_minors[minor] = preg;//放入元素
}
最后是往snd_minors元素中放入元素,其f_ops为snd_ctl_f_ops
snd_pcm_new
源码路径:kernel\sound\core\pcm.c
//kernel\sound\core\pcm.c
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);
}
_snd_pcm_new
//kernel\sound\core\pcm.c
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register,
.dev_disconnect = snd_pcm_dev_disconnect,
};
static struct snd_device_ops internal_ops = {
.dev_free = snd_pcm_dev_free,
};
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
pcm->card = card;
pcm->device = device;
pcm->internal = internal;
/*在snd_pcm_new_stream函数中确定播放节点格式为 pcmC%iD%ip,录音节点格式为pcmC%iD%ic*/
err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
playback_count);//播放
err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);//录音
err = snd_device_new(card, SNDRV_DEV_PCM, pcm,
internal ? &internal_ops : &ops);
//省略
snd_device_new前面提过,在注册声卡时导致ops的dev_register 函数被调用,也就是调用snd_pcm_dev_register
//kernel\sound\core\pcm.c
static int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
//省略
for (cidx = 0; cidx < 2; cidx++) {
/* register pcm */
err = snd_register_device(devtype, pcm->card, pcm->device,
&snd_pcm_f_ops[cidx], pcm,
&pcm->streams[cidx].dev);
//省略
return err;
}
对于录音和播放节点,都会调用snd_register_device往snd_minors数组中放入元素,播放节点对应的f_ops为snd_pcm_f_ops[0],录音对应的为snd_pcm_f_ops[1]
总结
在Alsa框架中,Linux驱动已经为我们准备好了这些file_operations结构体并放入snd_minors中,要写一个Alsa声卡驱动我们只需要做以下三点:
- 调用snd_card_new,分配snd_card,并创建控制节点
- 调用snd_pcm_new 创建pcm节点
- 调用snd_card_register注册snd_card
一个声卡必然会有控制节点。控制节点对应的file_operations为snd_ctl_f_ops
//kernel\sound\core\control.c
static const struct file_operations snd_ctl_f_ops =
{
.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,
};
一个声卡不一定会有pcm节点,pcm节点对应的file_operations为snd_pcm_f_ops。snd_pcm_f_ops[0]对应的是播放,snd_pcm_f_ops[1]对应的是录音
//kernel\sound\core\pcm_native.c
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_poll,
.unlocked_ioctl = snd_pcm_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_poll,
.unlocked_ioctl = snd_pcm_ioctl,
.compat_ioctl = snd_pcm_ioctl_compat,
.mmap = snd_pcm_mmap,
.fasync = snd_pcm_fasync,
.get_unmapped_area = snd_pcm_get_unmapped_area,
}
};