Linux USB ECM Gadget 驅動介紹

bigfish99發表於2021-05-24

​1 USB ECM介紹

USB ECM,屬於USB-IF定義的CDC(Communication Device Class)下的一個子類:Ethernet Networking Control Model,用於Host和Device之間交換乙太網幀。下圖是從USB ECM規範中擷取:

Linux USB ECM Gadget 驅動介紹

 

2 關鍵描述符解析

用USB tool抓取ECM裝置的描述符,部分關鍵描述符如下。

 

首先是IAD描述符。

Linux USB ECM Gadget 驅動介紹

IAD Descriptor        : Interface AssociationDescriptor,介面關聯描述符,將多個介面組合在一起。

bDescriptorType     : 0x0B 表示描述符型別是IAD描述符。

bInterfaceCount      : 0x02表示組合的介面數目是2個。

bFunctionClass        : 0x02表示CDC class。

bFunctionSubClass  : 0x06表示ECM subclass.

 

接下來是介面0的描述符,介面0用作ECM的control介面。

Linux USB ECM Gadget 驅動介紹

Interface Descriptor   : 介面描述符

bInterfaceNumber     : 0x00 標識該介面為介面0

bAlternateSetting      : 0x00 如果同一個介面有多個描述符設定,那該值就用來區分是哪個

bNumEndpoints         : 0x01表示該介面使用1個端點

bInterfaceClass           : 0x02 表示CDC class

bInterfaceSubClass     : 0x06 表示ECM subclass

bInterfaceProtocol      : 0x00 表示使用標準協議

 

以下三個CDC Interface Descriptor屬於functional descriptor,functional descriptor用來描述class-specific的資訊,從屬於某個標準介面描述符下。

Linux USB ECM Gadget 驅動介紹

HeaderFunctional Descriptor,CDC class-specific的描述符必須以這個描述符作為開頭。

 

Linux USB ECM Gadget 驅動介紹

UnionFunctional Descriptor,包含控制介面資訊。

 

Linux USB ECM Gadget 驅動介紹

EthernetNetworking Functional Descriptor,包含網路卡的資訊,比如MAC地址、統計能力等。其中MAC地址是通過字串index來間接表示的,位於該描述符第4個位元組,這裡是06,表示String Descriptor 6中存放了MAC地址。

 

Linux USB ECM Gadget 驅動介紹

介面0的端點描述符,使用IN-2端點,端點方向為IN(Device->Host),中斷傳輸方式。

 

接下來是介面1的描述符,大部分欄位的意義和介面0的描述符類似,因此不再重複解釋。介面1用作ECM的data介面。

Linux USB ECM Gadget 驅動介紹

介面1用做ECM的data介面。分配了兩個端點。

 

Linux USB ECM Gadget 驅動介紹

介面1的端點描述符,使用了IN-1端點,傳輸型別為Bulk。

 

Linux USB ECM Gadget 驅動介紹

介面1的另一個端點描述符,使用了OUT-1端點,傳輸型別為Bulk。

 

接下來是字串描述符,這裡只擷取了字串6,也就是存放MAC地址的字串。

Linux USB ECM Gadget 驅動介紹

3 資料通路

Device ->Host:

在ECM Gadget驅動中,USB角色是device,在本地註冊一個乙太網卡裝置,網路協議棧傳送資料到該網路卡,該網路卡驅動會將資料以USB傳輸的方式傳送到主機。Host端有ECM Host驅動,也會在Host端註冊一個乙太網卡,收到USB傳輸過來的資料後,網路卡會將資料上報給Host端的網路協議棧。

 

Host -> Devcie:

Host端網路協議棧把資料發給ECM Host驅動,ECM Host驅動以USB傳輸的方式將資料傳送給Device,Device端網路卡收到資料後,上報給Device端網路協議棧。

 

4 驅動流程

原始碼位置在:

drivers\usb\gadget\function\f_ecm.c

drivers\usb\gadget\function\u_ether.c

 

4.1 驅動的註冊

註冊function到USB gadget驅動框架中。

DECLARE_USB_FUNCTION_INIT(ecm, ecm_alloc_inst, ecm_alloc);

  

4.2 USB function的實現

  • ecm_alloc_inst函式主要是呼叫gether_setup_default建立一個net裝置。

opts->net = gether_setup_default();

  

  • ecm_alloc函式主要做兩件事:

一是從net裝置中獲取該網路卡host mac地址並記錄下來,後續bind時會新增到描述符中。

  status = gether_get_host_addr_cdc(opts->net, ecm->ethaddr,
            sizeof(ecm->ethaddr));

  

二是按USB function driver框架註冊各回撥函式。

  ecm->port.func.name = "cdc_ethernet";
  /* descriptors are per-instance copies */
  ecm->port.func.bind = ecm_bind;
  ecm->port.func.unbind = ecm_unbind;
  ecm->port.func.set_alt = ecm_set_alt;
  ecm->port.func.get_alt = ecm_get_alt;
  ecm->port.func.setup = ecm_setup;
  ecm->port.func.disable = ecm_disable;
  ecm->port.func.free_func = ecm_free;

  

  • ecm_bind函式主要完成USB bind的過程:

一是將net裝置和gadget關聯,並註冊net裝置。

  if (!ecm_opts->bound) {
    mutex_lock(&ecm_opts->lock);
    gether_set_gadget(ecm_opts->net, cdev->gadget);
    status = gether_register_netdev(ecm_opts->net);
    mutex_unlock(&ecm_opts->lock);
    if (status)
      return status;
    ecm_opts->bound = true;
  }

  

二是處理字串描述符。

  ecm_string_defs[1].s = ecm->ethaddr;
​
  us = usb_gstrings_attach(cdev, ecm_strings,
         ARRAY_SIZE(ecm_string_defs));
  if (IS_ERR(us))
    return PTR_ERR(us);
  ecm_control_intf.iInterface = us[0].id;
  ecm_data_intf.iInterface = us[2].id;
  ecm_desc.iMACAddress = us[1].id;
  ecm_iad_descriptor.iFunction = us[3].id;

  

三是分配interface,並將interface資訊更新到描述符中。

  /* allocate instance-specific interface IDs */
  status = usb_interface_id(c, f);
  if (status < 0)
    goto fail;
  ecm->ctrl_id = status;
  ecm_iad_descriptor.bFirstInterface = status;
​
  ecm_control_intf.bInterfaceNumber = status;
  ecm_union_desc.bMasterInterface0 = status;
​
  status = usb_interface_id(c, f);
  if (status < 0)
    goto fail;
  ecm->data_id = status;
​
  ecm_data_nop_intf.bInterfaceNumber = status;
  ecm_data_intf.bInterfaceNumber = status;
  ecm_union_desc.bSlaveInterface0 = status;

  

四是分配端點。

  /* allocate instance-specific endpoints */
  ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_in_desc);
  if (!ep)
    goto fail;
  ecm->port.in_ep = ep;
​
  ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_out_desc);
  if (!ep)
    goto fail;
  ecm->port.out_ep = ep;
  ep = usb_ep_autoconfig(cdev->gadget, &fs_ecm_notify_desc);
  if (!ep)
    goto fail;
  ecm->notify = ep;

  

五是分配描述符。

  status = usb_assign_descriptors(f, ecm_fs_function, ecm_hs_function,
      ecm_ss_function, NULL);

  

4.3 網路卡部分的實現

  • 網路卡裝置操作函式集

static const struct net_device_ops eth_netdev_ops = {
  .ndo_open    = eth_open,
  .ndo_stop    = eth_stop,
  .ndo_start_xmit    = eth_start_xmit,
  .ndo_set_mac_address   = eth_mac_addr,
  .ndo_validate_addr  = eth_validate_addr,
};

  

  • 分配網路卡裝置

static inline struct net_device *gether_setup_default(void)
{
  return gether_setup_name_default("usb");
}

  

  • 註冊網路卡裝置

int gether_register_netdev(struct net_device *net)

  

  • 資料的收發

static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
                    struct net_device *net)
static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
static void process_rx_w(struct work_struct *work)
static void process_tx_w(struct work_struct *w)

  

以上就是對Linux USB ECM Gadget驅動的介紹,謝謝閱讀。

文章會在公眾號“大魚嵌入式”同步釋出,歡迎關注,一起交流。

相關文章