字符设备驱动中,ioctl是一个很常见的IO设备操作函数,可以自定义cmd命令字并实现对应的设备IO控制。
音频设备的控制有所不同:驱动层大部分控制操作定义各种snd_kcontrol_new,然后注册到SNDRV_DEV_CONTROL模块中(sound\core\control.c),详见snd_kcontrol探究;而上层调用alsa-lib的snd_ctl_open/snd_mixer_open来打开底层的SNDRV_DEV_CONTROL模块,详见DAPM之二:audio paths与dapm kcontrol。这方法常见于mixer-control,如音量调整、部件开关、通路连接等等。
除此之外,alsa还是可以实现类似于ioctl的函数的,只不过它封装成一个设备模块SNDRV_DEV_HWDEP,代码sound\core\ hwdep.c。该模块实现了read/write/ioctl/llseek/poll/mmap等接口。hwdep是Hardware Dependant Interface的简称。
题外话:如果想看自己板上的alsa有什么类型的设备可以cat /proc/asound/devices,如
~ # cat /proc/asound/devices 0: [ 0] : control 4: [ 0- 0]: hardware dependent 16: [ 0- 0]: digital audio playback 24: [ 0- 0]: digital audio capture 33: : timer设备节点号minor=0是control,=4是hwdep,=16是pcm-playback,=24是pcm-capture,=33是timer。
如下简单分析ioctl:
//套接字接口函数集 static const struct file_operations snd_hwdep_f_ops = { .owner = THIS_MODULE, .llseek = snd_hwdep_llseek, .read = snd_hwdep_read, .write = snd_hwdep_write, .open = snd_hwdep_open, .release = snd_hwdep_release, .poll = snd_hwdep_poll, .unlocked_ioctl = snd_hwdep_ioctl, .compat_ioctl = snd_hwdep_ioctl_compat, .mmap = snd_hwdep_mmap, }; static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, unsigned long arg) { struct snd_hwdep *hw = file->private_data; void __user *argp = (void __user *)arg; switch (cmd) { case SNDRV_HWDEP_IOCTL_PVERSION: return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp); case SNDRV_HWDEP_IOCTL_INFO: return snd_hwdep_info(hw, argp); case SNDRV_HWDEP_IOCTL_DSP_STATUS: return snd_hwdep_dsp_status(hw, argp); case SNDRV_HWDEP_IOCTL_DSP_LOAD: return snd_hwdep_dsp_load(hw, argp); } if (hw->ops.ioctl) return hw->ops.ioctl(hw, file, cmd, arg); return -ENOTTY; }
从snd_hwdep_ioctl可以看出,系统默认只有4个cmd,功能主要是download dsp image。从return hw->ops.ioctl(hw, file, cmd, arg)语句可以看出,我们可自定义cmd和ioctl函数。
1、 首先实现需要的操作函数:
static int my_hwdep_open(struct snd_hwdep * hw, struct file *file) { printk(KERN_INFO "my_hwdep_open\n"); return 0; } static int my_hwdep_ioctl(struct snd_hwdep * hw, struct file *file, unsigned int cmd, unsigned long arg) { #define MY_SOC_IOCTL_SET_CALL_PATH _IOWR('H', 0x10, int) switch (cmd) { case MY_SOC_IOCTL_SET_CALL_PATH: //设置电话语音通路 return 0; break; //...... } err("Not supported ioctl for MY-HWDEP"); return -ENOIOCTLCMD; }
struct snd_hwdep *hwdep; if (snd_hwdep_new(codec->card, "MY-HWDEP", 0, &hwdep) < 0) { printk(KERN_ERR "create MY-HWDEP fail"); return; } sprintf(hwdep->name, "MY-HWDEP %d", 0); hwdep->iface = SNDRV_HWDEP_IFACE_WMT; hwdep->ops.open = wmt_hwdep_open; hwdep->ops.ioctl = wmt_hwdep_ioctl;这里摘录snd_hwdep_new的代码注释,让大家更明白上面的注册过程:
/** * snd_hwdep_new - create a new hwdep instance * @card: the card instance * @id: the id string * @device: the device index (zero-based) * @rhwdep: the pointer to store the new hwdep instance * * Creates a new hwdep instance with the given index on the card. * The callbacks (hwdep->ops) must be set on the returned instance * after this call manually by the caller. * * Returns zero if successful, or a negative error code on failure. */按照以上实现hwdep ioctl后,上层可以通过alsa-lib的相关接口来调用。
#include <fcntl.h> #include <sys/ioctl.h> #include <alsa/hwdep.h> #include <alsa/error.h> #include <stdio.h> #define MY_SOC_IOCTL_SET_CALL_PATH _IOWR('H', 0x10, int) int main() { const char *devicename = "hw:0,0"; snd_hwdep_t *hwdep; int err; int enable = 1; if ((err = snd_hwdep_open(&hwdep, devicename, O_RDWR)) < 0) { printf("hwdep interface open error: %s \n", snd_strerror(err)); return -1; } if ((err = snd_hwdep_ioctl(hwdep, MY_SOC_IOCTL_SET_CALL_PATH, &enable)) < 0) { printf("hwdep ioctl error: %s \n", snd_strerror(err)); } snd_hwdep_close(hwdep); return 0; }
可以看出hwdep的本意主要是用于download dsp image,但通过它也可实现类似于其他字符设备的ioctl。我说过音频大多控制是通过snd_kcontrol,但有些功能如果使用这种方式会比较繁琐且模块太过耦合。
举个例子:电话语音通路,它不同于音乐回放通路,通话时才需要打开。如果用snd_kcontrol,则上层需要调用多个control.set,并且更换CODEC芯片的话,上层也要跟着修改control name;如果使用hwdep ioctl的话,就没有这个问题,只需要保证命令字cmd一致,底层如何管理通话通路的一系列部件开关,上层都不需要关心。