kcontrol的创建和注册和调用流程
步骤1:创建过程
通过如下等宏来初始化一个 snd_kcontrol_new 结构的实例SOC_DOUBLE_R_TLV/SOC_ENUM/SOC_SINGLE/SOC_SINGLE_TLV......步骤2:注册过程
调用 snd_soc_add_codec_controls,该函数首先通过 snd_soc_cnew 函数将这些来自snd_kcontrol_new的成员组织到新分配的snd_kcontrol结构体成员中,然后调用 snd_ctl_add 函数,将这些音频控件添加到声卡对象(struct snd_card)的控件列表中(card->controls),同时为这个kcontrol分配一个唯一的id号。
步骤3:kcontrol的调用流程
kcontrol的操作是通过向控制设备节点:/dev/controlC0,进行ioctl来实现的。
kcontrol的读操作:case SNDRV_CTL_IOCTL_ELEM_READ:
snd_ctl_elem_read_user(card, argp);
|->snd_ctl_elem_read
|->snd_ctl_find_id
|->snd_ctl_get_ioff(kctl, &control->id);
|->snd_ctl_build_ioff(&control->id, kctl, index_offset);
|->result = kctl->get(kctl, control);
//如果是SOC_SINGLE宏定义的控件则是: snd_soc_get_volsw
kcontrol的写操作:case SNDRV_CTL_IOCTL_ELEM_WRITE:
|->snd_ctl_elem_write_user(ctl, argp);
|->control = memdup_user(_control, sizeof(*control));//拷贝应用空间的参数:snd_ctl_elem_value到内核空间
|->result = snd_ctl_elem_write(card, file, control);
|->snd_ctl_get_ioff(kctl, &control->id);//一个kcotrol占据多个numid,所以需要确认具体一个numid,
//在这个中间的偏移,根据这个偏移index值,就可以确定存储
//snd_ctl_elem_value.value.value[index]的下标
|->kctl = snd_ctl_find_id(card, &control->id);//根据num id号或控件的name在声卡对象的控件列表中,找到对应的控件。
|->snd_ctl_build_ioff(&control->id, kctl, index_offset);//更新index和numid为下面的put操作准备
|->result = kctl->put(kctl, control);
//如果是SOC_SINGLE宏定义的控件put函数指针则是: snd_soc_put_volsw
|->copy_to_user(_control, control, sizeof(*control))//将结果拷贝到用户空间
在用户空间跟内核空间进行ioctl交互时,涉及到两个很重要的数据结构:struct snd_ctl_elem_id 和 struct snd_ctl_elem_value:
以上结构体中各成员含义如下:
struct snd_ctl_elem_id {
unsigned int numid; /* numeric identifier, zero = invalid */
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
unsigned char name[44]; /* ASCII name of item */
unsigned int index; /* index of item */
};
unsigned int numid:是在控件注册时(snd_soc_add_codec_controls)分配的唯一标识号
snd_ctl_elem_iface_t iface:标识该控件的类型,如是mix,pcm等
unsigned char name:控件的名字,应用可以使用该字段来选定某个特定的控件
unsigned int index:在控件包含多个numid时,用这个字段来定位他在这多个numid中的偏移量,从而确定它在snd_ctl_elem_value值中的具体位置。
struct snd_ctl_elem_value {
struct snd_ctl_elem_id id; /* W: element ID */
unsigned int indirect: 1; /* W: indirect access - obsoleted */
union {
union {
long value[128];
long *value_ptr; /* obsoleted */
} integer;
union {
long long value[64];
long long *value_ptr; /* obsoleted */
} integer64;
union {
unsigned int item[128];
unsigned int *item_ptr; /* obsoleted */
} enumerated;
union {
unsigned char data[512];
unsigned char *data_ptr; /* obsoleted */
} bytes;
struct snd_aes_iec958 iec958;
} value; /* RO */
struct timespec tstamp;
unsigned char reserved[128-sizeof(struct timespec)];
};
以上结构体主要用来存储用户想要读取或是写入控件的值,该结构体是一个联合体(union),size of是512个字节,这512个字节,根据控件的不同,可以存储128个int,或是64 long long,或是512 byte的字节。譬如是做long value[128]使用时,为什么需要一个数组呢?这个主要是用于一个kcontrol包含多个numid的情况;这样这个value[index](0<=index<512)就是可以用来存储kcontrol.info.id.index对应的numid的物理控件的值,注意多个物理控件使用一个kcontrol来表示,但他们的值是对应数组的不同下标(index)的,而kcontrol.info.count则记录了该kcontrol包含了多少个物理控件。
kcontrol的控制台操作命令及方法
使用命令tinymix,操作方法有:
列出系统所有的kcontrol #tinymix
获取特定id或名字对应的kcontrol的当前值 #tinymix kctrl
设定特定id或名字对应的kcontrol到指定值 #tinymix kctrl value
dapm widget的创建和注册
snd_soc_dapm_new_controls
a: alloc memory for widget
b: set callback funciton: power_check
c: add it to card.list
snd_soc_dapm_new_widgets详细分析,见 蓝牙语音功能的实现
route/path的创建和注册
snd_soc_dapm_add_routes
详细分析,见 蓝牙语音功能的实现
kcontrol,dapm widget,path,route之间的关系
由于kcontrol,widget,path等的应用空间的操作都是通过他们的字符串名字进行的,所以需要特别注意他们的命名规则。如mix widget(snd_soc_dapm_mixer_named_ctl除外)所包含的path的long_name名字,是由w->name+w->kcontrol_news[i].name名字组和而成(参见:snd_soc_dapm_new_widgets函数)。
route是用来描述codec系统的路径信息的。他包含source,sink端,及用于控制source到sink这条path的kcontrol。具体数据结构描述如下:
/*
* DAPM audio route definition.
*
* Defines an audio route originating at source via control and finishing
* at sink.
*/
struct snd_soc_dapm_route {
const char *sink;
const char *control;
const char *source;
/* Note: currently only supported for links where source is a supply */
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink);
};
codec驱动中的,一个很重要的任务,就是定义出codec支持的所有可能的输入,输出path路径数组。在系统初始化的时候,将它注册到系统中去,这个过程请参见蓝牙语音功能的实现
path是dapm系统根据注册到系统的route信息自动生成的,path的详细生成过程,请参考上面的snd_soc_dapm_add_routes数,path相当于电路中的一根跳线,它把一个widget的输出端和另一个widget的输入端连接在一起,path用snd_soc_dapm_path结构来描述:
/* dapm audio path between two widgets */
struct snd_soc_dapm_path {
const char *name;
const char *long_name;
/* source (input) and sink (output) widgets */
struct snd_soc_dapm_widget *source;
struct snd_soc_dapm_widget *sink;
struct snd_kcontrol *kcontrol;
/* status */
u32 connect:1; /* source and sink widgets are connected */
u32 walked:1; /* path has been walked */
u32 weak:1; /* path ignored for power management */
int (*connected)(struct snd_soc_dapm_widget *source,
struct snd_soc_dapm_widget *sink);
struct list_head list_source;
struct list_head list_sink;
struct list_head list;
};
当widget之间发生连接关系时,snd_soc_dapm_path作为连接者,它的source字段会指向该连接的起始端widget,而它的sink字段指该连接的目的端widget,还记得前面snd_soc_dapm_widget结构中的两个链表头字段:sources和sinks么?widget的输入端和输出端可能连接着多个path,所有输入端的snd_soc_dapm_path结构通过list_sink字段挂在widget的souces链表中,同样,所有输出端的snd_soc_dapm_path结构通过list_source字段挂在widget的sinks链表中。这里可能大家会被搞得晕呼呼的,一会source,一会sink,不要紧,只要记住,连接的路径是这样的:
源端widget的输出----->>>path的输入=====path的输出>>>--->目的widget输入
widget,path之间链表的链接关系,通过如下图可以清楚表明他们之间的链接关系:
注意上图,由于path B-C和path A-C都是属于widget C的输入路径,所以都是通过path的list_sink链表链接到widget C的sources链表中。另外widget中的sources,sinks链表为什么是复数名字呢?因为一个widget可能会存在多个输入和输出路径,而且一个widget的所有输入路径都是挂在widget的sources链表上,而widget的所有输出路径都是挂在widget的sinks链表上的。
另外还有个很重要需要注意的地方就是(从 snd_soc_dapm_new_widgets函数可以发现这一点 ):path->kcontrol即path路径对应的控制(kcontrol)只可能是path->list_sink所指向的widget中的w->kcontrols[i]中的某一个kcontrol。如果path对应的kcontrol的value是on的,则path就是connected。
is_connected_input_ep/is_connected_output_ep/dapm_power_widgets函数
is_connected_input_ep/is_connected_output_ep函数是判断widget是否有到输出端点或是输入端点的完整路径,如果有,则添加到power up list列表,如果没有,则添加到power down list列表 关于以上两个函数的详细说明,请参考AZure的 DAPM之五:dapm机制深入分析(上) 的blog,图 文并茂的说明了is_connected_input_ep函数的递归执行过程。
对于那些对递归调用还不是很清楚的同学,建议装过vc编译器,然后单步调试递归程序,你就可以看到pc指针是如何在堆栈上移动的,这个是最形象而又最经济有效地方法来深度观察递归的执行过程的方法。
在这里只关注哪些变量会影响到一个完整路径的判断,由于这些变量的变化,会影响到一个widget是否处在一条完整路径上,根据这一条(即该widget是否在一条完整的路径上)会触发dapm自动对系统的所有相关的widget产生连锁反应的上电或下电操作,从而改变音频的路由,所以这些变量的研究有很实际的意义。相关变量罗列如下:
widget->active,widget->connected,widget->power,path->connect
widget->active只有dai widget才会有的,并且只有在音频流打开或关闭的时候(stream start/stop)才会被修改到。流程如下:
soc_pcm_prepare
|->snd_soc_dapm_stream_event(rtd, substream->stream, codec_dai,SND_SOC_DAPM_STREAM_START);
|->soc_dapm_stream_event
|->w = dai->playback_widget;
|->w = dai->capture_widget;
|->w = dapm_mark_dirty(w, "stream event");
|->w->active = 1;//w->active = 0;//修改widget->active的地方
|->dapm_power_widgets(dapm, event);
widget->connected在widget被创建的时候就会被初始化为1.
path->connect
snd_soc_dapm_new_control
|->w->connected = 1;
//snd_soc_dapm_force_enable_pin/snd_soc_dapm_enable_pin/snd_soc_dapm_disable_pin都有修改
w->connected
连接状态是根据path->connect来判断的,改变mixer/mux部件的path->connect有两个地方:(1).在path建立时,会检查mixer/mux的连接状态,具体见dapm_set_path_status:判定这个path对应的source是否被选中了,是则置p->connect = 1,否则置p->connect = 0。
(2).上层通过alsa_amixer或其他来操作dapm kcontrols时,会更新widget路径的连接状态path->connect,简略流程如下( 摘自AZure)
snd_soc_dapm_add_routes
|->snd_soc_dapm_add_route
|->dapm_connect_mux
|->dapm_set_path_status
//更新path->connect
amixer-应用层
|->snd_ctl_ioctl
|->snd_ctl_elem_write_user
|->snd_ctl_elem_wirte
|->kctl->put
|=snd_soc_dapm_put_volsw
|->snd_soc_dapm_mixer_update_power//找到使用这个kcontrol的path,根据该kcontrol的值来更新path的connect状态
//更新path->connect状态
音频路径如何切换(to be continued)
path是如何被调用和设置的。