Android音频驱动-ASOC之主&从设备号

设备号是在驱动module中分配并注册的,驱动module拥有这个设备号,而/dev目录下的设备文件是根据这个设备号创建的,
当访问/dev目录下的设备文件时,驱动module就知道,自己该出场服务了(当然是由内核通知)。
在Linux内核看来,主设备号标识设备对应的驱动程序,告诉Linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务;
而次设备号则用来标识具体且唯一的某个设备。

一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。

设备文件通常都在 /dev 目录下。如:
beyes@linux-beyes:~/C/kernel/memory> ll /dev |more
总计 0
crw-rw—- 1 root uucp 4, 70 04-14 18:16 ttyS6
crw-rw—- 1 root uucp 4, 71 04-14 18:16 ttyS7
crw-rw—- 1 root tty 7, 0 08-08 18:58 vcs
crw-rw—- 1 root tty 7, 1 08-08 18:58 vcs1
crw-rw-rw- 1 root root 1, 7 08-08 18:58 full
crw-rw-rw- 1 root root 1, 3 04-14 18:16 null

如上,前面第一个字符为c 的表示字符设备。在字符设备里,有主设备号和次设备号。如上1,4,7 分别是主设备号,0,1,3,7,70,71都是次设备号。一般的,主设备号标识出与设备关联的设备驱动。如 /dev/null 和 /dev/full 由 1 号驱动来管理,/dev/vcs 和/dev/vcs1由 7 号驱动来管理,/dev/ttyS6 由 4 号驱动来管理。

现在的 Linux 内核允许多个驱动共享一个主设备号,但更多的设备都遵循一个驱动对一个主设备号的原则。

内核由次设备号确定当前所指向的是哪个设备。根据所编写的驱动程序,可以从内核那里得到一个直接指向设备的指针,或者使用次设备号作为一个设备本地数组的索引。但不论如何,内核自身几乎不知道次设备号的什么事情。

对于Linux系统中,一般字符设备和驱动之间的函数调用关系如下图所示
Android音频驱动-ASOC之主&从设备号_第1张图片
上图描述了用户空间应用程序通过系统调用来调用程序的过程。一般而言在驱动程序的设计中,会关系 struct file 和 struct inode 这两个结构体。
用户空间使用open()系统调用函数打开一个字符设备时( int fd = open(“dev/demo”, O_RDWR) )大致有以下过程:

1、在虚拟文件系统VFS中的查找对应与字符设备对应 struct inode节点
2、遍历字符设备列表(chardevs数组),根据inod节点中的 cdev_t设备号找到cdev对象
3、创建struct file对象(系统采用一个数组来管理一个进程中的多个被打开的设备,每个文件秒速符作为数组下标标识了一个设备对象)
4、初始化struct file对象,将 struct file对象中的 file_operations成员指向 struct cdev对象中的 file_operations成员(file->fops =  cdev->fops)
5、回调file->fops->open函数

在文件(sound/core/sound.c)中,ALSA定义了一个字符设备(CONFIG_SND_MAJOR =116),所有AUDIO设备,都是该设备的字设备:
snd_open接口比较重要,snd_open接口通过文件节点inode得到了次设备号,通过snd_minors数组得到对应的声音逻辑设备的文件操作,
调用对应的open接口,并调用fops_put替换成了对应的逻辑设备的文件操作(snd_minors里维护).

//文件提供了处理设备号相关的宏:
MKDEV(major, minor)  //  设备的dev_t
MAJOR(dev_t)                     //  主设备号
MINOR(dev_t)                     //  次设备号
//需要注意的是这三个宏在在内核和用户态有不同的解释,下面截取kdev_t.h的相关片段:
#ifndef _LINUX_KDEV_T_H
#define _LINUX_KDEV_T_H
#ifdef __KERNEL__
#define MINORBITS     20
#define MINORMASK     ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
//...........................................
#else /* __KERNEL__ */
/*
Some programs want their definitions of MAJOR and MINOR and MKDEV
from the kernel sources. These must be the externally visible ones.
*/
#define MAJOR(dev) ((dev)>>8)
#define MINOR(dev) ((dev) & 0xff)
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
#endif /* __KERNEL__ */
#endif
//可以看到如果定义了__KERNEL__宏,主次设备号占据的位数不同。
static const struct file_operations snd_fops =
{
    .owner =    THIS_MODULE,
    .open =     snd_open,
    .llseek =   noop_llseek,
};
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;
    mptr = snd_minors[minor];//通过minor取得从设备
    new_fops = fops_get(mptr->f_ops);
    replace_fops(file, new_fops);
    if (file->f_op->open)
        err = file->f_op->open(inode, file);
    return err;
}
static inline unsigned iminor(const struct inode *inode)
{
    return MINOR(inode->i_rdev);
}
//注册alsa主设备,
static int major = CONFIG_SND_MAJOR;
static int __init alsa_sound_init(void)
{
    snd_major = major;
    snd_ecards_limit = cards_limit;
    if (register_chrdev(major, "alsa", &snd_fops)) {//注册声卡主设备,major为主设备号
        pr_err("ALSA core: 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( )申请指定的设备号,并且将其注册到字符设备驱动模型中.
  它所做的事情为:
1、注册设备号, 通过调用 __register_chrdev_region() 来实现
2、分配一个cdev, 通过调用 cdev_alloc() 来实现
3、将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现
4、将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev. 由于register_chrdev()是老的接口,这一步在新的接口中并不需要。
Android音频驱动-ASOC之主&从设备号_第2张图片

#define CHRDEV_MAJOR_HASH_SIZE 255  
static DEFINE_MUTEX(chrdevs_lock);

static struct char_device_struct {  
  struct char_device_struct *next; // 结构体指针  
  unsigned int major;              // 主设备号  
  unsigned int baseminor;          // 次设备起始号  
  int minorct;                     // 次备号个数  
  char name[64];  
  struct cdev *cdev; /* will die */  
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];// 只能挂255个字符主设备

#include 
int register_chardev (unsigned int major, const char *name, struct file_operations *fops)
说明:register_chrdev 是注册设备驱动程序的内核函数。
参数: 
    major 主设备号,该值为 0 时,自动运行分配。而实际值不是 0 。
    name 设备名称;
    fops file_operations 结构体变量地址(指针)。

返回值:
major 值为 0 ,正常注册后,返回分配的主设备号。如果分配失败,返回 EBUSY 的负值 ( -EBUSY ) 。
major 值若大于 linux/major.h (2.4内核)中声明的最大值 (#define MAX_CHRDEV      255) ,则返回EINVAL 的负值 (-EINVAL) 。
指定 major 值后,若有注册的设备,返回 EBUSY 的负值 (-EBUSY)。若正常注册,则返回 0 值。

register_chrdev
      __register_chrdev(major, 0, 256, name, fops);
            struct char_device_struct *cd;
            cd = __register_chrdev_region(major, baseminor, count, name);
                    cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
                    //如果主设备号是0的话,就会从 char_device_struct数组中选择一个没有分配的char_device_struct结构体
                    //如果找到的话,就以其下标作为主设备号,否则出错返回
                    cd->major = major;
                    cd->baseminor = baseminor;//第一个次设备号
                    cd->minorct = minorct;          //此设备号的个数
                    strlcpy(cd->name, name, sizeof(cd->name));//设置名字
                    i = major_to_index(major);
                          return major % CHRDEV_MAJOR_HASH_SIZE;//只有当指定的主设备号大于255时,i才会不等于major
            //下面的工作比较难懂,可以参考注释1
            cdev = cdev_alloc(); //分配字符设备结构体
            cdev->owner = fops->owner; //设置字符设备结构体
            cdev->ops = fops;
            kobject_set_name(&cdev->kobj, "%s", name);
            //向上注册,这里用到了cd->major和baseminor
            err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

Android N:/ # cat proc/devices
Character devices:
1 mem
2 pty
3 ttyp
5 /dev/tty
5 /dev/console
5 /dev/ptmx
10 misc
13 input
14 sound
29 fb
90 mtd
108 ppp
116 alsa //alsa主设备
128 ptm
136 pts
153 mtk_wmt_wifi_chrdev

Block devices:
259 blkext
7 loop
8 sd
31 mtdblock
65 sd

static int snd_pcm_dev_register(struct snd_device *device)
{
        err = snd_register_device_for_dev(devtype, pcm->card,
                          pcm->device,
                          &snd_pcm_f_ops[cidx],
                          pcm, str, dev);
}
int 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;
    preg = kmalloc(sizeof *preg, GFP_KERNEL);

    preg->type = type;//playback or capture
    preg->card = card ? card->number : -1;
    preg->device = dev;
    preg->f_ops = f_ops;//文件操作函数,用来操作/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
    preg->private_data = private_data;//保存pcm对象
    preg->card_ptr = card;

    minor = snd_kernel_minor(type, card, dev);
    snd_minors[minor] = preg;//使用全局变量保存pcm设备的上下文
    //创建pcm的设备节点/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc,其中major为主设备号,minor为从设备号
    preg->dev = device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name);
    return 0;
}
static int snd_kernel_minor(int type, struct snd_card *card, int dev)
{
    int minor;

    switch (type) {
    case SNDRV_DEVICE_TYPE_SEQUENCER:
    case SNDRV_DEVICE_TYPE_TIMER:
        minor = type;
        break;
    case SNDRV_DEVICE_TYPE_CONTROL:
        if (snd_BUG_ON(!card))
            return -EINVAL;
        minor = SNDRV_MINOR(card->number, type);
        break;
    case SNDRV_DEVICE_TYPE_HWDEP:
    case SNDRV_DEVICE_TYPE_RAWMIDI:
    case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
    case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
    case SNDRV_DEVICE_TYPE_COMPRESS:
        if (snd_BUG_ON(!card))
            return -EINVAL;
        minor = SNDRV_MINOR(card->number, type + dev);//生成minor,从设备号
        break;
    default:
        return -EINVAL;
    }
    if (snd_BUG_ON(minor < 0 || minor >= SNDRV_OS_MINORS))
        return -EINVAL;
    return minor;
}
#define SNDRV_MINOR(card, dev)      (((card) << 5) | (dev))
Android N:/dev/snd # ls -l
total 0
crw-rw---- 1 system audio  14,  12 2017-08-29 09:59 adsp
crw-rw---- 1 system audio  14,   4 2017-08-29 09:59 audio
crw-rw---- 1 system audio 116,  40 2017-08-29 09:59 comprC0D23
crw-rw---- 1 system audio 116,   2 2017-08-29 09:59 controlC0
crw-rw---- 1 system audio  14,   3 2017-08-29 09:59 dsp
crw-rw---- 1 system audio  14,   0 2017-08-29 09:59 mixer
crw-rw---- 1 system audio 116,   3 2017-08-29 09:59 pcmC0D0p
crw-rw---- 1 system audio 116,  19 2017-08-29 09:59 pcmC0D10p
crw-rw---- 1 system audio 116,  20 2017-08-29 09:59 pcmC0D11p
crw-rw---- 1 system audio 116,  21 2017-08-29 09:59 pcmC0D12c
crw-rw---- 1 system audio 116,  22 2017-08-29 09:59 pcmC0D13c
crw-rw---- 1 system audio 116,  23 2017-08-29 09:59 pcmC0D14p
crw-rw---- 1 system audio 116,  24 2017-08-29 09:59 pcmC0D15c
crw-rw---- 1 system audio 116,  25 2017-08-29 09:59 pcmC0D16c
crw-rw---- 1 system audio 116,  27 2017-08-29 09:59 pcmC0D17c
crw-rw---- 1 system audio 116,  26 2017-08-29 09:59 pcmC0D17p
crw-rw---- 1 system audio 116,  28 2017-08-29 09:59 pcmC0D18p
crw-rw---- 1 system audio 116,  29 2017-08-29 09:59 pcmC0D19p
crw-rw---- 1 system audio 116,   4 2017-08-29 09:59 pcmC0D1c
crw-rw---- 1 system audio 116,  30 2017-08-29 09:59 pcmC0D20p
crw-rw---- 1 system audio 116,  31 2017-08-29 09:59 pcmC0D21p
crw-rw---- 1 system audio 116,  34 2017-08-29 09:59 pcmC0D22c
crw-rw---- 1 system audio 116,  35 2017-08-29 09:59 pcmC0D24p
crw-rw---- 1 system audio 116,  37 2017-08-29 09:59 pcmC0D25c
crw-rw---- 1 system audio 116,  36 2017-08-29 09:59 pcmC0D25p
crw-rw---- 1 system audio 116,  39 2017-08-29 09:59 pcmC0D26c
crw-rw---- 1 system audio 116,  38 2017-08-29 09:59 pcmC0D26p
crw-rw---- 1 system audio 116,   6 2017-08-29 09:59 pcmC0D2c
crw-rw---- 1 system audio 116,   5 2017-08-29 09:59 pcmC0D2p
crw-rw---- 1 system audio 116,   8 2017-08-29 09:59 pcmC0D3c
crw-rw---- 1 system audio 116,   7 2017-08-29 09:59 pcmC0D3p
crw-rw---- 1 system audio 116,  10 2017-08-29 09:59 pcmC0D4c
crw-rw---- 1 system audio 116,   9 2017-08-29 09:59 pcmC0D4p
crw-rw---- 1 system audio 116,  12 2017-08-29 09:59 pcmC0D5c
crw-rw---- 1 system audio 116,  11 2017-08-29 09:59 pcmC0D5p
crw-rw---- 1 system audio 116,  14 2017-08-29 09:59 pcmC0D6c
crw-rw---- 1 system audio 116,  13 2017-08-29 09:59 pcmC0D6p
crw-rw---- 1 system audio 116,  16 2017-08-29 09:59 pcmC0D7c
crw-rw---- 1 system audio 116,  15 2017-08-29 09:59 pcmC0D7p
crw-rw---- 1 system audio 116,  17 2017-08-29 09:59 pcmC0D8p
crw-rw---- 1 system audio 116,  18 2017-08-29 09:59 pcmC0D9c
crw-rw---- 1 system audio 116,   1 2017-08-29 09:59 seq
crw-rw---- 1 system audio  14,   1 2017-08-29 09:59 sequencer
crw-rw---- 1 system audio  14,   8 2017-08-29 09:59 sequencer2
crw-rw---- 1 system audio 116,  33 2017-08-29 09:59 timer

写的一个字符设备驱动为例,在驱动初始化的代码里调用class_create为该设备创建一个class,
再为每个设备调用 device_create创建对应的设备,大致用法如下:
struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);
这样的module被加载时,就会自动在/dev下创建my_device设备文件。

//第一个参数指定类的所有者是哪个模块,第二个参数指定类名。 
struct class *class_create(struct module *owner, const char *name)
{
     struct class *cls;
     int retval;
     cls = kzalloc(sizeof(*cls), GFP_KERNEL);
     if (!cls) {
          retval = -ENOMEM;
          goto error;
     }
     cls->name = name;
     cls->owner = owner;
     cls->class_release = class_create_release;
     retval = class_register(cls);
     if (retval)
          goto error;
     return cls;
error:
     kfree(cls);
     return ERR_PTR(retval);
}
//第一个参数指定所要创建的设备所从属的类,
//第二个参数是这个设备的父设备,如果没有就指定为NULL,
//第三个参数是设备号,
//第四个参数是设备名称,
//第五个参数是从设备号。 
struct device *device_create(struct class *class, struct device *parent,
                        dev_t devt, const char *fmt, ...)
{
     va_list vargs;
     struct device *dev;
     va_start(vargs, fmt);
     dev = device_create_vargs(class, parent, devt, NULL, fmt, vargs);
     va_end(vargs);
     return dev;
}

以创建pcm设备节点为例:

//注册主设备alsa,设置声卡的回调函数
static int __init alsa_sound_init(void)
{
    snd_major = major;
    snd_ecards_limit = cards_limit;
    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;
}
//创建类名为sound的sound_class
static int __init init_soundcore(void)
{
    int rc;
    rc = init_oss_soundcore();
    sound_class = class_create(THIS_MODULE, "sound");
    sound_class->devnode = sound_devnode;
    return 0;
}
//创建pcm的设备节点/dev/snd/pcmCxxDxxp和/dev/snd/pcmCxxDxxc
int 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;
    preg = kmalloc(sizeof *preg, GFP_KERNEL);

    minor = snd_kernel_minor(type, card, dev);
    snd_minors[minor] = preg;//使用全局变量保存pcm设备的上下文
    preg->dev = device_create(sound_class, device, MKDEV(major, minor), private_data, "%s", name);
    return 0;
}
Android N:/sys/class/sound # ls -l
total 0
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 adsp -> ../../devices/platform/soc-audio/sound/card0/adsp
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 audio -> ../../devices/platform/soc-audio/sound/card0/audio
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 card0 -> ../../devices/platform/soc-audio/sound/card0
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 comprC0D23 -> ../../devices/platform/soc-audio/sound/card0/comprC0D23
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 controlC0 -> ../../devices/platform/soc-audio/sound/card0/controlC0
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 dsp -> ../../devices/platform/soc-audio/sound/card0/dsp
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 mixer -> ../../devices/platform/soc-audio/sound/card0/mixer
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D0p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D0p
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D10p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D10p
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D11p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D11p
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D12c -> ../../devices/platform/soc-audio/sound/card0/pcmC0D12c
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D13c -> ../../devices/platform/soc-audio/sound/card0/pcmC0D13c
lrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D14p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D14p

参考文档
http://www.cnblogs.com/chen-farsight/p/6177870.html

你可能感兴趣的:(ALSA,android,linux,kernel)