diff options
Diffstat (limited to 'drivers/net/usb/cdc_ncm.c')
-rw-r--r-- | drivers/net/usb/cdc_ncm.c | 236 |
1 files changed, 115 insertions, 121 deletions
diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 3a539a9cac5..7adc9f6b0ea 100644 --- a/drivers/net/usb/cdc_ncm.c +++ b/drivers/net/usb/cdc_ncm.c @@ -1,7 +1,7 @@ /* * cdc_ncm.c * - * Copyright (C) ST-Ericsson 2010-2011 + * Copyright (C) ST-Ericsson 2010-2012 * Contact: Alexey Orishko <alexey.orishko@stericsson.com> * Original author: Hans Petter Selasky <hans.petter.selasky@stericsson.com> * @@ -47,20 +47,19 @@ #include <linux/mii.h> #include <linux/crc32.h> #include <linux/usb.h> -#include <linux/timer.h> -#include <linux/spinlock.h> +#include <linux/hrtimer.h> #include <linux/atomic.h> #include <linux/usb/usbnet.h> #include <linux/usb/cdc.h> -#define DRIVER_VERSION "04-Aug-2011" +#define DRIVER_VERSION "14-Mar-2012" /* CDC NCM subclass 3.2.1 */ #define USB_CDC_NCM_NDP16_LENGTH_MIN 0x10 /* Maximum NTB length */ -#define CDC_NCM_NTB_MAX_SIZE_TX 16384 /* bytes */ -#define CDC_NCM_NTB_MAX_SIZE_RX 16384 /* bytes */ +#define CDC_NCM_NTB_MAX_SIZE_TX 32768 /* bytes */ +#define CDC_NCM_NTB_MAX_SIZE_RX 32768 /* bytes */ /* Minimum value for MaxDatagramSize, ch. 6.2.9 */ #define CDC_NCM_MIN_DATAGRAM_SIZE 1514 /* bytes */ @@ -68,19 +67,18 @@ #define CDC_NCM_MIN_TX_PKT 512 /* bytes */ /* Default value for MaxDatagramSize */ -#define CDC_NCM_MAX_DATAGRAM_SIZE 2048 /* bytes */ +#define CDC_NCM_MAX_DATAGRAM_SIZE 8192 /* bytes */ /* * Maximum amount of datagrams in NCM Datagram Pointer Table, not counting - * the last NULL entry. Any additional datagrams in NTB would be discarded. + * the last NULL entry. */ -#define CDC_NCM_DPT_DATAGRAMS_MAX 32 - -/* Maximum amount of IN datagrams in NTB */ -#define CDC_NCM_DPT_DATAGRAMS_IN_MAX 0 /* unlimited */ +#define CDC_NCM_DPT_DATAGRAMS_MAX 40 /* Restart the timer, if amount of datagrams is less than given value */ #define CDC_NCM_RESTART_TIMER_DATAGRAM_CNT 3 +#define CDC_NCM_TIMER_PENDING_CNT 2 +#define CDC_NCM_TIMER_INTERVAL (400UL * NSEC_PER_USEC) /* The following macro defines the minimum header space */ #define CDC_NCM_MIN_HDR_SIZE \ @@ -94,10 +92,10 @@ struct cdc_ncm_data { }; struct cdc_ncm_ctx { - struct cdc_ncm_data rx_ncm; struct cdc_ncm_data tx_ncm; struct usb_cdc_ncm_ntb_parameters ncm_parm; - struct timer_list tx_timer; + struct hrtimer tx_timer; + struct tasklet_struct bh; const struct usb_cdc_ncm_desc *func_desc; const struct usb_cdc_header_desc *header_desc; @@ -117,6 +115,7 @@ struct cdc_ncm_ctx { struct sk_buff *tx_rem_skb; spinlock_t mtx; + atomic_t stop; u32 tx_timer_pending; u32 tx_curr_offset; @@ -132,10 +131,13 @@ struct cdc_ncm_ctx { u16 tx_modulus; u16 tx_ndp_modulus; u16 tx_seq; + u16 rx_seq; u16 connected; }; -static void cdc_ncm_tx_timeout(unsigned long arg); +static void cdc_ncm_txpath_bh(unsigned long param); +static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx); +static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *hr_timer); static const struct driver_info cdc_ncm_info; static struct usb_driver cdc_ncm_driver; static const struct ethtool_ops cdc_ncm_ethtool_ops; @@ -361,27 +363,25 @@ size_err: if (err < 0) { pr_debug("GET_MAX_DATAGRAM_SIZE failed, use size=%u\n", CDC_NCM_MIN_DATAGRAM_SIZE); - kfree(max_datagram_size); } else { ctx->max_datagram_size = le16_to_cpu(*max_datagram_size); /* Check Eth descriptor value */ - if (eth_max_sz < CDC_NCM_MAX_DATAGRAM_SIZE) { - if (ctx->max_datagram_size > eth_max_sz) + if (ctx->max_datagram_size > eth_max_sz) ctx->max_datagram_size = eth_max_sz; - } else { - if (ctx->max_datagram_size > - CDC_NCM_MAX_DATAGRAM_SIZE) - ctx->max_datagram_size = + + if (ctx->max_datagram_size > CDC_NCM_MAX_DATAGRAM_SIZE) + ctx->max_datagram_size = CDC_NCM_MAX_DATAGRAM_SIZE; - } if (ctx->max_datagram_size < CDC_NCM_MIN_DATAGRAM_SIZE) ctx->max_datagram_size = CDC_NCM_MIN_DATAGRAM_SIZE; /* if value changed, update device */ - err = usb_control_msg(ctx->udev, + if (ctx->max_datagram_size != + le16_to_cpu(*max_datagram_size)) { + err = usb_control_msg(ctx->udev, usb_sndctrlpipe(ctx->udev, 0), USB_CDC_SET_MAX_DATAGRAM_SIZE, USB_TYPE_CLASS | USB_DIR_OUT @@ -389,14 +389,14 @@ size_err: 0, iface_no, max_datagram_size, 2, 1000); - kfree(max_datagram_size); -max_dgram_err: - if (err < 0) - pr_debug("SET_MAX_DATAGRAM_SIZE failed\n"); + if (err < 0) + pr_debug("SET_MAX_DGRAM_SIZE failed\n"); + } } - + kfree(max_datagram_size); } +max_dgram_err: if (ctx->netdev->mtu != (ctx->max_datagram_size - ETH_HLEN)) ctx->netdev->mtu = ctx->max_datagram_size - ETH_HLEN; @@ -441,8 +441,6 @@ static void cdc_ncm_free(struct cdc_ncm_ctx *ctx) if (ctx == NULL) return; - del_timer_sync(&ctx->tx_timer); - if (ctx->tx_rem_skb != NULL) { dev_kfree_skb_any(ctx->tx_rem_skb); ctx->tx_rem_skb = NULL; @@ -469,7 +467,11 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf) if (ctx == NULL) return -ENODEV; - init_timer(&ctx->tx_timer); + hrtimer_init(&ctx->tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ctx->tx_timer.function = &cdc_ncm_tx_timer_cb; + ctx->bh.data = (unsigned long)ctx; + ctx->bh.func = cdc_ncm_txpath_bh; + atomic_set(&ctx->stop, 0); spin_lock_init(&ctx->mtx); ctx->netdev = dev->net; @@ -579,11 +581,7 @@ advance: if (temp) goto error2; - dev_info(&dev->udev->dev, "MAC-Address: " - "0x%02x:0x%02x:0x%02x:0x%02x:0x%02x:0x%02x\n", - dev->net->dev_addr[0], dev->net->dev_addr[1], - dev->net->dev_addr[2], dev->net->dev_addr[3], - dev->net->dev_addr[4], dev->net->dev_addr[5]); + dev_info(&dev->udev->dev, "MAC-Address: %pM\n", dev->net->dev_addr); dev->in = usb_rcvbulkpipe(dev->udev, ctx->in_ep->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); @@ -621,6 +619,13 @@ static void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf) if (ctx == NULL) return; /* no setup */ + atomic_set(&ctx->stop, 1); + + if (hrtimer_active(&ctx->tx_timer)) + hrtimer_cancel(&ctx->tx_timer); + + tasklet_kill(&ctx->bh); + /* disconnect master --> disconnect slave */ if (intf == ctx->control && ctx->data) { usb_set_intfdata(ctx->data, NULL); @@ -791,7 +796,7 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb) ctx->tx_curr_last_offset = last_offset; /* set the pending count */ if (n < CDC_NCM_RESTART_TIMER_DATAGRAM_CNT) - ctx->tx_timer_pending = 2; + ctx->tx_timer_pending = CDC_NCM_TIMER_PENDING_CNT; goto exit_no_skb; } else { @@ -871,44 +876,49 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb) /* return skb */ ctx->tx_curr_skb = NULL; + ctx->netdev->stats.tx_packets += ctx->tx_curr_frame_num; return skb_out; exit_no_skb: + /* Start timer, if there is a remaining skb */ + if (ctx->tx_curr_skb != NULL) + cdc_ncm_tx_timeout_start(ctx); return NULL; } static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx) { /* start timer, if not already started */ - if (timer_pending(&ctx->tx_timer) == 0) { - ctx->tx_timer.function = &cdc_ncm_tx_timeout; - ctx->tx_timer.data = (unsigned long)ctx; - ctx->tx_timer.expires = jiffies + ((HZ + 999) / 1000); - add_timer(&ctx->tx_timer); - } + if (!(hrtimer_active(&ctx->tx_timer) || atomic_read(&ctx->stop))) + hrtimer_start(&ctx->tx_timer, + ktime_set(0, CDC_NCM_TIMER_INTERVAL), + HRTIMER_MODE_REL); } -static void cdc_ncm_tx_timeout(unsigned long arg) +static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *timer) { - struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)arg; - u8 restart; + struct cdc_ncm_ctx *ctx = + container_of(timer, struct cdc_ncm_ctx, tx_timer); - spin_lock(&ctx->mtx); - if (ctx->tx_timer_pending != 0) { - ctx->tx_timer_pending--; - restart = 1; - } else { - restart = 0; - } + if (!atomic_read(&ctx->stop)) + tasklet_schedule(&ctx->bh); + return HRTIMER_NORESTART; +} - spin_unlock(&ctx->mtx); +static void cdc_ncm_txpath_bh(unsigned long param) +{ + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)param; - if (restart) { - spin_lock(&ctx->mtx); + spin_lock_bh(&ctx->mtx); + if (ctx->tx_timer_pending != 0) { + ctx->tx_timer_pending--; cdc_ncm_tx_timeout_start(ctx); - spin_unlock(&ctx->mtx); + spin_unlock_bh(&ctx->mtx); } else if (ctx->netdev != NULL) { + spin_unlock_bh(&ctx->mtx); + netif_tx_lock_bh(ctx->netdev); usbnet_start_xmit(NULL, ctx->netdev); + netif_tx_unlock_bh(ctx->netdev); } } @@ -917,7 +927,6 @@ cdc_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) { struct sk_buff *skb_out; struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; - u8 need_timer = 0; /* * The Ethernet API we are using does not support transmitting @@ -929,19 +938,9 @@ cdc_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) if (ctx == NULL) goto error; - spin_lock(&ctx->mtx); + spin_lock_bh(&ctx->mtx); skb_out = cdc_ncm_fill_tx_frame(ctx, skb); - if (ctx->tx_curr_skb != NULL) - need_timer = 1; - - /* Start timer, if there is a remaining skb */ - if (need_timer) - cdc_ncm_tx_timeout_start(ctx); - - if (skb_out) - dev->net->stats.tx_packets += ctx->tx_curr_frame_num; - - spin_unlock(&ctx->mtx); + spin_unlock_bh(&ctx->mtx); return skb_out; error: @@ -954,108 +953,103 @@ error: static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) { struct sk_buff *skb; - struct cdc_ncm_ctx *ctx; - int sumlen; - int actlen; - int temp; + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; + int len; int nframes; int x; int offset; + struct usb_cdc_ncm_nth16 *nth16; + struct usb_cdc_ncm_ndp16 *ndp16; + struct usb_cdc_ncm_dpe16 *dpe16; - ctx = (struct cdc_ncm_ctx *)dev->data[0]; if (ctx == NULL) goto error; - actlen = skb_in->len; - sumlen = CDC_NCM_NTB_MAX_SIZE_RX; - - if (actlen < (sizeof(ctx->rx_ncm.nth16) + sizeof(ctx->rx_ncm.ndp16))) { + if (skb_in->len < (sizeof(struct usb_cdc_ncm_nth16) + + sizeof(struct usb_cdc_ncm_ndp16))) { pr_debug("frame too short\n"); goto error; } - memcpy(&(ctx->rx_ncm.nth16), ((u8 *)skb_in->data), - sizeof(ctx->rx_ncm.nth16)); + nth16 = (struct usb_cdc_ncm_nth16 *)skb_in->data; - if (le32_to_cpu(ctx->rx_ncm.nth16.dwSignature) != - USB_CDC_NCM_NTH16_SIGN) { + if (le32_to_cpu(nth16->dwSignature) != USB_CDC_NCM_NTH16_SIGN) { pr_debug("invalid NTH16 signature <%u>\n", - le32_to_cpu(ctx->rx_ncm.nth16.dwSignature)); + le32_to_cpu(nth16->dwSignature)); goto error; } - temp = le16_to_cpu(ctx->rx_ncm.nth16.wBlockLength); - if (temp > sumlen) { - pr_debug("unsupported NTB block length %u/%u\n", temp, sumlen); + len = le16_to_cpu(nth16->wBlockLength); + if (len > ctx->rx_max) { + pr_debug("unsupported NTB block length %u/%u\n", len, + ctx->rx_max); goto error; } - temp = le16_to_cpu(ctx->rx_ncm.nth16.wNdpIndex); - if ((temp + sizeof(ctx->rx_ncm.ndp16)) > actlen) { - pr_debug("invalid DPT16 index\n"); + if ((ctx->rx_seq + 1) != le16_to_cpu(nth16->wSequence) && + (ctx->rx_seq || le16_to_cpu(nth16->wSequence)) && + !((ctx->rx_seq == 0xffff) && !le16_to_cpu(nth16->wSequence))) { + pr_debug("sequence number glitch prev=%d curr=%d\n", + ctx->rx_seq, le16_to_cpu(nth16->wSequence)); + } + ctx->rx_seq = le16_to_cpu(nth16->wSequence); + + len = le16_to_cpu(nth16->wNdpIndex); + if ((len + sizeof(struct usb_cdc_ncm_ndp16)) > skb_in->len) { + pr_debug("invalid DPT16 index <%u>\n", + le16_to_cpu(nth16->wNdpIndex)); goto error; } - memcpy(&(ctx->rx_ncm.ndp16), ((u8 *)skb_in->data) + temp, - sizeof(ctx->rx_ncm.ndp16)); + ndp16 = (struct usb_cdc_ncm_ndp16 *)(((u8 *)skb_in->data) + len); - if (le32_to_cpu(ctx->rx_ncm.ndp16.dwSignature) != - USB_CDC_NCM_NDP16_NOCRC_SIGN) { + if (le32_to_cpu(ndp16->dwSignature) != USB_CDC_NCM_NDP16_NOCRC_SIGN) { pr_debug("invalid DPT16 signature <%u>\n", - le32_to_cpu(ctx->rx_ncm.ndp16.dwSignature)); + le32_to_cpu(ndp16->dwSignature)); goto error; } - if (le16_to_cpu(ctx->rx_ncm.ndp16.wLength) < - USB_CDC_NCM_NDP16_LENGTH_MIN) { + if (le16_to_cpu(ndp16->wLength) < USB_CDC_NCM_NDP16_LENGTH_MIN) { pr_debug("invalid DPT16 length <%u>\n", - le32_to_cpu(ctx->rx_ncm.ndp16.dwSignature)); + le32_to_cpu(ndp16->dwSignature)); goto error; } - nframes = ((le16_to_cpu(ctx->rx_ncm.ndp16.wLength) - + nframes = ((le16_to_cpu(ndp16->wLength) - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16)); nframes--; /* we process NDP entries except for the last one */ - pr_debug("nframes = %u\n", nframes); - - temp += sizeof(ctx->rx_ncm.ndp16); + len += sizeof(struct usb_cdc_ncm_ndp16); - if ((temp + nframes * (sizeof(struct usb_cdc_ncm_dpe16))) > actlen) { + if ((len + nframes * (sizeof(struct usb_cdc_ncm_dpe16))) > + skb_in->len) { pr_debug("Invalid nframes = %d\n", nframes); goto error; } - if (nframes > CDC_NCM_DPT_DATAGRAMS_MAX) { - pr_debug("Truncating number of frames from %u to %u\n", - nframes, CDC_NCM_DPT_DATAGRAMS_MAX); - nframes = CDC_NCM_DPT_DATAGRAMS_MAX; - } - - memcpy(&(ctx->rx_ncm.dpe16), ((u8 *)skb_in->data) + temp, - nframes * (sizeof(struct usb_cdc_ncm_dpe16))); + dpe16 = (struct usb_cdc_ncm_dpe16 *)(((u8 *)skb_in->data) + len); - for (x = 0; x < nframes; x++) { - offset = le16_to_cpu(ctx->rx_ncm.dpe16[x].wDatagramIndex); - temp = le16_to_cpu(ctx->rx_ncm.dpe16[x].wDatagramLength); + for (x = 0; x < nframes; x++, dpe16++) { + offset = le16_to_cpu(dpe16->wDatagramIndex); + len = le16_to_cpu(dpe16->wDatagramLength); /* * CDC NCM ch. 3.7 * All entries after first NULL entry are to be ignored */ - if ((offset == 0) || (temp == 0)) { + if ((offset == 0) || (len == 0)) { if (!x) goto error; /* empty NTB */ break; } /* sanity checking */ - if (((offset + temp) > actlen) || - (temp > CDC_NCM_MAX_DATAGRAM_SIZE) || (temp < ETH_HLEN)) { + if (((offset + len) > skb_in->len) || + (len > ctx->rx_max) || (len < ETH_HLEN)) { pr_debug("invalid frame detected (ignored)" "offset[%u]=%u, length=%u, skb=%p\n", - x, offset, temp, skb_in); + x, offset, len, skb_in); if (!x) goto error; break; @@ -1064,9 +1058,9 @@ static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) skb = skb_clone(skb_in, GFP_ATOMIC); if (!skb) goto error; - skb->len = temp; + skb->len = len; skb->data = ((u8 *)skb_in->data) + offset; - skb_set_tail_pointer(skb, temp); + skb_set_tail_pointer(skb, len); usbnet_skb_return(dev, skb); } } |