轉載自:http://blog.csdn.net/cfy_phonex/article/details/22684005
kernel 選項
│ │ --- USB Gadget Support
│ │ <*> USB Gadget Drivers (Ethernet Gadget (with CDC Ethernet support)) --->
│ │ Ethernet Gadget (with CDC Ethernet support)
│ │ [*] RNDIS support
│ │ [*] Ethernet Emulation Model (EEM) support
│ │
Android f_rndis 分析筆記
背景說明
RNDIS是一個以太網端口 ( Ethernet port )。最開始是微軟控制的,用以取代 CDC Ethernet 的協議。
公開發布的 RNDIS規範很模糊,並且不必要的複雜。 ActiveSync 等規範術語使情況更糟糕。
簡而言之,它是一個微軟控制的,而不是開源生態系統控制的協議。 Linux 支持它僅僅是因爲微軟不支持 CDC以太網標準。
RNDIS數據傳輸模型很複雜,每個USB 消息都包含了多個以太網包。
RNDIS默認期待自己作爲USB配置中的唯一功能;因此你不能把它用於USB複合設備;並且它期待自己是第一個usb配置。
很不幸,微軟的RNDIS驅動程序充滿了bug,經常死機或者系統凍結,且經常和規範矛盾。
對於Linux開源社區而言,既然改正這些bug, 或者從微軟拿到精確的RNDIS規範文檔從而繞過它都不可能,
也許你可以避免使用 RNDIS。
代碼分析
kernel/drivers/usb/gadget/f_rndis.c 文件開頭即定義了 f_rndis 數據結構
- struct f_rndis {
- struct gether port;
- u8 ctrl_id, data_id;
- u8 ethaddr[ETH_ALEN];
- u32 vendorID;
- const char *manufacturer;
- int config;
- struct usb_ep *notify;
- struct usb_request *notify_req;
- atomic_t notify_count;
- };
隨後,f_rndis.c 分別定義了各種 usb 接口 和 usb 描述符。
- static struct usb_interface_descriptor rndis_control_intf = {
- .bLength = sizeof rndis_control_intf,
- .bDescriptorType = USB_DT_INTERFACE,
- /* status endpoint is optional; this could be patched later */
- .bNumEndpoints = 1,
- .bInterfaceClass = USB_CLASS_COMM,
- .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
- .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR,
- };
其後, 分別定義了 full speed, high speed, super speed的描述符。
- static struct usb_descriptor_header *eth_ss_function[] = {
- (struct usb_descriptor_header *) &rndis_iad_descriptor,
- /* control interface matches ACM, not Ethernet */
- (struct usb_descriptor_header *) &rndis_control_intf,
- (struct usb_descriptor_header *) &header_desc,
- (struct usb_descriptor_header *) &call_mgmt_descriptor,
- (struct usb_descriptor_header *) &rndis_acm_descriptor,
- (struct usb_descriptor_header *) &rndis_union_desc,
- (struct usb_descriptor_header *) &ss_notify_desc,
- (struct usb_descriptor_header *) &ss_intr_comp_desc,
- /* data interface has no altsetting */
- (struct usb_descriptor_header *) &rndis_data_intf,
- (struct usb_descriptor_header *) &ss_in_desc,
- (struct usb_descriptor_header *) &ss_bulk_comp_desc,
- (struct usb_descriptor_header *) &ss_out_desc,
- (struct usb_descriptor_header *) &ss_bulk_comp_desc,
- NULL,
- };
前文分析過, rndis_bind_config_vendor() 是和 android usb 層溝通的橋樑函數,也是整個 f_rndis.c 文件的唯一入口函數。
rndis_bind_config_vendor()通過調用rndis_init(),向下打通了 kernel/drivers/usb/gadget/rndis.c 層。
在設置好 f_rndis 成員變量,也就是分配好必須的資源後,usb_add_function() 把該usb功能加入到配置中去。
對應 USB規範可知, 每個 usb 配置必須包含一個或多個usb功能。
- int
- rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
- u32 vendorID, const char *manufacturer, struct eth_dev *dev)
- {
- ...
- rndis = kzalloc(sizeof *rndis, GFP_KERNEL);
- if (!rndis)
- goto fail;
- memcpy(rndis->ethaddr, ethaddr, ETH_ALEN);
- rndis->vendorID = vendorID;
- rndis->manufacturer = manufacturer;
- rndis->port.ioport = dev;
- /* RNDIS activates when the host changes this filter */
- rndis->port.cdc_filter = 0;
- /* RNDIS has special (and complex) framing */
- rndis->port.header_len = sizeof(struct rndis_packet_msg_type);
- rndis->port.wrap = rndis_add_header;
- rndis->port.unwrap = rndis_rm_hdr;
- rndis->port.func.name = "rndis";
- rndis->port.func.strings = rndis_strings;
- /* descriptors are per-instance copies */
- rndis->port.func.bind = rndis_bind;
- rndis->port.func.unbind = rndis_unbind;
- rndis->port.func.set_alt = rndis_set_alt;
- rndis->port.func.setup = rndis_setup;
- rndis->port.func.disable = rndis_disable;
- status = usb_add_function(c, &rndis->port.func);
- ......
- }
usb_add_function() 將調用 rndis->port.func.bind 函數並返回其值,實際就是調用 rndis_bind() 函數。
rndis_bind() 進行以太網功能驅動的初始化和綁定操作。
通過 usb_interface_id() 分配usb 未使用的接口id值。 代碼中 status 應該更改爲 id 提高代碼可讀性。
- static int rndis_bind(struct usb_configuration *c, struct usb_function *f)
- {
- ......
- /* allocate instance-specific interface IDs */
- status = usb_interface_id(c, f);
- if (status < 0)
- goto fail;
- rndis->ctrl_id = status;
- rndis_iad_descriptor.bFirstInterface = status;
- rndis_control_intf.bInterfaceNumber = status;
- rndis_union_desc.bMasterInterface0 = status;
- status = usb_interface_id(c, f);
- if (status < 0)
- goto fail;
- rndis->data_id = status;
- rndis_data_intf.bInterfaceNumber = status;
- rndis_union_desc.bSlaveInterface0 = status;
- ......
- }
usb_interface_id() 是 drivers/usb/gadget/composite.c 的通用功能函數。 根據USB 2.0規範, 每個配置最大接口數 MAX_CONFIG_INTERFACES 爲16。
- /*
- * Returns the interface ID which was allocated; or -ENODEV if no
- * more interface IDs can be allocated.
- */
- int usb_interface_id(struct usb_configuration *config,
- struct usb_function *function)
- {
- unsigned id = config->next_interface_id;
- if (id < MAX_CONFIG_INTERFACES) {
- config->interface[id] = function;
- config->next_interface_id = id + 1;
- return id;
- }
- return -ENODEV;
- }
- ......
- /* allocate instance-specific endpoints */
- ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);
- rndis->port.in_ep = ep;
- ep->driver_data = cdev; /* claim */
- ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);
- rndis->port.out_ep = ep;
- ep->driver_data = cdev; /* claim */
- /* NOTE: a status/notification endpoint is, strictly speaking,
- * optional. We don't treat it that way though! It's simpler,
- * and some newer profiles don't treat it as optional.
- */
- ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc);
- rndis->notify = ep;
- ep->driver_data = cdev; /* claim */
- /* allocate notification request and buffer */
- rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
- rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);
- rndis->notify_req->length = STATUS_BYTECOUNT;
- rndis->notify_req->context = rndis;
- rndis->notify_req->complete = rndis_response_complete;
- ......
usb_ep_autoconfig() 函數根據給定的usb descriptor, 選擇對應的 usb endpoint 值並返回。該usb端點值後面會被 usb_ep_enable()使用。
爲了安全起見, 調用 usb_ep_autoconfig() 的 usb_function.bind 函數應該進行端點返回值檢查以適應不同的硬件差異。因爲返回值可能不是usb控制器希望的usb端點。
usb_ep_autoconfig() 定義在 kernel/drivers/usb/gadget/epautoconf.c 文件中。
usb_ep_alloc_request()函數分配並返回一個 usb_request 對象指針。usb_request 對象必須通過此函數分配,因爲usb_request 對象可能
是usb控制器特定相關的初始化,或者usb端點特定相關的資源,比如 DMA描述符的分配。
usb_request 可以通過 usb_ep_queue() 提交到隊列, 並且收到一個唯一的請求執行結束後的回調函數;
通過 usb_ep_free_request() 可以釋放 usb_request 資源。
對於 f_rndis, usb請求執行結束後的回調函數是 rndis_response_complete()。
如果usb_request 返回狀態正常,則通過 usb_ep_queue() 放入隊列;否則丟掉後返回。
- static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req)
- {
- struct f_rndis *rndis = req->context;
- struct usb_composite_dev *cdev = NULL;
- int status = req->status;
- /* In usb plug in/out and tetherring on/off
- * regression tests, port.func.config */
- /* may be NULL pointer.*/
- if (rndis->port.func.config != NULL)
- cdev = rndis->port.func.config->cdev;
- else
- printk(KERN_ERR "rndis gadget driver is removed.\n");
- /* after TX:
- * - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control)
- * - RNDIS_RESPONSE_AVAILABLE (status/irq)
- */
- switch (status) {
- case -ECONNRESET:
- case -ESHUTDOWN:
- /* connection gone */
- atomic_set(&rndis->notify_count, 0);
- break;
- default:
- if (cdev != NULL)
- DBG(cdev, "RNDIS %s response error %d, %d/%d\n",
- ep->name, status,
- req->actual, req->length);
- /* FALLTHROUGH */
- case 0:
- if (ep != rndis->notify)
- break;
- /* handle multiple pending RNDIS_RESPONSE_AVAILABLE
- * notifications by resending until we're done
- */
- if (atomic_dec_and_test(&rndis->notify_count))
- break;
- status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
- if (status) {
- atomic_dec(&rndis->notify_count);
- if (cdev != NULL)
- DBG(cdev, "notify/1 --> %d\n", status);
- }
- break;
- }
- }
回到 rndis_bind() 函數。
- 它進一步指定 fast speed, high speed, super speed的描述符;
- 註冊一個全局的函數指針,指向 rndis_response_available()。rndis_response_available() 函數發送 RNDIS RESPONSE_AVAILABLE 消息到端點。
- ......
- status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function,
- eth_ss_function);
- if (status)
- goto fail;
- rndis->port.open = rndis_open;
- rndis->port.close = rndis_close;
- status = rndis_register(rndis_response_available, rndis);
- if (status < 0)
- goto fail;
- rndis->config = status;
- ......
rndis_setup()函數:
- 使用CDC命令封裝機制來實現一個 RPC調用。
- 只檢查一種 USB_DIR_OUT, 一種 USB_DIR_IN。
- case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
- | USB_CDC_SEND_ENCAPSULATED_COMMAND:
- if (w_value || w_index != rndis->ctrl_id)
- goto invalid;
- /* read the request; process it later */
- value = w_length;
- req->complete = rndis_command_complete;
- req->context = rndis;
- /* later, rndis_response_available() sends a notification */
- break;
- if (w_value || w_index != rndis->ctrl_id)
- goto invalid;
- else {
- u8 *buf;
- u32 n;
- /* return the result */
- buf = rndis_get_next_response(rndis->config, &n);
- if (buf) {
- memcpy(req->buf, buf, n);
- req->complete = rndis_response_complete;
- req->context = rndis;
- rndis_free_response(rndis->config, buf);
- value = n;
- }
- /* else stalls ... spec says to avoid that */
- }
- break;
- 對於控制id,調用 usb_ep_enable()
- 對於數據id, 調用 gether_connect()
rndis_disable()函數:
- 釋放資源
- gether_disconnect() 斷開gether連接
- 調用 usb_ep_disable() 關閉端點。
從上面分析可知, f_rndis.c 溝通的下層是 drivers/usb/gadget/u_ether.c。