学习目的:
- 分析Linux中OSS声卡驱动框架
1、OSS声卡驱动框架
Linux下的声卡驱动架构主要分为OSS架构和ALSA架构,OSS全称是Open Sound System,叫做开放式音频系统,ALSA全称是Advanced Linux Sound Architecture,叫做Linux高级音频架构。
OSS架构是基于文件系统的访问方式,对声音的操作完成可以像对普通文件那样执行open、read等操作。
OSS标准中有2个最基本的音频设备:mixer(混音器)和DSP(数字信号处理器),mixer的作用将多个信号组合或者叠加到一起、可用来调整音量大小和选择音源。对于不同声卡来说,其混音器的作用可能各不相同。OSS驱动中,/dev/mixer设备文件是应用程序对mixer进行操作的软件接口。DSP也称编解码器,实现录音和放音的操作,其对应的设备文件是/dev/dsp和/dev/sound/dsp,向该设备写数据即意味着激活声卡上的D/A 转换器进行放音,而向该设备读数据则意味着激活声卡上的A/D 转换器进行录音。
下面从内核源码的角度来分析OSS声卡驱动框架
内核目录下sound/sound_core.c文件是OSS声卡驱动的核心层,sound_core.c文件向上提供了应用程序访问音频设备统一的入口,向下为不同音频设备提供了向上的注册接口。
sound_core.c文件中的入口函数init_soundcore
static int __init init_soundcore(void) { if (register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops)==-1) { ... } sound_class = class_create(THIS_MODULE, "sound"); ... return 0; }
init_soundcore中向内核注册了一个主设备号为14的字符设备,可以看出OSS架构的声卡驱动是将硬件作为字符设备来访问的。当应用程序访问音频设备节点时,会调用到soundcore_fops结构体中成员函数。
不过,这里注册的字符设备对应的file_operations类型结构体soundcore_fops中,只实现了.open函数
static const struct file_operations soundcore_fops= { /* We must have an owner or the module locking fails */ .owner = THIS_MODULE, .open = soundcore_open, }; int soundcore_open(struct inode *inode, struct file *file) { int chain; int unit = iminor(inode); struct sound_unit *s; const struct file_operations *new_fops = NULL; chain=unit&0x0F; ... s = __look_for_unit(chain, unit); if (s) new_fops = fops_get(s->unit_fops); ... if (new_fops) { ... const struct file_operations *old_fops = file->f_op; file->f_op = new_fops; if(file->f_op->open) err = file->f_op->open(inode,file); ... } ... }
打开音频设备时,soundcore_open函数会根据打开设备节点的次设备号找到一个sound_unit(__look_for_unit函数根据次设备号查找sound_unit是在以chains[]数组某项为头部的链表中查找的,关于这个指针数组的相关的内容后面会详细解释),并将找到的sound_unit中的file_operations结构体作为访问该设备的操作函数(file->f_op = new_fops)。
由此可以看出,注册的字符设备中的open函数仅起到了中转作用,open设备时会找到打开的设备对应的真实的操作函数,用真实的设备节点操作函数操作设备。这样做,起到了为访问不同的音频设备提供统一的入口函数的目的。
sound_unit结构体是OSS架构音频驱动的一个核心数据结构,用来描述一个物理声卡设备的,结构体成员信息如下:
struct sound_unit { int unit_minor; const struct file_operations *unit_fops; struct sound_unit *next; char name[32]; };
unit_minor代表这个设备的对应的次设备号;unit_fops中包含一系列操作该设备的函数;next代表sound_unit结构体是一个链表
sound_core.c中定义了一个长度为16的sound_unit类型的指针数组chains[],该数组每一项都表示一个链表头部。如chains[0]表示mixers设备挂载的链表头部,chins[3]表示DSP设备挂载链表头部。每注册一种类型的音频设备,描述设备的sound_unit结构体会插入到对应类型头部的链表中。同时,打开音频设备时,soundcore_open函数根据打开设备节点次设备号找到对应的sound_unit也时基于chains[]数组。
次设备号的低4位代表该设备节点是属于哪种类型音频驱动,如次设备号为0x00、0x10、0x20、0x30代表该设备节点属于Mixers,次设备号0x03、0x13、0x23、0x33代表设备节点属于DSP。根据次设备号查找对应sound_unit时,直接取次设备号低4位作为chains数组索引,可以直接得到描述设备的sound_unit结构体存放的链表头部,这样大大的节省了查找链表的效率。
chains[n]为头部的链表中的节点是在音频驱动程序向上注册音频设备时插入的,不同类型的音频设备的注册函数也是sound_core.c文件中提供的,这些注册函数在设备驱动程序中调用。所谓的注册也就是根据传入信息构造一个sound_unit,并将构造好的sound_unit插入到指定的链表中。
以内核中注册一个DSP音频设备为例,进行注册过程分析
int register_sound_dsp(const struct file_operations *fops, int dev) { return sound_insert_unit(&chains[3], fops, dev, 3, 131, "dsp", S_IWUSR | S_IRUSR, NULL); }
使用register_sound_dsp函数注册一个DSP音频设备,该函数有两个参数,一个参数是file_operations结构体指针fops,另一个是dev。fops指针传入的是该设备的操作函数,dev代表分配的DSP的编号(一般情况下传入-1,请求分配下一个空的DSP单元)。
register_sound_dsp函数中直接调用sound_insert_unit函数,传入参数list = &chains[3]表示DSP音频设备是插入到chains[3]为头部的链表中,参数low = 3,top = 131表示dsp音频设备使用的编号范围(0x03、0x13、0x23...0x83),注册时查找可用编号范围内是否有可插入点,如果有就插入到该点链表中,并将该点作为次设备号。
static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, const char *name, umode_t mode, struct device *dev) { struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL); int r;
...
r = __sound_insert_unit(s, list, fops, index, low, top); ... device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), s->name+6); ... }
sound_insert_unit函数动态分配了一个sound_unit结构体,然后调用了__sound_insert_unit函数。__sound_insert_unit函数根据传入的参数,在list为头部链表中找到合适位置,初始化传入的sound_unit结构体,将初始化好的sound_unit结构体挂入到链表中找到的位置中。最后调用device_create在sound_class类下创建了一个设备,device_create创建的设备信息会被udev机制使用在/dev目录下自动创建设备节点,这样用户程序可以使用/dev目录下创建的设备节点对音频设备进行访问。
由以上分析,可以总结出OSS架构的音频驱动框架如下图所示:
2、小结
OSS架构声卡驱动,将声卡设备当做字符设备来访问,注册的声卡设备的主设备号为14。OSS驱动核心层提供了各种类型声卡设备的注册函数,成功注册的声卡设备会存放到chains[n]的为头部的链表中,并会为该设备在/dev目录下创建设备节点。当应用程序通过设备节点访问该设备,在open该设备时,根据次设备号信息从chains[minor&0xf]为头部的链表中找到打开设备对应的file_operations结构体,使用该file_operations结构体中成员函数访问该设备。
因此,我们编写基于OSS架构声卡驱动程序时,只需完成一些硬件相关的操作,填充适用于声卡设备操作的file_operations结构体,然后调用OSS驱动核心层提供的设备注册函数register_sound_xxx向内核注册一个声卡设备。