Android f_rndis 分析笔记

时间:2021-12-21 06:36:06

背景说明

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;
}


随后, rndis_bind() 分配和配置必要的 usb 端点资源
......
	/* 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;

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。