summaryrefslogtreecommitdiffstats
path: root/drivers/net/usb/cdc_ncm.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-12-12 18:07:07 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2012-12-12 18:07:07 -0800
commit6be35c700f742e911ecedd07fcc43d4439922334 (patch)
treeca9f37214d204465fcc2d79c82efd291e357c53c /drivers/net/usb/cdc_ncm.c
parente37aa63e87bd581f9be5555ed0ba83f5295c92fc (diff)
parent520dfe3a3645257bf83660f672c47f8558f3d4c4 (diff)
downloadlinux-6be35c700f742e911ecedd07fcc43d4439922334.tar.gz
linux-6be35c700f742e911ecedd07fcc43d4439922334.tar.bz2
linux-6be35c700f742e911ecedd07fcc43d4439922334.zip
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
Pull networking changes from David Miller: 1) Allow to dump, monitor, and change the bridge multicast database using netlink. From Cong Wang. 2) RFC 5961 TCP blind data injection attack mitigation, from Eric Dumazet. 3) Networking user namespace support from Eric W. Biederman. 4) tuntap/virtio-net multiqueue support by Jason Wang. 5) Support for checksum offload of encapsulated packets (basically, tunneled traffic can still be checksummed by HW). From Joseph Gasparakis. 6) Allow BPF filter access to VLAN tags, from Eric Dumazet and Daniel Borkmann. 7) Bridge port parameters over netlink and BPDU blocking support from Stephen Hemminger. 8) Improve data access patterns during inet socket demux by rearranging socket layout, from Eric Dumazet. 9) TIPC protocol updates and cleanups from Ying Xue, Paul Gortmaker, and Jon Maloy. 10) Update TCP socket hash sizing to be more in line with current day realities. The existing heurstics were choosen a decade ago. From Eric Dumazet. 11) Fix races, queue bloat, and excessive wakeups in ATM and associated drivers, from Krzysztof Mazur and David Woodhouse. 12) Support DOVE (Distributed Overlay Virtual Ethernet) extensions in VXLAN driver, from David Stevens. 13) Add "oops_only" mode to netconsole, from Amerigo Wang. 14) Support set and query of VEB/VEPA bridge mode via PF_BRIDGE, also allow DCB netlink to work on namespaces other than the initial namespace. From John Fastabend. 15) Support PTP in the Tigon3 driver, from Matt Carlson. 16) tun/vhost zero copy fixes and improvements, plus turn it on by default, from Michael S. Tsirkin. 17) Support per-association statistics in SCTP, from Michele Baldessari. And many, many, driver updates, cleanups, and improvements. Too numerous to mention individually. * git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next: (1722 commits) net/mlx4_en: Add support for destination MAC in steering rules net/mlx4_en: Use generic etherdevice.h functions. net: ethtool: Add destination MAC address to flow steering API bridge: add support of adding and deleting mdb entries bridge: notify mdb changes via netlink ndisc: Unexport ndisc_{build,send}_skb(). uapi: add missing netconf.h to export list pkt_sched: avoid requeues if possible solos-pci: fix double-free of TX skb in DMA mode bnx2: Fix accidental reversions. bna: Driver Version Updated to 3.1.2.1 bna: Firmware update bna: Add RX State bna: Rx Page Based Allocation bna: TX Intr Coalescing Fix bna: Tx and Rx Optimizations bna: Code Cleanup and Enhancements ath9k: check pdata variable before dereferencing it ath5k: RX timestamp is reported at end of frame ath9k_htc: RX timestamp is reported at end of frame ...
Diffstat (limited to 'drivers/net/usb/cdc_ncm.c')
-rw-r--r--drivers/net/usb/cdc_ncm.c645
1 files changed, 293 insertions, 352 deletions
diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c
index 74fab1a40156..d38bc20a60e2 100644
--- a/drivers/net/usb/cdc_ncm.c
+++ b/drivers/net/usb/cdc_ncm.c
@@ -51,90 +51,10 @@
#include <linux/atomic.h>
#include <linux/usb/usbnet.h>
#include <linux/usb/cdc.h>
+#include <linux/usb/cdc_ncm.h>
#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 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 */
-
-#define CDC_NCM_MIN_TX_PKT 512 /* bytes */
-
-/* Default value for MaxDatagramSize */
-#define CDC_NCM_MAX_DATAGRAM_SIZE 8192 /* bytes */
-
-/*
- * Maximum amount of datagrams in NCM Datagram Pointer Table, not counting
- * the last NULL entry.
- */
-#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 \
- (sizeof(struct usb_cdc_ncm_nth16) + sizeof(struct usb_cdc_ncm_ndp16) + \
- (CDC_NCM_DPT_DATAGRAMS_MAX + 1) * sizeof(struct usb_cdc_ncm_dpe16))
-
-struct cdc_ncm_data {
- struct usb_cdc_ncm_nth16 nth16;
- struct usb_cdc_ncm_ndp16 ndp16;
- struct usb_cdc_ncm_dpe16 dpe16[CDC_NCM_DPT_DATAGRAMS_MAX + 1];
-};
-
-struct cdc_ncm_ctx {
- struct cdc_ncm_data tx_ncm;
- struct usb_cdc_ncm_ntb_parameters ncm_parm;
- struct hrtimer tx_timer;
- struct tasklet_struct bh;
-
- const struct usb_cdc_ncm_desc *func_desc;
- const struct usb_cdc_header_desc *header_desc;
- const struct usb_cdc_union_desc *union_desc;
- const struct usb_cdc_ether_desc *ether_desc;
-
- struct net_device *netdev;
- struct usb_device *udev;
- struct usb_host_endpoint *in_ep;
- struct usb_host_endpoint *out_ep;
- struct usb_host_endpoint *status_ep;
- struct usb_interface *intf;
- struct usb_interface *control;
- struct usb_interface *data;
-
- struct sk_buff *tx_curr_skb;
- struct sk_buff *tx_rem_skb;
-
- spinlock_t mtx;
- atomic_t stop;
-
- u32 tx_timer_pending;
- u32 tx_curr_offset;
- u32 tx_curr_last_offset;
- u32 tx_curr_frame_num;
- u32 rx_speed;
- u32 tx_speed;
- u32 rx_max;
- u32 tx_max;
- u32 max_datagram_size;
- u16 tx_max_datagrams;
- u16 tx_remainder;
- u16 tx_modulus;
- u16 tx_ndp_modulus;
- u16 tx_seq;
- u16 rx_seq;
- u16 connected;
-};
-
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);
@@ -158,17 +78,19 @@ static u8 cdc_ncm_setup(struct cdc_ncm_ctx *ctx)
u8 flags;
u8 iface_no;
int err;
+ int eth_hlen;
u16 ntb_fmt_supported;
+ u32 min_dgram_size;
+ u32 min_hdr_size;
+ struct usbnet *dev = netdev_priv(ctx->netdev);
iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber;
- err = usb_control_msg(ctx->udev,
- usb_rcvctrlpipe(ctx->udev, 0),
- USB_CDC_GET_NTB_PARAMETERS,
- USB_TYPE_CLASS | USB_DIR_IN
- | USB_RECIP_INTERFACE,
- 0, iface_no, &ctx->ncm_parm,
- sizeof(ctx->ncm_parm), 10000);
+ err = usbnet_read_cmd(dev, USB_CDC_GET_NTB_PARAMETERS,
+ USB_TYPE_CLASS | USB_DIR_IN
+ |USB_RECIP_INTERFACE,
+ 0, iface_no, &ctx->ncm_parm,
+ sizeof(ctx->ncm_parm));
if (err < 0) {
pr_debug("failed GET_NTB_PARAMETERS\n");
return 1;
@@ -184,10 +106,19 @@ static u8 cdc_ncm_setup(struct cdc_ncm_ctx *ctx)
ctx->tx_max_datagrams = le16_to_cpu(ctx->ncm_parm.wNtbOutMaxDatagrams);
ntb_fmt_supported = le16_to_cpu(ctx->ncm_parm.bmNtbFormatsSupported);
- if (ctx->func_desc != NULL)
+ eth_hlen = ETH_HLEN;
+ min_dgram_size = CDC_NCM_MIN_DATAGRAM_SIZE;
+ min_hdr_size = CDC_NCM_MIN_HDR_SIZE;
+ if (ctx->mbim_desc != NULL) {
+ flags = ctx->mbim_desc->bmNetworkCapabilities;
+ eth_hlen = 0;
+ min_dgram_size = CDC_MBIM_MIN_DATAGRAM_SIZE;
+ min_hdr_size = 0;
+ } else if (ctx->func_desc != NULL) {
flags = ctx->func_desc->bmNetworkCapabilities;
- else
+ } else {
flags = 0;
+ }
pr_debug("dwNtbInMaxSize=%u dwNtbOutMaxSize=%u "
"wNdpOutPayloadRemainder=%u wNdpOutDivisor=%u "
@@ -215,49 +146,19 @@ static u8 cdc_ncm_setup(struct cdc_ncm_ctx *ctx)
/* inform device about NTB input size changes */
if (ctx->rx_max != le32_to_cpu(ctx->ncm_parm.dwNtbInMaxSize)) {
+ __le32 dwNtbInMaxSize = cpu_to_le32(ctx->rx_max);
- if (flags & USB_CDC_NCM_NCAP_NTB_INPUT_SIZE) {
- struct usb_cdc_ncm_ndp_input_size *ndp_in_sz;
-
- ndp_in_sz = kzalloc(sizeof(*ndp_in_sz), GFP_KERNEL);
- if (!ndp_in_sz) {
- err = -ENOMEM;
- goto size_err;
- }
-
- err = usb_control_msg(ctx->udev,
- usb_sndctrlpipe(ctx->udev, 0),
- USB_CDC_SET_NTB_INPUT_SIZE,
- USB_TYPE_CLASS | USB_DIR_OUT
- | USB_RECIP_INTERFACE,
- 0, iface_no, ndp_in_sz, 8, 1000);
- kfree(ndp_in_sz);
- } else {
- __le32 *dwNtbInMaxSize;
- dwNtbInMaxSize = kzalloc(sizeof(*dwNtbInMaxSize),
- GFP_KERNEL);
- if (!dwNtbInMaxSize) {
- err = -ENOMEM;
- goto size_err;
- }
- *dwNtbInMaxSize = cpu_to_le32(ctx->rx_max);
-
- err = usb_control_msg(ctx->udev,
- usb_sndctrlpipe(ctx->udev, 0),
- USB_CDC_SET_NTB_INPUT_SIZE,
- USB_TYPE_CLASS | USB_DIR_OUT
- | USB_RECIP_INTERFACE,
- 0, iface_no, dwNtbInMaxSize, 4, 1000);
- kfree(dwNtbInMaxSize);
- }
-size_err:
+ err = usbnet_write_cmd(dev, USB_CDC_SET_NTB_INPUT_SIZE,
+ USB_TYPE_CLASS | USB_DIR_OUT
+ | USB_RECIP_INTERFACE,
+ 0, iface_no, &dwNtbInMaxSize, 4);
if (err < 0)
pr_debug("Setting NTB Input Size failed\n");
}
/* verify maximum size of transmitted NTB in bytes */
if ((ctx->tx_max <
- (CDC_NCM_MIN_HDR_SIZE + CDC_NCM_MIN_DATAGRAM_SIZE)) ||
+ (min_hdr_size + min_dgram_size)) ||
(ctx->tx_max > CDC_NCM_NTB_MAX_SIZE_TX)) {
pr_debug("Using default maximum transmit length=%d\n",
CDC_NCM_NTB_MAX_SIZE_TX);
@@ -299,93 +200,85 @@ size_err:
}
/* adjust TX-remainder according to NCM specification. */
- ctx->tx_remainder = ((ctx->tx_remainder - ETH_HLEN) &
- (ctx->tx_modulus - 1));
+ ctx->tx_remainder = ((ctx->tx_remainder - eth_hlen) &
+ (ctx->tx_modulus - 1));
/* additional configuration */
/* set CRC Mode */
if (flags & USB_CDC_NCM_NCAP_CRC_MODE) {
- err = usb_control_msg(ctx->udev, usb_sndctrlpipe(ctx->udev, 0),
- USB_CDC_SET_CRC_MODE,
- USB_TYPE_CLASS | USB_DIR_OUT
- | USB_RECIP_INTERFACE,
- USB_CDC_NCM_CRC_NOT_APPENDED,
- iface_no, NULL, 0, 1000);
+ err = usbnet_write_cmd(dev, USB_CDC_SET_CRC_MODE,
+ USB_TYPE_CLASS | USB_DIR_OUT
+ | USB_RECIP_INTERFACE,
+ USB_CDC_NCM_CRC_NOT_APPENDED,
+ iface_no, NULL, 0);
if (err < 0)
pr_debug("Setting CRC mode off failed\n");
}
/* set NTB format, if both formats are supported */
if (ntb_fmt_supported & USB_CDC_NCM_NTH32_SIGN) {
- err = usb_control_msg(ctx->udev, usb_sndctrlpipe(ctx->udev, 0),
- USB_CDC_SET_NTB_FORMAT, USB_TYPE_CLASS
- | USB_DIR_OUT | USB_RECIP_INTERFACE,
- USB_CDC_NCM_NTB16_FORMAT,
- iface_no, NULL, 0, 1000);
+ err = usbnet_write_cmd(dev, USB_CDC_SET_NTB_FORMAT,
+ USB_TYPE_CLASS | USB_DIR_OUT
+ | USB_RECIP_INTERFACE,
+ USB_CDC_NCM_NTB16_FORMAT,
+ iface_no, NULL, 0);
if (err < 0)
pr_debug("Setting NTB format to 16-bit failed\n");
}
- ctx->max_datagram_size = CDC_NCM_MIN_DATAGRAM_SIZE;
+ ctx->max_datagram_size = min_dgram_size;
/* set Max Datagram Size (MTU) */
if (flags & USB_CDC_NCM_NCAP_MAX_DATAGRAM_SIZE) {
- __le16 *max_datagram_size;
- u16 eth_max_sz = le16_to_cpu(ctx->ether_desc->wMaxSegmentSize);
-
- max_datagram_size = kzalloc(sizeof(*max_datagram_size),
- GFP_KERNEL);
- if (!max_datagram_size) {
- err = -ENOMEM;
+ __le16 max_datagram_size;
+ u16 eth_max_sz;
+ if (ctx->ether_desc != NULL)
+ eth_max_sz = le16_to_cpu(ctx->ether_desc->wMaxSegmentSize);
+ else if (ctx->mbim_desc != NULL)
+ eth_max_sz = le16_to_cpu(ctx->mbim_desc->wMaxSegmentSize);
+ else
goto max_dgram_err;
- }
- err = usb_control_msg(ctx->udev, usb_rcvctrlpipe(ctx->udev, 0),
- USB_CDC_GET_MAX_DATAGRAM_SIZE,
- USB_TYPE_CLASS | USB_DIR_IN
- | USB_RECIP_INTERFACE,
- 0, iface_no, max_datagram_size,
- 2, 1000);
+ err = usbnet_read_cmd(dev, USB_CDC_GET_MAX_DATAGRAM_SIZE,
+ USB_TYPE_CLASS | USB_DIR_IN
+ | USB_RECIP_INTERFACE,
+ 0, iface_no, &max_datagram_size, 2);
if (err < 0) {
pr_debug("GET_MAX_DATAGRAM_SIZE failed, use size=%u\n",
- CDC_NCM_MIN_DATAGRAM_SIZE);
+ min_dgram_size);
} else {
ctx->max_datagram_size =
- le16_to_cpu(*max_datagram_size);
+ le16_to_cpu(max_datagram_size);
/* Check Eth descriptor value */
if (ctx->max_datagram_size > eth_max_sz)
ctx->max_datagram_size = eth_max_sz;
if (ctx->max_datagram_size > CDC_NCM_MAX_DATAGRAM_SIZE)
- 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 (ctx->max_datagram_size < min_dgram_size)
+ ctx->max_datagram_size = min_dgram_size;
/* if value changed, update device */
if (ctx->max_datagram_size !=
- le16_to_cpu(*max_datagram_size)) {
- err = usb_control_msg(ctx->udev,
- usb_sndctrlpipe(ctx->udev, 0),
+ le16_to_cpu(max_datagram_size)) {
+ err = usbnet_write_cmd(dev,
USB_CDC_SET_MAX_DATAGRAM_SIZE,
USB_TYPE_CLASS | USB_DIR_OUT
| USB_RECIP_INTERFACE,
0,
- iface_no, max_datagram_size,
- 2, 1000);
+ iface_no, &max_datagram_size,
+ 2);
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;
+ if (ctx->netdev->mtu != (ctx->max_datagram_size - eth_hlen))
+ ctx->netdev->mtu = ctx->max_datagram_size - eth_hlen;
return 0;
}
@@ -451,7 +344,7 @@ static const struct ethtool_ops cdc_ncm_ethtool_ops = {
.nway_reset = usbnet_nway_reset,
};
-static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
+int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting)
{
struct cdc_ncm_ctx *ctx;
struct usb_driver *driver;
@@ -525,6 +418,13 @@ static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
ctx->func_desc = (const struct usb_cdc_ncm_desc *)buf;
break;
+ case USB_CDC_MBIM_TYPE:
+ if (buf[0] < sizeof(*(ctx->mbim_desc)))
+ break;
+
+ ctx->mbim_desc = (const struct usb_cdc_mbim_desc *)buf;
+ break;
+
default:
break;
}
@@ -537,7 +437,7 @@ advance:
/* check if we got everything */
if ((ctx->control == NULL) || (ctx->data == NULL) ||
- (ctx->ether_desc == NULL) || (ctx->control != intf))
+ ((!ctx->mbim_desc) && ((ctx->ether_desc == NULL) || (ctx->control != intf))))
goto error;
/* claim data interface, if different from control */
@@ -559,7 +459,7 @@ advance:
goto error2;
/* configure data interface */
- temp = usb_set_interface(dev->udev, iface_no, 1);
+ temp = usb_set_interface(dev->udev, iface_no, data_altsetting);
if (temp)
goto error2;
@@ -576,11 +476,13 @@ advance:
usb_set_intfdata(ctx->control, dev);
usb_set_intfdata(ctx->intf, dev);
- temp = usbnet_get_ethernet_addr(dev, ctx->ether_desc->iMACAddress);
- if (temp)
- goto error2;
+ if (ctx->ether_desc) {
+ temp = usbnet_get_ethernet_addr(dev, ctx->ether_desc->iMACAddress);
+ if (temp)
+ goto error2;
+ dev_info(&dev->udev->dev, "MAC-Address: %pM\n", dev->net->dev_addr);
+ }
- 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);
@@ -589,13 +491,6 @@ advance:
dev->status = ctx->status_ep;
dev->rx_urb_size = ctx->rx_max;
- /*
- * We should get an event when network connection is "connected" or
- * "disconnected". Set network connection in "disconnected" state
- * (carrier is OFF) during attach, so the IP network stack does not
- * start IPv6 negotiation and more.
- */
- netif_carrier_off(dev->net);
ctx->tx_speed = ctx->rx_speed = 0;
return 0;
@@ -609,8 +504,9 @@ error:
dev_info(&dev->udev->dev, "bind() failure\n");
return -ENODEV;
}
+EXPORT_SYMBOL_GPL(cdc_ncm_bind_common);
-static void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
+void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
{
struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
struct usb_driver *driver = driver_of(intf);
@@ -644,52 +540,121 @@ static void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
usb_set_intfdata(ctx->intf, NULL);
cdc_ncm_free(ctx);
}
+EXPORT_SYMBOL_GPL(cdc_ncm_unbind);
-static void cdc_ncm_zero_fill(u8 *ptr, u32 first, u32 end, u32 max)
+static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
{
- if (first >= max)
- return;
- if (first >= end)
- return;
- if (end > max)
- end = max;
- memset(ptr + first, 0, end - first);
+ int ret;
+
+ /* The MBIM spec defines a NCM compatible default altsetting,
+ * which we may have matched:
+ *
+ * "Functions that implement both NCM 1.0 and MBIM (an
+ * “NCM/MBIM function”) according to this recommendation
+ * shall provide two alternate settings for the
+ * Communication Interface. Alternate setting 0, and the
+ * associated class and endpoint descriptors, shall be
+ * constructed according to the rules given for the
+ * Communication Interface in section 5 of [USBNCM10].
+ * Alternate setting 1, and the associated class and
+ * endpoint descriptors, shall be constructed according to
+ * the rules given in section 6 (USB Device Model) of this
+ * specification."
+ *
+ * Do not bind to such interfaces, allowing cdc_mbim to handle
+ * them
+ */
+#if IS_ENABLED(CONFIG_USB_NET_CDC_MBIM)
+ if ((intf->num_altsetting == 2) &&
+ !usb_set_interface(dev->udev,
+ intf->cur_altsetting->desc.bInterfaceNumber,
+ CDC_NCM_COMM_ALTSETTING_MBIM) &&
+ cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))
+ return -ENODEV;
+#endif
+
+ /* NCM data altsetting is always 1 */
+ ret = cdc_ncm_bind_common(dev, intf, 1);
+
+ /*
+ * We should get an event when network connection is "connected" or
+ * "disconnected". Set network connection in "disconnected" state
+ * (carrier is OFF) during attach, so the IP network stack does not
+ * start IPv6 negotiation and more.
+ */
+ netif_carrier_off(dev->net);
+ return ret;
}
-static struct sk_buff *
-cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)
+static void cdc_ncm_align_tail(struct sk_buff *skb, size_t modulus, size_t remainder, size_t max)
{
+ size_t align = ALIGN(skb->len, modulus) - skb->len + remainder;
+
+ if (skb->len + align > max)
+ align = max - skb->len;
+ if (align && skb_tailroom(skb) >= align)
+ memset(skb_put(skb, align), 0, align);
+}
+
+/* return a pointer to a valid struct usb_cdc_ncm_ndp16 of type sign, possibly
+ * allocating a new one within skb
+ */
+static struct usb_cdc_ncm_ndp16 *cdc_ncm_ndp(struct cdc_ncm_ctx *ctx, struct sk_buff *skb, __le32 sign, size_t reserve)
+{
+ struct usb_cdc_ncm_ndp16 *ndp16 = NULL;
+ struct usb_cdc_ncm_nth16 *nth16 = (void *)skb->data;
+ size_t ndpoffset = le16_to_cpu(nth16->wNdpIndex);
+
+ /* follow the chain of NDPs, looking for a match */
+ while (ndpoffset) {
+ ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb->data + ndpoffset);
+ if (ndp16->dwSignature == sign)
+ return ndp16;
+ ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex);
+ }
+
+ /* align new NDP */
+ cdc_ncm_align_tail(skb, ctx->tx_ndp_modulus, 0, ctx->tx_max);
+
+ /* verify that there is room for the NDP and the datagram (reserve) */
+ if ((ctx->tx_max - skb->len - reserve) < CDC_NCM_NDP_SIZE)
+ return NULL;
+
+ /* link to it */
+ if (ndp16)
+ ndp16->wNextNdpIndex = cpu_to_le16(skb->len);
+ else
+ nth16->wNdpIndex = cpu_to_le16(skb->len);
+
+ /* push a new empty NDP */
+ ndp16 = (struct usb_cdc_ncm_ndp16 *)memset(skb_put(skb, CDC_NCM_NDP_SIZE), 0, CDC_NCM_NDP_SIZE);
+ ndp16->dwSignature = sign;
+ ndp16->wLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_ndp16) + sizeof(struct usb_cdc_ncm_dpe16));
+ return ndp16;
+}
+
+struct sk_buff *
+cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb, __le32 sign)
+{
+ struct usb_cdc_ncm_nth16 *nth16;
+ struct usb_cdc_ncm_ndp16 *ndp16;
struct sk_buff *skb_out;
- u32 rem;
- u32 offset;
- u32 last_offset;
- u16 n = 0, index;
+ u16 n = 0, index, ndplen;
u8 ready2send = 0;
/* if there is a remaining skb, it gets priority */
- if (skb != NULL)
+ if (skb != NULL) {
swap(skb, ctx->tx_rem_skb);
- else
+ swap(sign, ctx->tx_rem_sign);
+ } else {
ready2send = 1;
-
- /*
- * +----------------+
- * | skb_out |
- * +----------------+
- * ^ offset
- * ^ last_offset
- */
+ }
/* check if we are resuming an OUT skb */
- if (ctx->tx_curr_skb != NULL) {
- /* pop variables */
- skb_out = ctx->tx_curr_skb;
- offset = ctx->tx_curr_offset;
- last_offset = ctx->tx_curr_last_offset;
- n = ctx->tx_curr_frame_num;
+ skb_out = ctx->tx_curr_skb;
- } else {
- /* reset variables */
+ /* allocate a new OUT skb */
+ if (!skb_out) {
skb_out = alloc_skb((ctx->tx_max + 1), GFP_ATOMIC);
if (skb_out == NULL) {
if (skb != NULL) {
@@ -698,35 +663,21 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)
}
goto exit_no_skb;
}
+ /* fill out the initial 16-bit NTB header */
+ nth16 = (struct usb_cdc_ncm_nth16 *)memset(skb_put(skb_out, sizeof(struct usb_cdc_ncm_nth16)), 0, sizeof(struct usb_cdc_ncm_nth16));
+ nth16->dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN);
+ nth16->wHeaderLength = cpu_to_le16(sizeof(struct usb_cdc_ncm_nth16));
+ nth16->wSequence = cpu_to_le16(ctx->tx_seq++);
- /* make room for NTH and NDP */
- offset = ALIGN(sizeof(struct usb_cdc_ncm_nth16),
- ctx->tx_ndp_modulus) +
- sizeof(struct usb_cdc_ncm_ndp16) +
- (ctx->tx_max_datagrams + 1) *
- sizeof(struct usb_cdc_ncm_dpe16);
-
- /* store last valid offset before alignment */
- last_offset = offset;
- /* align first Datagram offset correctly */
- offset = ALIGN(offset, ctx->tx_modulus) + ctx->tx_remainder;
- /* zero buffer till the first IP datagram */
- cdc_ncm_zero_fill(skb_out->data, 0, offset, offset);
- n = 0;
+ /* count total number of frames in this NTB */
ctx->tx_curr_frame_num = 0;
}
- for (; n < ctx->tx_max_datagrams; n++) {
- /* check if end of transmit buffer is reached */
- if (offset >= ctx->tx_max) {
- ready2send = 1;
- break;
- }
- /* compute maximum buffer size */
- rem = ctx->tx_max - offset;
-
+ for (n = ctx->tx_curr_frame_num; n < ctx->tx_max_datagrams; n++) {
+ /* send any remaining skb first */
if (skb == NULL) {
skb = ctx->tx_rem_skb;
+ sign = ctx->tx_rem_sign;
ctx->tx_rem_skb = NULL;
/* check for end of skb */
@@ -734,7 +685,14 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)
break;
}
- if (skb->len > rem) {
+ /* get the appropriate NDP for this skb */
+ ndp16 = cdc_ncm_ndp(ctx, skb_out, sign, skb->len + ctx->tx_modulus + ctx->tx_remainder);
+
+ /* align beginning of next frame */
+ cdc_ncm_align_tail(skb_out, ctx->tx_modulus, ctx->tx_remainder, ctx->tx_max);
+
+ /* check if we had enough room left for both NDP and frame */
+ if (!ndp16 || skb_out->len + skb->len > ctx->tx_max) {
if (n == 0) {
/* won't fit, MTU problem? */
dev_kfree_skb_any(skb);
@@ -747,31 +705,30 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)
ctx->netdev->stats.tx_dropped++;
}
ctx->tx_rem_skb = skb;
+ ctx->tx_rem_sign = sign;
skb = NULL;
ready2send = 1;
}
break;
}
- memcpy(((u8 *)skb_out->data) + offset, skb->data, skb->len);
-
- ctx->tx_ncm.dpe16[n].wDatagramLength = cpu_to_le16(skb->len);
- ctx->tx_ncm.dpe16[n].wDatagramIndex = cpu_to_le16(offset);
-
- /* update offset */
- offset += skb->len;
-
- /* store last valid offset before alignment */
- last_offset = offset;
-
- /* align offset correctly */
- offset = ALIGN(offset, ctx->tx_modulus) + ctx->tx_remainder;
+ /* calculate frame number withing this NDP */
+ ndplen = le16_to_cpu(ndp16->wLength);
+ index = (ndplen - sizeof(struct usb_cdc_ncm_ndp16)) / sizeof(struct usb_cdc_ncm_dpe16) - 1;
- /* zero padding */
- cdc_ncm_zero_fill(skb_out->data, last_offset, offset,
- ctx->tx_max);
+ /* OK, add this skb */
+ ndp16->dpe16[index].wDatagramLength = cpu_to_le16(skb->len);
+ ndp16->dpe16[index].wDatagramIndex = cpu_to_le16(skb_out->len);
+ ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16));
+ memcpy(skb_put(skb_out, skb->len), skb->data, skb->len);
dev_kfree_skb_any(skb);
skb = NULL;
+
+ /* send now if this NDP is full */
+ if (index >= CDC_NCM_DPT_DATAGRAMS_MAX) {
+ ready2send = 1;
+ break;
+ }
}
/* free up any dangling skb */
@@ -787,16 +744,12 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)
/* wait for more frames */
/* push variables */
ctx->tx_curr_skb = skb_out;
- ctx->tx_curr_offset = offset;
- ctx->tx_curr_last_offset = last_offset;
goto exit_no_skb;
} else if ((n < ctx->tx_max_datagrams) && (ready2send == 0)) {
/* wait for more frames */
/* push variables */
ctx->tx_curr_skb = skb_out;
- ctx->tx_curr_offset = offset;
- ctx->tx_curr_last_offset = last_offset;
/* set the pending count */
if (n < CDC_NCM_RESTART_TIMER_DATAGRAM_CNT)
ctx->tx_timer_pending = CDC_NCM_TIMER_PENDING_CNT;
@@ -807,75 +760,24 @@ cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb)
/* variables will be reset at next call */
}
- /* check for overflow */
- if (last_offset > ctx->tx_max)
- last_offset = ctx->tx_max;
-
- /* revert offset */
- offset = last_offset;
-
/*
* If collected data size is less or equal CDC_NCM_MIN_TX_PKT bytes,
* we send buffers as it is. If we get more data, it would be more
* efficient for USB HS mobile device with DMA engine to receive a full
* size NTB, than canceling DMA transfer and receiving a short packet.
*/
- if (offset > CDC_NCM_MIN_TX_PKT)
- offset = ctx->tx_max;
-
- /* final zero padding */
- cdc_ncm_zero_fill(skb_out->data, last_offset, offset, ctx->tx_max);
-
- /* store last offset */
- last_offset = offset;
-
- if (((last_offset < ctx->tx_max) && ((last_offset %
- le16_to_cpu(ctx->out_ep->desc.wMaxPacketSize)) == 0)) ||
- (((last_offset == ctx->tx_max) && ((ctx->tx_max %
- le16_to_cpu(ctx->out_ep->desc.wMaxPacketSize)) == 0)) &&
- (ctx->tx_max < le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize)))) {
- /* force short packet */
- *(((u8 *)skb_out->data) + last_offset) = 0;
- last_offset++;
- }
-
- /* zero the rest of the DPEs plus the last NULL entry */
- for (; n <= CDC_NCM_DPT_DATAGRAMS_MAX; n++) {
- ctx->tx_ncm.dpe16[n].wDatagramLength = 0;
- ctx->tx_ncm.dpe16[n].wDatagramIndex = 0;
- }
+ if (skb_out->len > CDC_NCM_MIN_TX_PKT)
+ /* final zero padding */
+ memset(skb_put(skb_out, ctx->tx_max - skb_out->len), 0, ctx->tx_max - skb_out->len);
- /* fill out 16-bit NTB header */
- ctx->tx_ncm.nth16.dwSignature = cpu_to_le32(USB_CDC_NCM_NTH16_SIGN);
- ctx->tx_ncm.nth16.wHeaderLength =
- cpu_to_le16(sizeof(ctx->tx_ncm.nth16));
- ctx->tx_ncm.nth16.wSequence = cpu_to_le16(ctx->tx_seq);
- ctx->tx_ncm.nth16.wBlockLength = cpu_to_le16(last_offset);
- index = ALIGN(sizeof(struct usb_cdc_ncm_nth16), ctx->tx_ndp_modulus);
- ctx->tx_ncm.nth16.wNdpIndex = cpu_to_le16(index);
-
- memcpy(skb_out->data, &(ctx->tx_ncm.nth16), sizeof(ctx->tx_ncm.nth16));
- ctx->tx_seq++;
-
- /* fill out 16-bit NDP table */
- ctx->tx_ncm.ndp16.dwSignature =
- cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN);
- rem = sizeof(ctx->tx_ncm.ndp16) + ((ctx->tx_curr_frame_num + 1) *
- sizeof(struct usb_cdc_ncm_dpe16));
- ctx->tx_ncm.ndp16.wLength = cpu_to_le16(rem);
- ctx->tx_ncm.ndp16.wNextNdpIndex = 0; /* reserved */
-
- memcpy(((u8 *)skb_out->data) + index,
- &(ctx->tx_ncm.ndp16),
- sizeof(ctx->tx_ncm.ndp16));
+ /* do we need to prevent a ZLP? */
+ if (((skb_out->len % le16_to_cpu(ctx->out_ep->desc.wMaxPacketSize)) == 0) &&
+ (skb_out->len < le32_to_cpu(ctx->ncm_parm.dwNtbOutMaxSize)) && skb_tailroom(skb_out))
+ *skb_put(skb_out, 1) = 0; /* force short packet */
- memcpy(((u8 *)skb_out->data) + index + sizeof(ctx->tx_ncm.ndp16),
- &(ctx->tx_ncm.dpe16),
- (ctx->tx_curr_frame_num + 1) *
- sizeof(struct usb_cdc_ncm_dpe16));
-
- /* set frame length */
- skb_put(skb_out, last_offset);
+ /* set final frame length */
+ nth16 = (struct usb_cdc_ncm_nth16 *)skb_out->data;
+ nth16->wBlockLength = cpu_to_le16(skb_out->len);
/* return skb */
ctx->tx_curr_skb = NULL;
@@ -888,6 +790,7 @@ exit_no_skb:
cdc_ncm_tx_timeout_start(ctx);
return NULL;
}
+EXPORT_SYMBOL_GPL(cdc_ncm_fill_tx_frame);
static void cdc_ncm_tx_timeout_start(struct cdc_ncm_ctx *ctx)
{
@@ -922,6 +825,8 @@ static void cdc_ncm_txpath_bh(unsigned long param)
netif_tx_lock_bh(ctx->netdev);
usbnet_start_xmit(NULL, ctx->netdev);
netif_tx_unlock_bh(ctx->netdev);
+ } else {
+ spin_unlock_bh(&ctx->mtx);
}
}
@@ -942,7 +847,7 @@ cdc_ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
goto error;
spin_lock_bh(&ctx->mtx);
- skb_out = cdc_ncm_fill_tx_frame(ctx, skb);
+ skb_out = cdc_ncm_fill_tx_frame(ctx, skb, cpu_to_le32(USB_CDC_NCM_NDP16_NOCRC_SIGN));
spin_unlock_bh(&ctx->mtx);
return skb_out;
@@ -953,17 +858,12 @@ error:
return NULL;
}
-static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
+/* verify NTB header and return offset of first NDP, or negative error */
+int cdc_ncm_rx_verify_nth16(struct cdc_ncm_ctx *ctx, struct sk_buff *skb_in)
{
- struct sk_buff *skb;
- 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;
+ int len;
+ int ret = -EINVAL;
if (ctx == NULL)
goto error;
@@ -997,20 +897,23 @@ static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
}
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;
- }
+ ret = le16_to_cpu(nth16->wNdpIndex);
+error:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cdc_ncm_rx_verify_nth16);
- ndp16 = (struct usb_cdc_ncm_ndp16 *)(((u8 *)skb_in->data) + len);
+/* verify NDP header and return number of datagrams, or negative error */
+int cdc_ncm_rx_verify_ndp16(struct sk_buff *skb_in, int ndpoffset)
+{
+ struct usb_cdc_ncm_ndp16 *ndp16;
+ int ret = -EINVAL;
- if (le32_to_cpu(ndp16->dwSignature) != USB_CDC_NCM_NDP16_NOCRC_SIGN) {
- pr_debug("invalid DPT16 signature <%u>\n",
- le32_to_cpu(ndp16->dwSignature));
+ if ((ndpoffset + sizeof(struct usb_cdc_ncm_ndp16)) > skb_in->len) {
+ pr_debug("invalid NDP offset <%u>\n", ndpoffset);
goto error;
}
+ ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset);
if (le16_to_cpu(ndp16->wLength) < USB_CDC_NCM_NDP16_LENGTH_MIN) {
pr_debug("invalid DPT16 length <%u>\n",
@@ -1018,20 +921,52 @@ static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
goto error;
}
- nframes = ((le16_to_cpu(ndp16->wLength) -
+ ret = ((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 */
-
- len += sizeof(struct usb_cdc_ncm_ndp16);
+ ret--; /* we process NDP entries except for the last one */
- if ((len + nframes * (sizeof(struct usb_cdc_ncm_dpe16))) >
+ if ((sizeof(struct usb_cdc_ncm_ndp16) + ret * (sizeof(struct usb_cdc_ncm_dpe16))) >
skb_in->len) {
- pr_debug("Invalid nframes = %d\n", nframes);
- goto error;
+ pr_debug("Invalid nframes = %d\n", ret);
+ ret = -EINVAL;
}
- dpe16 = (struct usb_cdc_ncm_dpe16 *)(((u8 *)skb_in->data) + len);
+error:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cdc_ncm_rx_verify_ndp16);
+
+static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
+{
+ struct sk_buff *skb;
+ struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
+ int len;
+ int nframes;
+ int x;
+ int offset;
+ struct usb_cdc_ncm_ndp16 *ndp16;
+ struct usb_cdc_ncm_dpe16 *dpe16;
+ int ndpoffset;
+ int loopcount = 50; /* arbitrary max preventing infinite loop */
+
+ ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in);
+ if (ndpoffset < 0)
+ goto error;
+
+next_ndp:
+ nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset);
+ if (nframes < 0)
+ goto error;
+
+ ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset);
+
+ if (le32_to_cpu(ndp16->dwSignature) != USB_CDC_NCM_NDP16_NOCRC_SIGN) {
+ pr_debug("invalid DPT16 signature <%u>\n",
+ le32_to_cpu(ndp16->dwSignature));
+ goto err_ndp;
+ }
+ dpe16 = ndp16->dpe16;
for (x = 0; x < nframes; x++, dpe16++) {
offset = le16_to_cpu(dpe16->wDatagramIndex);
@@ -1043,7 +978,7 @@ static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
*/
if ((offset == 0) || (len == 0)) {
if (!x)
- goto error; /* empty NTB */
+ goto err_ndp; /* empty NTB */
break;
}
@@ -1054,7 +989,7 @@ static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
"offset[%u]=%u, length=%u, skb=%p\n",
x, offset, len, skb_in);
if (!x)
- goto error;
+ goto err_ndp;
break;
} else {
@@ -1067,6 +1002,12 @@ static int cdc_ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
usbnet_skb_return(dev, skb);
}
}
+err_ndp:
+ /* are there more NDPs to process? */
+ ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex);
+ if (ndpoffset && loopcount--)
+ goto next_ndp;
+
return 1;
error:
return 0;
@@ -1131,7 +1072,7 @@ static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)
* USB_CDC_NOTIFY_NETWORK_CONNECTION notification shall be
* sent by device after USB_CDC_NOTIFY_SPEED_CHANGE.
*/
- ctx->connected = event->wValue;
+ ctx->connected = le16_to_cpu(event->wValue);
printk(KERN_INFO KBUILD_MODNAME ": %s: network connection:"
" %sconnected\n",