Linux usb 4. Device 詳解【轉】

轉自:https://blog.csdn.net/pwl999/article/details/120997525

文章目錄
1. 簡介
2. Platform Layer
2.1 Platform Device
2.2 Platform Driver
3. UDC/Gadget Layer
3.1 Gadget Bus
3.2 Gadget Device
3.2.1 Endpoint Alloc
3.2.2 EndPoint Access
3.2.3 UDC Control
3.3 Gadget Driver (Configfs)
3.3.1 configfs 使用
3.3.2 configfs 層次結構
3.3.3 gadget driver
3.4 Gadget Driver (Legacy)
3.4.1 gadget driver
3.4.2 composite device
4. Function Layer
4.1 Function 註冊
4.2 Gadget API
5. UDC Hardware
5.1 Data Mode
5.2 Endpoint FIFO Mode
5.3 Endpoint Resource
5.4 Calculating FIFO Size
5.5 FIFO Mapping
5.6 Interrupt Cascade
5.7 Data Transfer
參考資料
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 = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
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:

drivers\usb\dwc2\platform.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:、

 

type list descript
Device udc_list 所有Device全集
Driver gadget_driver_pending_list 只包含沒有適配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\n",
driver->function);
ret = 0;
}

mutex_unlock(&udc_lock);
if (ret)
pr_warn("udc-core: couldn't find an available UDC or it's busy\n");
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]\n",
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()
1
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:

drivers\usb\gadget\function\f_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 展開的:

drivers\usb\gadget\function\f_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 定義了子文件和相關操作函數。

我們通過解析 drivers\usb\gadget\configfs.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\n");
return ERR_PTR(-EINVAL);
}
*instance_name = '\0';
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 的定義:

drivers\usb\gadget\configfs.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()
1
本質上是 使用 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\n", 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\n", 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\n", 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 的定義:

drivers\usb\gadget\composite.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,
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
驅動提供了一個註冊函數 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 和一系列配置。例如:

drivers\usb\gadget\legacy\acm_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);

#define module_usb_composite_driver(__usb_composite_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()
1
在 acm_ms_bind() 函數中創建 composite device 的 Configuration 和 Function/Interface,並且和 Gadget Device / UDC 進行綁定。

其他操作和 Configfs Gadget Driver 類似。

4. Function Layer
4.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 來進行實例化。

DECLARE_USB_FUNCTION_INIT(acm, acm_alloc_instance, acm_alloc_func);

#define DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc) \
static struct usb_function_driver _name ## usb_func = { \
.name = __stringify(_name), \
.mod = THIS_MODULE, \
.alloc_inst = _inst_alloc, \
.alloc_func = _func_alloc, \
}; \
MODULE_ALIAS("usbfunc:"__stringify(_name));

#define DECLARE_USB_FUNCTION_INIT(_name, _inst_alloc, _func_alloc) \
DECLARE_USB_FUNCTION(_name, _inst_alloc, _func_alloc) \
static int __init _name ## mod_init(void) \
{ \
return usb_function_register(&_name ## usb_func); \
} \
static void __exit _name ## mod_exit(void) \
{ \
usb_function_unregister(&_name ## usb_func); \
} \
module_init(_name ## mod_init); \
module_exit(_name ## mod_exit)

4.2 Gadget API
在 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 Mode
UDC 實現的一項主要工作是數據搬移:

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
————————————————
版權聲明:本文爲CSDN博主「pwl999」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/pwl999/article/details/120997525

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章