diff options
-rw-r--r-- | arch/arm/mach-at91/Kconfig | 4 | ||||
-rw-r--r-- | arch/arm/mach-at91/board-csb337.c | 2 | ||||
-rw-r--r-- | arch/avr32/Kconfig | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/cadence/Kconfig | 8 | ||||
-rw-r--r-- | drivers/net/ethernet/cadence/Makefile | 2 | ||||
-rw-r--r-- | drivers/net/ethernet/cadence/at91_ether.c | 5 | ||||
-rw-r--r-- | drivers/net/ethernet/cadence/macb.c | 8 | ||||
-rw-r--r-- | drivers/net/usb/Kconfig | 18 | ||||
-rw-r--r-- | drivers/net/usb/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/usb/cdc_mbim.c | 412 | ||||
-rw-r--r-- | drivers/net/usb/cdc_ncm.c | 584 | ||||
-rw-r--r-- | include/linux/platform_data/macb.h | 1 | ||||
-rw-r--r-- | include/linux/usb/cdc.h | 23 | ||||
-rw-r--r-- | include/linux/usb/cdc_ncm.h | 134 | ||||
-rw-r--r-- | include/net/ip6_fib.h | 10 | ||||
-rw-r--r-- | net/ipv6/ip6_fib.c | 57 | ||||
-rw-r--r-- | net/ipv6/route.c | 136 |
17 files changed, 1072 insertions, 334 deletions
diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig index b14207101938..682abf13d0f4 100644 --- a/arch/arm/mach-at91/Kconfig +++ b/arch/arm/mach-at91/Kconfig @@ -45,7 +45,6 @@ config SOC_AT91RM9200 config SOC_AT91SAM9260 bool "AT91SAM9260, AT91SAM9XE or AT91SAM9G20" select HAVE_AT91_DBGU0 - select HAVE_NET_MACB select SOC_AT91SAM9 help Select this if you are using one of Atmel's AT91SAM9260, AT91SAM9XE @@ -63,7 +62,6 @@ config SOC_AT91SAM9263 bool "AT91SAM9263" select HAVE_AT91_DBGU1 select HAVE_FB_ATMEL - select HAVE_NET_MACB select SOC_AT91SAM9 config SOC_AT91SAM9RL @@ -76,7 +74,6 @@ config SOC_AT91SAM9G45 bool "AT91SAM9G45 or AT91SAM9M10 families" select HAVE_AT91_DBGU1 select HAVE_FB_ATMEL - select HAVE_NET_MACB select SOC_AT91SAM9 help Select this if you are using one of Atmel's AT91SAM9G45 family SoC. @@ -86,7 +83,6 @@ config SOC_AT91SAM9X5 bool "AT91SAM9x5 family" select HAVE_AT91_DBGU0 select HAVE_FB_ATMEL - select HAVE_NET_MACB select SOC_AT91SAM9 help Select this if you are using one of Atmel's AT91SAM9x5 family SoC. diff --git a/arch/arm/mach-at91/board-csb337.c b/arch/arm/mach-at91/board-csb337.c index 3e37437a7a61..aa9b320bad59 100644 --- a/arch/arm/mach-at91/board-csb337.c +++ b/arch/arm/mach-at91/board-csb337.c @@ -53,6 +53,8 @@ static void __init csb337_init_early(void) static struct macb_platform_data __initdata csb337_eth_data = { .phy_irq_pin = AT91_PIN_PC2, .is_rmii = 0, + /* The CSB337 bootloader stores the MAC the wrong-way around */ + .rev_eth_addr = 1, }; static struct at91_usbh_data __initdata csb337_usbh_data = { diff --git a/arch/avr32/Kconfig b/arch/avr32/Kconfig index 06e73bf665e9..09f9fa800b33 100644 --- a/arch/avr32/Kconfig +++ b/arch/avr32/Kconfig @@ -80,7 +80,6 @@ config PLATFORM_AT32AP select ARCH_REQUIRE_GPIOLIB select GENERIC_ALLOCATOR select HAVE_FB_ATMEL - select HAVE_NET_MACB # # CPU types diff --git a/drivers/net/ethernet/cadence/Kconfig b/drivers/net/ethernet/cadence/Kconfig index 57f78abe11d9..40172d1ca605 100644 --- a/drivers/net/ethernet/cadence/Kconfig +++ b/drivers/net/ethernet/cadence/Kconfig @@ -2,13 +2,9 @@ # Atmel device configuration # -config HAVE_NET_MACB - bool - config NET_CADENCE bool "Cadence devices" default y - depends on HAVE_NET_MACB || (ARM && ARCH_AT91RM9200) ---help--- If you have a network (Ethernet) card belonging to this class, say Y. Make sure you know the name of your card. Read the Ethernet-HOWTO, @@ -25,16 +21,14 @@ if NET_CADENCE config ARM_AT91_ETHER tristate "AT91RM9200 Ethernet support" - depends on ARM && ARCH_AT91RM9200 select NET_CORE - select PHYLIB + select MACB ---help--- If you wish to compile a kernel for the AT91RM9200 and enable ethernet support, then you should always answer Y to this. config MACB tristate "Cadence MACB/GEM support" - depends on HAVE_NET_MACB select PHYLIB ---help--- The Cadence MACB ethernet interface is found on many Atmel AT32 and diff --git a/drivers/net/ethernet/cadence/Makefile b/drivers/net/ethernet/cadence/Makefile index 798b1e0fd220..9068b8331ed1 100644 --- a/drivers/net/ethernet/cadence/Makefile +++ b/drivers/net/ethernet/cadence/Makefile @@ -2,5 +2,5 @@ # Makefile for the Atmel network device drivers. # -obj-$(CONFIG_ARM_AT91_ETHER) += at91_ether.o macb.o +obj-$(CONFIG_ARM_AT91_ETHER) += at91_ether.o obj-$(CONFIG_MACB) += macb.o diff --git a/drivers/net/ethernet/cadence/at91_ether.c b/drivers/net/ethernet/cadence/at91_ether.c index 375d272d1a5f..b92815aabc65 100644 --- a/drivers/net/ethernet/cadence/at91_ether.c +++ b/drivers/net/ethernet/cadence/at91_ether.c @@ -32,8 +32,6 @@ #include <linux/phy.h> #include <linux/io.h> -#include <asm/mach-types.h> - #include "macb.h" #define DRV_NAME "at91_ether" @@ -61,9 +59,10 @@ static short __init unpack_mac_address(struct net_device *dev, unsigned int hi, unsigned int lo) { + struct macb *lp = netdev_priv(dev); char addr[6]; - if (machine_is_csb337()) { + if (lp->board_data.rev_eth_addr) { addr[5] = (lo & 0xff); /* The CSB337 bootloader stores the MAC the wrong-way around */ addr[4] = (lo & 0xff00) >> 8; addr[3] = (lo & 0xff0000) >> 16; diff --git a/drivers/net/ethernet/cadence/macb.c b/drivers/net/ethernet/cadence/macb.c index 6c84a1104aff..6a4f4998cfe5 100644 --- a/drivers/net/ethernet/cadence/macb.c +++ b/drivers/net/ethernet/cadence/macb.c @@ -553,7 +553,7 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id) while (status) { /* close possible race with dev_close */ if (unlikely(!netif_running(dev))) { - macb_writel(bp, IDR, ~0UL); + macb_writel(bp, IDR, -1); break; } @@ -789,11 +789,11 @@ static void macb_reset_hw(struct macb *bp) macb_writel(bp, NCR, MACB_BIT(CLRSTAT)); /* Clear all status flags */ - macb_writel(bp, TSR, ~0UL); - macb_writel(bp, RSR, ~0UL); + macb_writel(bp, TSR, -1); + macb_writel(bp, RSR, -1); /* Disable all interrupts */ - macb_writel(bp, IDR, ~0UL); + macb_writel(bp, IDR, -1); macb_readl(bp, ISR); } diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index c1ae76968f47..2ab8043e1e28 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -219,6 +219,24 @@ config USB_NET_CDC_NCM * ST-Ericsson M343 HSPA Mobile Broadband Modem (reference design) * Ericsson F5521gw Mobile Broadband Module +config USB_NET_CDC_MBIM + tristate "CDC MBIM support" + depends on USB_USBNET + select USB_WDM + select USB_NET_CDC_NCM + help + This driver provides support for CDC MBIM (Mobile Broadband + Interface Model) devices. The CDC MBIM specification is + available from <http://www.usb.org/>. + + MBIM devices require configuration using the management + protocol defined by the MBIM specification. This driver + provides unfiltered access to the MBIM control channel + through the associated /dev/cdc-wdmx character device. + + To compile this driver as a module, choose M here: the + module will be called cdc_mbim. + config USB_NET_DM9601 tristate "Davicom DM9601 based USB 1.1 10/100 ethernet devices" depends on USB_USBNET diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index bf063008c1af..478691326f37 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -31,4 +31,5 @@ obj-$(CONFIG_USB_NET_CX82310_ETH) += cx82310_eth.o obj-$(CONFIG_USB_NET_CDC_NCM) += cdc_ncm.o obj-$(CONFIG_USB_VL600) += lg-vl600.o obj-$(CONFIG_USB_NET_QMI_WWAN) += qmi_wwan.o +obj-$(CONFIG_USB_NET_CDC_MBIM) += cdc_mbim.o diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c new file mode 100644 index 000000000000..42f51c71ec1f --- /dev/null +++ b/drivers/net/usb/cdc_mbim.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2012 Smith Micro Software, Inc. + * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> + * + * This driver is based on and reuse most of cdc_ncm, which is + * Copyright (C) ST-Ericsson 2010-2012 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/ethtool.h> +#include <linux/if_vlan.h> +#include <linux/ip.h> +#include <linux/mii.h> +#include <linux/usb.h> +#include <linux/usb/cdc.h> +#include <linux/usb/usbnet.h> +#include <linux/usb/cdc-wdm.h> +#include <linux/usb/cdc_ncm.h> + +/* driver specific data - must match cdc_ncm usage */ +struct cdc_mbim_state { + struct cdc_ncm_ctx *ctx; + atomic_t pmcount; + struct usb_driver *subdriver; + struct usb_interface *control; + struct usb_interface *data; +}; + +/* using a counter to merge subdriver requests with our own into a combined state */ +static int cdc_mbim_manage_power(struct usbnet *dev, int on) +{ + struct cdc_mbim_state *info = (void *)&dev->data; + int rv = 0; + + dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(&info->pmcount), on); + + if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) { + /* need autopm_get/put here to ensure the usbcore sees the new value */ + rv = usb_autopm_get_interface(dev->intf); + if (rv < 0) + goto err; + dev->intf->needs_remote_wakeup = on; + usb_autopm_put_interface(dev->intf); + } +err: + return rv; +} + +static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) +{ + struct usbnet *dev = usb_get_intfdata(intf); + + /* can be called while disconnecting */ + if (!dev) + return 0; + + return cdc_mbim_manage_power(dev, status); +} + + +static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf) +{ + struct cdc_ncm_ctx *ctx; + struct usb_driver *subdriver = ERR_PTR(-ENODEV); + int ret = -ENODEV; + u8 data_altsetting = CDC_NCM_DATA_ALTSETTING_NCM; + struct cdc_mbim_state *info = (void *)&dev->data; + + /* see if interface supports MBIM alternate setting */ + if (intf->num_altsetting == 2) { + if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) + usb_set_interface(dev->udev, + intf->cur_altsetting->desc.bInterfaceNumber, + CDC_NCM_COMM_ALTSETTING_MBIM); + data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM; + } + + /* Probably NCM, defer for cdc_ncm_bind */ + if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting)) + goto err; + + ret = cdc_ncm_bind_common(dev, intf, data_altsetting); + if (ret) + goto err; + + ctx = info->ctx; + + /* The MBIM descriptor and the status endpoint are required */ + if (ctx->mbim_desc && dev->status) + subdriver = usb_cdc_wdm_register(ctx->control, + &dev->status->desc, + le16_to_cpu(ctx->mbim_desc->wMaxControlMessage), + cdc_mbim_wdm_manage_power); + if (IS_ERR(subdriver)) { + ret = PTR_ERR(subdriver); + cdc_ncm_unbind(dev, intf); + goto err; + } + + /* can't let usbnet use the interrupt endpoint */ + dev->status = NULL; + info->subdriver = subdriver; + + /* MBIM cannot do ARP */ + dev->net->flags |= IFF_NOARP; + + /* no need to put the VLAN tci in the packet headers */ + dev->net->features |= NETIF_F_HW_VLAN_TX; +err: + return ret; +} + +static void cdc_mbim_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + + /* disconnect subdriver from control interface */ + if (info->subdriver && info->subdriver->disconnect) + info->subdriver->disconnect(ctx->control); + info->subdriver = NULL; + + /* let NCM unbind clean up both control and data interface */ + cdc_ncm_unbind(dev, intf); +} + + +static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) +{ + struct sk_buff *skb_out; + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + __le32 sign = cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN); + u16 tci = 0; + u8 *c; + + if (!ctx) + goto error; + + if (skb) { + if (skb->len <= sizeof(ETH_HLEN)) + goto error; + + /* mapping VLANs to MBIM sessions: + * no tag => IPS session <0> + * 1 - 255 => IPS session <vlanid> + * 256 - 511 => DSS session <vlanid - 256> + * 512 - 4095 => unsupported, drop + */ + vlan_get_tag(skb, &tci); + + switch (tci & 0x0f00) { + case 0x0000: /* VLAN ID 0 - 255 */ + /* verify that datagram is IPv4 or IPv6 */ + skb_reset_mac_header(skb); + switch (eth_hdr(skb)->h_proto) { + case htons(ETH_P_IP): + case htons(ETH_P_IPV6): + break; + default: + goto error; + } + c = (u8 *)&sign; + c[3] = tci; + break; + case 0x0100: /* VLAN ID 256 - 511 */ + sign = cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN); + c = (u8 *)&sign; + c[3] = tci; + break; + default: + netif_err(dev, tx_err, dev->net, + "unsupported tci=0x%04x\n", tci); + goto error; + } + skb_pull(skb, ETH_HLEN); + } + + spin_lock_bh(&ctx->mtx); + skb_out = cdc_ncm_fill_tx_frame(ctx, skb, sign); + spin_unlock_bh(&ctx->mtx); + return skb_out; + +error: + if (skb) + dev_kfree_skb_any(skb); + + return NULL; +} + +static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci) +{ + __be16 proto = htons(ETH_P_802_3); + struct sk_buff *skb = NULL; + + if (tci < 256) { /* IPS session? */ + if (len < sizeof(struct iphdr)) + goto err; + + switch (*buf & 0xf0) { + case 0x40: + proto = htons(ETH_P_IP); + break; + case 0x60: + proto = htons(ETH_P_IPV6); + break; + default: + goto err; + } + } + + skb = netdev_alloc_skb_ip_align(dev->net, len + ETH_HLEN); + if (!skb) + goto err; + + /* add an ethernet header */ + skb_put(skb, ETH_HLEN); + skb_reset_mac_header(skb); + eth_hdr(skb)->h_proto = proto; + memset(eth_hdr(skb)->h_source, 0, ETH_ALEN); + memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); + + /* add datagram */ + memcpy(skb_put(skb, len), buf, len); + + /* map MBIM session to VLAN */ + if (tci) + vlan_put_tag(skb, tci); +err: + return skb; +} + +static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in) +{ + struct sk_buff *skb; + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + 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 */ + u8 *c; + u16 tci; + + 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); + + switch (ndp16->dwSignature & cpu_to_le32(0x00ffffff)) { + case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN): + c = (u8 *)&ndp16->dwSignature; + tci = c[3]; + break; + case cpu_to_le32(USB_CDC_MBIM_NDP16_DSS_SIGN): + c = (u8 *)&ndp16->dwSignature; + tci = c[3] + 256; + break; + default: + netif_dbg(dev, rx_err, dev->net, + "unsupported NDP signature <0x%08x>\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); + len = le16_to_cpu(dpe16->wDatagramLength); + + /* + * CDC NCM ch. 3.7 + * All entries after first NULL entry are to be ignored + */ + if ((offset == 0) || (len == 0)) { + if (!x) + goto err_ndp; /* empty NTB */ + break; + } + + /* sanity checking */ + if (((offset + len) > skb_in->len) || (len > ctx->rx_max)) { + netif_dbg(dev, rx_err, dev->net, + "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n", + x, offset, len, skb_in); + if (!x) + goto err_ndp; + break; + } else { + skb = cdc_mbim_process_dgram(dev, skb_in->data + offset, len, tci); + if (!skb) + goto error; + 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; +} + +static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message) +{ + int ret = 0; + struct usbnet *dev = usb_get_intfdata(intf); + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + + if (ctx == NULL) { + ret = -1; + goto error; + } + + ret = usbnet_suspend(intf, message); + if (ret < 0) + goto error; + + if (intf == ctx->control && info->subdriver && info->subdriver->suspend) + ret = info->subdriver->suspend(intf, message); + if (ret < 0) + usbnet_resume(intf); + +error: + return ret; +} + +static int cdc_mbim_resume(struct usb_interface *intf) +{ + int ret = 0; + struct usbnet *dev = usb_get_intfdata(intf); + struct cdc_mbim_state *info = (void *)&dev->data; + struct cdc_ncm_ctx *ctx = info->ctx; + bool callsub = (intf == ctx->control && info->subdriver && info->subdriver->resume); + + if (callsub) + ret = info->subdriver->resume(intf); + if (ret < 0) + goto err; + ret = usbnet_resume(intf); + if (ret < 0 && callsub && info->subdriver->suspend) + info->subdriver->suspend(intf, PMSG_SUSPEND); +err: + return ret; +} + +static const struct driver_info cdc_mbim_info = { + .description = "CDC MBIM", + .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, + .bind = cdc_mbim_bind, + .unbind = cdc_mbim_unbind, + .manage_power = cdc_mbim_manage_power, + .rx_fixup = cdc_mbim_rx_fixup, + .tx_fixup = cdc_mbim_tx_fixup, +}; + +static const struct usb_device_id mbim_devs[] = { + /* This duplicate NCM entry is intentional. MBIM devices can + * be disguised as NCM by default, and this is necessary to + * allow us to bind the correct driver_info to such devices. + * + * bind() will sort out this for us, selecting the correct + * entry and reject the other + */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, + }, + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), + .driver_info = (unsigned long)&cdc_mbim_info, + }, + { + }, +}; +MODULE_DEVICE_TABLE(usb, mbim_devs); + +static struct usb_driver cdc_mbim_driver = { + .name = "cdc_mbim", + .id_table = mbim_devs, + .probe = usbnet_probe, + .disconnect = usbnet_disconnect, + .suspend = cdc_mbim_suspend, + .resume = cdc_mbim_resume, + .reset_resume = cdc_mbim_resume, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; +module_usb_driver(cdc_mbim_driver); + +MODULE_AUTHOR("Greg Suarez <gsuarez@smithmicro.com>"); +MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>"); +MODULE_DESCRIPTION("USB CDC MBIM host driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/usb/cdc_ncm.c b/drivers/net/usb/cdc_ncm.c index 4cd582a4f625..0ed03b1ba39a 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,7 +78,10 @@ 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; iface_no = ctx->control->cur_altsetting->desc.bInterfaceNumber; @@ -184,10 +107,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,41 +147,21 @@ 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; - 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); + 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: if (err < 0) pr_debug("Setting NTB Input Size failed\n"); @@ -257,7 +169,7 @@ size_err: /* 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,8 +211,8 @@ 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 */ @@ -327,12 +239,18 @@ size_err: 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); + 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; max_datagram_size = kzalloc(sizeof(*max_datagram_size), GFP_KERNEL); @@ -349,7 +267,7 @@ size_err: 2, 1000); 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); @@ -358,12 +276,10 @@ size_err: 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 != @@ -384,8 +300,8 @@ size_err: } 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 +367,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 +441,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 +460,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 interfaces, if any */ @@ -557,7 +480,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; @@ -574,11 +497,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); @@ -587,13 +512,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; @@ -607,8 +525,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); @@ -638,52 +557,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) { @@ -692,35 +680,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 */ @@ -728,7 +702,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); @@ -741,31 +722,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 */ @@ -781,16 +761,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; @@ -801,75 +777,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)); - - 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)); + /* 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 */ - /* 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; @@ -882,6 +807,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) { @@ -936,7 +862,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; @@ -947,17 +873,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; @@ -991,20 +912,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", @@ -1012,20 +936,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); @@ -1037,7 +993,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; } @@ -1048,7 +1004,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 { @@ -1061,6 +1017,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; diff --git a/include/linux/platform_data/macb.h b/include/linux/platform_data/macb.h index b081c7245ec8..044a124bfbbc 100644 --- a/include/linux/platform_data/macb.h +++ b/include/linux/platform_data/macb.h @@ -12,6 +12,7 @@ struct macb_platform_data { u32 phy_mask; int phy_irq_pin; /* PHY IRQ */ u8 is_rmii; /* using RMII interface? */ + u8 rev_eth_addr; /* reverse Ethernet address byte order */ }; #endif /* __MACB_PDATA_H__ */ diff --git a/include/linux/usb/cdc.h b/include/linux/usb/cdc.h index 81a927930bfd..f35aa0a338c7 100644 --- a/include/linux/usb/cdc.h +++ b/include/linux/usb/cdc.h @@ -19,6 +19,7 @@ #define USB_CDC_SUBCLASS_OBEX 0x0b #define USB_CDC_SUBCLASS_EEM 0x0c #define USB_CDC_SUBCLASS_NCM 0x0d +#define USB_CDC_SUBCLASS_MBIM 0x0e #define USB_CDC_PROTO_NONE 0 @@ -33,6 +34,7 @@ #define USB_CDC_PROTO_EEM 7 #define USB_CDC_NCM_PROTO_NTB 1 +#define USB_CDC_MBIM_PROTO_NTB 2 /*-------------------------------------------------------------------------*/ @@ -53,6 +55,7 @@ #define USB_CDC_DMM_TYPE 0x14 #define USB_CDC_OBEX_TYPE 0x15 #define USB_CDC_NCM_TYPE 0x1a +#define USB_CDC_MBIM_TYPE 0x1b /* "Header Functional Descriptor" from CDC spec 5.2.3.1 */ struct usb_cdc_header_desc { @@ -187,6 +190,21 @@ struct usb_cdc_ncm_desc { __le16 bcdNcmVersion; __u8 bmNetworkCapabilities; } __attribute__ ((packed)); + +/* "MBIM Control Model Functional Descriptor" */ +struct usb_cdc_mbim_desc { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + + __le16 bcdMBIMVersion; + __le16 wMaxControlMessage; + __u8 bNumberFilters; + __u8 bMaxFilterSize; + __le16 wMaxSegmentSize; + __u8 bmNetworkCapabilities; +} __attribute__ ((packed)); + /*-------------------------------------------------------------------------*/ /* @@ -332,6 +350,11 @@ struct usb_cdc_ncm_nth32 { #define USB_CDC_NCM_NDP32_CRC_SIGN 0x316D636E /* ncm1 */ #define USB_CDC_NCM_NDP32_NOCRC_SIGN 0x306D636E /* ncm0 */ +#define USB_CDC_MBIM_NDP16_IPS_SIGN 0x00535049 /* IPS<sessionID> : IPS0 for now */ +#define USB_CDC_MBIM_NDP32_IPS_SIGN 0x00737069 /* ips<sessionID> : ips0 for now */ +#define USB_CDC_MBIM_NDP16_DSS_SIGN 0x00535344 /* DSS<sessionID> */ +#define USB_CDC_MBIM_NDP32_DSS_SIGN 0x00737364 /* dss<sessionID> */ + /* 16-bit NCM Datagram Pointer Entry */ struct usb_cdc_ncm_dpe16 { __le16 wDatagramIndex; diff --git a/include/linux/usb/cdc_ncm.h b/include/linux/usb/cdc_ncm.h new file mode 100644 index 000000000000..3b8f9d4fc3fe --- /dev/null +++ b/include/linux/usb/cdc_ncm.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) ST-Ericsson 2010-2012 + * Contact: Alexey Orishko <alexey.orishko@stericsson.com> + * Original author: Hans Petter Selasky <hans.petter.selasky@stericsson.com> + * + * USB Host Driver for Network Control Model (NCM) + * http://www.usb.org/developers/devclass_docs/NCM10.zip + * + * The NCM encoding, decoding and initialization logic + * derives from FreeBSD 8.x. if_cdce.c and if_cdcereg.h + * + * This software is available to you under a choice of one of two + * licenses. You may choose this file to be licensed under the terms + * of the GNU General Public License (GPL) Version 2 or the 2-clause + * BSD license listed below: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define CDC_NCM_COMM_ALTSETTING_NCM 0 +#define CDC_NCM_COMM_ALTSETTING_MBIM 1 + +#define CDC_NCM_DATA_ALTSETTING_NCM 1 +#define CDC_NCM_DATA_ALTSETTING_MBIM 2 + +/* 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 */ + +/* Minimum value for MaxDatagramSize, ch. 8.1.3 */ +#define CDC_MBIM_MIN_DATAGRAM_SIZE 2048 /* 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)) + +#define CDC_NCM_NDP_SIZE \ + (sizeof(struct usb_cdc_ncm_ndp16) + \ + (CDC_NCM_DPT_DATAGRAMS_MAX + 1) * sizeof(struct usb_cdc_ncm_dpe16)) + +#define cdc_ncm_comm_intf_is_mbim(x) ((x)->desc.bInterfaceSubClass == USB_CDC_SUBCLASS_MBIM && \ + (x)->desc.bInterfaceProtocol == USB_CDC_PROTO_NONE) +#define cdc_ncm_data_intf_is_mbim(x) ((x)->desc.bInterfaceProtocol == USB_CDC_MBIM_PROTO_NTB) + +struct cdc_ncm_ctx { + 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_mbim_desc *mbim_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; + __le32 tx_rem_sign; + + spinlock_t mtx; + atomic_t stop; + + u32 tx_timer_pending; + 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; +}; + +extern int cdc_ncm_bind_common(struct usbnet *dev, struct usb_interface *intf, u8 data_altsetting); +extern void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf); +extern struct sk_buff *cdc_ncm_fill_tx_frame(struct cdc_ncm_ctx *ctx, struct sk_buff *skb, __le32 sign); +extern int cdc_ncm_rx_verify_nth16(struct cdc_ncm_ctx *ctx, struct sk_buff *skb_in); +extern int cdc_ncm_rx_verify_ndp16(struct sk_buff *skb_in, int ndpoffset); diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h index 8a2a203eb15d..20210d79e36a 100644 --- a/include/net/ip6_fib.h +++ b/include/net/ip6_fib.h @@ -47,6 +47,8 @@ struct fib6_config { unsigned long fc_expires; struct nlattr *fc_mx; int fc_mx_len; + int fc_mp_len; + struct nlattr *fc_mp; struct nl_info fc_nlinfo; }; @@ -99,6 +101,14 @@ struct rt6_info { struct in6_addr rt6i_gateway; + /* Multipath routes: + * siblings is a list of rt6_info that have the the same metric/weight, + * destination, but not the same gateway. nsiblings is just a cache + * to speed up lookup. + */ + struct list_head rt6i_siblings; + unsigned int rt6i_nsiblings; + atomic_t rt6i_ref; /* These are in a separate cache line. */ diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index 24995a93ef8c..710cafd2e1a9 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -672,6 +672,8 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, iter->rt6i_idev == rt->rt6i_idev && ipv6_addr_equal(&iter->rt6i_gateway, &rt->rt6i_gateway)) { + if (rt->rt6i_nsiblings) + rt->rt6i_nsiblings = 0; if (!(iter->rt6i_flags & RTF_EXPIRES)) return -EEXIST; if (!(rt->rt6i_flags & RTF_EXPIRES)) @@ -680,6 +682,21 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, rt6_set_expires(iter, rt->dst.expires); return -EEXIST; } + /* If we have the same destination and the same metric, + * but not the same gateway, then the route we try to + * add is sibling to this route, increment our counter + * of siblings, and later we will add our route to the + * list. + * Only static routes (which don't have flag + * RTF_EXPIRES) are used for ECMPv6. + * + * To avoid long list, we only had siblings if the + * route have a gateway. + */ + if (rt->rt6i_flags & RTF_GATEWAY && + !(rt->rt6i_flags & RTF_EXPIRES) && + !(iter->rt6i_flags & RTF_EXPIRES)) + rt->rt6i_nsiblings++; } if (iter->rt6i_metric > rt->rt6i_metric) @@ -692,6 +709,35 @@ static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt, if (ins == &fn->leaf) fn->rr_ptr = NULL; + /* Link this route to others same route. */ + if (rt->rt6i_nsiblings) { + unsigned int rt6i_nsiblings; + struct rt6_info *sibling, *temp_sibling; + + /* Find the first route that have the same metric */ + sibling = fn->leaf; + while (sibling) { + if (sibling->rt6i_metric == rt->rt6i_metric) { + list_add_tail(&rt->rt6i_siblings, + &sibling->rt6i_siblings); + break; + } + sibling = sibling->dst.rt6_next; + } + /* For each sibling in the list, increment the counter of + * siblings. BUG() if counters does not match, list of siblings + * is broken! + */ + rt6i_nsiblings = 0; + list_for_each_entry_safe(sibling, temp_sibling, + &rt->rt6i_siblings, rt6i_siblings) { + sibling->rt6i_nsiblings++; + BUG_ON(sibling->rt6i_nsiblings != rt->rt6i_nsiblings); + rt6i_nsiblings++; + } + BUG_ON(rt6i_nsiblings != rt->rt6i_nsiblings); + } + /* * insert node */ @@ -1193,6 +1239,17 @@ static void fib6_del_route(struct fib6_node *fn, struct rt6_info **rtp, if (fn->rr_ptr == rt) fn->rr_ptr = NULL; + /* Remove this entry from other siblings */ + if (rt->rt6i_nsiblings) { + struct rt6_info *sibling, *next_sibling; + + list_for_each_entry_safe(sibling, next_sibling, + &rt->rt6i_siblings, rt6i_siblings) + sibling->rt6i_nsiblings--; + rt->rt6i_nsiblings = 0; + list_del_init(&rt->rt6i_siblings); + } + /* Adjust walkers */ read_lock(&fib6_walker_lock); FOR_WALKERS(w) { diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 7c7e963260e1..126da562d3eb 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -57,6 +57,7 @@ #include <net/xfrm.h> #include <net/netevent.h> #include <net/netlink.h> +#include <net/nexthop.h> #include <asm/uaccess.h> @@ -289,6 +290,8 @@ static inline struct rt6_info *ip6_dst_alloc(struct net *net, memset(dst + 1, 0, sizeof(*rt) - sizeof(*dst)); rt6_init_peer(rt, table ? &table->tb6_peers : net->ipv6.peers); rt->rt6i_genid = rt_genid(net); + INIT_LIST_HEAD(&rt->rt6i_siblings); + rt->rt6i_nsiblings = 0; } return rt; } @@ -385,6 +388,69 @@ static bool rt6_need_strict(const struct in6_addr *daddr) (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK); } +/* Multipath route selection: + * Hash based function using packet header and flowlabel. + * Adapted from fib_info_hashfn() + */ +static int rt6_info_hash_nhsfn(unsigned int candidate_count, + const struct flowi6 *fl6) +{ + unsigned int val = fl6->flowi6_proto; + + val ^= fl6->daddr.s6_addr32[0]; + val ^= fl6->daddr.s6_addr32[1]; + val ^= fl6->daddr.s6_addr32[2]; + val ^= fl6->daddr.s6_addr32[3]; + + val ^= fl6->saddr.s6_addr32[0]; + val ^= fl6->saddr.s6_addr32[1]; + val ^= fl6->saddr.s6_addr32[2]; + val ^= fl6->saddr.s6_addr32[3]; + + /* Work only if this not encapsulated */ + switch (fl6->flowi6_proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_SCTP: + val ^= fl6->fl6_sport; + val ^= fl6->fl6_dport; + break; + + case IPPROTO_ICMPV6: + val ^= fl6->fl6_icmp_type; + val ^= fl6->fl6_icmp_code; + break; + } + /* RFC6438 recommands to use flowlabel */ + val ^= fl6->flowlabel; + + /* Perhaps, we need to tune, this function? */ + val = val ^ (val >> 7) ^ (val >> 12); + return val % candidate_count; +} + +static struct rt6_info *rt6_multipath_select(struct rt6_info *match, + struct flowi6 *fl6) +{ + struct rt6_info *sibling, *next_sibling; + int route_choosen; + + route_choosen = rt6_info_hash_nhsfn(match->rt6i_nsiblings + 1, fl6); + /* Don't change the route, if route_choosen == 0 + * (siblings does not include ourself) + */ + if (route_choosen) + list_for_each_entry_safe(sibling, next_sibling, + &match->rt6i_siblings, rt6i_siblings) { + route_choosen--; + if (route_choosen == 0) { + match = sibling; + break; + } + } + return match; +} + /* * Route lookup. Any table->tb6_lock is implied. */ @@ -702,6 +768,8 @@ static struct rt6_info *ip6_pol_route_lookup(struct net *net, restart: rt = fn->leaf; rt = rt6_device_match(net, rt, &fl6->saddr, fl6->flowi6_oif, flags); + if (rt->rt6i_nsiblings && fl6->flowi6_oif == 0) + rt = rt6_multipath_select(rt, fl6); BACKTRACK(net, &fl6->saddr); out: dst_use(&rt->dst, jiffies); @@ -863,7 +931,8 @@ restart_2: restart: rt = rt6_select(fn, oif, strict | reachable); - + if (rt->rt6i_nsiblings && oif == 0) + rt = rt6_multipath_select(rt, fl6); BACKTRACK(net, &fl6->saddr); if (rt == net->ipv6.ip6_null_entry || rt->rt6i_flags & RTF_CACHE) @@ -2249,6 +2318,7 @@ static const struct nla_policy rtm_ipv6_policy[RTA_MAX+1] = { [RTA_IIF] = { .type = NLA_U32 }, [RTA_PRIORITY] = { .type = NLA_U32 }, [RTA_METRICS] = { .type = NLA_NESTED }, + [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) }, }; static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh, @@ -2326,11 +2396,65 @@ static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh, if (tb[RTA_TABLE]) cfg->fc_table = nla_get_u32(tb[RTA_TABLE]); + if (tb[RTA_MULTIPATH]) { + cfg->fc_mp = nla_data(tb[RTA_MULTIPATH]); + cfg->fc_mp_len = nla_len(tb[RTA_MULTIPATH]); + } + err = 0; errout: return err; } +static int ip6_route_multipath(struct fib6_config *cfg, int add) +{ + struct fib6_config r_cfg; + struct rtnexthop *rtnh; + int remaining; + int attrlen; + int err = 0, last_err = 0; + +beginning: + rtnh = (struct rtnexthop *)cfg->fc_mp; + remaining = cfg->fc_mp_len; + + /* Parse a Multipath Entry */ + while (rtnh_ok(rtnh, remaining)) { + memcpy(&r_cfg, cfg, sizeof(*cfg)); + if (rtnh->rtnh_ifindex) + r_cfg.fc_ifindex = rtnh->rtnh_ifindex; + + attrlen = rtnh_attrlen(rtnh); + if (attrlen > 0) { + struct nlattr *nla, *attrs = rtnh_attrs(rtnh); + + nla = nla_find(attrs, attrlen, RTA_GATEWAY); + if (nla) { + nla_memcpy(&r_cfg.fc_gateway, nla, 16); + r_cfg.fc_flags |= RTF_GATEWAY; + } + } + err = add ? ip6_route_add(&r_cfg) : ip6_route_del(&r_cfg); + if (err) { + last_err = err; + /* If we are trying to remove a route, do not stop the + * loop when ip6_route_del() fails (because next hop is + * already gone), we should try to remove all next hops. + */ + if (add) { + /* If add fails, we should try to delete all + * next hops that have been already added. + */ + add = 0; + goto beginning; + } + } + rtnh = rtnh_next(rtnh, &remaining); + } + + return last_err; +} + static int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) { struct fib6_config cfg; @@ -2340,7 +2464,10 @@ static int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *a if (err < 0) return err; - return ip6_route_del(&cfg); + if (cfg.fc_mp) + return ip6_route_multipath(&cfg, 0); + else + return ip6_route_del(&cfg); } static int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) @@ -2352,7 +2479,10 @@ static int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *a if (err < 0) return err; - return ip6_route_add(&cfg); + if (cfg.fc_mp) + return ip6_route_multipath(&cfg, 1); + else + return ip6_route_add(&cfg); } static inline size_t rt6_nlmsg_size(void) |