0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

關(guān)于Linux usb Device詳解

Linux閱碼場 ? 來源:Linux閱碼場 ? 作者:Linux閱碼場 ? 2022-06-13 09:44 ? 次閱讀

1. 簡介

f29d9aae-eaac-11ec-ba43-dac502259ad0.png

整個 USB 系統(tǒng)的通訊模型如上圖所示,本文詳細解析其中 Device 各模塊的架構(gòu)和原理 (圖中彩色部分)。

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 },  {},};

該驅(qū)動的主要功能是創(chuàng)建和注冊 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 結(jié)構(gòu) */  udc = kzalloc(sizeof(*udc), GFP_KERNEL);  if (!udc)    goto error;
  /* (1.2) 初始化 udc 結(jié)構(gòu) */  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,提供統(tǒng)一的向上接口。Gadget Driver 又把各式各樣的 Function 和 Gadget Device 鏈接起來。

3.1 Gadget Bus

Gadget Layer 層沒有定義一個標準的 Bus 總線,而是自定義了兩條鏈表來分別存儲 Device 和 Driver:

f2d3a31a-eaac-11ec-ba43-dac502259ad0.png

它們的使用場景如下:

  • 1、在 Gadget Device 創(chuàng)建時,首先把 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 創(chuàng)建時,首先嘗試和 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) 數(shù)據(jù)成員的賦值 */  udc->driver = driver;  udc->dev.driver = &driver->driver;  udc->gadget->dev.driver = &driver->driver;
  usb_gadget_udc_set_speed(udc, driver->max_speed);
  /* (2) 調(diào)用 Gadget Driver 的 bind() 函數(shù) */  ret = driver->bind(udc->gadget, driver);  if (ret)    goto err1;
  /* (3) 調(diào)用 Gadget Device 的 start() 函數(shù)      udc->gadget->ops->udc_start(udc->gadget, udc->driver);   */  ret = usb_gadget_udc_start(udc);  if (ret) {    driver->unbind(udc->gadget);    goto err1;  }
  /* (4) 調(diào)用 Gadget Device 的 pullup() 函數(shù)      gadget->ops->pullup(gadget, 1/0);   */  usb_udc_connect_control(udc);
  kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);  return 0;
}

注意:這里和一般的 Device 和 Driver 的適配規(guī)則有些不一樣。一般的規(guī)則是一個 Dirver 可以適配多個 Device,而一個 Device 只能適配一個 Driver。而這里的規(guī)則是一個 Gadget Device 只能適配一個 Gadget Driver,而一個 Gadget Driver 只能適配一個 Gadget Device。Gadget Device 代表的是一個 UDC,而 Gadget Driver 代表的是一個 Composite Device。

3.2 Gadget Device

上一節(jié)說過 Gadget Device 由 UDC Driver 創(chuàng)建。

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 在調(diào)用 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 驅(qū)動自己來處理   */  if (epnum)    list_add_tail(&hs_ep->ep.ep_list, &hsotg->gadget.ep_list);
  /* (2.2) 初始化 endpoint 的結(jié)構(gòu)體成員 */  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 最重要的結(jié)構(gòu)體成員,endpoint 操作函數(shù)集    endpoint 的相關(guān)操作最后調(diào)用到這些函數(shù)上   */  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 就可以通過調(diào)用 Gadget Api 來動態(tài)分配 Endpoint 了。例如:

static intacm_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() 函數(shù)從 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 收發(fā)數(shù)據(jù)的底層支持。在上一節(jié)的 endpoint 初始化時,就已經(jīng)設置 endpoint 的操作函數(shù)集 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 最重要的結(jié)構(gòu)體成員,endpoint 操作函數(shù)集    endpoint 的相關(guān)操作最后調(diào)用到這些函數(shù)上   */  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 讀寫數(shù)據(jù)。在 Host 側(cè)對 endpoint 進行一次操作請求的數(shù)據(jù)結(jié)構(gòu)是 struct urb,而在 Device 側(cè)也有類似的數(shù)據(jù)結(jié)構(gòu)稱為 struct usb_request,對 endpoint 的數(shù)據(jù)讀寫就是圍繞 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` 數(shù)據(jù)結(jié)構(gòu) */  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() 函數(shù)就會調(diào)用 endpoint 的操作函數(shù)集 dwc2_hsotg_ep_ops 中的 .queue 函數(shù):


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) 實際調(diào)用 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 層級的一些操作函數(shù),UDC Driver 在調(diào)用 usb_add_gadget_udc() 注冊 Gadget Device 之前,初始化了 Gadget 的 操作函數(shù)集:

dwc2_driver_probe() → dwc2_gadget_init():
int dwc2_gadget_init(struct dwc2_hsotg *hsotg){
  hsotg->gadget.max_speed = USB_SPEED_HIGH;  /* (1) 初始化 Gadget Device 的操作函數(shù)集  */  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 提供了一些內(nèi)部函數(shù)來調(diào)用:

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 的實現(xiàn),而 Function Layer 又需要使用這些 Api。怎么樣將兩者適配起來?Gadget Driver 就是用來完成這項工作的。

目前存在兩種風格的 Gadget Driver,其中包括:

  • Legacy。這是早期風格的 Gadget Driver,只能通過靜態(tài)編譯的方式指定使用哪些 Function。

  • Configfs。這是目前流行的 Gadget Driver,可以通過 configfs 文件系統(tǒng),不用重新編譯內(nèi)核,動態(tài)的配置需要使用的 Function。

我們首先介紹 configfs 風格的 Gadget Driver。

3.3.1 configfs 使用

首先從使用上體驗一下 configfs 的便捷。例如創(chuàng)建一個 ACM Function:

// 1、掛載configfs文件系統(tǒng)。mount -t configfs none /sys/kernel/configcd /sys/kernel/config/usb_gadget
// 2、創(chuàng)建g1目錄,實例化一個新的gadget模板 (composite device)。mkdir g1cd g1
// 3.1、定義USB產(chǎn)品的VID和PID。echo "0x1d6b" > idVendorecho "0x0104" > idProduct
// 3.2、實例化英語語言ID。(0x409是USB language ID 美國英語,不是任意的,可以在USBIF網(wǎng)站上下載文檔查詢。)mkdir strings/0x409ls strings/0x409/// 3.3、將開發(fā)商、產(chǎn)品和序列號字符串寫入內(nèi)核。echo "0123456789" > strings/0x409/serialnumberecho "AAAA Inc." > strings/0x409/manufacturerecho "Bar Gadget" > strings/0x409/product
// 4、創(chuàng)建 `Function` 功能實例,需要注意的是,一個功能如果有多個實例的話,擴展名必須用數(shù)字編號。mkdir functions/acm.GS0
// 5.1、創(chuàng)建一個USB `Configuration` 配置實例:mkdir configs/c.1ls configs/c.1// 5.2、定義配置描述符使用的字符串mkdir configs/c.1/strings/0x409ls configs/c.1/strings/0x409/echo "ACM" > configs/c.1/strings/0x409/configuration
// 6、捆綁功能 `Function` 實例到 `Configuration` 配置c.1ln -s functions/acm.GS0 configs/c.1
// 7.1、查找本機可獲得的UDC實例 (即 gadget device)# ls /sys/class/udc/10200000.usb// 7.2、將gadget驅(qū)動注冊到UDC上,插上USB線到電腦上,電腦就會枚舉USB設備。echo"10200000.usb">UDC

3.3.2 configfs 層次結(jié)構(gòu)

configfs 并不是 gadget 專用的,它是一個通用文件系統(tǒng),方便用戶通過文件系統(tǒng)創(chuàng)建文件夾、文件的方式來創(chuàng)建內(nèi)核對象。

configfs 是很好理解的,struct config_group 相當于一個文件夾,struct config_item_type 是這個文件夾的屬性集。其中 config_item_type->ct_group_ops->make_group()/drop_item() 定義了創(chuàng)建/銷毀下一層子文件夾的方法,config_item_type->ct_attrs 定義了子文件和相關(guān)操作函數(shù)。

我們通過解析 driversusbgadgetconfigfs.c 文件來深入理解 configfs 的使用方法:

1、首先創(chuàng)建首層文件夾 /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、創(chuàng)建 /sys/kernel/config/usb_gadget/g1 ,相當于創(chuàng)建一個全新的 composite device。會調(diào)用頂層 struct config_group 的 config_item_type->ct_group_ops->make_group() 函數(shù),即 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) 創(chuàng)建頂層文件夾 `/sys/kernel/config/usb_gadget/g1` 對應的 `struct config_group` 結(jié)構(gòu)      `/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) 創(chuàng)建子文件夾 `/sys/kernel/config/usb_gadget/g1/functions`      `functions_type` 中定義了進一步創(chuàng)建子文件夾的操作函數(shù)   */  config_group_init_type_name(&gi->functions_group, "functions",      &functions_type);  configfs_add_default_group(&gi->functions_group, &gi->group);
  /* (3) 創(chuàng)建子文件夾 `/sys/kernel/config/usb_gadget/g1/configs`      `config_desc_type` 中定義了進一步創(chuàng)建子文件夾的操作函數(shù)   */  config_group_init_type_name(&gi->configs_group, "configs",      &config_desc_type);  configfs_add_default_group(&gi->configs_group, &gi->group);
  /* (4) 創(chuàng)建子文件夾 `/sys/kernel/config/usb_gadget/g1/strings`      `gadget_strings_strings_type` 中定義了進一步創(chuàng)建子文件夾的操作函數(shù)   */  config_group_init_type_name(&gi->strings_group, "strings",      &gadget_strings_strings_type);  configfs_add_default_group(&gi->strings_group, &gi->group);
  /* (5) 創(chuàng)建子文件夾 `/sys/kernel/config/usb_gadget/g1/os_desc`      `os_desc_type` 中定義了進一步創(chuàng)建哪些子文件   */  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` 的目的很明確就是創(chuàng)建一個 `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、創(chuàng)建 /sys/kernel/config/usb_gadget/g1/functions/acm.GS0。會調(diào)用 functions_type 中定義的 function_make() 函數(shù):

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) 根據(jù) func_name 在全局鏈表中查找對應 function      usb_get_function_instance() → try_get_usb_function_instance() → fd->alloc_inst() → acm_alloc_instance():      并調(diào)用 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 實例安裝實際的函數(shù):

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 實例的成員函數(shù) */  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,};

在調(diào)用 echo "/sys/class/udc/10200000.usb" > /sys/kernel/config/usb_gadget/g1/UDC 時,將上述 gadget driver 進行注冊,和 UDC 已經(jīng)注冊好的 gadget device 進行動態(tài)適配。

gadget_dev_desc_UDC_store()→usb_gadget_probe_driver(&gi->composite.gadget_driver)→udc_bind_to_driver()

本質(zhì)上是 使用 configfs 創(chuàng)建好的 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 intacm_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 是被動狀態(tài),只有連上 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 驅(qū)動來說,相當于 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,  },};

驅(qū)動提供了一個注冊函數(shù) usb_composite_probe(),以供 composite device 來進行調(diào)用:

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 由用戶來創(chuàng)建 composite device,只能使用一個文件來創(chuàng)建 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) 驅(qū)動一開始就調(diào)用 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 驅(qū)動適配后,調(diào)用 bind() 函數(shù):

usb_gadget_probe_driver()→udc_bind_to_driver()→composite_bind()→acm_ms_bind()

在 acm_ms_bind() 函數(shù)中創(chuàng)建 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() 宏定義來調(diào)用 usb_function_register() 函數(shù),把 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 時最底層的控制器。在硬件層面實現(xiàn)了以下功能:

5.1 Data Mode

UDC 實現(xiàn)的一項主要工作是數(shù)據(jù)搬移:

  • UDC 發(fā)送時,數(shù)據(jù)先從內(nèi)存 Memory 搬移到 UDC 的內(nèi)部 FIFO 當中,然后由 UDC 發(fā)送到 USB 物理線路上。

  • UDC 接收時,數(shù)據(jù)先從 USB 物理線路接收到 UDC 的內(nèi)部 FIFO 當中,然后再從 FIFO 拷貝到 內(nèi)存 Memory 當中。

對于 FIFO 和 Memory 之間的數(shù)據(jù)搬移工作,支持兩種方式:

  • 1、DMA Mode。

f2eb2bf2-eaac-11ec-ba43-dac502259ad0.png

由 UDC 內(nèi)部的 DMA 模塊來承擔數(shù)據(jù)搬移工作,只要使用寄存器配置好 FIFO 的分配,以及在寄存器中配置好 DMA 的其實地址,DMA 會完成數(shù)據(jù)的搬移。

  • 2、Slave Mode。

f3227eae-eaac-11ec-ba43-dac502259ad0.png

也可以不使用 DMA 而直接使用 CPU 來搬移,這種方式非常消耗 CPU 的帶寬,CPU 被簡單重復的數(shù)據(jù)拷貝拖住不能做其他的事情。這種方式一般用于 Debug 模式。

5.2 Endpoint FIFO Mode

不同的 UDC 中 Endpoint 對 FIFO 的使用有多種模式,UDC 選用的是 Shared Transmit FIFO 模式。

f3857cb6-eaac-11ec-ba43-dac502259ad0.png

在 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 協(xié)議定義一個 Device 最多可以實現(xiàn) 16 個 IN endpoint + 16 個 OUT endpoint。除了 endpoint 0 IN/OUT 被系統(tǒng)默認使用,剩下的可以被驅(qū)動動態(tài)分配使用。

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)

如上一節(jié)所描述,UDC 是Shared Transmit FIFO 模式,periodic IN endpoint 需要擁有一個獨立的 transmit FIFO。最多有兩個這樣的 transmit FIFO 資源,供驅(qū)動動態(tài)分配。

Endpoint Type Number Register
IN 15 Periodic Transmit FIFO (1-15)

DPTXFSIZ1

DPTXFSIZ2

如果驅(qū)動創(chuàng)建一個 periodic IN endpoint 它分配到了第一個 endpoint 資源,但是沒有分配到 transmit FIFO 資源,也會創(chuàng)建失敗。

5.4 Calculating FIFO Size

f3b8f172-eaac-11ec-ba43-dac502259ad0.png

由上幾節(jié)的描述可以看到,UDC 有多個模塊需要使用內(nèi)部 FIFO。包括:

  • (1) OUT endpoints RxFIFO。

  • (2) IN non-periodic endpoints TxFIFO。

  • (3) IN periodic endpoints TxFIFO。

  • (4) DMA 。

UDC 內(nèi)部 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 / 4Periodic Endpoint-Specific TxFIFOs= largest periodic USB packet used for an endpoint / 4

Internal Register Storage Space Allocation

當在內(nèi)部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

f3e265a2-eaac-11ec-ba43-dac502259ad0.png

由上幾節(jié)可知對一個端點 Endpoint 來說,它對應的 FIFO 是動態(tài)分配的。在 DMA 模式下,一旦初始化時配置完成就不用再去管 Endpoint FIFO 的地址。但是對 Slave 模式來說,在數(shù)據(jù)收發(fā)過程中需要 CPU 訪問對應 FIFO 空間。

為了方便 CPU 對 Endpoint FIFO 的訪問,UDC 把 Endpoint FIFO 映射到了固定地址。其中讀操作會映射到 OUT Endpoint FIFO,寫操作會映射到 IN Endpoint FIFO。

5.6 Interrupt Cascade

由于 UDC 的中斷狀態(tài)較多,所以分成 3 級級聯(lián):

layer

register

descript

1

GINTSTS & GINTMSK

全局中斷,每一 bit 表示一個全局中斷狀態(tài)。其中:OEPInt 表示有 Out Endpoint 中斷發(fā)生IEPInt 表示有 In Endpoint 中斷發(fā)生
2 DAINT & DAINTMSK Endpoint 中斷,每一 bit 表示一個 Endpoint 發(fā)生了中斷。
3

DOEPINTn & DOEPMSK

DIEPINTn & DIEPMSK

Endpoint 中斷細節(jié),每一個 Endpoint 擁有一組這樣的寄存器。

寄存器中的每一 bit 代表某個 Endpoint 的某種中斷狀態(tài)

5.7 Data Transfer

f43fdd36-eaac-11ec-ba43-dac502259ad0.png

UDC 內(nèi)部的數(shù)據(jù)收發(fā)流程如上圖所示。主要的工作就是根據(jù) USB 接收到的讀寫指令,把數(shù)據(jù)在 FIFO 和 Memory 之間進行搬移。具體分為幾種情況:

  • OUT Endpoint。所有 OUT Endpoint 的線路數(shù)據(jù)會接收到一個統(tǒng)一的 Rx FIFO 當中,然后根據(jù)接收數(shù)據(jù)的具體 Endpoint配置的 Memory 地址和長度,DMA 把數(shù)據(jù)從 FIFO 搬移到對應 Memory 當中,最后產(chǎn)生中斷。

  • IN Non-period Endpoint。所有 IN Non-period Endpoint 共享一個統(tǒng)一的 Tx Non-period FIFO ,根據(jù)Endpoint配置的 Memory 地址和長度,DMA 把數(shù)據(jù)從 Memory 搬移到統(tǒng)一的 FIFO 當中,發(fā)送到線路上后產(chǎn)生中斷。IN Non-period Endpoint 需要配置 Next Endpoint 指針,這樣 DMA處理完一個 Endpoint 的數(shù)據(jù)后才知道下一個需要處理的 Endpoint。

  • IN Period Endpoint。每一個 IN Period Endpoint 擁有自己獨立的 FIFO,根據(jù)Endpoint配置的 Memory 地址和長度,DMA 把數(shù)據(jù)從 Memory 搬移到對應的 FIFO 當中,發(fā)送到線路上后產(chǎn)生中斷。

參考資料

1.USB 2.0 Specification

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • usb
    usb
    +關(guān)注

    關(guān)注

    60

    文章

    7963

    瀏覽量

    265240
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11324

    瀏覽量

    209949
  • UDC
    UDC
    +關(guān)注

    關(guān)注

    0

    文章

    4

    瀏覽量

    8969

原文標題:Linux usb Device 詳解

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    快速入門!RNDIS網(wǎng)卡實現(xiàn)USB上網(wǎng)~

    本文將帶你使用RNDIS的方式,搭配Linux主控終端,快速實現(xiàn)USB上網(wǎng)。 ? RNDIS是指Remote NDIS,基于USB實現(xiàn)RNDIS實際上就是TCP/IP over USB
    的頭像 發(fā)表于 12-24 17:02 ?578次閱讀
    快速入門!RNDIS網(wǎng)卡實現(xiàn)<b class='flag-5'>USB</b>上網(wǎng)~

    CYUSB3014燒錄失敗Cypress Benicia USB Boot Device

    我在用CYUSB3014進行USB3.0驗證時遇到了這樣一個問題。pmode設置為z11。插上USB之后進行驅(qū)動更新,但是名稱顯示Cypress Benicia USB Boot Device
    發(fā)表于 11-20 15:42

    PCM2706輸出iis信號給PMC1794解碼,插上電源/USB插上電腦后電腦提示“unkown USB device”,為什么?

    如同標題!本人用的是有源12MHz的外部晶體,PCM2706輸出iis信號給PMC1794解碼! 電路如下: 問題是插上電源/USB插上電腦后電腦提示“unkown USB device” 請
    發(fā)表于 11-07 08:13

    Linux用戶管理詳解

    用戶分為普通用戶和超級用戶,超級用戶在Windows系統(tǒng)中為Administrator在Linux系統(tǒng)中為root。登陸Linux系統(tǒng)需要提供用戶名與密碼,登陸后通過一定的方法管理該系統(tǒng)。
    的頭像 發(fā)表于 11-01 09:48 ?206次閱讀

    詳解linux內(nèi)核的uevent機制

    linux內(nèi)核中,uevent機制是一種內(nèi)核和用戶空間通信的機制,用于通知用戶空間應用程序各種硬件更改或其他事件,比如插入或移除硬件設備(如USB驅(qū)動器或網(wǎng)絡接口)。uevent表示“用戶空間
    的頭像 發(fā)表于 09-29 17:01 ?821次閱讀

    詳解Linux中的權(quán)限控制

    本章將和大家分享Linux中的權(quán)限控制。廢話不多說,下面我們直接進入主題。
    的頭像 發(fā)表于 08-05 15:32 ?623次閱讀
    <b class='flag-5'>詳解</b><b class='flag-5'>Linux</b>中的權(quán)限控制

    關(guān)鍵指南針-NXP USB CDC_VCOM虛擬串口例程

    最近有小伙伴反應USB中的 usb_examples/usb_device_cdc_vcom 例程(USB虛擬串口VCOM)中的一些使用問題,今天集中來說說使用example的必知要點
    的頭像 發(fā)表于 07-25 09:17 ?2140次閱讀
    關(guān)鍵指南針-NXP <b class='flag-5'>USB</b> CDC_VCOM虛擬串口例程

    運行usb/device /usb_webcam報錯no versions of leeebo/tinyusb_src match &gt;=0.15.0~6的原因?

    /esp-idf-v5.2.1/tools/cmake/build.cmake:544 (message): ERROR: Because no versions of usb_device_uvc match &
    發(fā)表于 06-27 06:44

    ESP32S3使用例程usb_host_lib枚舉device失敗的原因?

    。 將核心板重新上電后,當接入HTC的tracker(USB Composite Device,3 HID interfaces),例程報錯提示: E (23785) HUB: Bad
    發(fā)表于 06-07 06:53

    請問STM32 USB device口能實現(xiàn)USB HUB功能嗎?

    STM32 USB device口能實現(xiàn)USB HUB功能嗎?
    發(fā)表于 04-15 06:33

    關(guān)于stm32u575芯片作為usb device和PC實現(xiàn)雙向通信的疑問

    平臺:STM32U575qii-EV板 模塊:USBX,ThreadX 目的:stm32u575芯片作為usb device和PC實現(xiàn)雙向通信,device為HID Custom類 現(xiàn)狀:當前
    發(fā)表于 03-13 06:56

    關(guān)于STM32 USB端點配置的疑惑求解

    小弟剛開始摸索STM32,結(jié)合工作方向,正在學習USB部分,看到STM32_USB-FS-Device_Lib_V4.1.0這個USB庫的pack,這個里面的示例有個地方看不明白了。 具體位置
    發(fā)表于 03-12 07:13

    emusb-device調(diào)用USBD_Start()提示找不到USB_Main.c是怎么回事?

    /release-v1.2.0/docs/html/index.html#section_emusb_device_quick_start,一步一步操作 關(guān)于出現(xiàn)這個錯誤
    發(fā)表于 03-05 07:23

    miniwiggler選擇UDAS顯示no device,重新選擇JTAGE OVER USB CHIP軟件沒反應怎么解決?

    miniwiggler接了TRST,TDI,TDO,TMS,GND,TCK,RST,VDD這8個引腳,選擇UDAS顯示no device,重新選擇JTAGE OVER USB CHIP軟件沒反應。請問可能是哪些原因?qū)е碌哪兀?/div>
    發(fā)表于 02-20 08:29

    求助,請問Linux下如何監(jiān)控USB數(shù)據(jù)呢?

    Linux下之前是通過lsusb查看一些信息,但有時候也需要監(jiān)控到USB數(shù)據(jù),故請教一下各位有沒有Linux下的USB監(jiān)控軟件,或者一些監(jiān)控的方法。謝謝各位。
    發(fā)表于 01-24 06:52