As ncm, aggergate multi skb packages and transfer them at one URB. In USB2.0, the network throughput can be improved from about 18MB/S to 35MB/S. Signed-off-by: Surong Pang <surong.pang@xxxxxxxxxx> --- drivers/usb/gadget/function/f_rndis.c | 123 ++++++++++++++++++++--- drivers/usb/gadget/function/rndis.c | 135 +++++++++++++++++++++----- drivers/usb/gadget/function/rndis.h | 13 ++- 3 files changed, 234 insertions(+), 37 deletions(-) diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c index b47f99d17ee9..a87497b80946 100644 --- a/drivers/usb/gadget/function/f_rndis.c +++ b/drivers/usb/gadget/function/f_rndis.c @@ -77,6 +77,12 @@ struct f_rndis { struct usb_ep *notify; struct usb_request *notify_req; atomic_t notify_count; + + struct net_device *netdev; + /* For multi-frame RNDIS TX */ + u16 prepared_tx_skb_count; + struct sk_buff *prepared_tx_skb; + struct hrtimer task_timer; }; static inline struct f_rndis *func_to_rndis(struct usb_function *f) @@ -92,6 +98,7 @@ static inline struct f_rndis *func_to_rndis(struct usb_function *f) #define RNDIS_STATUS_INTERVAL_MS 32 #define STATUS_BYTECOUNT 8 /* 8 bytes data */ +#define TX_TIMEOUT_NSECS 200000 /* interface descriptor: */ @@ -102,9 +109,9 @@ static struct usb_interface_descriptor rndis_control_intf = { /* .bInterfaceNumber = DYNAMIC */ /* 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, + .bInterfaceClass = USB_CLASS_WIRELESS_CONTROLLER, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_PCCA101_WAKE, /* .iInterface = DYNAMIC */ }; @@ -162,10 +169,10 @@ rndis_iad_descriptor = { .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, .bFirstInterface = 0, /* XXX, hardcoded */ - .bInterfaceCount = 2, // control + data - .bFunctionClass = USB_CLASS_COMM, - .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, - .bFunctionProtocol = USB_CDC_PROTO_NONE, + .bInterfaceCount = 2, // control + data + .bFunctionClass = USB_CLASS_WIRELESS_CONTROLLER, + .bFunctionSubClass = 0x01, + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_PCCA101_WAKE, /* .iFunction = DYNAMIC */ }; @@ -352,20 +359,104 @@ static struct usb_gadget_strings *rndis_strings[] = { NULL, }; +/* + * The transmit should only be run if no skb data has been sent + * for a certain duration. + */ +static enum hrtimer_restart rndis_tx_timeout(struct hrtimer *data) +{ + struct f_rndis *rndis = container_of(data, struct f_rndis, task_timer); + struct net_device *netdev = READ_ONCE(rndis->netdev); + + if (netdev) { + /* XXX This allowance of a NULL skb argument to ndo_start_xmit + * XXX is not sane. The gadget layer should be redesigned so + * XXX that the dev->wrap() invocations to build SKBs is transparent + * XXX and performed in some way outside of the ndo_start_xmit + * XXX interface. + * + * This will call directly into u_ether's eth_start_xmit() + */ + netdev->netdev_ops->ndo_start_xmit(NULL, netdev); + } + return HRTIMER_NORESTART; +} + +static struct sk_buff *package_for_tx(struct f_rndis *rndis) +{ + struct sk_buff *skb = NULL; + + /* Stop the timer */ + hrtimer_try_to_cancel(&rndis->task_timer); + + /* Merge the skbs */ + swap(skb, rndis->prepared_tx_skb); + + return skb; +} + /*-------------------------------------------------------------------------*/ static struct sk_buff *rndis_add_header(struct gether *port, struct sk_buff *skb) { - struct sk_buff *skb2; + struct f_rndis *rndis = func_to_rndis(&port->func); + struct usb_composite_dev *cdev = rndis->port.func.config->cdev; + struct rndis_params *params = rndis->params; + struct sk_buff *skb2 = NULL; + int head_len = sizeof(struct rndis_packet_msg_type); + + if (skb) { + if (rndis->prepared_tx_skb && + (rndis->prepared_tx_skb_count >= params->max_in_pkts_per_xfer || + (rndis->prepared_tx_skb->len + skb->len + head_len) >= + params->max_in_size_per_xfer)) { + DBG(cdev, "prepared tx skb count %d, len %d\n", + rndis->prepared_tx_skb_count, rndis->prepared_tx_skb->len); + skb2 = package_for_tx(rndis); + } + + if (!rndis->prepared_tx_skb) { + /* Create a new skb for multi xfer. */ + DBG(cdev, "create a new multi skb, len %d\n", params->max_in_size_per_xfer); + + rndis->prepared_tx_skb = + alloc_skb(params->max_in_size_per_xfer, GFP_ATOMIC); + if (!rndis->prepared_tx_skb) + goto err; - if (!skb) - return NULL; + rndis->prepared_tx_skb->dev = rndis->netdev; + rndis->prepared_tx_skb_count = 0; + /* Start the timer. */ + hrtimer_start(&rndis->task_timer, TX_TIMEOUT_NSECS, + HRTIMER_MODE_REL_SOFT); + } + + /* + * Add the new data to the skb + * PacketAlignmentFactor is 0, no need to add padding + */ + rndis_copy_hdr(rndis->prepared_tx_skb, skb); + skb_put_data(rndis->prepared_tx_skb, skb->data, skb->len); + rndis->prepared_tx_skb_count++; + dev_consume_skb_any(skb); + skb = NULL; + } else if (rndis->prepared_tx_skb) { + /* If we get here eth_start_xmit() was called with NULL skb by + * rndis_tx_timeout() - hence, this is our signal to flush/send. + */ + DBG(cdev, "timer expired, prepared tx skb count %d, len %d\n", + rndis->prepared_tx_skb_count, rndis->prepared_tx_skb->len); + skb2 = package_for_tx(rndis); + } + return skb2; + +err: skb2 = skb_realloc_headroom(skb, sizeof(struct rndis_packet_msg_type)); rndis_add_hdr(skb2); - dev_kfree_skb(skb); + dev_consume_skb_any(skb); return skb2; } @@ -546,6 +637,7 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (rndis->port.in_ep->enabled) { DBG(cdev, "reset rndis\n"); + rndis->netdev = NULL; gether_disconnect(&rndis->port); } @@ -582,8 +674,9 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) net = gether_connect(&rndis->port); if (IS_ERR(net)) return PTR_ERR(net); + rndis->netdev = net; - rndis_set_param_dev(rndis->params, net, + rndis_set_param_dev(&rndis->port, rndis->params, net, &rndis->port.cdc_filter); } else goto fail; @@ -604,6 +697,7 @@ static void rndis_disable(struct usb_function *f) DBG(cdev, "rndis deactivated\n"); rndis_uninit(rndis->params); + rndis->netdev = NULL; gether_disconnect(&rndis->port); usb_ep_disable(rndis->notify); @@ -793,6 +887,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) goto fail_free_descs; } + hrtimer_init(&rndis->task_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + rndis->task_timer.function = rndis_tx_timeout; + /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code * until we're activated via set_alt(). @@ -956,6 +1053,8 @@ static void rndis_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_rndis *rndis = func_to_rndis(f); + hrtimer_cancel(&rndis->task_timer); + kfree(f->os_desc_table); f->os_desc_n = 0; usb_free_all_descriptors(f); diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c index 29bf8664bf58..fd321b53e46f 100644 --- a/drivers/usb/gadget/function/rndis.c +++ b/drivers/usb/gadget/function/rndis.c @@ -39,6 +39,8 @@ #include "rndis.h" +static int max_out_pkts_per_xfer; +static int max_out_size_per_xfer; /* The driver for your USB chip needs to support ep0 OUT to work with * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional). @@ -574,12 +576,12 @@ static int rndis_init_response(struct rndis_params *params, resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION); resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS); resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3); - resp->MaxPacketsPerTransfer = cpu_to_le32(1); - resp->MaxTransferSize = cpu_to_le32( - params->dev->mtu + resp->MaxPacketsPerTransfer = cpu_to_le32(params->max_out_pkts_per_xfer); + resp->MaxTransferSize = cpu_to_le32(params->max_out_pkts_per_xfer * + (params->dev->mtu + sizeof(struct ethhdr) + sizeof(struct rndis_packet_msg_type) - + 22); + + 22)); resp->PacketAlignmentFactor = cpu_to_le32(0); resp->AFListOffset = cpu_to_le32(0); resp->AFListSize = cpu_to_le32(0); @@ -790,7 +792,7 @@ EXPORT_SYMBOL_GPL(rndis_set_host_mac); */ int rndis_msg_parser(struct rndis_params *params, u8 *buf) { - u32 MsgType, MsgLength; + u32 MsgType, MsgLength, RequestID, MajorVersion, MinorVersion, MaxTransferSize; __le32 *tmp; if (!buf) @@ -813,7 +815,12 @@ int rndis_msg_parser(struct rndis_params *params, u8 *buf) case RNDIS_MSG_INIT: pr_debug("%s: RNDIS_MSG_INIT\n", __func__); + RequestID = get_unaligned_le32(tmp++); + MajorVersion = get_unaligned_le32(tmp++); + MinorVersion = get_unaligned_le32(tmp++); + MaxTransferSize = get_unaligned_le32(tmp++); params->state = RNDIS_INITIALIZED; + params->max_in_size_per_xfer = MaxTransferSize; return rndis_init_response(params, (rndis_init_msg_type *)buf); case RNDIS_MSG_HALT: @@ -922,6 +929,8 @@ struct rndis_params *rndis_register(void (*resp_avail)(void *v), void *v) params->media_state = RNDIS_MEDIA_STATE_DISCONNECTED; params->resp_avail = resp_avail; params->v = v; + params->max_in_pkts_per_xfer = RNDIS_MAX_IN_PKTS_PER_XFER; + params->max_out_pkts_per_xfer = RNDIS_MAX_OUT_PKTS_PER_XFER; INIT_LIST_HEAD(¶ms->resp_queue); spin_lock_init(¶ms->resp_lock); pr_debug("%s: configNr = %d\n", __func__, i); @@ -954,8 +963,8 @@ void rndis_deregister(struct rndis_params *params) rndis_put_nr(i); } EXPORT_SYMBOL_GPL(rndis_deregister); -int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev, - u16 *cdc_filter) +int rndis_set_param_dev(struct gether *port, struct rndis_params *params, + struct net_device *dev, u16 *cdc_filter) { pr_debug("%s:\n", __func__); if (!dev) @@ -965,7 +974,18 @@ int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev, params->dev = dev; params->filter = cdc_filter; - + params->max_out_size_per_xfer = (params->max_out_pkts_per_xfer * + (dev->mtu + + sizeof(struct ethhdr) + + sizeof(struct rndis_packet_msg_type) + + 22)); + port->is_fixed = true; + port->fixed_out_len = params->max_out_size_per_xfer; + + pr_debug("mtu %d, fixed_out_len %d\n", dev->mtu, port->fixed_out_len); + + max_out_pkts_per_xfer = params->max_out_pkts_per_xfer; + max_out_size_per_xfer = params->max_out_size_per_xfer; return 0; } EXPORT_SYMBOL_GPL(rndis_set_param_dev); @@ -1013,6 +1033,23 @@ void rndis_add_hdr(struct sk_buff *skb) } EXPORT_SYMBOL_GPL(rndis_add_hdr); +void rndis_copy_hdr(struct sk_buff *dest_skb, struct sk_buff *new_skb) +{ + struct rndis_packet_msg_type header; + int head_len = sizeof(header); + + if (!dest_skb || !new_skb) + return; + + memset(&header, 0, head_len); + header.MessageType = cpu_to_le32(RNDIS_MSG_PACKET); + header.MessageLength = cpu_to_le32(head_len + new_skb->len); + header.DataOffset = cpu_to_le32(36); + header.DataLength = cpu_to_le32(new_skb->len); + skb_put_data(dest_skb, &header, head_len); +} +EXPORT_SYMBOL_GPL(rndis_copy_hdr); + void rndis_free_response(struct rndis_params *params, u8 *buf) { rndis_resp_t *r, *n; @@ -1071,26 +1108,78 @@ int rndis_rm_hdr(struct gether *port, struct sk_buff *skb, struct sk_buff_head *list) { - /* tmp points to a struct rndis_packet_msg_type */ - __le32 *tmp = (void *)skb->data; + int ret = 0; + int num_pkts = 1; - /* MessageType, MessageLength */ - if (cpu_to_le32(RNDIS_MSG_PACKET) - != get_unaligned(tmp++)) { - dev_kfree_skb_any(skb); - return -EINVAL; - } - tmp++; + while (skb->len) { + struct rndis_packet_msg_type *hdr; + struct sk_buff *skb2; + u32 msg_len, data_offset, data_len; + + /* some rndis hosts send extra byte to avoid zlp, ignore it */ + if (skb->len == 1) { + pr_info("skb len 1, should ignore!\n"); + break; + } + + if (skb->len < sizeof(*hdr)) { + pr_err("invalid rndis pkt: skblen:%u hdr_len:%zu", + skb->len, sizeof(*hdr)); + skb->len = 0; + ret = -EINVAL; + break; + } - /* DataOffset, DataLength */ - if (!skb_pull(skb, get_unaligned_le32(tmp++) + 8)) { - dev_kfree_skb_any(skb); - return -EOVERFLOW; + hdr = (void *)skb->data; + msg_len = le32_to_cpu(hdr->MessageLength); + data_offset = le32_to_cpu(hdr->DataOffset); + data_len = le32_to_cpu(hdr->DataLength); + + if (skb->len < msg_len || + ((data_offset + data_len + 8) > msg_len)) { + pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n", + le32_to_cpu(hdr->MessageType), + msg_len, data_offset, data_len, skb->len); + skb->len = 0; + ret = -EOVERFLOW; + break; + } + if (le32_to_cpu(hdr->MessageType) != RNDIS_MSG_PACKET) { + pr_err("invalid rndis message: %d/%d/%d/%d, len:%d\n", + le32_to_cpu(hdr->MessageType), msg_len, + data_offset, data_len, skb->len); + skb->len = 0; + ret = -EINVAL; + break; + } + + skb_pull(skb, data_offset + 8); + + if (data_len == skb->len) { + skb_trim(skb, data_len); + break; + } + + skb2 = skb_clone(skb, GFP_ATOMIC); + if (!skb2) { + pr_err("%s:skb clone failed\n", __func__); + skb->len = 0; + ret = -ENOMEM; + break; + } + + skb_pull(skb, msg_len - (data_offset + 8)); + skb_trim(skb2, data_len); + skb_queue_tail(list, skb2); + + num_pkts++; } - skb_trim(skb, get_unaligned_le32(tmp++)); + + if (num_pkts > max_out_pkts_per_xfer) + pr_err("max out pkts per xfer rcvd %d\n", num_pkts); skb_queue_tail(list, skb); - return 0; + return ret; } EXPORT_SYMBOL_GPL(rndis_rm_hdr); diff --git a/drivers/usb/gadget/function/rndis.h b/drivers/usb/gadget/function/rndis.h index 6206b8b7490f..d6acbc1577f8 100644 --- a/drivers/usb/gadget/function/rndis.h +++ b/drivers/usb/gadget/function/rndis.h @@ -19,6 +19,9 @@ #define RNDIS_MAXIMUM_FRAME_SIZE 1518 #define RNDIS_MAX_TOTAL_SIZE 1558 +#define RNDIS_MAX_IN_PKTS_PER_XFER 10 +#define RNDIS_MAX_OUT_PKTS_PER_XFER 3 + typedef struct rndis_init_msg_type { __le32 MessageType; __le32 MessageLength; @@ -175,18 +178,24 @@ typedef struct rndis_params { void *v; struct list_head resp_queue; spinlock_t resp_lock; + + u32 max_in_size_per_xfer; + u32 max_in_pkts_per_xfer; + u32 max_out_size_per_xfer; + u32 max_out_pkts_per_xfer; } rndis_params; /* RNDIS Message parser and other useless functions */ int rndis_msg_parser(struct rndis_params *params, u8 *buf); struct rndis_params *rndis_register(void (*resp_avail)(void *v), void *v); void rndis_deregister(struct rndis_params *params); -int rndis_set_param_dev(struct rndis_params *params, struct net_device *dev, - u16 *cdc_filter); +int rndis_set_param_dev(struct gether *port, struct rndis_params *params, + struct net_device *dev, u16 *cdc_filter); int rndis_set_param_vendor(struct rndis_params *params, u32 vendorID, const char *vendorDescr); int rndis_set_param_medium(struct rndis_params *params, u32 medium, u32 speed); +void rndis_copy_hdr(struct sk_buff *dest_skb, struct sk_buff *new_skb); void rndis_add_hdr(struct sk_buff *skb); int rndis_rm_hdr(struct gether *port, struct sk_buff *skb, struct sk_buff_head *list); -- 2.34.1