1. 簡介
整個 USB 系統的通訊模型如上圖所示,本文詳細解析其中 Device 各模塊的架構和原理 (圖中彩色部分)。
2. Platform Layer
最底層是 UDC (Usb Device Controller)。
2.1 Platform Device
通常情況下,在 DTS 中定義一個 UDC platform device:
usbd: usb@10200000 {
compatible = "snps,dwc2";
reg = <0x10200000 0x1000>;
interrupts =
; clocks = <&ccu CLK_USBD>, <&ccu CLK_USB_PHY0>;
clock-names = "otg";
resets = <&rst RESET_USBD>, <&rst RESET_USBPHY0>;
reset-names = "dwc2", "dwc2-ecc";
g-rx-fifo-size = <380>;
g-np-tx-fifo-size = <600>;
g-tx-fifo-size = <8 8>;
dr_mode = "peripheral";
status = "okay";
};
2.2 Platform Driver
對應的 UDC platform driver:
driversusbdwc2platform.c:
static struct platform_driver dwc2_platform_driver = {
.driver = {
.name = dwc2_driver_name,
.of_match_table = dwc2_of_match_table,
.pm = &dwc2_dev_pm_ops,
},
.probe = dwc2_driver_probe,
.remove = dwc2_driver_remove,
.shutdown = dwc2_driver_shutdown,
};
const struct of_device_id dwc2_of_match_table[] = {
...
{ .compatible = "snps,dwc2", .data = dwc2_set_usb_params },
{},
};
該驅動的主要功能是創建和注冊 Gadget Device,一個 UDC 對應一個 Gadget Device:
dwc2_driver_probe() → usb_add_gadget_udc() → usb_add_gadget_udc_release() → usb_add_gadget():
int usb_add_gadget(struct usb_gadget *gadget)
{
struct usb_udc *udc;
int ret = -ENOMEM;
/* (1.1) 分配 udc 結構 */
udc = kzalloc(sizeof(*udc), GFP_KERNEL);
if (!udc)
goto error;
/* (1.2) 初始化 udc 結構 */
device_initialize(&udc->dev);
udc->dev.release = usb_udc_release;
udc->dev.class = udc_class;
udc->dev.groups = usb_udc_attr_groups;
udc->dev.parent = gadget->dev.parent;
ret = dev_set_name(&udc->dev, "%s",
kobject_name(&gadget->dev.parent->kobj));
if (ret)
goto err_put_udc;
/* (2.1) 注冊 gadget device */
ret = device_add(&gadget->dev);
if (ret)
goto err_put_udc;
/* (2.2) 鏈接 gadget 和 udc */
udc->gadget = gadget;
gadget->udc = udc;
mutex_lock(&udc_lock);
/* (1.3) 將 udc 加入全局鏈表 */
list_add_tail(&udc->list, &udc_list);
/* (1.4) 注冊 udc device */
ret = device_add(&udc->dev);
if (ret)
goto err_unlist_udc;
usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
udc->vbus = true;
/* pick up one of pending gadget drivers */
/* (3) 嘗試 match gadget 的 device 和 driver */
ret = check_pending_gadget_drivers(udc);
if (ret)
goto err_del_udc;
mutex_unlock(&udc_lock);
}
3. UDC/Gadget Layer
Gadget Layer 層把各式各樣的 UDC 封裝成標準的 Gadget Device,提供統一的向上接口。Gadget Driver 又把各式各樣的 Function 和 Gadget Device 鏈接起來。
3.1 Gadget Bus
Gadget Layer 層沒有定義一個標準的 Bus 總線,而是自定義了兩條鏈表來分別存儲 Device 和 Driver:
它們的使用場景如下:
-
1、在 Gadget Device 創建時,首先把 Device 加入到 udc_list 鏈表,然后嘗試和 gadget_driver_pending_list 鏈表中的 Driver 進行 match():
usb_add_gadget_udc() → usb_add_gadget_udc_release() → usb_add_gadget():
int usb_add_gadget(struct usb_gadget *gadget)
{
/* (1) 將 device 加入全局鏈表 */
list_add_tail(&udc->list, &udc_list);
/* pick up one of pending gadget drivers */
/* (2) 嘗試 match gadget 的 device 和 driver */
ret = check_pending_gadget_drivers(udc);
if (ret)
goto err_del_udc;
mutex_unlock(&udc_lock);
}
↓
static int check_pending_gadget_drivers(struct usb_udc *udc)
{
struct usb_gadget_driver *driver;
int ret = 0;
/* (2.1) 遍歷 `gadget_driver_pending_list` 鏈表中的 Driver,和 Device 進行 match()
且一個 Driver 只能 match 一個 Device,Driver match 成功后會從鏈表刪除
*/
list_for_each_entry(driver, &gadget_driver_pending_list, pending)
if (!driver->udc_name || strcmp(driver->udc_name,
dev_name(&udc->dev)) == 0) {
/* (2.2) Match 成功,對 Device 和 Driver 進行 bind() */
ret = udc_bind_to_driver(udc, driver);
if (ret != -EPROBE_DEFER)
/* (2.3) Driver Match 成功后,從pending鏈表刪除 */
list_del_init(&driver->pending);
break;
}
return ret;
}
-
2、在 Gadget Driver 創建時,首先嘗試和 udc_list 鏈表中的 Device 進行 match(),match() 不成功則把 Driver 加入到 gadget_driver_pending_list 鏈表中:
gadget_dev_desc_UDC_store() → usb_gadget_probe_driver():
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
{
struct usb_udc *udc = NULL;
int ret = -ENODEV;
if (!driver || !driver->bind || !driver->setup)
return -EINVAL;
mutex_lock(&udc_lock);
/* (1.1) 如果 Driver 有 udc_name,嘗試和 udc_list 鏈表中 Device 的 Name 進行 match() */
if (driver->udc_name) {
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)
ret = -EBUSY;
else
goto found;
/* (1.2) 如果 Driver 沒有 udc_name,嘗試適配 udc_list 鏈表中第一個沒有適配的 Device */
} else {
list_for_each_entry(udc, &udc_list, list) {
/* For now we take the first one */
if (!udc->driver)
goto found;
}
}
if (!driver->match_existing_only) {
/* (2) 如果沒有 match() 成功,則把 Driver 加入到 pending 鏈表 */
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 ",
driver->function);
ret = 0;
}
mutex_unlock(&udc_lock);
if (ret)
pr_warn("udc-core: couldn't find an available UDC or it's busy ");
return ret;
found:
/* (3) 如果 Match 成功,對 Device 和 Driver 進行 bind() */
ret = udc_bind_to_driver(udc, driver);
mutex_unlock(&udc_lock);
return ret;
}
-
3、在 Device 和 Driver Match 成功時的 bind() 動作:
static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{
int ret;
dev_dbg(&udc->dev, "registering UDC driver [%s] ",
driver->function);
/* (1) 數據成員的賦值 */
udc->driver = driver;
udc->dev.driver = &driver->driver;
udc->gadget->dev.driver = &driver->driver;
usb_gadget_udc_set_speed(udc, driver->max_speed);
/* (2) 調用 Gadget Driver 的 bind() 函數 */
ret = driver->bind(udc->gadget, driver);
if (ret)
goto err1;
/* (3) 調用 Gadget Device 的 start() 函數
udc->gadget->ops->udc_start(udc->gadget, udc->driver);
*/
ret = usb_gadget_udc_start(udc);
if (ret) {
driver->unbind(udc->gadget);
goto err1;
}
/* (4) 調用 Gadget Device 的 pullup() 函數
gadget->ops->pullup(gadget, 1/0);
*/
usb_udc_connect_control(udc);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
return 0;
}
注意:這里和一般的 Device 和 Driver 的適配規則有些不一樣。一般的規則是一個 Dirver 可以適配多個 Device,而一個 Device 只能適配一個 Driver。而這里的規則是一個 Gadget Device 只能適配一個 Gadget Driver,而一個 Gadget Driver 只能適配一個 Gadget Device。Gadget Device 代表的是一個 UDC,而 Gadget Driver 代表的是一個 Composite Device。
3.2 Gadget Device
上一節說過 Gadget Device 由 UDC Driver 創建。
dwc2_driver_probe()→usb_add_gadget_udc()→usb_add_gadget_udc_release()→usb_add_gadget()
Gadget Device 的主要作用是提供了 Endpoint 資源,供 Function Layer 使用標準的 Gadget API 來進行訪問。
3.2.1 Endpoint Alloc
UDC Driver 在調用 usb_add_gadget_udc() 注冊 Gadget Device 之前,初始化了 Gadget 的 Endpoint 資源鏈表:
dwc2_driver_probe() → dwc2_gadget_init():
int dwc2_gadget_init(struct dwc2_hsotg *hsotg)
{
/* (1) 初始化 Gadget Device 的 Endpoint 資源鏈表為空 */
INIT_LIST_HEAD(&hsotg->gadget.ep_list);
hsotg->gadget.ep0 = &hsotg->eps_out[0]->ep;
/* initialise the endpoints now the core has been initialised */
/* (2) 初始化 UDC 擁有的 Endpoint,加入到 Gadget Device 的 Endpoint 資源鏈表中 */
for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) {
if (hsotg->eps_in[epnum])
dwc2_hsotg_initep(hsotg, hsotg->eps_in[epnum],
epnum, 1);
if (hsotg->eps_out[epnum])
dwc2_hsotg_initep(hsotg, hsotg->eps_out[epnum],
epnum, 0);
}
}
↓
static void dwc2_hsotg_initep(struct dwc2_hsotg *hsotg,
struct dwc2_hsotg_ep *hs_ep,
int epnum,
bool dir_in)
{
INIT_LIST_HEAD(&hs_ep->queue);
INIT_LIST_HEAD(&hs_ep->ep.ep_list);
/* add to the list of endpoints known by the gadget driver */
/* (2.1) UDC 中除了 endpoint0 以外,其他的 endpoint 都加入到Gadget Device 的 Endpoint 資源鏈表 `gadget.ep_list` 中
endpoint0 的操作由 UDC 驅動自己來處理
*/
if (epnum)
list_add_tail(&hs_ep->ep.ep_list, &hsotg->gadget.ep_list);
/* (2.2) 初始化 endpoint 的結構體成員 */
hs_ep->parent = hsotg;
hs_ep->ep.name = hs_ep->name;
if (hsotg->params.speed == DWC2_SPEED_PARAM_LOW)
usb_ep_set_maxpacket_limit(&hs_ep->ep, 8);
else
usb_ep_set_maxpacket_limit(&hs_ep->ep,
epnum ? 1024 : EP0_MPS_LIMIT);
/* (2.3) endpoint 最重要的結構體成員,endpoint 操作函數集
endpoint 的相關操作最后調用到這些函數上
*/
hs_ep->ep.ops = &dwc2_hsotg_ep_ops;
if (epnum == 0) {
hs_ep->ep.caps.type_control = true;
} else {
if (hsotg->params.speed != DWC2_SPEED_PARAM_LOW) {
hs_ep->ep.caps.type_iso = true;
hs_ep->ep.caps.type_bulk = true;
}
hs_ep->ep.caps.type_int = true;
}
if (dir_in)
hs_ep->ep.caps.dir_in = true;
else
hs_ep->ep.caps.dir_out = true;
}
Gadget Device 準備好了 Endpoint 資源鏈表以后,通過 usb_add_gadget_udc() 注冊。這樣就可以 Function Layer 就可以通過調用 Gadget Api 來動態分配 Endpoint 了。例如:
static int
acm_bind(struct usb_configuration *c, struct usb_function *f)
{
/* allocate instance-specific endpoints */
/* (1) 從 Gadget Device 中分配一個 in endpoint */
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
if (!ep)
goto fail;
acm->port.in = ep;
/* (2) 從 Gadget Device 中分配一個 out endpoint */
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
if (!ep)
goto fail;
acm->port.out = ep;
/* (3) 從 Gadget Device 中分配一個 notify endpoint */
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
if (!ep)
goto fail;
acm->notify = ep;
}
其中通過 usb_ep_autoconfig() 函數從 Gadget Device 的 Endpoint 資源鏈表中分配空閑的 endpoint:
driversusbgadgetfunctionf_acm.c:
usb_ep_autoconfig() → usb_ep_autoconfig_ss():
struct usb_ep *usb_ep_autoconfig_ss(
struct usb_gadget *gadget,
struct usb_endpoint_descriptor *desc,
struct usb_ss_ep_comp_descriptor *ep_comp
)
{
struct usb_ep *ep;
if (gadget->ops->match_ep) {
ep = gadget->ops->match_ep(gadget, desc, ep_comp);
if (ep)
goto found_ep;
}
/* Second, look at endpoints until an unclaimed one looks usable */
/* (1) 從 Gadget Device 的 Endpoint 資源鏈表中查找一個空閑的(ep->claimed為空) 且符合要求的 endpoint */
list_for_each_entry (ep, &gadget->ep_list, ep_list) {
if (usb_gadget_ep_match_desc(gadget, ep, desc, ep_comp))
goto found_ep;
}
/* Fail */
return NULL;
found_ep:
...
ep->address = desc->bEndpointAddress;
ep->desc = NULL;
ep->comp_desc = NULL;
/* (2) 設置 endpoint 為已分配 */
ep->claimed = true;
return ep;
}
3.2.2 EndPoint Access
Gadget Device 不僅僅為 Gadget Api 提供了分配 endpoint 的支持,還支持對 endpoint 收發數據的底層支持。在上一節的 endpoint 初始化時,就已經設置 endpoint 的操作函數集 dwc2_hsotg_ep_ops:
dwc2_driver_probe() → dwc2_gadget_init() → dwc2_hsotg_initep():
static void dwc2_hsotg_initep(struct dwc2_hsotg *hsotg,
struct dwc2_hsotg_ep *hs_ep,
int epnum,
bool dir_in)
{
/* (2.3) endpoint 最重要的結構體成員,endpoint 操作函數集
endpoint 的相關操作最后調用到這些函數上
*/
hs_ep->ep.ops = &dwc2_hsotg_ep_ops;
}
↓
static const struct usb_ep_ops dwc2_hsotg_ep_ops = {
.enable = dwc2_hsotg_ep_enable,
.disable = dwc2_hsotg_ep_disable_lock,
.alloc_request = dwc2_hsotg_ep_alloc_request,
.free_request = dwc2_hsotg_ep_free_request,
.queue = dwc2_hsotg_ep_queue_lock,
.dequeue = dwc2_hsotg_ep_dequeue,
.set_halt = dwc2_hsotg_ep_sethalt_lock,
/* note, don't believe we have any call for the fifo routines */
};
Gadget Api 提供了以下接口來操作 endpoint 讀寫數據。在 Host 側對 endpoint 進行一次操作請求的數據結構是 struct urb,而在 Device 側也有類似的數據結構稱為 struct usb_request,對 endpoint 的數據讀寫就是圍繞 struct usb_request 展開的:
driversusbgadgetfunctionf_acm.c:
static int acm_cdc_notify(struct f_acm *acm, u8 type, u16 value,
void *data, unsigned length)
{
struct usb_ep *ep = acm->notify;
struct usb_request *req;
struct usb_cdc_notification *notify;
const unsigned len = sizeof(*notify) + length;
void *buf;
int status;
/* (1) 初始化 `struct usb_request` 數據結構 */
req = acm->notify_req;
acm->notify_req = NULL;
acm->pending = false;
req->length = len;
notify = req->buf;
buf = notify + 1;
notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
| USB_RECIP_INTERFACE;
notify->bNotificationType = type;
notify->wValue = cpu_to_le16(value);
notify->wIndex = cpu_to_le16(acm->ctrl_id);
notify->wLength = cpu_to_le16(length);
memcpy(buf, data, length);
/* ep_queue() can complete immediately if it fills the fifo... */
spin_unlock(&acm->lock);
/* (2) 提交 `usb_request` 請求到 endpoint 處理隊列中 */
status = usb_ep_queue(ep, req, GFP_ATOMIC);
spin_lock(&acm->lock);
}
其中 usb_ep_queue() 函數就會調用 endpoint 的操作函數集 dwc2_hsotg_ep_ops 中的 .queue 函數:
int usb_ep_queue(struct usb_ep *ep,
struct usb_request *req, gfp_t gfp_flags)
{
int ret = 0;
if (WARN_ON_ONCE(!ep->enabled && ep->address)) {
ret = -ESHUTDOWN;
goto out;
}
/* (1) 實際調用 dwc2_hsotg_ep_queue_lock() */
ret = ep->ops->queue(ep, req, gfp_flags);
out:
trace_usb_ep_queue(ep, req, ret);
return ret;
}
3.2.3 UDC Control
Gadget Device 還提供了 UDC 層級的一些操作函數,UDC Driver 在調用 usb_add_gadget_udc() 注冊 Gadget Device 之前,初始化了 Gadget 的 操作函數集:
dwc2_driver_probe() → dwc2_gadget_init():
int dwc2_gadget_init(struct dwc2_hsotg *hsotg)
{
hsotg->gadget.max_speed = USB_SPEED_HIGH;
/* (1) 初始化 Gadget Device 的操作函數集 */
hsotg->gadget.ops = &dwc2_hsotg_gadget_ops;
hsotg->gadget.name = dev_name(dev);
hsotg->remote_wakeup_allowed = 0;
}
↓
static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = {
.get_frame = dwc2_hsotg_gadget_getframe,
.set_selfpowered = dwc2_hsotg_set_selfpowered,
.udc_start = dwc2_hsotg_udc_start,
.udc_stop = dwc2_hsotg_udc_stop,
.pullup = dwc2_hsotg_pullup,
.vbus_session = dwc2_hsotg_vbus_session,
.vbus_draw = dwc2_hsotg_vbus_draw,
};
Gadget Api 提供了一些內部函數來調用:
static inline int usb_gadget_udc_start(struct usb_udc *udc)
{
return udc->gadget->ops->udc_start(udc->gadget, udc->driver);
}
static inline void usb_gadget_udc_stop(struct usb_udc *udc)
{
udc->gadget->ops->udc_stop(udc->gadget);
}
static inline void usb_gadget_udc_set_speed(struct usb_udc *udc,
enum usb_device_speed speed)
{
if (udc->gadget->ops->udc_set_speed) {
enum usb_device_speed s;
s = min(speed, udc->gadget->max_speed);
udc->gadget->ops->udc_set_speed(udc->gadget, s);
}
}
int usb_gadget_connect(struct usb_gadget *gadget)
{
int ret = 0;
if (!gadget->ops->pullup) {
ret = -EOPNOTSUPP;
goto out;
}
if (gadget->deactivated) {
/*
* If gadget is deactivated we only save new state.
* Gadget will be connected automatically after activation.
*/
gadget->connected = true;
goto out;
}
ret = gadget->ops->pullup(gadget, 1);
if (!ret)
gadget->connected = 1;
out:
trace_usb_gadget_connect(gadget, ret);
return ret;
}
int usb_gadget_disconnect(struct usb_gadget *gadget)
{
int ret = 0;
if (!gadget->ops->pullup) {
ret = -EOPNOTSUPP;
goto out;
}
if (!gadget->connected)
goto out;
if (gadget->deactivated) {
/*
* If gadget is deactivated we only save new state.
* Gadget will stay disconnected after activation.
*/
gadget->connected = false;
goto out;
}
ret = gadget->ops->pullup(gadget, 0);
if (!ret) {
gadget->connected = 0;
gadget->udc->driver->disconnect(gadget);
}
out:
trace_usb_gadget_disconnect(gadget, ret);
return ret;
}
3.3 Gadget Driver (Configfs)
Gadget Device 支撐了核心 Gadget Api 的實現,而 Function Layer 又需要使用這些 Api。怎么樣將兩者適配起來?Gadget Driver 就是用來完成這項工作的。
目前存在兩種風格的 Gadget Driver,其中包括:
-
Legacy。這是早期風格的 Gadget Driver,只能通過靜態編譯的方式指定使用哪些 Function。
-
Configfs。這是目前流行的 Gadget Driver,可以通過 configfs 文件系統,不用重新編譯內核,動態的配置需要使用的 Function。
我們首先介紹 configfs 風格的 Gadget Driver。
3.3.1 configfs 使用
首先從使用上體驗一下 configfs 的便捷。例如創建一個 ACM Function:
// 1、掛載configfs文件系統。
mount -t configfs none /sys/kernel/config
cd /sys/kernel/config/usb_gadget
// 2、創建g1目錄,實例化一個新的gadget模板 (composite device)。
mkdir g1
cd g1
// 3.1、定義USB產品的VID和PID。
echo "0x1d6b" > idVendor
echo "0x0104" > idProduct
// 3.2、實例化英語語言ID。(0x409是USB language ID 美國英語,不是任意的,可以在USBIF網站上下載文檔查詢。)
mkdir strings/0x409
ls strings/0x409/
// 3.3、將開發商、產品和序列號字符串寫入內核。
echo "0123456789" > strings/0x409/serialnumber
echo "AAAA Inc." > strings/0x409/manufacturer
echo "Bar Gadget" > strings/0x409/product
// 4、創建 `Function` 功能實例,需要注意的是,一個功能如果有多個實例的話,擴展名必須用數字編號。
mkdir functions/acm.GS0
// 5.1、創建一個USB `Configuration` 配置實例:
mkdir configs/c.1
ls configs/c.1
// 5.2、定義配置描述符使用的字符串
mkdir configs/c.1/strings/0x409
ls configs/c.1/strings/0x409/
echo "ACM" > configs/c.1/strings/0x409/configuration
// 6、捆綁功能 `Function` 實例到 `Configuration` 配置c.1
ln -s functions/acm.GS0 configs/c.1
// 7.1、查找本機可獲得的UDC實例 (即 gadget device)
# ls /sys/class/udc/
10200000.usb
// 7.2、將gadget驅動注冊到UDC上,插上USB線到電腦上,電腦就會枚舉USB設備。
echo"10200000.usb">UDC
3.3.2 configfs 層次結構
configfs 并不是 gadget 專用的,它是一個通用文件系統,方便用戶通過文件系統創建文件夾、文件的方式來創建內核對象。
configfs 是很好理解的,struct config_group 相當于一個文件夾,struct config_item_type 是這個文件夾的屬性集。其中 config_item_type->ct_group_ops->make_group()/drop_item() 定義了創建/銷毀下一層子文件夾的方法,config_item_type->ct_attrs 定義了子文件和相關操作函數。
我們通過解析 driversusbgadgetconfigfs.c 文件來深入理解 configfs 的使用方法:
1、首先創建首層文件夾 /sys/kernel/config/usb_gadget:
static struct configfs_group_operations gadgets_ops = {
.make_group = &gadgets_make,
.drop_item = &gadgets_drop,
};
static const struct config_item_type gadgets_type = {
.ct_group_ops = &gadgets_ops,
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem gadget_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "usb_gadget",
.ci_type = &gadgets_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(gadget_subsys.su_mutex),
};
static int __init gadget_cfs_init(void)
{
int ret;
config_group_init(&gadget_subsys.su_group);
ret = configfs_register_subsystem(&gadget_subsys);
return ret;
}
module_init(gadget_cfs_init);
2、創建 /sys/kernel/config/usb_gadget/g1 ,相當于創建一個全新的 composite device。會調用頂層 struct config_group 的 config_item_type->ct_group_ops->make_group() 函數,即 gadgets_make():
static struct config_group *gadgets_make(
struct config_group *group,
const char *name)
{
struct gadget_info *gi;
gi = kzalloc(sizeof(*gi), GFP_KERNEL);
if (!gi)
return ERR_PTR(-ENOMEM);
/* (1) 創建頂層文件夾 `/sys/kernel/config/usb_gadget/g1` 對應的 `struct config_group` 結構
`/sys/kernel/config/usb_gadget/g1` 下對應不少子文件,在 gadget_root_type.ct_attrs 中定義,即 `gadget_root_attrs`:
static struct configfs_attribute *gadget_root_attrs[] = {
&gadget_dev_desc_attr_bDeviceClass,
&gadget_dev_desc_attr_bDeviceSubClass,
&gadget_dev_desc_attr_bDeviceProtocol,
&gadget_dev_desc_attr_bMaxPacketSize0,
&gadget_dev_desc_attr_idVendor,
&gadget_dev_desc_attr_idProduct,
&gadget_dev_desc_attr_bcdDevice,
&gadget_dev_desc_attr_bcdUSB,
&gadget_dev_desc_attr_UDC,
&gadget_dev_desc_attr_max_speed,
NULL,
};
*/
config_group_init_type_name(&gi->group, name, &gadget_root_type);
/* (2) 創建子文件夾 `/sys/kernel/config/usb_gadget/g1/functions`
`functions_type` 中定義了進一步創建子文件夾的操作函數
*/
config_group_init_type_name(&gi->functions_group, "functions",
&functions_type);
configfs_add_default_group(&gi->functions_group, &gi->group);
/* (3) 創建子文件夾 `/sys/kernel/config/usb_gadget/g1/configs`
`config_desc_type` 中定義了進一步創建子文件夾的操作函數
*/
config_group_init_type_name(&gi->configs_group, "configs",
&config_desc_type);
configfs_add_default_group(&gi->configs_group, &gi->group);
/* (4) 創建子文件夾 `/sys/kernel/config/usb_gadget/g1/strings`
`gadget_strings_strings_type` 中定義了進一步創建子文件夾的操作函數
*/
config_group_init_type_name(&gi->strings_group, "strings",
&gadget_strings_strings_type);
configfs_add_default_group(&gi->strings_group, &gi->group);
/* (5) 創建子文件夾 `/sys/kernel/config/usb_gadget/g1/os_desc`
`os_desc_type` 中定義了進一步創建哪些子文件
*/
config_group_init_type_name(&gi->os_desc_group, "os_desc",
&os_desc_type);
configfs_add_default_group(&gi->os_desc_group, &gi->group);
/* (6) `configfs.c` 的目的很明確就是創建一個 `composite device`
由用戶添加和配置這個 `device` 當中的多個 `interface` 即 `function`
*/
gi->composite.bind = configfs_do_nothing;
gi->composite.unbind = configfs_do_nothing;
gi->composite.suspend = NULL;
gi->composite.resume = NULL;
gi->composite.max_speed = USB_SPEED_SUPER_PLUS;
spin_lock_init(&gi->spinlock);
mutex_init(&gi->lock);
INIT_LIST_HEAD(&gi->string_list);
INIT_LIST_HEAD(&gi->available_func);
composite_init_dev(&gi->cdev);
gi->cdev.desc.bLength = USB_DT_DEVICE_SIZE;
gi->cdev.desc.bDescriptorType = USB_DT_DEVICE;
gi->cdev.desc.bcdDevice = cpu_to_le16(get_default_bcdDevice());
gi->composite.gadget_driver = configfs_driver_template;
gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);
gi->composite.name = gi->composite.gadget_driver.function;
if (!gi->composite.gadget_driver.function)
goto err;
return &gi->group;
err:
kfree(gi);
return ERR_PTR(-ENOMEM);
}
3、創建 /sys/kernel/config/usb_gadget/g1/functions/acm.GS0。會調用 functions_type 中定義的 function_make() 函數:
static struct config_group *function_make(
struct config_group *group,
const char *name)
{
struct gadget_info *gi;
struct usb_function_instance *fi;
char buf[MAX_NAME_LEN];
char *func_name;
char *instance_name;
int ret;
ret = snprintf(buf, MAX_NAME_LEN, "%s", name);
if (ret >= MAX_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG);
/* (1) 把 `acm.GS0` 分割成兩部分:
func_name = `acm`
instance_name = `GS0`
*/
func_name = buf;
instance_name = strchr(func_name, '.');
if (!instance_name) {
pr_err("Unable to locate . in FUNC.INSTANCE ");
return ERR_PTR(-EINVAL);
}
*instance_name = '?';
instance_name++;
/* (2) 根據 func_name 在全局鏈表中查找對應 function
usb_get_function_instance() → try_get_usb_function_instance() → fd->alloc_inst() → acm_alloc_instance():
并調用 usb_function_driver->alloc_inst() 分配一個 function 實例
*/
fi = usb_get_function_instance(func_name);
if (IS_ERR(fi))
return ERR_CAST(fi);
/* (3) 初始化 function 實例 */
ret = config_item_set_name(&fi->group.cg_item, "%s", name);
if (ret) {
usb_put_function_instance(fi);
return ERR_PTR(ret);
}
if (fi->set_inst_name) {
ret = fi->set_inst_name(fi, instance_name);
if (ret) {
usb_put_function_instance(fi);
return ERR_PTR(ret);
}
}
gi = container_of(group, struct gadget_info, functions_group);
mutex_lock(&gi->lock);
/* (4) 將 function 實例掛載到 composite device 的 function 鏈表當中去 */
list_add_tail(&fi->cfs_list, &gi->available_func);
mutex_unlock(&gi->lock);
return &fi->group;
}
在 ln -s functions/acm.GS0 configs/c.1 時給 function 實例安裝實際的函數:
config_usb_cfg_link() → usb_get_function() → fi->fd->alloc_func() → acm_alloc_func():
static struct usb_function *acm_alloc_func(struct usb_function_instance *fi)
{
struct f_serial_opts *opts;
struct f_acm *acm;
/* (2.1) 對應分配一個 func 實例 */
acm = kzalloc(sizeof(*acm), GFP_KERNEL);
if (!acm)
return ERR_PTR(-ENOMEM);
spin_lock_init(&acm->lock);
/* (2.2) 初始化 func 實例的成員函數 */
acm->port.connect = acm_connect;
acm->port.disconnect = acm_disconnect;
acm->port.send_break = acm_send_break;
acm->port.func.name = "acm";
acm->port.func.strings = acm_strings;
/* descriptors are per-instance copies */
acm->port.func.bind = acm_bind;
acm->port.func.set_alt = acm_set_alt;
acm->port.func.setup = acm_setup;
acm->port.func.disable = acm_disable;
opts = container_of(fi, struct f_serial_opts, func_inst);
acm->port_num = opts->port_num;
acm->port.func.unbind = acm_unbind;
acm->port.func.free_func = acm_free_func;
acm->port.func.resume = acm_resume;
acm->port.func.suspend = acm_suspend;
return &acm->port.func;
}
3.3.3 gadget driver
Configfs 風格的 gadget driver 的定義:
driversusbgadgetconfigfs.c:
static const struct usb_gadget_driver configfs_driver_template = {
.bind = configfs_composite_bind,
.unbind = configfs_composite_unbind,
.setup = configfs_composite_setup,
.reset = configfs_composite_disconnect,
.disconnect = configfs_composite_disconnect,
.suspend = configfs_composite_suspend,
.resume = configfs_composite_resume,
.max_speed = USB_SPEED_SUPER_PLUS,
.driver = {
.owner = THIS_MODULE,
.name = "configfs-gadget",
},
.match_existing_only = 1,
};
在調用 echo "/sys/class/udc/10200000.usb" > /sys/kernel/config/usb_gadget/g1/UDC 時,將上述 gadget driver 進行注冊,和 UDC 已經注冊好的 gadget device 進行動態適配。
gadget_dev_desc_UDC_store()→usb_gadget_probe_driver(&gi->composite.gadget_driver)→udc_bind_to_driver()
本質上是 使用 configfs 創建好的 composite device 和 gadget device 進行綁定:
gadget_dev_desc_UDC_store() → usb_gadget_probe_driver() → udc_bind_to_driver() → configfs_composite_bind() → usb_add_function() → function->bind() → acm_bind():
static int
acm_bind(struct usb_configuration *c, struct usb_function *f)
{
/* (1) 這樣 function 實例和 gadget device 進行了綁定 */
struct usb_composite_dev *cdev = c->cdev;
struct f_acm *acm = func_to_acm(f);
/* allocate instance-specific endpoints */
/* (2) function 實例可以從 gadget device 中分配得到 endpoint */
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
if (!ep)
goto fail;
acm->port.in = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
if (!ep)
goto fail;
acm->port.out = ep;
ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
if (!ep)
goto fail;
acm->notify = ep;
}
但是 bind() 以后 function 實例只是分配了 endpoint 資源還沒有被啟動,因為 Device 是被動狀態,只有連上 Host,被 Host Set Configuration 操作以后。某一組 Configuration 被配置,相應的 Function 實例 才會被啟用:
dwc2_hsotg_complete_setup() → dwc2_hsotg_process_control() → hsotg->driver->setup() → configfs_composite_setup() → composite_setup() → set_config() → f->set_alt() → acm_set_alt():
static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
{
struct f_acm *acm = func_to_acm(f);
struct usb_composite_dev *cdev = f->config->cdev;
/* we know alt == 0, so this is an activation or a reset */
/* (1) 使能 endpoint,并且提交 `struct usb_request` 請求 */
if (intf == acm->ctrl_id) {
if (acm->notify->enabled) {
dev_vdbg(&cdev->gadget->dev,
"reset acm control interface %d ", intf);
usb_ep_disable(acm->notify);
}
if (!acm->notify->desc)
if (config_ep_by_speed(cdev->gadget, f, acm->notify))
return -EINVAL;
usb_ep_enable(acm->notify);
} else if (intf == acm->data_id) {
if (acm->notify->enabled) {
dev_dbg(&cdev->gadget->dev,
"reset acm ttyGS%d ", acm->port_num);
gserial_disconnect(&acm->port);
}
if (!acm->port.in->desc || !acm->port.out->desc) {
dev_dbg(&cdev->gadget->dev,
"activate acm ttyGS%d ", acm->port_num);
if (config_ep_by_speed(cdev->gadget, f,
acm->port.in) ||
config_ep_by_speed(cdev->gadget, f,
acm->port.out)) {
acm->port.in->desc = NULL;
acm->port.out->desc = NULL;
return -EINVAL;
}
}
gserial_connect(&acm->port, acm->port_num);
} else
return -EINVAL;
return 0;
}
3.4 Gadget Driver (Legacy)
對于 Legacy Gadget Driver 驅動來說,相當于 Configfs Gadget Driver 的一個簡化版。
3.4.1 gadget driver
Legacy 風格的 gadget driver 的定義:
driversusbgadgetcomposite.c:
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,
},
};
驅動提供了一個注冊函數 usb_composite_probe(),以供 composite device 來進行調用:
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";
/* (1) 把傳遞過來的 `usb_composite_driver` 包裝成 `usb_gadget_driver` */
driver->gadget_driver = composite_driver_template;
gadget_driver = &driver->gadget_driver;
gadget_driver->function = (char *) driver->name;
gadget_driver->driver.name = driver->name;
gadget_driver->max_speed = driver->max_speed;
/* (2) 注冊 gadget driver,讓其和 gadget device 適配 */
return usb_gadget_probe_driver(gadget_driver);
}
EXPORT_SYMBOL_GPL(usb_composite_probe);
3.4.2 composite device
沒有了 configfs 由用戶來創建 composite device,只能使用一個文件來創建 composite device 定義其使用哪些 function 和一系列配置。例如:
driversusbgadgetlegacyacm_ms.c
static struct usb_composite_driver acm_ms_driver = {
.name = "g_acm_ms",
.dev = &device_desc,
.max_speed = USB_SPEED_SUPER,
.strings = dev_strings,
.bind = acm_ms_bind,
.unbind = acm_ms_unbind,
};
/* (1) 驅動一開始就調用 usb_composite_probe() 來注冊 acm_ms_driver
因為 acm_ms_driver 沒有指定 udc_name 所以只能適配第一個 udc
*/
module_usb_composite_driver(acm_ms_driver);
module_driver(__usb_composite_driver, usb_composite_probe,
usb_composite_unregister)
在 gadget driver 驅動適配后,調用 bind() 函數:
usb_gadget_probe_driver()→udc_bind_to_driver()→composite_bind()→acm_ms_bind()
在 acm_ms_bind() 函數中創建 composite device 的 Configuration 和 Function/Interface,并且和 Gadget Device / UDC 進行綁定。
其他操作和 Configfs Gadget Driver 類似。
4. Function Layer4.1 Function 注冊
在 drivers/usb/gadget/function/ 路徑下有一批 Gadget Function 的定義:
$ ls drivers/usb/gadget/function/f*
f_acm.c f_ecm.c f_eem.c f_fs.c f_hid.c f_loopback.c f_mass_storage.c f_mass_storage.h
f_midi.c f_ncm.c f_obex.c f_phonet.c f_printer.c f_rndis.c f_serial.c f_sourcesink.c
f_subset.c f_tcm.c f_uac1.c f_uac1_legacy.c f_uac2.c f_uvc.c f_uvc.h
大家使用 DECLARE_USB_FUNCTION_INIT() 宏定義來調用 usb_function_register() 函數,把 usb_function_driver 注冊到全局鏈表 func_list 中。等待 composite device 來進行實例化。
4.2 Gadget APIDECLARE_USB_FUNCTION_INIT(acm,acm_alloc_instance,acm_alloc_func);
static struct usb_function_driver _name
.name = __stringify(_name),
.mod = THIS_MODULE,
.alloc_inst = _inst_alloc,
.alloc_func = _func_alloc,
};
MODULE_ALIAS("usbfunc:"__stringify(_name));
DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc)
static int __init _name
{
return usb_function_register(&_name
}
static void __exit _name
{
usb_function_unregister(&_name
}
module_init(_name
module_exit(_name
在 Function Layer 主要使用以下 Gadget Layer 層提供的 API:
usb_ep_autoconfig()
usb_ep_enable()
usb_ep_disable()
usb_ep_alloc_request()
usb_ep_free_request()
usb_ep_queue()
usb_ep_dequeue()
5. UDC Hardware
UDC 全稱 Usb Device Controller,是設備作為 Usb Device 時最底層的控制器。在硬件層面實現了以下功能:
5.1 Data ModeUDC 實現的一項主要工作是數據搬移:
-
UDC 發送時,數據先從內存 Memory 搬移到 UDC 的內部 FIFO 當中,然后由 UDC 發送到 USB 物理線路上。
-
UDC 接收時,數據先從 USB 物理線路接收到 UDC 的內部 FIFO 當中,然后再從 FIFO 拷貝到 內存 Memory 當中。
對于 FIFO 和 Memory 之間的數據搬移工作,支持兩種方式:
-
1、DMA Mode。
由 UDC 內部的 DMA 模塊來承擔數據搬移工作,只要使用寄存器配置好 FIFO 的分配,以及在寄存器中配置好 DMA 的其實地址,DMA 會完成數據的搬移。
-
2、Slave Mode。
也可以不使用 DMA 而直接使用 CPU 來搬移,這種方式非常消耗 CPU 的帶寬,CPU 被簡單重復的數據拷貝拖住不能做其他的事情。這種方式一般用于 Debug 模式。
5.2 Endpoint FIFO Mode
不同的 UDC 中 Endpoint 對 FIFO 的使用有多種模式,UDC 選用的是 Shared Transmit FIFO 模式。
在 Shared Transmit FIFO 模式中,Endpoint 對 FIFO 使用模式如下:
-
所有的 non-periodic IN endpoints 共享一個 transmit FIFO。non-periodic endpoints 包括 isochronous transfers 和 interrupt transfers。
-
每一個 periodic IN endpoint 獨立擁有一個 transmit FIFO。periodic endpoints 包括 bulk transfers 和 control transfers。
-
所有的 OUT endpoints 共享一個 receive FIFO。
5.3 Endpoint Resource
USB 協議定義一個 Device 最多可以實現 16 個 IN endpoint + 16 個 OUT endpoint。除了 endpoint 0 IN/OUT 被系統默認使用,剩下的可以被驅動動態分配使用。
Endpoint Type | Number | Register |
IN | 5 endpoints (0-15) |
DIEPCTL(0-15) DIEPINT(0-15) DIEPTSIZ(0-15) DIEPDMA(0-15) |
OUT | 5 endpoints (0-15) |
DOEPCTL(0-15) DOEPINT(0-15) DOEPTSIZ(0-15) DOEPDMA(0-15) |
如上一節所描述,UDC 是Shared Transmit FIFO 模式,periodic IN endpoint 需要擁有一個獨立的 transmit FIFO。最多有兩個這樣的 transmit FIFO 資源,供驅動動態分配。
Endpoint Type | Number | Register |
IN | 15 Periodic Transmit FIFO (1-15) |
DPTXFSIZ1 DPTXFSIZ2 |
如果驅動創建一個 periodic IN endpoint 它分配到了第一個 endpoint 資源,但是沒有分配到 transmit FIFO 資源,也會創建失敗。
5.4 Calculating FIFO Size
由上幾節的描述可以看到,UDC 有多個模塊需要使用內部 FIFO。包括:
-
(1) OUT endpoints RxFIFO。
-
(2) IN non-periodic endpoints TxFIFO。
-
(3) IN periodic endpoints TxFIFO。
-
(4) DMA 。
UDC 內部 FIFO 總大小是固定的,那么怎么樣來分配 FIFO 空間給這些模塊呢?UDC 提供了以下計算公式:
Receive FIFO RAM allocation
計算公式:
Device RxFIFO = (5 * number of control endpoints + 8) + ((largest USB packet used / 4) + 1 for status information) + (2 * number of OUT endpoints) + 1 for Global NAK
Transmit FIFO RAM allocation
計算公式:
Non-Periodic TxFIFO = largest non-periodic USB packet used / 4
Periodic Endpoint-Specific TxFIFOs= largest periodic USB packet used for an endpoint / 4
Internal Register Storage Space Allocation
當在內部DMA模式下運行時,核心將端點DMA地址寄存器(DI/OEPDMA)存儲在SPRAM中。必須為每個端點分配一個位置。
例如,如果一個端點是雙向的,那么必須分配兩個位置。如果端點是IN或OUT,則必須只分配一個位置。
Example
The MPS is 1,024 bytes for a periodic USB packet and 512 bytes for a non-periodic USB packet.
There are three OUT endpoints, three IN endpoints, one control endpoint.
-
Device RxFIFO = (5 * 1 + 8) + ((1,024 / 4) +1) + (2 * 4) + 1 = 279
-
Non-Periodic TxFIFO = (512 / 4) = 128
-
Device Periodic TxFIFO:
EP 1 = (1,024 / 4) = 256
EP 2 = (1,024 / 4) = 256
EP 3 = (1,024 / 4) = 256
5.5 FIFO Mapping
由上幾節可知對一個端點 Endpoint 來說,它對應的 FIFO 是動態分配的。在 DMA 模式下,一旦初始化時配置完成就不用再去管 Endpoint FIFO 的地址。但是對 Slave 模式來說,在數據收發過程中需要 CPU 訪問對應 FIFO 空間。
為了方便 CPU 對 Endpoint FIFO 的訪問,UDC 把 Endpoint FIFO 映射到了固定地址。其中讀操作會映射到 OUT Endpoint FIFO,寫操作會映射到 IN Endpoint FIFO。
5.6 Interrupt Cascade
由于 UDC 的中斷狀態較多,所以分成 3 級級聯:
layer |
register |
descript |
1 |
GINTSTS & GINTMSK |
全局中斷,每一 bit 表示一個全局中斷狀態。其中:OEPInt 表示有 Out Endpoint 中斷發生IEPInt 表示有 In Endpoint 中斷發生 |
2 | DAINT & DAINTMSK | Endpoint 中斷,每一 bit 表示一個 Endpoint 發生了中斷。 |
3 |
DOEPINTn & DOEPMSK DIEPINTn & DIEPMSK |
Endpoint 中斷細節,每一個 Endpoint 擁有一組這樣的寄存器。 寄存器中的每一 bit 代表某個 Endpoint 的某種中斷狀態 |
5.7 Data Transfer
UDC 內部的數據收發流程如上圖所示。主要的工作就是根據 USB 接收到的讀寫指令,把數據在 FIFO 和 Memory 之間進行搬移。具體分為幾種情況:
-
OUT Endpoint。所有 OUT Endpoint 的線路數據會接收到一個統一的 Rx FIFO 當中,然后根據接收數據的具體 Endpoint配置的 Memory 地址和長度,DMA 把數據從 FIFO 搬移到對應 Memory 當中,最后產生中斷。
-
IN Non-period Endpoint。所有 IN Non-period Endpoint 共享一個統一的 Tx Non-period FIFO ,根據Endpoint配置的 Memory 地址和長度,DMA 把數據從 Memory 搬移到統一的 FIFO 當中,發送到線路上后產生中斷。IN Non-period Endpoint 需要配置 Next Endpoint 指針,這樣 DMA處理完一個 Endpoint 的數據后才知道下一個需要處理的 Endpoint。
-
IN Period Endpoint。每一個 IN Period Endpoint 擁有自己獨立的 FIFO,根據Endpoint配置的 Memory 地址和長度,DMA 把數據從 Memory 搬移到對應的 FIFO 當中,發送到線路上后產生中斷。
1.USB 2.0 Specification
審核編輯 :李倩
-
usb
+關注
關注
60文章
7950瀏覽量
264858 -
Linux
+關注
關注
87文章
11311瀏覽量
209687 -
UDC
+關注
關注
0文章
4瀏覽量
8968
原文標題:Linux usb Device 詳解
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論