alsa驱动分析(1)
0. ALSA驱动分析
a) 重要数据结构
i. snd_minors 维护了所有声音主设备的次设备信息,次设备号是下标
1. 信息包括类型,文件操作,私有数据等
b) 重要概念
i. alsa逻辑设备包括:controlCxx,pcmCxDxp,pcmCxDxc,timer,seq
1. controlCxx用于直接读写codec寄存器,打开关闭开关,调节滑块如音量等
2. pcmCxDxp用于播放,就是playback,关键接口是write和ioctl
3. pcmCxDxc 用于录制,就是capture,关键接口是read和ioctl
4. timer和seq作用不明显
ii. alsa框架基于字符设备,上边提到的都是alsa逻辑设备,也就是说是同一个主设备下的次设备,共享同一个驱动入口
1. alsa_sound_init(Sound.c)注册了主设备号为major(CONFIG_SND_MAJOR)的字符设备文件,文件操作是snd_fops,snd_open接口比较重要,snd_open接口通过文件节点inode得到了次设备号,通过snd_minors数组得到对应的声音逻辑设备的文件操作,调用对应的open接口,并调用fops_put替换成了对应的逻辑设备的文件操作(snd_minors里维护).
iii. Alsa声音设备驱动probe流程
1. 参考atmel_abdac_probe (abdac.c)接口流程
2. snd_card_create – 创建声卡
a) 接口里,会自动调用snd_ctl_create创建control逻辑设备
3. snd_ctl_create 创建control逻辑设备
a) 调用snd_device_new,传递snd_device_ops ops,这个ops很关键
i. snd_device_new接口把control逻辑设备放在了card->devices里
b) ops中的snd_ctl_dev_register接口,实际会被下边的snd_card_register接口调用到,snd_ctl_dev_register接口调用snd_register_device,传递snd_ctl_f_ops,这个ops就是实际使用到的control设备的文件操作.
4. snd_pcm_new创建pcm逻辑设备
a) _snd_pcm_new创建
i. 调用snd_device_new,传递snd_device_ops ops,类似control设备,
ii. ops里的snd_pcm_dev_register接口,会被snd_card_register调用,调用snd_register_device_for_dev传递snd_pcm_f_ops,这个ops就是实际pcm逻辑设备使用到的文件操作,这个接口里还调用snd_pcm_timer_init创建了timer设备
iii. 调用snd_pcm_new_stream两次,分别建立了playback和capture连个substream.
iv. 注意,每个pcm逻辑设备包括了两个substream,PLAYBACK和CAPTURE,snd_pcm_f_ops有对应的两类ops.
5. snd_pcm_set_ops接口用来设备pcm设备,流的操作,流的操作会被pcm逻辑设备的文件操作所回调.
6. snd_card_register注册声卡
a) 调用device_create创建设备
b) 调用snd_device_register_all注册声卡所有的逻辑设备,调用了之前注册的card->devices里的所有逻辑设备的dev->ops->dev_register接口.
c) 至此,逻辑设备节点就建立了.
iv. 逻辑设备打开
1. 上边分析,所有音频设备都是声卡的次设备,主设备相同,共享snd_open接口,snd_open接口里会调用对应逻辑设备的open,并替换文件操作为逻辑设备的文件操作.
2. Control设备
a) snd_ctl_f_ops中的snd_ctl_open在control逻辑设备打开时本snd_open调用.
b) snd_ctl_open
i. snd_minors中存储的control逻辑设备的私有数据是snd_card *card
ii. 创建一个snd_ctl_file *ctl,同时设备card数据
iii. snd_ctl_file *ctl作为这个file 的private_data,以备后用
3. Pcm设备
a) snd_pcm_f_ops中的snd_pcm_playback_open和snd_pcm_capture_open分别对应pcmXXXp和pcmXXXc逻辑设备的打开.
b) snd_pcm_playback_open
i. 调用snd_lookup_minor_data拿到snd_minors保存的私有数据,是snd_pcm *pcm
ii. 接着调用snd_pcm_open
1. snd_pcm_open调用snd_pcm_open_file,snd_pcm_open_file调用snd_pcm_open_substream打开substream,substream被填充赋值.
2. 之后snd_pcm_open_file用substream填充了snd_pcm_file *pcm_file,作为file的file->private_data.
c) snd_pcm_capture_open
i. 与snd_pcm_playback_open几乎相同.
v. Pcm逻辑设备文件操作分析
1. snd_pcm_f_ops结构包括了playback和capture两个流,对应pcmXXXp和pcmXXXc两个逻辑设备的文件操作.
2. pcm操作过程中,pcm逻辑设备的文件操作不停的被调用,alsa实现了pcm逻辑设备的操作,而alsa允许真正的设备相关的操作是留给上层驱动的接口,也就是substream的ops,所有的substream ops都会在适当的时机被调用.Asoc是alsa驱动的有一层封装,Asoc正是封装了substream的接口以此来提供Asoc逻辑层的machine/platform/codec/dai的逻辑操作.
3. Playback
i. .open = snd_pcm_playback_open,
1. snd_lookup_minor_data查找逻辑设备数据
2. snd_pcm_open打开pcm播放流
a) snd_pcm_open_file
i. snd_pcm_open_substream创建substream
a) snd_pcm_attach_substream
b) snd_pcm_hw_constraints_init 初始化常量
c) snd_pcm_hw_constraints_complete初始常量
ii. file->private_data = pcm_file;设置file的私有数据
iii. 设置了substream的runtime数据
3. 打开完成.
ii. .write = snd_pcm_write,
1. 取出file私有数据pcm_file
2. 取出pcm_file的substream
3. 取出substream的runtime
4. 调用snd_pcm_lib_write写
a) 调用snd_pcm_lib_writ传递snd_pcm_lib_write_transfer接口
i. snd_pcm_lib_write_transfer接口被调用传递数据.
ii. 写到了runtime->dma_area区域
iii. 判断是prepared状态,并且snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold,调用snd_pcm_start接口
a) 实际会调用到snd_pcm_action_start里的接口substream->ops->trigger最后后被调用到.
b) 这样就开始了dma传送数据的过程.
iii. .aio_write = snd_pcm_aio_write,
1. TODO
iv. .release = snd_pcm_release,
1. TODO
v. .llseek = no_llseek,
1. TODO
vi. .poll = snd_pcm_playback_poll,
1. TODO
vii. .unlocked_ioctl = snd_pcm_playback_ioctl,
1. snd_pcm_playback_ioctl1
a) 判断命令动作类型,包括很多
i. SNDRV_PCM_IOCTL_WRITEI_FRAMES 写数据
ii. SNDRV_PCM_IOCTL_HW_PARAMS 设置参数
iii. SNDRV_PCM_IOCTL_PREPARE 准备流
iv. SNDRV_PCM_IOCTL_START 开始流
viii. .compat_ioctl = snd_pcm_ioctl_compat,
1. TODO
ix. .mmap = snd_pcm_mmap,
1. TODO
x. .fasync = snd_pcm_fasync,
1. TODO
xi. .get_unmapped_area = snd_pcm_get_unmapped_area,
1. TODO
4. Capture
i. .open = snd_pcm_capture_open,
1. 类似snd_pcm_playback_open
ii. .read = snd_pcm_read,
1. 和snd_pcm_write的流程是相反的
iii. .aio_read = snd_pcm_aio_read,
1. TODO
iv. .release = snd_pcm_release,
1. TODO
v. .llseek = no_llseek,
1. TODO
vi. .poll = snd_pcm_capture_poll,
1. TODO
vii. .unlocked_ioctl = snd_pcm_capture_ioctl,
1. 类似snd_pcm_playback_ioctl
viii. .compat_ioctl = snd_pcm_ioctl_compat,
1. TODO
ix. .mmap = snd_pcm_mmap,
1. TODO
x. .fasync = snd_pcm_fasync,
1. TODO
xi. .get_unmapped_area = snd_pcm_get_unmapped_area,
1. TODO
vi. Control逻辑设备文件操作分析
1. snd_control_f_ops结构包括了control逻辑设备的文件操作
2. 类似pcm设备,alsa实现了control逻辑设备的文件操作,alsa留给上层驱动的接口是kcontrol,kcontrol的info,put,get接口被调用.Asoc对control设备的封装体现在dapm控件以及提供了一些方便实用的宏.
3. control的控件由驱动实现者类添加,实现.
4. 如下
a) .open = snd_ctl_open,
i. TODO
b) .read = snd_ctl_read,
i. TODO
c) .release = snd_ctl_release,
i. TODO
d) .llseek = no_llseek,
i. TODO
e) .poll = snd_ctl_poll,
i. TODO
f) .unlocked_ioctl = snd_ctl_ioctl,
i. 根据不同的动作,不同的操作,动作由alsa-lib调用
1. SNDRV_CTL_IOCTL_ELEM_LIST获取所有控件列表
2. SNDRV_CTL_IOCTL_ELEM_INFO获取指定控件信息
3. SNDRV_CTL_IOCTL_ELEM_READ读操作
a) snd_ctl_elem_read_user
i. snd_ctl_elem_read
A. snd_ctl_find_id获取kctl
B. kctl->get接口被调用
4. SNDRV_CTL_IOCTL_ELEM_WRITE
a) 类似SNDRV_CTL_IOCTL_ELEM_READ,最终kctl->put被调用
g) .compat_ioctl = snd_ctl_ioctl_compat,
h) .fasync = snd_ctl_fasync,