本文分析的是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;
}