基于Linux UAC gadget驱动实现Audio Control处理-Kernel层SET

date
Mar 24, 2022
slug
2022-03-24-linux-uac-gadget-audio-control-kernel-set
status
Published
tags
USB
UAC
type
Post
AI summary
summary
本文总结了SigmaStar提供的在基于Linux kernel的UAC gadget设备中增加audio control控制功能的完整工作流程,内核层SET部分。
基于对SigmaStar Webcam方案源代码的学习,通过三篇文章的篇幅,整体总结了SigmaStar提供的在基于Linux kernel的UAC gadget设备中增加audio control控制功能的完整工作流程:
  1. 应用层
  1. 内核层1-Audio Control Get类命令的处理流程
  1. 内核层2-Audio Control Set类命令的处理流程
 
以下为内核层SET类命令的完整总结。
 
延续前面的Get类命令的分析。下面来继续分析Audio Control Set类型命令的处理接口audio_set_intf_req:
static int audio_set_intf_req(struct usb_function *f,
                const struct usb_ctrlrequest *ctrl)
{
        struct f_uac1           *uac1 = func_to_uac1(f);
        struct usb_composite_dev *cdev = f->config->cdev;
        struct usb_request      *req = cdev->req;
        u8                      id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
        u16                     len = le16_to_cpu(ctrl->wLength);
        u16                     w_value = le16_to_cpu(ctrl->wValue);
        u8                      con_sel = (w_value >> 8) & 0xFF;
        u8                      cmd = (ctrl->bRequest & 0x0F);
        struct usb_audio_control_selector *cs;
        struct usb_audio_control *con;

        DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n",
                        ctrl->bRequest, w_value, len, id);

				//下面的这一段就是从前面初始化阶段初始化的两个列表中查找,这个get命令针对的是哪个Unit的那个command
        list_for_each_entry(cs, &uac1->cs, list) {
                if (cs->id == id) {
                        list_for_each_entry(con, &cs->control, list) {
                                if (con->type == con_sel) {
																				//注意,这里没有执行具体的命令,只是给uac1->set_con做了赋值
																				//后续在req->complete上注册的f_audio_complete回调会执行set处理逻辑
                                        uac1->set_con = con;
                                        break;
                                }
                        }
                        break;
                }
        }

        uac1->set_cmd = cmd;
        req->context = uac1;
				//给req->complete赋值一个f_audio_complete回调函数,那么当这次控制传输结束后会自动调用这个f_audio_complete
        req->complete = f_audio_complete;

        return len;
}
  • 整体的工作逻辑与Get类的处理基本类似。最大的不同就是通过轮询两个队列,找到对应的audio control以后,只是把这个audio control结构体赋值给uac1->set_con,并没有做具体的set操作。这个set操作需要在req->complete上注册的回调函数f_audio_complete中执行。
    • 放到f_audio_complete中执行的原因是,set类型的命令需要收到data stage发过来的设置参数,而audio_set_intf_req执行的时候这个data stage还没发过来。所以只能在这里先记下来,然后等整个控制传输结束,这个时候会自动回调req->complete上注册的f_audio_complete函数。因此在f_audio_complete中处理实际的set命令比较合适。
因此Set类型命令的实际处理入口函数就是f_audio_complete:
static void f_audio_complete(struct usb_ep *ep, struct usb_request *req)
{
        struct f_uac1 *uac1 = req->context;
        int status = req->status;
        u32 data = 0;

        switch (status) {
        case 0:                         /* normal completion? */
                if (uac1->set_con) {
                        memcpy(&data, req->buf, req->length);//req->buf就是data stage收到的set参数
                        uac1->set_con->set(uac1->set_con, uac1->set_cmd,
                                        le16_to_cpu(data));//实际上调用的仍然是audio control的set函数,bug这个时候可以把要set的参数传递过去了
                        uac1->set_con = NULL;//重新把uac1->set_con设置为null,等待接收下次set命令
                }

                break;
        case -ESHUTDOWN:
        default:
                break;
        }
}
  • 可以看到,上面最终执行的set功能仍然是audio control command自己的set函数。这个set函数在对应的audio control结构体的定义中已经设置好。例如可以查看前面的capture_volume_control变量定义的时候给set赋值的处理函数是generic_set_cmd。
generic_set_cmd的分析:
static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value)
{
        struct f_uac1 *uac1 = g_f_uac1;
        if (!uac1)
                return -EINVAL;

        if (uac1->ready_con)
                return -EINVAL;

        uac1->ready_con = con;
        uac1->ready_cmd = cmd;
        uac1->ready_value = value;

        con->data[cmd] = value;//给内核中维护的audio control参数赋值,这样以后的get命令返回的就是这个新设置的值
				//执行到这里,实际上参数的修改已经生效,但是需要把这个set操作通知到应用层,在应用层让这个set生效。
				//这个功能通过前面在f_audio_alloc阶段定义的schedule work来执行
        schedule_work(&uac1->cmd_work);
        return 0;
}
  • 前面的f_audio_alloc函数中定义了一个schedule work,对应的执行函数是mixer_cmd_work。因此这里的schedule_work(&uac1->cmd_work)实际上就是给kernel的调度器提交了一个执行mixer_cmd_work的执行任务。
通过调度器执行的mixer_cmd_work函数来负责audio control功能在应用层的生效处理,其执行分析如下:
static void mixer_cmd_work(struct work_struct *data)
{
        struct f_uac1 *uac1 = g_f_uac1;
        struct usb_audio_control *con = uac1->ready_con;
        int value = uac1->ready_value;

        if (!con)
                return;

        switch (con->type)
        {
                case UAC_FU_VOLUME://针对feature unit中的volume调节
                        uac1->g_audio.volume = UAC_VOLUME_ATTR_TO_DB(value);//单位转换
                        g_audio_notify(&uac1->g_audio);//调用g_audio_notify实现具体功能
                        break;
                default:
                        break;
        }

        uac1->ready_con = NULL;
}
g_audio_notify利用linux声卡驱动的ALSA架构来实现与应用层之间的通信。在u_audio.c文件中定义:
void g_audio_notify(struct g_audio *g_audio)
{
        struct snd_card *card = g_audio->uac->card;
				//这里直接找到volume control对应的snd_kcontrol结构体。
				//实际上更合理的设计是,在g_audio_notify函数上传递一个参数,使用这个参数区分:Host发送的是哪个audio control的设置命令,然后针对性的向应用层发出这个参数的修改通知
        struct snd_kcontrol *ctl = g_audio->uac->volume_ctl;
				//通过Alsa的SNDRV_CTL_EVENT_MASK_VALUE方式向应用层发出通知,这样应用层就能收到通知并实现对应的功能,不同的audio control应该找到并传递不同的ctl->id
        snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &ctl->id);
}
EXPORT_SYMBOL_GPL(g_audio_notify)
  • 需要注意,上面的g_audio_notify的实现非常简略。实际上只提供了一个volume调整上传应用层的机制,如果需要增加其他audio control的控制,就要在以上代码的基础上增加对应的处理逻辑,可以通过id把不同的命令区分开来,告诉应用层究竟需要对哪个audio control进行控制。
那么问题来了,上面的g_audio->uac->volume_ctl究竟是什么?为什么snd_ctl_notify函数可以向应用层发出volume control变化的通知?
这是因为UAC的实现借助了Linux的ALSA网卡驱动框架。ALSA网卡驱动框架提供了一个kcontrol的机制来实现在应用层和kernel层进行音频参数控制功能。
每个音频参数的设置都对应一个kcontrol结构体,也就是所谓的struct snd_kcontrol,把这个结构体定义并填充好以后,注册到内容中。应用层就可以访问到kernel中注册的所有kcontrol的列表,需要控制哪个参数,就通过对应的kcontrol控制的接口来进行控制即可。
那么对于volume control这个kcontrol而言,其定义及其在kernel中的注册流程如下:
static int snd_uac_pcm_vol_info(struct snd_kcontrol *kcontrol,
                                   struct snd_ctl_elem_info *uinfo)
{
        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
        uinfo->count = 1;
        uinfo->value.integer.min = CAPTURE_VOLUME_MIN;
        uinfo->value.integer.max = CAPTURE_VOLUME_MAX;
        uinfo->value.integer.step = CAPTURE_VOLUME_STEP;
        return 0;
}

static int snd_uac_pcm_vol_get(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
{       
        struct g_audio *g_audio= (struct g_audio *)kcontrol->private_data;
        int value = g_audio->volume;
        ucontrol->value.integer.value[0] = value;
        return 0;
}

static int snd_uac_pcm_vol_put(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
{
        int value;
        value = ucontrol->value.integer.value[0];
        return 0;
}

static struct snd_kcontrol_new snd_uac_pcm_volume = {//volume kcontrol的定义,应用层对volume的参数访问就会调用下面注册的这几个函数
        .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
        .name = "Capture Volume Control",
        .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
        .index = 0,
        .info = snd_uac_pcm_vol_info,
        .get = snd_uac_pcm_vol_get,
        .put = snd_uac_pcm_vol_put,
};

int g_audio_setup(struct g_audio *g_audio, const char *pcm_name,
                                        const char *card_name)
{
				......
				uac->volume_ctl = snd_ctl_new1(&snd_uac_pcm_volume, g_audio);
        if ((err = snd_ctl_add(card, uac->volume_ctl)) < 0)//在这里把volume control的这个snd_kcontrol_new结构体注册到kernel中
                return err;

        err = snd_card_register(card);
				......
}
  • 所以如果还要增加新的audio control的控制类型,就需要像上面的volume control一样,先定义清楚这个新的audio control对应的snd_kcontrol_new结构体,然后调用snd_ctl_add把这个kcontrol注册到内核中。这样后续就可以在g_audio_notify中通过snd_ctl_notify函数向应用层发出audio control command了。

© Pavel Han 2020 - 2024