From 8beabf5115c68d906bf187d0bd44f27eb6ae2dcd Mon Sep 17 00:00:00 2001 From: Vamsi Krishna Samavedam Date: Thu, 14 Aug 2014 14:48:44 +0800 Subject: [PATCH] usb: Add support for rndis uplink aggregation RNDIS protocol supports data aggregation on uplink and can help reduce mips by reducing number of interrupts on device. Throughput also improved by 20-30%. Aggregation is disabled by setting aggregation packet size to 1. To help better UL throughput, set as ul aggregation support to 3 rndis packets by default. It can be configured via sysfs device attribute ul_max_pkt_per_xfer. Change-Id: I689d37824e71835ed50bf15c3e35a0eced8f116d Signed-off-by: Vamsi Krishna Samavedam Signed-off-by: Xerox Lin Signed-off-by: Jack Pham --- drivers/usb/gadget/function/f_rndis.c | 15 ++++ drivers/usb/gadget/function/rndis.c | 84 ++++++++++++++----- drivers/usb/gadget/function/rndis.h | 2 + drivers/usb/gadget/function/u_ether.c | 27 ++++++ drivers/usb/gadget/function/u_ether.h | 17 ++++ .../usb/gadget/function/u_ether_configfs.h | 41 +++++++++ 6 files changed, 167 insertions(+), 19 deletions(-) diff --git a/drivers/usb/gadget/function/f_rndis.c b/drivers/usb/gadget/function/f_rndis.c index d48df36622b7..c2dfc6fdb34e 100644 --- a/drivers/usb/gadget/function/f_rndis.c +++ b/drivers/usb/gadget/function/f_rndis.c @@ -66,6 +66,8 @@ * - MS-Windows drivers sometimes emit undocumented requests. */ +#define RNDIS_UL_MAX_PKT_PER_XFER 3 + struct f_rndis { struct gether port; u8 ctrl_id, data_id; @@ -670,6 +672,7 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) struct usb_string *us; int status; struct usb_ep *ep; + unsigned int max; struct f_rndis_opts *rndis_opts; @@ -795,6 +798,11 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_set_param_medium(rndis->params, RNDIS_MEDIUM_802_3, 0); rndis_set_host_mac(rndis->params, rndis->ethaddr); + max = gether_get_ul_max_pkts_per_xfer(rndis_opts->net); + if (!max) + max = RNDIS_UL_MAX_PKT_PER_XFER; + + rndis_set_max_pkt_xfer(rndis->params, max); if (rndis->manufacturer && rndis->vendorID && rndis_set_param_vendor(rndis->params, rndis->vendorID, @@ -875,6 +883,10 @@ USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(rndis, subclass); /* f_rndis_opts_protocol */ USB_ETHER_CONFIGFS_ITEM_ATTR_U8_RW(rndis, protocol); +/* f_rndis_opts_ul_max_pkt_per_xfer */ +USB_ETHER_CONFIGFS_ITEM_ATTR_UL_MAX_PKT_PER_XFER(rndis); + + static struct configfs_attribute *rndis_attrs[] = { &rndis_opts_attr_dev_addr, &rndis_opts_attr_host_addr, @@ -883,6 +895,7 @@ static struct configfs_attribute *rndis_attrs[] = { &rndis_opts_attr_class, &rndis_opts_attr_subclass, &rndis_opts_attr_protocol, + &rndis_opts_attr_ul_max_pkt_per_xfer, NULL, }; @@ -1004,6 +1017,8 @@ static struct usb_function *rndis_alloc(struct usb_function_instance *fi) rndis->port.header_len = sizeof(struct rndis_packet_msg_type); rndis->port.wrap = rndis_add_header; rndis->port.unwrap = rndis_rm_hdr; + if (!gether_get_ul_max_pkts_per_xfer(opts->net)) + rndis->port.ul_max_pkts_per_xfer = RNDIS_UL_MAX_PKT_PER_XFER; rndis->port.func.name = "rndis"; /* descriptors are per-instance copies */ diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c index 04c142c13075..d568a44589b7 100644 --- a/drivers/usb/gadget/function/rndis.c +++ b/drivers/usb/gadget/function/rndis.c @@ -39,7 +39,6 @@ #include "rndis.h" - /* 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 +573,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_pkt_per_xfer); + resp->MaxTransferSize = cpu_to_le32(params->max_pkt_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); @@ -993,6 +992,13 @@ int rndis_set_param_medium(struct rndis_params *params, u32 medium, u32 speed) } EXPORT_SYMBOL_GPL(rndis_set_param_medium); +void rndis_set_max_pkt_xfer(struct rndis_params *params, u8 max_pkt_per_xfer) +{ + pr_debug("%s:\n", __func__); + + params->max_pkt_per_xfer = max_pkt_per_xfer; +} + void rndis_add_hdr(struct sk_buff *skb) { struct rndis_packet_msg_type *header; @@ -1059,23 +1065,63 @@ 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; + while (skb->len) { + struct rndis_packet_msg_type *hdr; + struct sk_buff *skb2; + u32 msg_len, data_offset, data_len; - /* MessageType, MessageLength */ - if (cpu_to_le32(RNDIS_MSG_PACKET) - != get_unaligned(tmp++)) { - dev_kfree_skb_any(skb); - return -EINVAL; - } - tmp++; + /* some rndis hosts send extra byte to avoid zlp, ignore it */ + if (skb->len == 1) { + dev_kfree_skb_any(skb); + return 0; + } - /* DataOffset, DataLength */ - if (!skb_pull(skb, get_unaligned_le32(tmp++) + 8)) { - dev_kfree_skb_any(skb); - return -EOVERFLOW; + if (skb->len < sizeof(*hdr)) { + pr_err("invalid rndis pkt: skblen:%u hdr_len:%lu\n", + skb->len, sizeof(*hdr)); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + 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); + dev_kfree_skb_any(skb); + return -EOVERFLOW; + } + 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); + dev_kfree_skb_any(skb); + return -EINVAL; + } + + skb_pull(skb, data_offset + 8); + + if (msg_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__); + dev_kfree_skb_any(skb); + return -ENOMEM; + } + + skb_pull(skb, msg_len - sizeof(*hdr)); + skb_trim(skb2, data_len); + skb_queue_tail(list, skb2); } - skb_trim(skb, get_unaligned_le32(tmp++)); skb_queue_tail(list, skb); return 0; diff --git a/drivers/usb/gadget/function/rndis.h b/drivers/usb/gadget/function/rndis.h index c7e3a70ce6c1..4bd6dc918a38 100644 --- a/drivers/usb/gadget/function/rndis.h +++ b/drivers/usb/gadget/function/rndis.h @@ -170,6 +170,7 @@ typedef struct rndis_params { struct net_device *dev; u32 vendorID; + u8 max_pkt_per_xfer; const char *vendorDescr; void (*resp_avail)(void *v); void *v; @@ -186,6 +187,7 @@ 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_set_max_pkt_xfer(struct rndis_params *params, u8 max_pkt_per_xfer); void rndis_add_hdr(struct sk_buff *skb); int rndis_rm_hdr(struct gether *port, struct sk_buff *skb, struct sk_buff_head *list); diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index fbe96ef1ac7a..05a01cce49b1 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -67,6 +67,7 @@ struct eth_dev { unsigned qmult; unsigned header_len; + unsigned int ul_max_pkts_per_xfer; struct sk_buff *(*wrap)(struct gether *, struct sk_buff *skb); int (*unwrap)(struct gether *, struct sk_buff *skb, @@ -213,10 +214,14 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) size -= size % out->maxpacket; } + if (dev->ul_max_pkts_per_xfer) + size *= dev->ul_max_pkts_per_xfer; + if (dev->port_usb->is_fixed) size = max_t(size_t, size, dev->port_usb->fixed_out_len); spin_unlock_irqrestore(&dev->lock, flags); + DBG(dev, "%s: size: %zd\n", __func__, size); skb = __netdev_alloc_skb(dev->net, size + NET_IP_ALIGN, gfp_flags); if (skb == NULL) { DBG(dev, "no rx skb\n"); @@ -1012,6 +1017,26 @@ int gether_get_ifname(struct net_device *net, char *name, int len) } EXPORT_SYMBOL_GPL(gether_get_ifname); +unsigned int gether_get_ul_max_pkts_per_xfer(struct net_device *net) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + return dev->ul_max_pkts_per_xfer; +} +EXPORT_SYMBOL(gether_get_ul_max_pkts_per_xfer); + +int gether_set_ul_max_pkts_per_xfer(struct net_device *net, unsigned int max) +{ + struct eth_dev *dev; + + dev = netdev_priv(net); + dev->ul_max_pkts_per_xfer = max; + + return 0; +} +EXPORT_SYMBOL(gether_set_ul_max_pkts_per_xfer); + /** * gether_cleanup - remove Ethernet-over-USB device * Context: may sleep @@ -1081,6 +1106,8 @@ struct net_device *gether_connect(struct gether *link) dev->header_len = link->header_len; dev->unwrap = link->unwrap; dev->wrap = link->wrap; + if (!dev->ul_max_pkts_per_xfer) + dev->ul_max_pkts_per_xfer = link->ul_max_pkts_per_xfer; spin_lock(&dev->lock); dev->port_usb = link; diff --git a/drivers/usb/gadget/function/u_ether.h b/drivers/usb/gadget/function/u_ether.h index 332307d54292..35810229ca48 100644 --- a/drivers/usb/gadget/function/u_ether.h +++ b/drivers/usb/gadget/function/u_ether.h @@ -69,6 +69,10 @@ struct gether { bool is_fixed; u32 fixed_out_len; u32 fixed_in_len; + unsigned int ul_max_pkts_per_xfer; +/* Max number of SKB packets to be used to create Multi Packet RNDIS */ +#define TX_SKB_HOLD_THRESHOLD 3 + bool multi_pkt_xfer; bool supports_multi_frame; struct sk_buff *(*wrap)(struct gether *port, struct sk_buff *skb); @@ -244,6 +248,19 @@ unsigned gether_get_qmult(struct net_device *net); */ int gether_get_ifname(struct net_device *net, char *name, int len); +/** + * gether_get_ul_max_pkts_per_xfer - get max pks/xfer for UL aggrregarion + * @net: device representing this link + */ +unsigned int gether_get_ul_max_pkts_per_xfer(struct net_device *net); + +/** + * gether_set_ul_max_pkts_per_xfer - set max pks/xfer for UL aggrregarion + * @net: device representing this link + * @max: max number of packets + */ +int gether_set_ul_max_pkts_per_xfer(struct net_device *net, unsigned int max); + void gether_cleanup(struct eth_dev *dev); /* connect/disconnect is handled by individual functions */ diff --git a/drivers/usb/gadget/function/u_ether_configfs.h b/drivers/usb/gadget/function/u_ether_configfs.h index d8b92485b727..f8b1a19f71cd 100644 --- a/drivers/usb/gadget/function/u_ether_configfs.h +++ b/drivers/usb/gadget/function/u_ether_configfs.h @@ -185,4 +185,45 @@ out: \ \ CONFIGFS_ATTR(_f_##_opts_, _n_) +#define USB_ETHER_CONFIGFS_ITEM_ATTR_UL_MAX_PKT_PER_XFER(_f_) \ + static ssize_t \ + _f_##_opts_ul_max_pkt_per_xfer_show(struct config_item *item,\ + char *page) \ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + unsigned int max; \ + \ + mutex_lock(&opts->lock); \ + max = gether_get_ul_max_pkts_per_xfer(opts->net); \ + mutex_unlock(&opts->lock); \ + return scnprintf(page, PAGE_SIZE, "%d\n", max); \ + } \ + \ + static ssize_t \ + _f_##_opts_ul_max_pkt_per_xfer_store(struct config_item *item,\ + const char *page, size_t len)\ + { \ + struct f_##_f_##_opts *opts = to_f_##_f_##_opts(item); \ + u8 val; \ + int ret; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto out; \ + } \ + \ + ret = kstrtou8(page, 0, &val); \ + if (ret) \ + goto out; \ + \ + gether_set_ul_max_pkts_per_xfer(opts->net, val); \ + ret = len; \ +out: \ + mutex_unlock(&opts->lock); \ + return ret; \ + } \ + \ + CONFIGFS_ATTR(_f_##_opts_, ul_max_pkt_per_xfer) + #endif /* __U_ETHER_CONFIGFS_H */