Commit 6d3865f9 authored by Jim Baxter's avatar Jim Baxter Committed by Felipe Balbi

usb: gadget: NCM: Add transmit multi-frame.

This adds multi-frame support to the NCM NTB's for
the gadget driver. This allows multiple network
packets to be put inside a single USB NTB with a
maximum size of 16kB.

It has a time out of 300ms to ensure that smaller
number of packets still maintain a normal latency.

Also the .fp_index and .next_fp_index have been
changed to .ndp_index and .next_ndp_index to
match the latest CDC-NCM specification and
help with maintenance.

Results transmitting from gadget to host.

Before the change:

TCP_STREAM Throughput (10^6bits/sec): 22.72
UDP_STREAM Throughput (10^6bits/sec): 25.94

Latency:
netperf -H 192.168.1.101 -v2 -l 50 -t TCP_RR -- -r 16384,16384
Trans.   RoundTrip  Throughput
Rate     Latency    10^6bits/s
per sec  usec/Tran  Outbound

100.83   9918.116   13.215

After the change:

TCP_STREAM Throughput (10^6bits/sec): 124.26
UDP_STREAM Throughput (10^6bits/sec): 227.48

Latency:
netperf -H 192.168.1.101 -v2 -l 50 -t TCP_RR -- -r 16384,16384
Trans.   RoundTrip  Throughput
Rate     Latency    10^6bits/s
per sec  usec/Tran  Outbound

156.80   6377.730   20.552
Signed-off-by: default avatarJim Baxter <jim_baxter@mentor.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 370af734
...@@ -68,6 +68,18 @@ struct f_ncm { ...@@ -68,6 +68,18 @@ struct f_ncm {
* callback and ethernet open/close * callback and ethernet open/close
*/ */
spinlock_t lock; spinlock_t lock;
struct net_device *netdev;
/* For multi-frame NDP TX */
struct sk_buff *skb_tx_data;
struct sk_buff *skb_tx_ndp;
u16 ndp_dgram_count;
bool timer_force_tx;
struct tasklet_struct tx_tasklet;
struct hrtimer task_timer;
bool timer_stopping;
}; };
static inline struct f_ncm *func_to_ncm(struct usb_function *f) static inline struct f_ncm *func_to_ncm(struct usb_function *f)
...@@ -92,15 +104,20 @@ static inline unsigned ncm_bitrate(struct usb_gadget *g) ...@@ -92,15 +104,20 @@ static inline unsigned ncm_bitrate(struct usb_gadget *g)
* If the host can group frames, allow it to do that, 16K is selected, * If the host can group frames, allow it to do that, 16K is selected,
* because it's used by default by the current linux host driver * because it's used by default by the current linux host driver
*/ */
#define NTB_DEFAULT_IN_SIZE USB_CDC_NCM_NTB_MIN_IN_SIZE #define NTB_DEFAULT_IN_SIZE 16384
#define NTB_OUT_SIZE 16384 #define NTB_OUT_SIZE 16384
/* /* Allocation for storing the NDP, 32 should suffice for a
* skbs of size less than that will not be aligned * 16k packet. This allows a maximum of 32 * 507 Byte packets to
* to NCM's dwNtbInMaxSize to save bus bandwidth * be transmitted in a single 16kB skb, though when sending full size
* packets this limit will be plenty.
* Smaller packets are not likely to be trying to maximize the
* throughput and will be mstly sending smaller infrequent frames.
*/ */
#define TX_MAX_NUM_DPE 32
#define MAX_TX_NONFIXED (512 * 3) /* Delay for the transmit to wait before sending an unfilled NTB frame. */
#define TX_TIMEOUT_NSECS 300000
#define FORMATS_SUPPORTED (USB_CDC_NCM_NTB16_SUPPORTED | \ #define FORMATS_SUPPORTED (USB_CDC_NCM_NTB16_SUPPORTED | \
USB_CDC_NCM_NTB32_SUPPORTED) USB_CDC_NCM_NTB32_SUPPORTED)
...@@ -355,14 +372,15 @@ struct ndp_parser_opts { ...@@ -355,14 +372,15 @@ struct ndp_parser_opts {
u32 ndp_sign; u32 ndp_sign;
unsigned nth_size; unsigned nth_size;
unsigned ndp_size; unsigned ndp_size;
unsigned dpe_size;
unsigned ndplen_align; unsigned ndplen_align;
/* sizes in u16 units */ /* sizes in u16 units */
unsigned dgram_item_len; /* index or length */ unsigned dgram_item_len; /* index or length */
unsigned block_length; unsigned block_length;
unsigned fp_index; unsigned ndp_index;
unsigned reserved1; unsigned reserved1;
unsigned reserved2; unsigned reserved2;
unsigned next_fp_index; unsigned next_ndp_index;
}; };
#define INIT_NDP16_OPTS { \ #define INIT_NDP16_OPTS { \
...@@ -370,13 +388,14 @@ struct ndp_parser_opts { ...@@ -370,13 +388,14 @@ struct ndp_parser_opts {
.ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN, \ .ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN, \
.nth_size = sizeof(struct usb_cdc_ncm_nth16), \ .nth_size = sizeof(struct usb_cdc_ncm_nth16), \
.ndp_size = sizeof(struct usb_cdc_ncm_ndp16), \ .ndp_size = sizeof(struct usb_cdc_ncm_ndp16), \
.dpe_size = sizeof(struct usb_cdc_ncm_dpe16), \
.ndplen_align = 4, \ .ndplen_align = 4, \
.dgram_item_len = 1, \ .dgram_item_len = 1, \
.block_length = 1, \ .block_length = 1, \
.fp_index = 1, \ .ndp_index = 1, \
.reserved1 = 0, \ .reserved1 = 0, \
.reserved2 = 0, \ .reserved2 = 0, \
.next_fp_index = 1, \ .next_ndp_index = 1, \
} }
...@@ -385,13 +404,14 @@ struct ndp_parser_opts { ...@@ -385,13 +404,14 @@ struct ndp_parser_opts {
.ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN, \ .ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN, \
.nth_size = sizeof(struct usb_cdc_ncm_nth32), \ .nth_size = sizeof(struct usb_cdc_ncm_nth32), \
.ndp_size = sizeof(struct usb_cdc_ncm_ndp32), \ .ndp_size = sizeof(struct usb_cdc_ncm_ndp32), \
.dpe_size = sizeof(struct usb_cdc_ncm_dpe32), \
.ndplen_align = 8, \ .ndplen_align = 8, \
.dgram_item_len = 2, \ .dgram_item_len = 2, \
.block_length = 2, \ .block_length = 2, \
.fp_index = 2, \ .ndp_index = 2, \
.reserved1 = 1, \ .reserved1 = 1, \
.reserved2 = 2, \ .reserved2 = 2, \
.next_fp_index = 2, \ .next_ndp_index = 2, \
} }
static const struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS; static const struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS;
...@@ -803,6 +823,8 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) ...@@ -803,6 +823,8 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (ncm->port.in_ep->driver_data) { if (ncm->port.in_ep->driver_data) {
DBG(cdev, "reset ncm\n"); DBG(cdev, "reset ncm\n");
ncm->timer_stopping = true;
ncm->netdev = NULL;
gether_disconnect(&ncm->port); gether_disconnect(&ncm->port);
ncm_reset_values(ncm); ncm_reset_values(ncm);
} }
...@@ -839,6 +861,8 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) ...@@ -839,6 +861,8 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
net = gether_connect(&ncm->port); net = gether_connect(&ncm->port);
if (IS_ERR(net)) if (IS_ERR(net))
return PTR_ERR(net); return PTR_ERR(net);
ncm->netdev = net;
ncm->timer_stopping = false;
} }
spin_lock(&ncm->lock); spin_lock(&ncm->lock);
...@@ -865,95 +889,232 @@ static int ncm_get_alt(struct usb_function *f, unsigned intf) ...@@ -865,95 +889,232 @@ static int ncm_get_alt(struct usb_function *f, unsigned intf)
return ncm->port.in_ep->driver_data ? 1 : 0; return ncm->port.in_ep->driver_data ? 1 : 0;
} }
static struct sk_buff *package_for_tx(struct f_ncm *ncm)
{
__le16 *ntb_iter;
struct sk_buff *skb2 = NULL;
unsigned ndp_pad;
unsigned ndp_index;
unsigned new_len;
const struct ndp_parser_opts *opts = ncm->parser_opts;
const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment);
const int dgram_idx_len = 2 * 2 * opts->dgram_item_len;
/* Stop the timer */
hrtimer_try_to_cancel(&ncm->task_timer);
ndp_pad = ALIGN(ncm->skb_tx_data->len, ndp_align) -
ncm->skb_tx_data->len;
ndp_index = ncm->skb_tx_data->len + ndp_pad;
new_len = ndp_index + dgram_idx_len + ncm->skb_tx_ndp->len;
/* Set the final BlockLength and wNdpIndex */
ntb_iter = (void *) ncm->skb_tx_data->data;
/* Increment pointer to BlockLength */
ntb_iter += 2 + 1 + 1;
put_ncm(&ntb_iter, opts->block_length, new_len);
put_ncm(&ntb_iter, opts->ndp_index, ndp_index);
/* Set the final NDP wLength */
new_len = opts->ndp_size +
(ncm->ndp_dgram_count * dgram_idx_len);
ncm->ndp_dgram_count = 0;
/* Increment from start to wLength */
ntb_iter = (void *) ncm->skb_tx_ndp->data;
ntb_iter += 2;
put_unaligned_le16(new_len, ntb_iter);
/* Merge the skbs */
swap(skb2, ncm->skb_tx_data);
if (ncm->skb_tx_data) {
dev_kfree_skb_any(ncm->skb_tx_data);
ncm->skb_tx_data = NULL;
}
/* Insert NDP alignment. */
ntb_iter = (void *) skb_put(skb2, ndp_pad);
memset(ntb_iter, 0, ndp_pad);
/* Copy NTB across. */
ntb_iter = (void *) skb_put(skb2, ncm->skb_tx_ndp->len);
memcpy(ntb_iter, ncm->skb_tx_ndp->data, ncm->skb_tx_ndp->len);
dev_kfree_skb_any(ncm->skb_tx_ndp);
ncm->skb_tx_ndp = NULL;
/* Insert zero'd datagram. */
ntb_iter = (void *) skb_put(skb2, dgram_idx_len);
memset(ntb_iter, 0, dgram_idx_len);
return skb2;
}
static struct sk_buff *ncm_wrap_ntb(struct gether *port, static struct sk_buff *ncm_wrap_ntb(struct gether *port,
struct sk_buff *skb) struct sk_buff *skb)
{ {
struct f_ncm *ncm = func_to_ncm(&port->func); struct f_ncm *ncm = func_to_ncm(&port->func);
struct sk_buff *skb2; struct sk_buff *skb2 = NULL;
int ncb_len = 0; int ncb_len = 0;
__le16 *tmp; __le16 *ntb_data;
int div; __le16 *ntb_ndp;
int rem; int dgram_pad;
int pad;
int ndp_align;
int ndp_pad;
unsigned max_size = ncm->port.fixed_in_len; unsigned max_size = ncm->port.fixed_in_len;
const struct ndp_parser_opts *opts = ncm->parser_opts; const struct ndp_parser_opts *opts = ncm->parser_opts;
unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0; const int ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment);
const int div = le16_to_cpu(ntb_parameters.wNdpInDivisor);
div = le16_to_cpu(ntb_parameters.wNdpInDivisor); const int rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder);
rem = le16_to_cpu(ntb_parameters.wNdpInPayloadRemainder); const int dgram_idx_len = 2 * 2 * opts->dgram_item_len;
ndp_align = le16_to_cpu(ntb_parameters.wNdpInAlignment);
ncb_len += opts->nth_size;
ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
ncb_len += ndp_pad;
ncb_len += opts->ndp_size;
ncb_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */
ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
pad = ALIGN(ncb_len, div) + rem - ncb_len;
ncb_len += pad;
if (ncb_len + skb->len + crc_len > max_size) { if (!skb && !ncm->skb_tx_data)
dev_kfree_skb_any(skb);
return NULL; return NULL;
}
skb2 = skb_copy_expand(skb, ncb_len, if (skb) {
max_size - skb->len - ncb_len - crc_len, /* Add the CRC if required up front */
GFP_ATOMIC); if (ncm->is_crc) {
dev_kfree_skb_any(skb); uint32_t crc;
if (!skb2) __le16 *crc_pos;
return NULL;
crc = ~crc32_le(~0,
skb->data,
skb->len);
crc_pos = (void *) skb_put(skb, sizeof(uint32_t));
put_unaligned_le32(crc, crc_pos);
}
skb = skb2; /* If the new skb is too big for the current NCM NTB then
* set the current stored skb to be sent now and clear it
* ready for new data.
* NOTE: Assume maximum align for speed of calculation.
*/
if (ncm->skb_tx_data
&& (ncm->ndp_dgram_count >= TX_MAX_NUM_DPE
|| (ncm->skb_tx_data->len +
div + rem + skb->len +
ncm->skb_tx_ndp->len + ndp_align + (2 * dgram_idx_len))
> max_size)) {
skb2 = package_for_tx(ncm);
if (!skb2)
goto err;
}
tmp = (void *) skb_push(skb, ncb_len); if (!ncm->skb_tx_data) {
memset(tmp, 0, ncb_len); ncb_len = opts->nth_size;
dgram_pad = ALIGN(ncb_len, div) + rem - ncb_len;
ncb_len += dgram_pad;
put_unaligned_le32(opts->nth_sign, tmp); /* dwSignature */ /* Create a new skb for the NTH and datagrams. */
tmp += 2; ncm->skb_tx_data = alloc_skb(max_size, GFP_ATOMIC);
/* wHeaderLength */ if (!ncm->skb_tx_data)
put_unaligned_le16(opts->nth_size, tmp++); goto err;
tmp++; /* skip wSequence */
put_ncm(&tmp, opts->block_length, skb->len); /* (d)wBlockLength */
/* (d)wFpIndex */
/* the first pointer is right after the NTH + align */
put_ncm(&tmp, opts->fp_index, opts->nth_size + ndp_pad);
tmp = (void *)tmp + ndp_pad; ntb_data = (void *) skb_put(ncm->skb_tx_data, ncb_len);
memset(ntb_data, 0, ncb_len);
/* dwSignature */
put_unaligned_le32(opts->nth_sign, ntb_data);
ntb_data += 2;
/* wHeaderLength */
put_unaligned_le16(opts->nth_size, ntb_data++);
/* Allocate an skb for storing the NDP,
* TX_MAX_NUM_DPE should easily suffice for a
* 16k packet.
*/
ncm->skb_tx_ndp = alloc_skb((int)(opts->ndp_size
+ opts->dpe_size
* TX_MAX_NUM_DPE),
GFP_ATOMIC);
if (!ncm->skb_tx_ndp)
goto err;
ntb_ndp = (void *) skb_put(ncm->skb_tx_ndp,
opts->ndp_size);
memset(ntb_ndp, 0, ncb_len);
/* dwSignature */
put_unaligned_le32(ncm->ndp_sign, ntb_ndp);
ntb_ndp += 2;
/* NDP */ /* There is always a zeroed entry */
put_unaligned_le32(ncm->ndp_sign, tmp); /* dwSignature */ ncm->ndp_dgram_count = 1;
tmp += 2;
/* wLength */
put_unaligned_le16(ncb_len - opts->nth_size - pad, tmp++);
tmp += opts->reserved1; /* Note: we skip opts->next_ndp_index */
tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */ }
tmp += opts->reserved2;
if (ncm->is_crc) { /* Delay the timer. */
uint32_t crc; hrtimer_start(&ncm->task_timer,
ktime_set(0, TX_TIMEOUT_NSECS),
HRTIMER_MODE_REL);
/* Add the datagram position entries */
ntb_ndp = (void *) skb_put(ncm->skb_tx_ndp, dgram_idx_len);
memset(ntb_ndp, 0, dgram_idx_len);
ncb_len = ncm->skb_tx_data->len;
dgram_pad = ALIGN(ncb_len, div) + rem - ncb_len;
ncb_len += dgram_pad;
/* (d)wDatagramIndex */
put_ncm(&ntb_ndp, opts->dgram_item_len, ncb_len);
/* (d)wDatagramLength */
put_ncm(&ntb_ndp, opts->dgram_item_len, skb->len);
ncm->ndp_dgram_count++;
/* Add the new data to the skb */
ntb_data = (void *) skb_put(ncm->skb_tx_data, dgram_pad);
memset(ntb_data, 0, dgram_pad);
ntb_data = (void *) skb_put(ncm->skb_tx_data, skb->len);
memcpy(ntb_data, skb->data, skb->len);
dev_kfree_skb_any(skb);
skb = NULL;
crc = ~crc32_le(~0, } else if (ncm->skb_tx_data && ncm->timer_force_tx) {
skb->data + ncb_len, /* If the tx was requested because of a timeout then send */
skb->len - ncb_len); skb2 = package_for_tx(ncm);
put_unaligned_le32(crc, skb->data + skb->len); if (!skb2)
skb_put(skb, crc_len); goto err;
} }
/* (d)wDatagramIndex[0] */ return skb2;
put_ncm(&tmp, opts->dgram_item_len, ncb_len);
/* (d)wDatagramLength[0] */ err:
put_ncm(&tmp, opts->dgram_item_len, skb->len - ncb_len); ncm->netdev->stats.tx_dropped++;
/* (d)wDatagramIndex[1] and (d)wDatagramLength[1] already zeroed */
if (skb)
dev_kfree_skb_any(skb);
if (ncm->skb_tx_data)
dev_kfree_skb_any(ncm->skb_tx_data);
if (ncm->skb_tx_ndp)
dev_kfree_skb_any(ncm->skb_tx_ndp);
return NULL;
}
/*
* This transmits the NTB if there are frames waiting.
*/
static void ncm_tx_tasklet(unsigned long data)
{
struct f_ncm *ncm = (void *)data;
if (skb->len > MAX_TX_NONFIXED) if (ncm->timer_stopping)
memset(skb_put(skb, max_size - skb->len), return;
0, max_size - skb->len);
/* Only send if data is available. */
if (ncm->skb_tx_data) {
ncm->timer_force_tx = true;
ncm->netdev->netdev_ops->ndo_start_xmit(NULL, ncm->netdev);
ncm->timer_force_tx = false;
}
}
return skb; /*
* The transmit should only be run if no skb data has been sent
* for a certain duration.
*/
static enum hrtimer_restart ncm_tx_timeout(struct hrtimer *data)
{
struct f_ncm *ncm = container_of(data, struct f_ncm, task_timer);
tasklet_schedule(&ncm->tx_tasklet);
return HRTIMER_NORESTART;
} }
static int ncm_unwrap_ntb(struct gether *port, static int ncm_unwrap_ntb(struct gether *port,
...@@ -996,7 +1157,7 @@ static int ncm_unwrap_ntb(struct gether *port, ...@@ -996,7 +1157,7 @@ static int ncm_unwrap_ntb(struct gether *port,
goto err; goto err;
} }
ndp_index = get_ncm(&tmp, opts->fp_index); ndp_index = get_ncm(&tmp, opts->ndp_index);
/* Run through all the NDP's in the NTB */ /* Run through all the NDP's in the NTB */
do { do {
...@@ -1033,7 +1194,7 @@ static int ncm_unwrap_ntb(struct gether *port, ...@@ -1033,7 +1194,7 @@ static int ncm_unwrap_ntb(struct gether *port,
} }
tmp += opts->reserved1; tmp += opts->reserved1;
/* Check for another NDP (d)wNextNdpIndex */ /* Check for another NDP (d)wNextNdpIndex */
ndp_index = get_ncm(&tmp, opts->next_fp_index); ndp_index = get_ncm(&tmp, opts->next_ndp_index);
tmp += opts->reserved2; tmp += opts->reserved2;
ndp_len -= opts->ndp_size; ndp_len -= opts->ndp_size;
...@@ -1107,8 +1268,11 @@ static void ncm_disable(struct usb_function *f) ...@@ -1107,8 +1268,11 @@ static void ncm_disable(struct usb_function *f)
DBG(cdev, "ncm deactivated\n"); DBG(cdev, "ncm deactivated\n");
if (ncm->port.in_ep->driver_data) if (ncm->port.in_ep->driver_data) {
ncm->timer_stopping = true;
ncm->netdev = NULL;
gether_disconnect(&ncm->port); gether_disconnect(&ncm->port);
}
if (ncm->notify->driver_data) { if (ncm->notify->driver_data) {
usb_ep_disable(ncm->notify); usb_ep_disable(ncm->notify);
...@@ -1277,6 +1441,10 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f) ...@@ -1277,6 +1441,10 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
ncm->port.open = ncm_open; ncm->port.open = ncm_open;
ncm->port.close = ncm_close; ncm->port.close = ncm_close;
tasklet_init(&ncm->tx_tasklet, ncm_tx_tasklet, (unsigned long) ncm);
hrtimer_init(&ncm->task_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
ncm->task_timer.function = ncm_tx_timeout;
DBG(cdev, "CDC Network: %s speed IN/%s OUT/%s NOTIFY/%s\n", DBG(cdev, "CDC Network: %s speed IN/%s OUT/%s NOTIFY/%s\n",
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
ncm->port.in_ep->name, ncm->port.out_ep->name, ncm->port.in_ep->name, ncm->port.out_ep->name,
...@@ -1390,6 +1558,10 @@ static void ncm_unbind(struct usb_configuration *c, struct usb_function *f) ...@@ -1390,6 +1558,10 @@ static void ncm_unbind(struct usb_configuration *c, struct usb_function *f)
DBG(c->cdev, "ncm unbind\n"); DBG(c->cdev, "ncm unbind\n");
hrtimer_cancel(&ncm->task_timer);
tasklet_kill(&ncm->tx_tasklet);
ncm_string_defs[0].id = 0;
usb_free_all_descriptors(f); usb_free_all_descriptors(f);
kfree(ncm->notify_req->buf); kfree(ncm->notify_req->buf);
...@@ -1426,6 +1598,7 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi) ...@@ -1426,6 +1598,7 @@ static struct usb_function *ncm_alloc(struct usb_function_instance *fi)
ncm->port.ioport = netdev_priv(opts->net); ncm->port.ioport = netdev_priv(opts->net);
mutex_unlock(&opts->lock); mutex_unlock(&opts->lock);
ncm->port.is_fixed = true; ncm->port.is_fixed = true;
ncm->port.supports_multi_frame = true;
ncm->port.func.name = "cdc_network"; ncm->port.func.name = "cdc_network";
/* descriptors are per-instance copies */ /* descriptors are per-instance copies */
......
...@@ -483,7 +483,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, ...@@ -483,7 +483,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
struct net_device *net) struct net_device *net)
{ {
struct eth_dev *dev = netdev_priv(net); struct eth_dev *dev = netdev_priv(net);
int length = skb->len; int length = 0;
int retval; int retval;
struct usb_request *req = NULL; struct usb_request *req = NULL;
unsigned long flags; unsigned long flags;
...@@ -500,13 +500,13 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, ...@@ -500,13 +500,13 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
} }
spin_unlock_irqrestore(&dev->lock, flags); spin_unlock_irqrestore(&dev->lock, flags);
if (!in) { if (skb && !in) {
dev_kfree_skb_any(skb); dev_kfree_skb_any(skb);
return NETDEV_TX_OK; return NETDEV_TX_OK;
} }
/* apply outgoing CDC or RNDIS filters */ /* apply outgoing CDC or RNDIS filters */
if (!is_promisc(cdc_filter)) { if (skb && !is_promisc(cdc_filter)) {
u8 *dest = skb->data; u8 *dest = skb->data;
if (is_multicast_ether_addr(dest)) { if (is_multicast_ether_addr(dest)) {
...@@ -557,11 +557,17 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, ...@@ -557,11 +557,17 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
if (dev->port_usb) if (dev->port_usb)
skb = dev->wrap(dev->port_usb, skb); skb = dev->wrap(dev->port_usb, skb);
spin_unlock_irqrestore(&dev->lock, flags); spin_unlock_irqrestore(&dev->lock, flags);
if (!skb) if (!skb) {
/* Multi frame CDC protocols may store the frame for
* later which is not a dropped frame.
*/
if (dev->port_usb->supports_multi_frame)
goto multiframe;
goto drop; goto drop;
}
length = skb->len;
} }
length = skb->len;
req->buf = skb->data; req->buf = skb->data;
req->context = skb; req->context = skb;
req->complete = tx_complete; req->complete = tx_complete;
...@@ -604,6 +610,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, ...@@ -604,6 +610,7 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
dev_kfree_skb_any(skb); dev_kfree_skb_any(skb);
drop: drop:
dev->net->stats.tx_dropped++; dev->net->stats.tx_dropped++;
multiframe:
spin_lock_irqsave(&dev->req_lock, flags); spin_lock_irqsave(&dev->req_lock, flags);
if (list_empty(&dev->tx_reqs)) if (list_empty(&dev->tx_reqs))
netif_start_queue(net); netif_start_queue(net);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/usb/composite.h> #include <linux/usb/composite.h>
#include <linux/usb/cdc.h> #include <linux/usb/cdc.h>
#include <linux/netdevice.h>
#include "gadget_chips.h" #include "gadget_chips.h"
...@@ -74,6 +75,7 @@ struct gether { ...@@ -74,6 +75,7 @@ struct gether {
bool is_fixed; bool is_fixed;
u32 fixed_out_len; u32 fixed_out_len;
u32 fixed_in_len; u32 fixed_in_len;
bool supports_multi_frame;
struct sk_buff *(*wrap)(struct gether *port, struct sk_buff *(*wrap)(struct gether *port,
struct sk_buff *skb); struct sk_buff *skb);
int (*unwrap)(struct gether *port, int (*unwrap)(struct gether *port,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment