Linux ALSA聲卡驅(qū)動之三:PCM設備的創(chuàng)建
4. 設備文件節(jié)點的建立(dev/snd/pcmCxxDxxp、pcmCxxDxxc)
本文引用地址:http://www.ex-cimer.com/article/201612/341593.htm4.1 struct snd_minor
每個snd_minor結(jié)構(gòu)體保存了聲卡下某個邏輯設備的上下文信息,他在邏輯設備建立階段被填充,在邏輯設備被使用時就可以從該結(jié)構(gòu)體中得到相應的信息。pcm設備也不例外,也需要使用該結(jié)構(gòu)體。該結(jié)構(gòu)體在include/sound/core.h中定義。
[c-sharp] view plain copystruct snd_minor {
int type; /* SNDRV_DEVICE_TYPE_XXX */
int card; /* card number */
int device; /* device number */
const struct file_operations *f_ops; /* file operations */
void *private_data; /* private data for f_ops->open */
struct device *dev; /* device for sysfs */
};
在sound/sound.c中定義了一個snd_minor指針的全局數(shù)組:
[c-sharp] view plain copystatic struct snd_minor *snd_minors[256];
前面說過,在聲卡的注冊階段(snd_card_register),會調(diào)用pcm的回調(diào)函數(shù)snd_pcm_dev_register(),這個函數(shù)里會調(diào)用函數(shù)snd_register_device_for_dev():
[c-sharp] view plain copystatic int snd_pcm_dev_register(struct snd_device *device)
{
......
/* register pcm */
err = snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],
pcm, str, dev);
......
}
我們再進入snd_register_device_for_dev():
[c-sharp] view plain copyint snd_register_device_for_dev(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data,
const char *name, struct device *device)
{
int minor;
struct snd_minor *preg;
if (snd_BUG_ON(!name))
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;
preg->private_data = private_data;
mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
minor = snd_find_free_minor();
#else
minor = snd_kernel_minor(type, card, dev);
if (minor >= 0 && snd_minors[minor])
minor = -EBUSY;
#endif
if (minor < 0) {
mutex_unlock(&sound_mutex);
kfree(preg);
return minor;
}
snd_minors[minor] = preg;
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
if (IS_ERR(preg->dev)) {
snd_minors[minor] = NULL;
mutex_unlock(&sound_mutex);
minor = PTR_ERR(preg->dev);
kfree(preg);
return minor;
}
mutex_unlock(&sound_mutex);
return 0;
}
首先,分配并初始化一個snd_minor結(jié)構(gòu)中的各字段
type:SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE
card: card的編號
device:pcm實例的編號,大多數(shù)情況為0
f_ops:snd_pcm_f_ops
private_data:指向該pcm的實例
根據(jù)type,card和pcm的編號,確定數(shù)組的索引值minor,minor也作為pcm設備的此設備號
把該snd_minor結(jié)構(gòu)的地址放入全局數(shù)組snd_minors[minor]中
最后,調(diào)用device_create創(chuàng)建設備節(jié)點
4.2 設備文件的建立
在4.1節(jié)的最后,設備文件已經(jīng)建立,不過4.1節(jié)的重點在于snd_minors數(shù)組的賦值過程,在本節(jié)中,我們把重點放在設備文件中。
回到pcm的回調(diào)函數(shù)snd_pcm_dev_register()中:
[c-sharp] view plain copystatic int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
char str[16];
struct snd_pcm *pcm;
struct device *dev;
pcm = device->device_data;
......
for (cidx = 0; cidx < 2; cidx++) {
......
switch (cidx) {
case SNDRV_PCM_STREAM_PLAYBACK:
sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
break;
case SNDRV_PCM_STREAM_CAPTURE:
sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
break;
}
/* device pointer to use, pcm->dev takes precedence if
* it is assigned, otherwise fall back to card's device
* if possible */
dev = pcm->dev;
if (!dev)
dev = snd_card_get_device_link(pcm->card);
/* register pcm */
err = snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],
pcm, str, dev);
......
}
......
}
以上代碼我們可以看出,對于一個pcm設備,可以生成兩個設備文件,一個用于playback,一個用于capture,代碼中也確定了他們的命名規(guī)則:
playback -- pcmCxDxp,通常系統(tǒng)中只有一各聲卡和一個pcm,它就是pcmC0D0p
capture -- pcmCxDxc,通常系統(tǒng)中只有一各聲卡和一個pcm,它就是pcmC0D0c
snd_pcm_f_ops
snd_pcm_f_ops是一個標準的文件系統(tǒng)file_operations結(jié)構(gòu)數(shù)組,它的定義在sound/core/pcm_native.c中:
[c-sharp] view plain copyconst struct file_operations snd_pcm_f_ops[2] = {
{
.owner = THIS_MODULE,
.write = snd_pcm_write,
.aio_write = snd_pcm_aio_write,
.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,
.aio_read = snd_pcm_aio_read,
.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,
}
};
snd_pcm_f_ops作為snd_register_device_for_dev的參數(shù)被傳入,并被記錄在snd_minors[minor]中的字段f_ops中。最后,在snd_register_device_for_dev中創(chuàng)建設備節(jié)點:
[c-sharp] view plain copysnd_minors[minor] = preg;
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
4.3 層層深入,從應用程序到驅(qū)動層pcm
4.3.1 字符設備注冊
在sound/core/sound.c中有alsa_sound_init()函數(shù),定義如下:
[c-sharp] view plain copystatic int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) {
snd_printk(KERN_ERR "unable to register native major device number %d/n", major);
return -EIO;
}
if (snd_info_init() < 0) {
unregister_chrdev(major, "alsa");
return -ENOMEM;
}
snd_info_minor_register();
return 0;
}
register_chrdev中的參數(shù)major與之前創(chuàng)建pcm設備是device_create時的major是同一個,這樣的結(jié)果是,當應用程序open設備文件/dev/snd/pcmCxDxp時,會進入snd_fops的open回調(diào)函數(shù),我們將在下一節(jié)中講述open的過程。
4.3.2 打開pcm設備
從上一節(jié)中我們得知,open一個pcm設備時,將會調(diào)用snd_fops的open回調(diào)函數(shù),我們先看看snd_fops的定義:
[c-sharp] view plain copystatic const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open
};
跟入snd_open函數(shù),它首先從inode中取出此設備號,然后以次設備號為索引,從snd_minors全局數(shù)組中取出當初注冊pcm設備時填充的snd_minor結(jié)構(gòu)(參看4.1節(jié)的內(nèi)容),然后從snd_minor結(jié)構(gòu)中取出pcm設備的f_ops,并且把file->f_op替換為pcm設備的f_ops,緊接著直接調(diào)用pcm設備的f_ops->open(),然后返回。因為file->f_op已經(jīng)被替換,以后,應用程序的所有read/write/ioctl調(diào)用都會進入pcm設備自己的回調(diào)函數(shù)中,也就是4.2節(jié)中提到的snd_pcm_f_ops結(jié)構(gòu)中定義的回調(diào)。
[c-sharp] view plain copystatic int snd_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *old_fops;
int err = 0;
if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
mutex_lock(&sound_mutex);
mptr = snd_minors[minor];
if (mptr == NULL) {
mptr = autoload_device(minor);
if (!mptr) {
mutex_unlock(&sound_mutex);
return -ENODEV;
}
}
old_fops = file->f_op;
file->f_op = fops_get(mptr->f_ops);
if (file->f_op == NULL) {
file->f_op = old_fops;
err = -ENODEV;
}
mutex_unlock(&sound_mutex);
if (err < 0)
return err;
if (file->f_op->open) {
err = file->f_op->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);
return err;
}
下面的序列圖展示了應用程序如何最終調(diào)用到snd_pcm_f_ops結(jié)構(gòu)中的回調(diào)函數(shù):
圖4.3.2.1 應用程序操作pcm設備
評論