Android f_rndis 分析筆記

轉載自: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 分析筆記

分類: Android USB 1430人閱讀 評論(0)收藏 舉報

目錄(?)[+]

背景說明

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 數據結構

  1. struct f_rndis {  
  2.     struct gether           port;  
  3.     u8              ctrl_id, data_id;  
  4.     u8              ethaddr[ETH_ALEN];  
  5.     u32             vendorID;  
  6.     const char          *manufacturer;  
  7.     int             config;  
  8.   
  9.     struct usb_ep           *notify;  
  10.     struct usb_request      *notify_req;  
  11.     atomic_t            notify_count;  
  12. };  

隨後,f_rndis.c 分別定義了各種  usb 接口 和 usb 描述符。
  1. static struct usb_interface_descriptor rndis_control_intf = {  
  2.     .bLength =      sizeof rndis_control_intf,  
  3.     .bDescriptorType =  USB_DT_INTERFACE,  
  4.   
  5.     /* status endpoint is optional; this could be patched later */  
  6.     .bNumEndpoints =    1,  
  7.     .bInterfaceClass =  USB_CLASS_COMM,  
  8.     .bInterfaceSubClass =   USB_CDC_SUBCLASS_ACM,  
  9.     .bInterfaceProtocol =   USB_CDC_ACM_PROTO_VENDOR,  
  10. };  

其後, 分別定義了 full speed, high speed, super speed的描述符。
  1. static struct usb_descriptor_header *eth_ss_function[] = {  
  2.     (struct usb_descriptor_header *) &rndis_iad_descriptor,  
  3.   
  4.     /* control interface matches ACM, not Ethernet */  
  5.     (struct usb_descriptor_header *) &rndis_control_intf,  
  6.     (struct usb_descriptor_header *) &header_desc,  
  7.     (struct usb_descriptor_header *) &call_mgmt_descriptor,  
  8.     (struct usb_descriptor_header *) &rndis_acm_descriptor,  
  9.     (struct usb_descriptor_header *) &rndis_union_desc,  
  10.     (struct usb_descriptor_header *) &ss_notify_desc,  
  11.     (struct usb_descriptor_header *) &ss_intr_comp_desc,  
  12.   
  13.     /* data interface has no altsetting */  
  14.     (struct usb_descriptor_header *) &rndis_data_intf,  
  15.     (struct usb_descriptor_header *) &ss_in_desc,  
  16.     (struct usb_descriptor_header *) &ss_bulk_comp_desc,  
  17.     (struct usb_descriptor_header *) &ss_out_desc,  
  18.     (struct usb_descriptor_header *) &ss_bulk_comp_desc,  
  19.     NULL,  
  20. };  

前文分析過, 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功能。

  1. int  
  2. rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],  
  3.         u32 vendorID, const char *manufacturer, struct eth_dev *dev)  
  4. {  
  5. ...  
  6.     rndis = kzalloc(sizeof *rndis, GFP_KERNEL);  
  7.     if (!rndis)  
  8.         goto fail;  
  9.   
  10.     memcpy(rndis->ethaddr, ethaddr, ETH_ALEN);  
  11.     rndis->vendorID = vendorID;  
  12.     rndis->manufacturer = manufacturer;  
  13.   
  14.     rndis->port.ioport = dev;  
  15.     /* RNDIS activates when the host changes this filter */  
  16.     rndis->port.cdc_filter = 0;  
  17.   
  18.     /* RNDIS has special (and complex) framing */  
  19.     rndis->port.header_len = sizeof(struct rndis_packet_msg_type);  
  20.     rndis->port.wrap = rndis_add_header;  
  21.     rndis->port.unwrap = rndis_rm_hdr;  
  22.   
  23.     rndis->port.func.name = "rndis";  
  24.     rndis->port.func.strings = rndis_strings;  
  25.     /* descriptors are per-instance copies */  
  26.     rndis->port.func.bind = rndis_bind;  
  27.     rndis->port.func.unbind = rndis_unbind;  
  28.     rndis->port.func.set_alt = rndis_set_alt;  
  29.     rndis->port.func.setup = rndis_setup;  
  30.     rndis->port.func.disable = rndis_disable;  
  31.   
  32.     status = usb_add_function(c, &rndis->port.func);  
  33. ......  
  34. }   

usb_add_function() 將調用 rndis->port.func.bind 函數並返回其值,實際就是調用 rndis_bind() 函數。

rndis_bind() 進行以太網功能驅動的初始化和綁定操作。


通過 usb_interface_id() 分配usb 未使用的接口id值。 代碼中 status 應該更改爲 id 提高代碼可讀性。

  1. static int rndis_bind(struct usb_configuration *c, struct usb_function *f)  
  2. {  
  3. ......  
  4.     /* allocate instance-specific interface IDs */  
  5.     status = usb_interface_id(c, f);  
  6.     if (status < 0)  
  7.         goto fail;  
  8.     rndis->ctrl_id = status;  
  9.     rndis_iad_descriptor.bFirstInterface = status;  
  10.   
  11.     rndis_control_intf.bInterfaceNumber = status;  
  12.     rndis_union_desc.bMasterInterface0 = status;  
  13.   
  14.     status = usb_interface_id(c, f);  
  15.     if (status < 0)  
  16.         goto fail;  
  17.     rndis->data_id = status;  
  18.   
  19.     rndis_data_intf.bInterfaceNumber = status;  
  20.     rndis_union_desc.bSlaveInterface0 = status;  
  21. ......  
  22. }  

usb_interface_id() 是 drivers/usb/gadget/composite.c 的通用功能函數。 根據USB 2.0規範, 每個配置最大接口數 MAX_CONFIG_INTERFACES 爲16。

  1. /* 
  2.  * Returns the interface ID which was allocated; or -ENODEV if no 
  3.  * more interface IDs can be allocated. 
  4.  */  
  5. int usb_interface_id(struct usb_configuration *config,  
  6.         struct usb_function *function)  
  7. {  
  8.     unsigned id = config->next_interface_id;  
  9.   
  10.     if (id < MAX_CONFIG_INTERFACES) {  
  11.         config->interface[id] = function;  
  12.         config->next_interface_id = id + 1;  
  13.         return id;  
  14.     }  
  15.     return -ENODEV;  
  16. }  


隨後, rndis_bind() 分配和配置必要的 usb 端點資源
  1. ......  
  2.     /* allocate instance-specific endpoints */  
  3.     ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);  
  4.     rndis->port.in_ep = ep;  
  5.     ep->driver_data = cdev;  /* claim */  
  6.   
  7.     ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);  
  8.     rndis->port.out_ep = ep;  
  9.     ep->driver_data = cdev;  /* claim */  
  10.   
  11.     /* NOTE:  a status/notification endpoint is, strictly speaking, 
  12.      * optional.  We don't treat it that way though!  It's simpler, 
  13.      * and some newer profiles don't treat it as optional. 
  14.      */  
  15.     ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc);  
  16.     rndis->notify = ep;  
  17.     ep->driver_data = cdev;  /* claim */  
  18.   
  19.     /* allocate notification request and buffer */  
  20.     rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);  
  21.     rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);  
  22.     rndis->notify_req->length = STATUS_BYTECOUNT;  
  23.     rndis->notify_req->context = rndis;  
  24.     rndis->notify_req->complete = rndis_response_complete;  
  25. ......  

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() 放入隊列;否則丟掉後返回。

  1. static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req)  
  2. {  
  3.     struct f_rndis          *rndis = req->context;  
  4.     struct usb_composite_dev    *cdev = NULL;  
  5.     int             status = req->status;  
  6.   
  7.     /* In usb plug in/out and tetherring on/off 
  8.      * regression tests, port.func.config */  
  9.     /* may be NULL pointer.*/  
  10.     if (rndis->port.func.config != NULL)  
  11.         cdev = rndis->port.func.config->cdev;  
  12.     else  
  13.         printk(KERN_ERR "rndis gadget driver is removed.\n");  
  14.   
  15.     /* after TX: 
  16.      *  - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control) 
  17.      *  - RNDIS_RESPONSE_AVAILABLE (status/irq) 
  18.      */  
  19.     switch (status) {  
  20.     case -ECONNRESET:  
  21.     case -ESHUTDOWN:  
  22.         /* connection gone */  
  23.         atomic_set(&rndis->notify_count, 0);  
  24.         break;  
  25.     default:  
  26.         if (cdev != NULL)  
  27.             DBG(cdev, "RNDIS %s response error %d, %d/%d\n",  
  28.             ep->name, status,  
  29.             req->actual, req->length);  
  30.         /* FALLTHROUGH */  
  31.     case 0:  
  32.         if (ep != rndis->notify)  
  33.             break;  
  34.   
  35.         /* handle multiple pending RNDIS_RESPONSE_AVAILABLE 
  36.          * notifications by resending until we're done 
  37.          */  
  38.         if (atomic_dec_and_test(&rndis->notify_count))  
  39.             break;  
  40.         status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);  
  41.         if (status) {  
  42.             atomic_dec(&rndis->notify_count);  
  43.             if (cdev != NULL)  
  44.                 DBG(cdev, "notify/1 --> %d\n", status);  
  45.         }  
  46.         break;  
  47.     }  
  48. }  

回到 rndis_bind() 函數。
  • 它進一步指定 fast speed, high speed, super speed的描述符;
  • 註冊一個全局的函數指針,指向 rndis_response_available()。rndis_response_available() 函數發送 RNDIS RESPONSE_AVAILABLE 消息到端點。
  1. ......  
  2.     status = usb_assign_descriptors(f, eth_fs_function, eth_hs_function,  
  3.             eth_ss_function);  
  4.     if (status)  
  5.         goto fail;  
  6.   
  7.     rndis->port.open = rndis_open;  
  8.     rndis->port.close = rndis_close;  
  9.   
  10.     status = rndis_register(rndis_response_available, rndis);  
  11.     if (status < 0)  
  12.         goto fail;  
  13.     rndis->config = status;  
  14. ......  

rndis_setup()函數:
  • 使用CDC命令封裝機制來實現一個 RPC調用。
  • 只檢查一種 USB_DIR_OUT, 一種 USB_DIR_IN。
  1.     case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)  
  2.             | USB_CDC_SEND_ENCAPSULATED_COMMAND:  
  3.         if (w_value || w_index != rndis->ctrl_id)  
  4.             goto invalid;  
  5.         /* read the request; process it later */  
  6.         value = w_length;  
  7.         req->complete = rndis_command_complete;  
  8.         req->context = rndis;  
  9.         /* later, rndis_response_available() sends a notification */  
  10.         break;  
  11.   
  12.         if (w_value || w_index != rndis->ctrl_id)  
  13.             goto invalid;  
  14.         else {  
  15.             u8 *buf;  
  16.             u32 n;  
  17.   
  18.             /* return the result */  
  19.             buf = rndis_get_next_response(rndis->config, &n);  
  20.             if (buf) {  
  21.                 memcpy(req->buf, buf, n);  
  22.                 req->complete = rndis_response_complete;  
  23.                 req->context = rndis;  
  24.                 rndis_free_response(rndis->config, buf);  
  25.                 value = n;  
  26.             }  
  27.             /* else stalls ... spec says to avoid that */  
  28.         }  
  29.         break;  
rndis_set_alt()函數:
  • 對於控制id,調用 usb_ep_enable()
  • 對於數據id, 調用 gether_connect()

rndis_disable()函數:

  • 釋放資源
  • gether_disconnect() 斷開gether連接
  • 調用 usb_ep_disable() 關閉端點。

從上面分析可知, f_rndis.c 溝通的下層是  drivers/usb/gadget/u_ether.c。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章