USB ECM,屬于USB-IF定義的CDC(Communication Device Class)下的一個子類:Ethernet Networking Control Model,用于Host和Device之間交換以太網幀。
1 USB ECM介紹
USB ECM,屬于USB-IF定義的CDC(Communication Device Class)下的一個子類:Ethernet Networking Control Model,用于Host和Device之間交換以太網幀。下圖是從USB ECM規範中截取:
2 關鍵描述符解析
用USB tool抓取ECM裝置的描述符,部分關鍵描述符如下。
首先是IAD描述符。
IAD Descriptor : Interface AssociationDescriptor,接口關聯描述符,将多個接口組合在一起。
bDescriptorType : 0x0B 表示描述符類型是IAD描述符。
bInterfaceCount : 0x02表示組合的接口數目是2個。
bFunctionClass : 0x02表示CDC class。
bFunctionSubClass : 0x06表示ECM subclass.
接下來是接口0的描述符,接口0用作ECM的control接口。
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的資訊,從屬于某個标準接口描述符下。
HeaderFunctional Descriptor,CDC class-specific的描述符必須以這個描述符作為開頭。
UnionFunctional Descriptor,包含控制接口資訊。
EthernetNetworking Functional Descriptor,包含網卡的資訊,比如MAC位址、統計能力等。其中MAC位址是通過字元串index來間接表示的,位于該描述符第4個位元組,這裡是06,表示String Descriptor 6中存放了MAC位址。
接口0的端點描述符,使用IN-2端點,端點方向為IN(Device->Host),中斷傳輸方式。
接下來是接口1的描述符,大部分字段的意義和接口0的描述符類似,是以不再重複解釋。接口1用作ECM的data接口。
接口1用做ECM的data接口。配置設定了兩個端點。
接口1的端點描述符,使用了IN-1端點,傳輸類型為Bulk。
接口1的另一個端點描述符,使用了OUT-1端點,傳輸類型為Bulk。
接下來是字串描述符,這裡隻截取了字串6,也就是存放MAC位址的字串。
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驅動的介紹,謝謝閱讀。
文章會在公衆号“大魚嵌入式”同步釋出,歡迎關注,一起交流。