Linux USB 3.0驱动分析(十)——Gadget UAC2驱动分析
本文分析的是linux-5.4.3
一.Gadget Audio设备驱动分析
drivers/usb/gadget/legacy/audio.c
因为项目的问题, 了解usb音频设备的工作原理,为啥它能让PC识别成“speak”或者“mic”,以及你能够播放录音。
主要涉及下面两个层次:
Gadget功能驱动层: 最主要的结构是struct usb_composite_driver,这个结构在这层定义,并且实现结构中的各个函数。
USB设备层: 最主要的数据结构是struct usb_composite_dev与usb_gadget_driver。前一个代表一个USB复合设备,而后一个是Gadget驱动,与UDC层交互。
USB设备层: 最主要的数据结构是struct usb_composite_dev与usb_gadget_driver。前一个代表一个USB复合设备,而后一个是Gadget驱动,与UDC层交互。
这边主要是usb结构中的设备和配置相关
首先我们来看初始化流程,主要是注册audio_driver这个复合设备驱动
static struct usb_composite_driver audio_driver = {
.name = "g_audio",
.dev = &device_desc, //这是一个设备描述符
.strings = audio_strings, //一个给定语言的字符串集合,字符串描述符
.max_speed = USB_SPEED_HIGH, //设备速度
.bind = audio_bind, //注册绑定回调函数
.unbind = audio_unbind,
};
/* 设置#define module_usb_composite_driver(__usb_composite_driver) \
module_driver(__usb_composite_driver, usb_composite_probe, \
usb_composite_unregister) */
//module_usb_composite_driver是一个宏定义用usb_composite_probe注册复合驱动程序
module_usb_composite_driver(audio_driver);
我们再来看看usb_composite_probe里面具体做了什么,主要是赋值了composite_driver_template,这个是所有复合设备共有的
static const struct usb_gadget_driver composite_driver_template = {
.bind = composite_bind,
.unbind = composite_unbind,
.setup = composite_setup,
.reset = composite_disconnect,
.disconnect = composite_disconnect,
.suspend = composite_suspend,
.resume = composite_resume,
.driver = {
.owner = THIS_MODULE,
},
};
int usb_composite_probe(struct usb_composite_driver *driver) //注册复合设备
{
struct usb_gadget_driver *gadget_driver;
if (!driver || !driver->dev || !driver->bind)
return -EINVAL;
if (!driver->name)
driver->name = "composite";
driver->gadget_driver = composite_driver_template; //赋值一个usb_gadget_driver实例,应该只有一个
gadget_driver = &driver->gadget_driver;
gadget_driver->function = (char *) driver->name;
gadget_driver->driver.name = driver->name;
gadget_driver->max_speed = driver->max_speed;
return usb_gadget_probe_driver(gadget_driver); //继续调用
}
后面继续调用usb_gadget_probe_driver,进行注册。
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
{
mutex_lock(&udc_lock);
if (driver->udc_name) { //如果有udc的名字,就直接遍历比较
list_for_each_entry(udc, &udc_list, list) {
ret = strcmp(driver->udc_name, dev_name(&udc->dev));
if (!ret)
break;
}
if (ret)
ret = -ENODEV;
else if (udc->driver) //已经有匹配的driver,正在忙
ret = -EBUSY;
else
goto found;
} else {
list_for_each_entry(udc, &udc_list, list) { //找到第一个udc
/* For now we take the first one */
if (!udc->driver) //没有绑定驱动,就说明找到了
goto found;
}
}
if (!driver->match_existing_only) { //加入到等待链表,udc注册会匹配这个等待的驱动
list_add_tail(&driver->pending, &gadget_driver_pending_list);
pr_info("udc-core: couldn't find an available UDC - added [%s] to list of pending drivers\n",
driver->function);
ret = 0;
}
mutex_unlock(&udc_lock);
return ret;
found:
ret = udc_bind_to_driver(udc, driver); //如果找到了,进行绑定
mutex_unlock(&udc_lock);
return ret;
}
从上面usb_gadget_probe_driver()函数的代码片段可知,它在遍历udc_list链表,找到一个udc(usb设备控制器驱动描述结构体)没有对应驱动的实例。
static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{
dev_dbg(&udc->dev, "registering UDC driver [%s]\n",
driver->function);
udc->driver = driver; //这里是usb_gadget_driver实例composite_driver_template
udc->dev.driver = &driver->driver; //这里是usb_composite_driver实例audio_driver
udc->gadget->dev.driver = &driver->driver;
usb_gadget_udc_set_speed(udc, driver->max_speed);
ret = driver->bind(udc->gadget, driver); //这里是调用composite_driver_template的composite_bind
if (ret)
goto err1;
ret = usb_gadget_udc_start(udc); //这里会调用udc->gadget->ops->udc_start(udc->gadget, udc->driver),上一篇有分析
if (ret) {
driver->unbind(udc->gadget);
goto err1;
}
//软件连接UDC。 usb_udc_connect_control -> usb_gadget_connect -> (gadget->ops->pullup)这里是调用dwc3_gadget_pullup
usb_udc_connect_control(udc); //主要是是能D+或者D-的上拉,让host可以检测到
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); //通过发送uevent通知用户空间
return 0;
}
主要是给udc相关的驱动数据结构赋值。既然有udc队列的遍历取出,必然有udc队列的添加,每注册一个udc驱动(usb_add_gadget_udc())就会把udc结构体添加到udc队列的尾部,上一篇udc相关分析有用到。
我们再来分析composite_driver_template的composite_bind函数,这里对复合设备做一些前期准备,然后调用复合设备的bind函数
static int composite_bind(struct usb_gadget *gadget,
struct usb_gadget_driver *gdriver)
{
cdev = kzalloc(sizeof *cdev, GFP_KERNEL); //分配usb_composite_dev结构体,表示一个复合usb gadget
if (!cdev)
return status;
status = composite_dev_prepare(composite, cdev); //对复合设备的前期准备
if (status)
goto fail;
/* composite gadget needs to assign strings for whole device (like
* serial number), register function drivers, potentially update
* power state and consumption, etc
*/
status = composite->bind(cdev); //调用复合设备的bind函数,这里是audio_bind
if (status < 0)
goto fail;
if (cdev->use_os_string) { //用来获取系统的描述符
status = composite_os_desc_req_prepare(cdev, gadget->ep0);
if (status)
goto fail;
}
update_unchanged_dev_desc(&cdev->desc, composite->dev); //更新设备描述符
/* has userspace failed to provide a serial number? */
if (composite->needs_serial && !cdev->desc.iSerialNumber)
WARNING(cdev, "userspace failed to provide iSerialNumber\n");
INFO(cdev, "%s ready\n", composite->name);
return 0;
}
对复合设备的前期准备composite_dev_prepare
主要是初始化端点0, ep0是设备控制传输用到的端点,尤其在设备枚举、属性配置上起关键作用,所以我们要首先初始化
int composite_dev_prepare(struct usb_composite_driver *composite,
struct usb_composite_dev *cdev)
{
/* preallocate control response and buffer */
//调用ep->ops->alloc_request(ep, gfp_flags),也就是dwc3_gadget_ep_alloc_request,分配与此端点一起使用的请求对象
cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
if (!cdev->req)
return -ENOMEM;
cdev->req->buf = kmalloc(USB_COMP_EP0_BUFSIZ, GFP_KERNEL); //分配内存
if (!cdev->req->buf)
goto fail;
ret = device_create_file(&gadget->dev, &dev_attr_suspended);
if (ret)
goto fail_dev;
cdev->req->complete = composite_setup_complete; //完成函数,在端点处理完一个usb request的时候调用
cdev->req->context = cdev;
gadget->ep0->driver_data = cdev;
cdev->driver = composite;
/*
* As per USB compliance update, a device that is actively drawing
* more than 100mA from USB must report itself as bus-powered in
* the GetStatus(DEVICE) call.
*/
if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW)
usb_gadget_set_selfpowered(gadget);
/* interface and string IDs start at zero via kzalloc.
* we force endpoints to start unassigned; few controller
* drivers will zero ep->driver_data.
*/
usb_ep_autoconfig_reset(gadget);
return 0;
}
复合设备的bind函数,这里是audio_bind
这里设备描述符相关在这里被初始化
static int audio_bind(struct usb_composite_dev *cdev)
{
#ifndef CONFIG_GADGET_UAC1
fi_uac2 = usb_get_function_instance("uac2"); //默认是UAC2,搜索功能function实例,在f_hid中初始化,初始化接口位于usb/gadget/functions.c
if (IS_ERR(fi_uac2))
return PTR_ERR(fi_uac2);
#else
#ifndef CONFIG_GADGET_UAC1_LEGACY
fi_uac1 = usb_get_function_instance("uac1");
#else
fi_uac1 = usb_get_function_instance("uac1_legacy");
#endif
if (IS_ERR(fi_uac1))
return PTR_ERR(fi_uac1);
#endif
status = usb_string_ids_tab(cdev, strings_dev); //批量分配未使用的字符串id
if (status < 0)
goto fail;
device_desc.iManufacturer = strings_dev[USB_GADGET_MANUFACTURER_IDX].id; //这里设备描述符相关
device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;
if (gadget_is_otg(cdev->gadget) && !otg_desc[0]) { //如果是otg
struct usb_descriptor_header *usb_desc;
usb_desc = usb_otg_descriptor_alloc(cdev->gadget); //分配otg描述符
if (!usb_desc)
goto fail;
usb_otg_descriptor_init(cdev->gadget, usb_desc); //初始化otg描述符,有三种协议:HNP,SRP,ADP
otg_desc[0] = usb_desc;
otg_desc[1] = NULL;
}
status = usb_add_config(cdev, &audio_config_driver, audio_do_config); //为复合层绑定,设备添加配置。
if (status < 0)
goto fail_otg_desc;
usb_composite_overwrite_options(cdev, &coverwrite); //重新赋值设备描述符
INFO(cdev, "%s, version: %s\n", DRIVER_DESC, DRIVER_VERSION);
return 0;
}
在复合层绑定,进行配置的初始化,配置完成之后,这个ugadget就具有UAC的功能了。
int usb_add_config(struct usb_composite_dev *cdev,
struct usb_configuration *config,
int (*bind)(struct usb_configuration *))
{
DBG(cdev, "adding config #%u '%s'/%p\n",
config->bConfigurationValue,
config->label, config);
status = usb_add_config_only(cdev, config); //把config交给cdev管理
if (status)
goto done;
status = bind(config); //用到出入的函数指针,实际执行do_config, 往config中添加function
if (status < 0) { //绑定config和function失败
while (!list_empty(&config->functions)) {
struct usb_function *f;
f = list_first_entry(&config->functions,
struct usb_function, list);
list_del(&f->list);
if (f->unbind) {
DBG(cdev, "unbind function '%s'/%p\n",
f->name, f);
f->unbind(config, f);
/* may free memory for "f" */
}
}
list_del(&config->list);
config->cdev = NULL;
} else { //绑定成功,后面都是调试打印
unsigned i;
DBG(cdev, "cfg %d/%p speeds:%s%s%s%s\n",
config->bConfigurationValue, config,
config->superspeed_plus ? " superplus" : "",
config->superspeed ? " super" : "",
config->highspeed ? " high" : "",
config->fullspeed
? (gadget_is_dualspeed(cdev->gadget)
? " full"
: " full/low")
: "");
for (i = 0; i < MAX_CONFIG_INTERFACES; i++) { //这里这是用来打印调试
struct usb_function *f = config->interface[i];
if (!f)
continue;
DBG(cdev, " interface %d = %s/%p\n",
i, f->name, f);
}
}
/* set_alt(), or next bind(), sets up ep->claimed as needed */
usb_ep_autoconfig_reset(cdev->gadget);
}
我们来看看关键的do_config函数
static int audio_do_config(struct usb_configuration *c)
{
/* FIXME alloc iConfiguration string, set it in c->strings */
if (gadget_is_otg(c->cdev->gadget)) { //如果是otg
c->descriptors = otg_desc; //就赋值otg设置
c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
}
#ifdef CONFIG_GADGET_UAC1
f_uac1 = usb_get_function(fi_uac1); //获取function, fi->fd->alloc_func(fi),也就是afunc_alloc
if (IS_ERR(f_uac1)) {
status = PTR_ERR(f_uac1);
return status;
}
status = usb_add_function(c, f_uac1); ///向config中添加function
if (status < 0) {
usb_put_function(f_uac1);
return status;
}
#else
f_uac2 = usb_get_function(fi_uac2);
if (IS_ERR(f_uac2)) {
status = PTR_ERR(f_uac2);
return status;
}
status = usb_add_function(c, f_uac2);
if (status < 0) {
usb_put_function(f_uac2);
return status;
}
#endif
return 0;
}
通过usb_add_dunction完成config和function的绑定
int usb_add_function(struct usb_configuration *config,
struct usb_function *function)
{
function->config = config;
list_add_tail(&function->list, &config->functions);
if (function->bind_deactivated) {
value = usb_function_deactivate(function);
if (value)
goto done;
}
/* REVISIT *require* function->bind? */
if (function->bind) {
value = function->bind(config, function); //这里调用bind,对于uac2就是afunc_bind,前面一篇有分析
if (value < 0) {
list_del(&function->list);
function->config = NULL;
}
} else
value = 0;
/* We allow configurations that don't work at both speeds.
* If we run into a lowspeed Linux system, treat it the same
* as full speed ... it's the function drivers that will need
* to avoid bulk and ISO transfers.
*/
if (!config->fullspeed && function->fs_descriptors)
config->fullspeed = true;
if (!config->highspeed && function->hs_descriptors)
config->highspeed = true;
if (!config->superspeed && function->ss_descriptors)
config->superspeed = true;
if (!config->superspeed_plus && function->ssp_descriptors)
config->superspeed_plus = true;
done:
if (value)
DBG(config->cdev, "adding '%s'/%p --> %d\n",
function->name, function, value);
return value;
}