Android音频驱动-ASOC之主&从设备号
来源:互联网 发布:英语老师教学软件 编辑:程序博客网 时间:2024/05/20 02:26
设备号是在驱动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系统中,一般字符设备和驱动之间的函数调用关系如下图所示
上图描述了用户空间应用程序通过系统调用来调用程序的过程。一般而言在驱动程序的设计中,会关系 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里维护).
<linux/kdev_t.h>//文件提供了处理设备号相关的宏:MKDEV(major, minor) // 设备的dev_tMAJOR(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 MKDEVfrom 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()是老的接口,这一步在新的接口中并不需要。
#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 <linux.fs.h>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 -ltotal 0crw-rw---- 1 system audio 14, 12 2017-08-29 09:59 adspcrw-rw---- 1 system audio 14, 4 2017-08-29 09:59 audiocrw-rw---- 1 system audio 116, 40 2017-08-29 09:59 comprC0D23crw-rw---- 1 system audio 116, 2 2017-08-29 09:59 controlC0crw-rw---- 1 system audio 14, 3 2017-08-29 09:59 dspcrw-rw---- 1 system audio 14, 0 2017-08-29 09:59 mixercrw-rw---- 1 system audio 116, 3 2017-08-29 09:59 pcmC0D0pcrw-rw---- 1 system audio 116, 19 2017-08-29 09:59 pcmC0D10pcrw-rw---- 1 system audio 116, 20 2017-08-29 09:59 pcmC0D11pcrw-rw---- 1 system audio 116, 21 2017-08-29 09:59 pcmC0D12ccrw-rw---- 1 system audio 116, 22 2017-08-29 09:59 pcmC0D13ccrw-rw---- 1 system audio 116, 23 2017-08-29 09:59 pcmC0D14pcrw-rw---- 1 system audio 116, 24 2017-08-29 09:59 pcmC0D15ccrw-rw---- 1 system audio 116, 25 2017-08-29 09:59 pcmC0D16ccrw-rw---- 1 system audio 116, 27 2017-08-29 09:59 pcmC0D17ccrw-rw---- 1 system audio 116, 26 2017-08-29 09:59 pcmC0D17pcrw-rw---- 1 system audio 116, 28 2017-08-29 09:59 pcmC0D18pcrw-rw---- 1 system audio 116, 29 2017-08-29 09:59 pcmC0D19pcrw-rw---- 1 system audio 116, 4 2017-08-29 09:59 pcmC0D1ccrw-rw---- 1 system audio 116, 30 2017-08-29 09:59 pcmC0D20pcrw-rw---- 1 system audio 116, 31 2017-08-29 09:59 pcmC0D21pcrw-rw---- 1 system audio 116, 34 2017-08-29 09:59 pcmC0D22ccrw-rw---- 1 system audio 116, 35 2017-08-29 09:59 pcmC0D24pcrw-rw---- 1 system audio 116, 37 2017-08-29 09:59 pcmC0D25ccrw-rw---- 1 system audio 116, 36 2017-08-29 09:59 pcmC0D25pcrw-rw---- 1 system audio 116, 39 2017-08-29 09:59 pcmC0D26ccrw-rw---- 1 system audio 116, 38 2017-08-29 09:59 pcmC0D26pcrw-rw---- 1 system audio 116, 6 2017-08-29 09:59 pcmC0D2ccrw-rw---- 1 system audio 116, 5 2017-08-29 09:59 pcmC0D2pcrw-rw---- 1 system audio 116, 8 2017-08-29 09:59 pcmC0D3ccrw-rw---- 1 system audio 116, 7 2017-08-29 09:59 pcmC0D3pcrw-rw---- 1 system audio 116, 10 2017-08-29 09:59 pcmC0D4ccrw-rw---- 1 system audio 116, 9 2017-08-29 09:59 pcmC0D4pcrw-rw---- 1 system audio 116, 12 2017-08-29 09:59 pcmC0D5ccrw-rw---- 1 system audio 116, 11 2017-08-29 09:59 pcmC0D5pcrw-rw---- 1 system audio 116, 14 2017-08-29 09:59 pcmC0D6ccrw-rw---- 1 system audio 116, 13 2017-08-29 09:59 pcmC0D6pcrw-rw---- 1 system audio 116, 16 2017-08-29 09:59 pcmC0D7ccrw-rw---- 1 system audio 116, 15 2017-08-29 09:59 pcmC0D7pcrw-rw---- 1 system audio 116, 17 2017-08-29 09:59 pcmC0D8pcrw-rw---- 1 system audio 116, 18 2017-08-29 09:59 pcmC0D9ccrw-rw---- 1 system audio 116, 1 2017-08-29 09:59 seqcrw-rw---- 1 system audio 14, 1 2017-08-29 09:59 sequencercrw-rw---- 1 system audio 14, 8 2017-08-29 09:59 sequencer2crw-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_classstatic 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/pcmCxxDxxcint 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 -ltotal 0lrwxrwxrwx 1 root root 0 2017-09-01 15:44 adsp -> ../../devices/platform/soc-audio/sound/card0/adsplrwxrwxrwx 1 root root 0 2017-09-01 15:44 audio -> ../../devices/platform/soc-audio/sound/card0/audiolrwxrwxrwx 1 root root 0 2017-09-01 15:44 card0 -> ../../devices/platform/soc-audio/sound/card0lrwxrwxrwx 1 root root 0 2017-09-01 15:44 comprC0D23 -> ../../devices/platform/soc-audio/sound/card0/comprC0D23lrwxrwxrwx 1 root root 0 2017-09-01 15:44 controlC0 -> ../../devices/platform/soc-audio/sound/card0/controlC0lrwxrwxrwx 1 root root 0 2017-09-01 15:44 dsp -> ../../devices/platform/soc-audio/sound/card0/dsplrwxrwxrwx 1 root root 0 2017-09-01 15:44 mixer -> ../../devices/platform/soc-audio/sound/card0/mixerlrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D0p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D0plrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D10p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D10plrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D11p -> ../../devices/platform/soc-audio/sound/card0/pcmC0D11plrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D12c -> ../../devices/platform/soc-audio/sound/card0/pcmC0D12clrwxrwxrwx 1 root root 0 2017-09-01 15:44 pcmC0D13c -> ../../devices/platform/soc-audio/sound/card0/pcmC0D13clrwxrwxrwx 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
- Android音频驱动-ASOC之主&从设备号
- Android音频驱动-ASOC之创建设备节点
- Android音频驱动-ASOC之Machine
- Android音频驱动-ASOC之Codec
- Android音频驱动-ASOC之Platform
- Android音频驱动-ASOC之CPU DAI
- Android音频驱动-ASOC之PCM Open
- Android音频驱动-ASOC之常用对象
- Android音频驱动-ASOC之PCM Prepare
- Android音频驱动-ASOC之PCM Write
- Android音频驱动-ASOC之Control Open
- Android音频驱动-ASOC之DAMP
- linux驱动:音频驱动(四)ASoc之machine设备
- linux驱动:音频驱动(六)ASoc之codec设备
- Android音频驱动-ASOC之Sound Card注册
- Android音频驱动-ASOC之PCM Device创建
- Android音频驱动-ASOC之Sound Card创建
- Android音频驱动-ASOC之PCM HW Params
- JAVA 设计模式 外观模式
- python,回文语句判断
- 字节对齐
- JAVA 设计模式 命令模式
- shellSrot
- Android音频驱动-ASOC之主&从设备号
- 应对软件后门检测的临时方法,以麦枫通达OA为例的服务器设置方法
- JAVA 设计模式 迭代器模式
- JAVA 设计模式 观察者模式
- scala只有一个参数的提取值
- JAVA 设计模式 解释器模式
- 禁用easyUI中toolbar的按钮
- APP优化之路
- Chapter 5