色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

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

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

3天內不再提示

關于Linux usb Device詳解

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

1. 簡介

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

整個 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:

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

它們的使用場景如下:

  • 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 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() 函數從 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/configcd /sys/kernel/config/usb_gadget
// 2、創建g1目錄,實例化一個新的gadget模板 (composite device)。mkdir g1cd g1
// 3.1、定義USB產品的VID和PID。echo "0x1d6b" > idVendorecho "0x0104" > idProduct
// 3.2、實例化英語語言ID。(0x409是USB language ID 美國英語,不是任意的,可以在USBIF網站上下載文檔查詢。)mkdir strings/0x409ls strings/0x409/// 3.3、將開發商、產品和序列號字符串寫入內核。echo "0123456789" > strings/0x409/serialnumberecho "AAAA Inc." > strings/0x409/manufacturerecho "Bar Gadget" > strings/0x409/product
// 4、創建 `Function` 功能實例,需要注意的是,一個功能如果有多個實例的話,擴展名必須用數字編號。mkdir functions/acm.GS0
// 5.1、創建一個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驅動注冊到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 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 是被動狀態,只有連上 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);
#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()

在 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 來進行實例化。

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。

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

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

  • 2、Slave Mode。

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

也可以不使用 DMA 而直接使用 CPU 來搬移,這種方式非常消耗 CPU 的帶寬,CPU 被簡單重復的數據拷貝拖住不能做其他的事情。這種方式一般用于 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 協議定義一個 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

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

由上幾節的描述可以看到,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 / 4Periodic 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

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

由上幾節可知對一個端點 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

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

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
    usb
    +關注

    關注

    60

    文章

    7950

    瀏覽量

    264858
  • Linux
    +關注

    關注

    87

    文章

    11311

    瀏覽量

    209687
  • UDC
    UDC
    +關注

    關注

    0

    文章

    4

    瀏覽量

    8968

原文標題:Linux usb Device 詳解

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

收藏 人收藏

    評論

    相關推薦

    CYUSB3014燒錄失敗Cypress Benicia USB Boot Device

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

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

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

    Linux用戶管理詳解

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

    詳解linux內核的uevent機制

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

    詳解Linux中的權限控制

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

    關鍵指南針-NXP USB CDC_VCOM虛擬串口例程

    最近有小伙伴反應USB中的 usb_examples/usb_device_cdc_vcom 例程(USB虛擬串口VCOM)中的一些使用問題,今天集中來說說使用example的必知要點
    的頭像 發表于 07-25 09:17 ?2056次閱讀
    關鍵指南針-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 &
    發表于 06-27 06:44

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

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

    請問STM32 USB device口能實現USB HUB功能嗎?

    STM32 USB device口能實現USB HUB功能嗎?
    發表于 04-15 06:33

    關于stm32u575芯片作為usb device和PC實現雙向通信的疑問

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

    關于STM32 USB端點配置的疑惑求解

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

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

    /release-v1.2.0/docs/html/index.html#section_emusb_device_quick_start,一步一步操作 關于出現這個錯誤
    發表于 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軟件沒反應。請問可能是哪些原因導致的呢?
    發表于 02-20 08:29

    求助,請問Linux下如何監控USB數據呢?

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

    Linux內核中信號詳解

    信號和多線程程序 4 與信號相關的數據結構 4.2.1 x86/Linux2.6.11的定義 4.2.2 x86-64/Linux2.6.11的定義 4.2.3 x86-64/linux
    的頭像 發表于 01-13 09:40 ?1398次閱讀
    <b class='flag-5'>Linux</b>內核中信號<b class='flag-5'>詳解</b>
    主站蜘蛛池模板: 胸大的姑娘中文字幕视频| 一边亲着一面膜下奶韩剧免费| 乱奷XXXXXHD| 麻花豆传媒剧国产免费mv观看| 毛片免费在线视频| 嗯啊…跟校草在教室里做h| 女的把腿张开男的往里面插| 欧美精品华人在线| 三叶草未满十八岁| 偷拍 自怕 亚洲 在线| 亚洲国产货青视觉盛宴| 一区一区三区产品| 51国产午夜精品免费视频| 99视频免视看| 国产AV视频一区二区蜜桃| 国产亚洲高清视频| 久久久影院亚洲精品| 欧美激情一区二区三区视频| 色欲AV蜜臀AV在线观看麻豆| 亚洲成人免费观看| 中文字幕亚洲无线码在线| 啊灬啊别停灬用力啊在线观看视频 | 在线电台收听| A级超碰视频在线观看| 疯狂小护士| 极品少妇小泬50PTHEPON| 美女撒尿无遮挡免费中国| 日日AV夜夜添久久奶无码| 亚洲第一成年网站视频| 最新国产精品福利2020| 纯肉高H放荡受BL文库| 国产亚洲免费观看| 妈妈的职业3完整版在线播放 | 国产AV精品久久久毛片| 花蝴蝶免费版高清版| 欧美激情视频一区二区| 性生大片免费看| 97精品在线播放| 国产毛A片啊久久久久久A| 蜜臀久久99精品久久久久久做爰| 污文乖不疼的|