diff mbox

[net-next,1/1,v3] drivers: net: rmnet: Initial implementation

Message ID 1492146329-4304-2-git-send-email-subashab@codeaurora.org
State Changes Requested, archived
Delegated to: David Miller
Headers show

Commit Message

Subash Abhinov Kasiviswanathan April 14, 2017, 5:05 a.m. UTC
RmNet driver provides a transport agnostic MAP (multiplexing and
aggregation protocol) support in embedded module. Module provides
virtual network devices which can be attached to any IP-mode
physical device. This will be used to provide all MAP functionality
on future hardware in a single consistent location.

Signed-off-by: Subash Abhinov Kasiviswanathan <subashab@codeaurora.org>
---
 Documentation/networking/rmnet.txt    |  83 +++++
 drivers/net/Kconfig                   |   2 +
 drivers/net/Makefile                  |   1 +
 drivers/net/rmnet/Kconfig             |  23 ++
 drivers/net/rmnet/Makefile            |  14 +
 drivers/net/rmnet/rmnet_config.c      | 592 ++++++++++++++++++++++++++++++++++
 drivers/net/rmnet/rmnet_config.h      |  79 +++++
 drivers/net/rmnet/rmnet_handlers.c    | 517 +++++++++++++++++++++++++++++
 drivers/net/rmnet/rmnet_handlers.h    |  24 ++
 drivers/net/rmnet/rmnet_main.c        |  52 +++
 drivers/net/rmnet/rmnet_map.h         | 100 ++++++
 drivers/net/rmnet/rmnet_map_command.c | 180 +++++++++++
 drivers/net/rmnet/rmnet_map_data.c    | 145 +++++++++
 drivers/net/rmnet/rmnet_private.h     |  76 +++++
 drivers/net/rmnet/rmnet_stats.c       |  86 +++++
 drivers/net/rmnet/rmnet_stats.h       |  61 ++++
 drivers/net/rmnet/rmnet_vnd.c         | 353 ++++++++++++++++++++
 drivers/net/rmnet/rmnet_vnd.h         |  34 ++
 include/uapi/linux/Kbuild             |   1 +
 include/uapi/linux/if_arp.h           |   1 +
 include/uapi/linux/if_ether.h         |   4 +-
 include/uapi/linux/rmnet.h            |  34 ++
 22 files changed, 2461 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/networking/rmnet.txt
 create mode 100644 drivers/net/rmnet/Kconfig
 create mode 100644 drivers/net/rmnet/Makefile
 create mode 100644 drivers/net/rmnet/rmnet_config.c
 create mode 100644 drivers/net/rmnet/rmnet_config.h
 create mode 100644 drivers/net/rmnet/rmnet_handlers.c
 create mode 100644 drivers/net/rmnet/rmnet_handlers.h
 create mode 100644 drivers/net/rmnet/rmnet_main.c
 create mode 100644 drivers/net/rmnet/rmnet_map.h
 create mode 100644 drivers/net/rmnet/rmnet_map_command.c
 create mode 100644 drivers/net/rmnet/rmnet_map_data.c
 create mode 100644 drivers/net/rmnet/rmnet_private.h
 create mode 100644 drivers/net/rmnet/rmnet_stats.c
 create mode 100644 drivers/net/rmnet/rmnet_stats.h
 create mode 100644 drivers/net/rmnet/rmnet_vnd.c
 create mode 100644 drivers/net/rmnet/rmnet_vnd.h
 create mode 100644 include/uapi/linux/rmnet.h

Comments

Jiri Pirko April 14, 2017, 9:07 a.m. UTC | #1
Fri, Apr 14, 2017 at 07:05:29AM CEST, subashab@codeaurora.org wrote:
>RmNet driver provides a transport agnostic MAP (multiplexing and
>aggregation protocol) support in embedded module. Module provides
>virtual network devices which can be attached to any IP-mode
>physical device. This will be used to provide all MAP functionality
>on future hardware in a single consistent location.
>
>Signed-off-by: Subash Abhinov Kasiviswanathan <subashab@codeaurora.org>
>---
> Documentation/networking/rmnet.txt    |  83 +++++
> drivers/net/Kconfig                   |   2 +
> drivers/net/Makefile                  |   1 +
> drivers/net/rmnet/Kconfig             |  23 ++
> drivers/net/rmnet/Makefile            |  14 +
> drivers/net/rmnet/rmnet_config.c      | 592 ++++++++++++++++++++++++++++++++++
> drivers/net/rmnet/rmnet_config.h      |  79 +++++
> drivers/net/rmnet/rmnet_handlers.c    | 517 +++++++++++++++++++++++++++++
> drivers/net/rmnet/rmnet_handlers.h    |  24 ++
> drivers/net/rmnet/rmnet_main.c        |  52 +++
> drivers/net/rmnet/rmnet_map.h         | 100 ++++++
> drivers/net/rmnet/rmnet_map_command.c | 180 +++++++++++
> drivers/net/rmnet/rmnet_map_data.c    | 145 +++++++++
> drivers/net/rmnet/rmnet_private.h     |  76 +++++
> drivers/net/rmnet/rmnet_stats.c       |  86 +++++
> drivers/net/rmnet/rmnet_stats.h       |  61 ++++
> drivers/net/rmnet/rmnet_vnd.c         | 353 ++++++++++++++++++++
> drivers/net/rmnet/rmnet_vnd.h         |  34 ++
> include/uapi/linux/Kbuild             |   1 +
> include/uapi/linux/if_arp.h           |   1 +
> include/uapi/linux/if_ether.h         |   4 +-
> include/uapi/linux/rmnet.h            |  34 ++
> 22 files changed, 2461 insertions(+), 1 deletion(-)
> create mode 100644 Documentation/networking/rmnet.txt
> create mode 100644 drivers/net/rmnet/Kconfig
> create mode 100644 drivers/net/rmnet/Makefile
> create mode 100644 drivers/net/rmnet/rmnet_config.c
> create mode 100644 drivers/net/rmnet/rmnet_config.h
> create mode 100644 drivers/net/rmnet/rmnet_handlers.c
> create mode 100644 drivers/net/rmnet/rmnet_handlers.h
> create mode 100644 drivers/net/rmnet/rmnet_main.c
> create mode 100644 drivers/net/rmnet/rmnet_map.h
> create mode 100644 drivers/net/rmnet/rmnet_map_command.c
> create mode 100644 drivers/net/rmnet/rmnet_map_data.c
> create mode 100644 drivers/net/rmnet/rmnet_private.h
> create mode 100644 drivers/net/rmnet/rmnet_stats.c
> create mode 100644 drivers/net/rmnet/rmnet_stats.h
> create mode 100644 drivers/net/rmnet/rmnet_vnd.c
> create mode 100644 drivers/net/rmnet/rmnet_vnd.h
> create mode 100644 include/uapi/linux/rmnet.h
>
>diff --git a/Documentation/networking/rmnet.txt b/Documentation/networking/rmnet.txt
>new file mode 100644
>index 0000000..58d3ea2
>--- /dev/null
>+++ b/Documentation/networking/rmnet.txt
>@@ -0,0 +1,83 @@
>+1. Introduction
>+
>+rmnet driver is used for supporting the Multiplexing and aggregation
>+Protocol (MAP). This protocol is used by all recent chipsets using Qualcomm
>+Technologies, Inc. modems.
>+
>+This driver can be used to register onto any physical network device in
>+IP mode. Physical transports include USB, HSIC, PCIe and IP accelerator.
>+
>+Multiplexing allows for creation of logical netdevices (rmnet devices) to
>+handle multiple private data networks (PDN) like a default internet, tethering,
>+multimedia messaging service (MMS) or IP media subsystem (IMS). Hardware sends
>+packets with MAP headers to rmnet. Based on the multiplexer id, rmnet
>+routes to the appropriate PDN after removing the MAP header.
>+
>+Aggregation is required to achieve high data rates. This involves hardware
>+sending aggregated bunch of MAP frames. rmnet driver will de-aggregate
>+these MAP frames and send them to appropriate PDN's.
>+
>+2. Packet format
>+
>+a. MAP packet (data / control)
>+
>+MAP header has the same endianness of the IP packet.
>+
>+Packet format -
>+
>+Bit             0             1           2-7      8 - 15           16 - 31
>+Function   Command / Data   Reserved     Pad   Multiplexer ID    Payload length
>+Bit            32 - x
>+Function     Raw  Bytes
>+
>+Command (1)/ Data (0) bit value is to indicate if the packet is a MAP command
>+or data packet. Control packet is used for transport level flow control. Data
>+packets are standard IP packets.
>+
>+Reserved bits are usually zeroed out and to be ignored by receiver.
>+
>+Padding is number of bytes to be added for 4 byte alignment if required by
>+hardware.
>+
>+Multiplexer ID is to indicate the PDN on which data has to be sent.
>+
>+Payload length includes the padding length but does not include MAP header
>+length.
>+
>+b. MAP packet (command specific)
>+
>+Bit             0             1           2-7      8 - 15           16 - 31
>+Function   Command         Reserved     Pad   Multiplexer ID    Payload length
>+Bit          32 - 39        40 - 45    46 - 47       48 - 63
>+Function   Command name    Reserved   Command Type   Reserved
>+Bit          64 - 95
>+Function   Transaction ID
>+Bit          96 - 127
>+Function   Command data
>+
>+Command 1 indicates disabling flow while 2 is enabling flow
>+
>+Command types -
>+0 for MAP command request
>+1 is to acknowledge the receipt of a command
>+2 is for unsupported commands
>+3 is for error during processing of commands
>+
>+c. Aggregation
>+
>+Aggregation is multiple MAP packets (can be data or command) delivered to
>+rmnet in a single linear skb. rmnet will process the individual
>+packets and either ACK the MAP command or deliver the IP packet to the
>+network stack as needed
>+
>+MAP header|IP Packet|Optional padding|MAP header|IP Packet|Optional padding....
>+MAP header|IP Packet|Optional padding|MAP header|Command Packet|Optional pad...
>+
>+3. Userspace configuration
>+
>+rmnet userspace configuration is done through netlink library librmnetctl
>+and command line utility rmnetcli. Utility is hosted in codeaurora forum git.
>+The driver uses rtnl_link_ops for communication.
>+
>+https://source.codeaurora.org/quic/la/platform/vendor/qcom-opensource/\
>+dataservices/tree/rmnetctl
>diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
>index 100fbdc..c4ccd6d 100644
>--- a/drivers/net/Kconfig
>+++ b/drivers/net/Kconfig
>@@ -477,4 +477,6 @@ config FUJITSU_ES
> 
> source "drivers/net/hyperv/Kconfig"
> 
>+source "drivers/net/rmnet/Kconfig"
>+
> endif # NETDEVICES
>diff --git a/drivers/net/Makefile b/drivers/net/Makefile
>index 98ed4d9..29b3945 100644
>--- a/drivers/net/Makefile
>+++ b/drivers/net/Makefile
>@@ -74,3 +74,4 @@ obj-$(CONFIG_HYPERV_NET) += hyperv/
> obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o
> 
> obj-$(CONFIG_FUJITSU_ES) += fjes/
>+obj-$(CONFIG_RMNET) += rmnet/
>diff --git a/drivers/net/rmnet/Kconfig b/drivers/net/rmnet/Kconfig
>new file mode 100644
>index 0000000..63cd477
>--- /dev/null
>+++ b/drivers/net/rmnet/Kconfig
>@@ -0,0 +1,23 @@
>+#
>+# RMNET MAP driver
>+#
>+
>+menuconfig RMNET
>+	depends on NETDEVICES
>+	bool "RmNet MAP driver"
>+	default n
>+	---help---
>+	  If you say Y here, then the rmnet module will be statically
>+	  compiled into the kernel. The rmnet module provides MAP
>+	  functionality for embedded and bridged traffic.
>+if RMNET
>+
>+config RMNET_DEBUG
>+	bool "RmNet Debug Logging"

No, please use standard facilities in order to do debug logging.


>+	default n
>+	---help---
>+	  Say Y here if you want RmNet to be able to log packets in main
>+	  system log. This should not be enabled on production builds as it can
>+	  impact system performance. Note that simply enabling it here will not
>+	  enable the logging; it must be enabled at run-time as well.
>+endif # RMNET
>diff --git a/drivers/net/rmnet/Makefile b/drivers/net/rmnet/Makefile
>new file mode 100644
>index 0000000..2b6c9cf
>--- /dev/null
>+++ b/drivers/net/rmnet/Makefile
>@@ -0,0 +1,14 @@
>+#
>+# Makefile for the RMNET module
>+#
>+
>+rmnet-y		 := rmnet_main.o
>+rmnet-y		 += rmnet_config.o
>+rmnet-y		 += rmnet_vnd.o
>+rmnet-y		 += rmnet_handlers.o
>+rmnet-y		 += rmnet_map_data.o
>+rmnet-y		 += rmnet_map_command.o
>+rmnet-y		 += rmnet_stats.o
>+obj-$(CONFIG_RMNET) += rmnet.o
>+
>+CFLAGS_rmnet_main.o := -I$(src)
>diff --git a/drivers/net/rmnet/rmnet_config.c b/drivers/net/rmnet/rmnet_config.c
>new file mode 100644
>index 0000000..a4bc76b
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_config.c
>@@ -0,0 +1,592 @@
>+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ * RMNET configuration engine
>+ *
>+ */
>+
>+#include <net/sock.h>
>+#include <linux/module.h>
>+#include <linux/netlink.h>
>+#include <linux/netdevice.h>
>+#include <linux/skbuff.h>
>+#include <linux/spinlock.h>
>+#include <linux/rmnet.h>
>+#include "rmnet_config.h"
>+#include "rmnet_handlers.h"
>+#include "rmnet_vnd.h"
>+#include "rmnet_private.h"
>+
>+RMNET_LOG_MODULE(RMNET_LOGMASK_CONFIG);
>+
>+/* Local Definitions and Declarations */
>+#define RMNET_LOCAL_LOGICAL_ENDPOINT -1
>+
>+/* _rmnet_is_physical_endpoint_associated() - Determines if device is associated
>+ * @dev:      Device to get check
>+ *
>+ * Compares device rx_handler callback pointer against known function
>+ */
>+static inline int _rmnet_is_physical_endpoint_associated(struct net_device *dev)
>+{
>+	rx_handler_func_t *rx_handler;
>+
>+	rx_handler = rcu_dereference(dev->rx_handler);
>+
>+	if (rx_handler == rmnet_rx_handler)
>+		return 1;
=>+	else
>+		return 0;
>+}
>+
>+/* _rmnet_get_phys_ep_config() - Get physical ep config for an associated device
>+ * @dev:      Device to get endpoint configuration from
>+ */
>+static inline struct rmnet_phys_ep_conf_s *_rmnet_get_phys_ep_config
>+						(struct net_device *dev)
>+{
>+	if (_rmnet_is_physical_endpoint_associated(dev))
>+		return (struct rmnet_phys_ep_conf_s *)
>+			rcu_dereference(dev->rx_handler_data);
>+	else
>+		return 0;
>+}
>+
>+struct rmnet_free_vnd_work {
>+	struct work_struct work;
>+	int vnd_id[RMNET_MAX_VND];
>+	int count;
>+};
>+
>+/* _rmnet_get_logical_ep() - Gets the logical end point configuration
>+ * structure for a network device
>+ * @dev:             Device to get endpoint configuration from
>+ * @config_id:       Logical endpoint id on device
>+ * Retrieves the logical_endpoint_config structure.
>+ */
>+static struct rmnet_logical_ep_conf_s *_rmnet_get_logical_ep

Dont use "_" at the start of a name without purpose



>+	(struct net_device *dev, int config_id)
>+{
>+	struct rmnet_phys_ep_conf_s *config;
>+	struct rmnet_logical_ep_conf_s *epconfig_l;
>+
>+	if (rmnet_vnd_is_vnd(dev)) {
>+		epconfig_l = rmnet_vnd_get_le_config(dev);
>+	} else {
>+		config = _rmnet_get_phys_ep_config(dev);
>+
>+		if (!config)
>+			return NULL;
>+
>+		if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT)
>+			epconfig_l = &config->local_ep;
>+		else
>+			epconfig_l = &config->muxed_ep[config_id];
>+	}
>+
>+	return epconfig_l;
>+}
>+
>+/* rmnet_unassociate_network_device() - Unassociate network device
>+ * @dev:      Device to unassociate
>+ *
>+ * Frees all structures generate for device. Unregisters rx_handler
>+ */
>+static int rmnet_unassociate_network_device(struct net_device *dev)
>+{
>+	struct rmnet_phys_ep_conf_s *config;
>+	int config_id = RMNET_LOCAL_LOGICAL_ENDPOINT;
>+	struct rmnet_logical_ep_conf_s *epconfig_l;
>+
>+	ASSERT_RTNL();
>+
>+	LOGL("(%s);", dev->name);
>+
>+	if (!dev || !_rmnet_is_physical_endpoint_associated(dev))
>+		return -EINVAL;
>+
>+	for (; config_id < RMNET_MAX_LOGICAL_EP; config_id++) {
>+		epconfig_l = _rmnet_get_logical_ep(dev, config_id);
>+		if (epconfig_l && epconfig_l->refcount)
>+			return -EINVAL;
>+	}
>+
>+	config = (struct rmnet_phys_ep_conf_s *)
>+		  rcu_dereference(dev->rx_handler_data);
>+
>+	if (!config)
>+		return -EINVAL;
>+
>+	kfree(config);
>+
>+	netdev_rx_handler_unregister(dev);
>+
>+	dev_put(dev);
>+	return 0;
>+}
>+
>+/* rmnet_set_ingress_data_format() - Set ingress data format on network device
>+ * @dev:                 Device to ingress data format on
>+ * @egress_data_format:  32-bit unsigned bitmask of ingress format
>+ *
>+ * Network device must already have association with RmNet Data driver
>+ */
>+static int rmnet_set_ingress_data_format(struct net_device *dev,
>+					 u32 ingress_data_format)
>+{
>+	struct rmnet_phys_ep_conf_s *config;
>+
>+	ASSERT_RTNL();
>+
>+	LOGL("(%s,0x%08X);", dev->name, ingress_data_format);
>+
>+	if (!dev)
>+		return -EINVAL;
>+
>+	config = _rmnet_get_phys_ep_config(dev);
>+	if (!config)
>+		return -EINVAL;
>+
>+	config->ingress_data_format = ingress_data_format;
>+
>+	return 0;
>+}
>+
>+/* rmnet_set_egress_data_format() - Set egress data format on network device
>+ * @dev:                 Device to egress data format on
>+ * @egress_data_format:  32-bit unsigned bitmask of egress format
>+ *
>+ * Network device must already have association with RmNet Data driver
>+ */
>+static int rmnet_set_egress_data_format(struct net_device *dev,
>+					u32 egress_data_format,
>+					u16 agg_size,
>+					u16 agg_count)
>+{
>+	struct rmnet_phys_ep_conf_s *config;
>+
>+	ASSERT_RTNL();
>+
>+	LOGL("(%s,0x%08X, %d, %d);",
>+	     dev->name, egress_data_format, agg_size, agg_count);
>+
>+	if (!dev)
>+		return -EINVAL;
>+
>+	config = _rmnet_get_phys_ep_config(dev);
>+	if (!config)
>+		return -EINVAL;
>+
>+	config->egress_data_format = egress_data_format;
>+
>+	return 0;
>+}
>+
>+/* rmnet_associate_network_device() - Associate network device
>+ * @dev:      Device to register with RmNet data
>+ *
>+ * Typically used on physical network devices. Registers RX handler and private
>+ * metadata structures.
>+ */
>+static int rmnet_associate_network_device(struct net_device *dev)

I would expect to see here you making connection between real_dev and
rmnet dev. I don't see such thing. Name of the function is misleading. 


>+{
>+	struct rmnet_phys_ep_conf_s *config;
>+	int rc;
>+
>+	ASSERT_RTNL();
>+
>+	LOGL("(%s);\n", dev->name);
>+
>+	if (!dev || _rmnet_is_physical_endpoint_associated(dev) ||
>+	    rmnet_vnd_is_vnd(dev)) {
>+		LOGM("cannot register with this dev");
>+		return -EINVAL;
>+	}
>+
>+	config = kmalloc(sizeof(*config), GFP_ATOMIC);

kzalloc, and you don't have to zero the memory.


>+	if (!config)
>+		return -ENOMEM;
>+
>+	memset(config, 0, sizeof(struct rmnet_phys_ep_conf_s));
>+	config->dev = dev;
>+
>+	rc = netdev_rx_handler_register(dev, rmnet_rx_handler, config);
>+
>+	if (rc) {
>+		LOGM("netdev_rx_handler_register returns %d", rc);
>+		kfree(config);
>+		return -EBUSY;
>+	}
>+
>+	dev_hold(dev);
>+	return 0;
>+}
>+
>+/* __rmnet_set_logical_endpoint_config() - Set logical endpoing config on device
>+ * @dev:         Device to set endpoint configuration on
>+ * @config_id:   logical endpoint id on device
>+ * @epconfig:    endpoint configuration structure to set
>+ */
>+static int __rmnet_set_logical_endpoint_config
>+	(struct net_device *dev,
>+	 int config_id,
>+	 struct rmnet_logical_ep_conf_s *epconfig)
>+{
>+	struct rmnet_logical_ep_conf_s *epconfig_l;
>+
>+	ASSERT_RTNL();
>+
>+	if (!dev || config_id < RMNET_LOCAL_LOGICAL_ENDPOINT ||
>+	    config_id >= RMNET_MAX_LOGICAL_EP)
>+		return -EINVAL;
>+
>+	epconfig_l = _rmnet_get_logical_ep(dev, config_id);
>+
>+	if (!epconfig_l || epconfig_l->refcount)
>+		return -EINVAL;
>+
>+	memcpy(epconfig_l, epconfig, sizeof(struct rmnet_logical_ep_conf_s));
>+	if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT)
>+		epconfig_l->mux_id = 0;
>+	else
>+		epconfig_l->mux_id = config_id;
>+
>+	/* Explicitly hold a reference to the egress device */
>+	dev_hold(epconfig_l->egress_dev);
>+	return 0;
>+}
>+
>+/* _rmnet_unset_logical_endpoint_config() - Un-set the logical endpoing config
>+ * on device
>+ * @dev:         Device to set endpoint configuration on
>+ * @config_id:   logical endpoint id on device
>+ */
>+static int _rmnet_unset_logical_endpoint_config(struct net_device *dev,
>+						int config_id)
>+{
>+	struct rmnet_logical_ep_conf_s *epconfig_l = 0;
>+
>+	ASSERT_RTNL();
>+
>+	if (!dev || config_id < RMNET_LOCAL_LOGICAL_ENDPOINT ||
>+	    config_id >= RMNET_MAX_LOGICAL_EP)
>+		return -EINVAL;
>+
>+	epconfig_l = _rmnet_get_logical_ep(dev, config_id);
>+
>+	if (!epconfig_l || !epconfig_l->refcount)
>+		return -EINVAL;
>+
>+	/* Explicitly release the reference from the egress device */
>+	dev_put(epconfig_l->egress_dev);
>+	memset(epconfig_l, 0, sizeof(struct rmnet_logical_ep_conf_s));
>+
>+	return 0;
>+}
>+
>+/* rmnet_set_logical_endpoint_config() - Set logical endpoint config on a device
>+ * @dev:            Device to set endpoint configuration on
>+ * @config_id:      logical endpoint id on device
>+ * @rmnet_mode:     endpoint mode. Values from: rmnet_config_endpoint_modes_e
>+ * @egress_device:  device node to forward packet to once done processing in
>+ *                  ingress/egress handlers
>+ *
>+ * Creates a logical_endpoint_config structure and fills in the information from
>+ * function arguments. Calls __rmnet_set_logical_endpoint_config() to finish
>+ * configuration. Network device must already have association with RmNet Data
>+ * driver
>+ */
>+static int rmnet_set_logical_endpoint_config(struct net_device *dev,
>+					     int config_id,
>+					     u8 rmnet_mode,
>+					     struct net_device *egress_dev)
>+{
>+	struct rmnet_logical_ep_conf_s epconfig;
>+
>+	LOGL("(%s, %d, %d, %s);",
>+	     dev->name, config_id, rmnet_mode, egress_dev->name);
>+
>+	if (!egress_dev ||
>+	    ((!_rmnet_is_physical_endpoint_associated(egress_dev)) &&
>+	    (!rmnet_vnd_is_vnd(egress_dev)))) {
>+		return -EINVAL;
>+	}
>+
>+	memset(&epconfig, 0, sizeof(struct rmnet_logical_ep_conf_s));
>+	epconfig.refcount = 1;
>+	epconfig.rmnet_mode = rmnet_mode;
>+	epconfig.egress_dev = egress_dev;
>+
>+	return __rmnet_set_logical_endpoint_config(dev, config_id, &epconfig);
>+}
>+
>+/* rmnet_unset_logical_endpoint_config() - Un-set logical endpoing configuration
>+ * on a device
>+ * @dev:            Device to set endpoint configuration on
>+ * @config_id:      logical endpoint id on device
>+ *
>+ * Retrieves the logical_endpoint_config structure and frees the egress device.
>+ * Network device must already have association with RmNet Data driver
>+ */
>+static int rmnet_unset_logical_endpoint_config(struct net_device *dev,
>+					       int config_id)
>+{
>+	LOGL("(%s, %d);", dev->name, config_id);
>+
>+	if (!dev || ((!_rmnet_is_physical_endpoint_associated(dev)) &&
>+		     (!rmnet_vnd_is_vnd(dev)))) {
>+		return -EINVAL;
>+	}
>+
>+	return _rmnet_unset_logical_endpoint_config(dev, config_id);
>+}
>+
>+/* rmnet_free_vnd() - Free virtual network device node
>+ * @id:       RmNet virtual device node id
>+ */
>+int rmnet_free_vnd(int id)
>+{
>+	LOGL("(%d);", id);
>+	return rmnet_vnd_free_dev(id);
>+}
>+
>+static void _rmnet_free_vnd_later(struct work_struct *work)
>+{
>+	int i;
>+	struct rmnet_free_vnd_work *fwork;
>+
>+	fwork = container_of(work, struct rmnet_free_vnd_work, work);
>+
>+	for (i = 0; i < fwork->count; i++)
>+		rmnet_free_vnd(fwork->vnd_id[i]);
>+	kfree(fwork);
>+}
>+
>+/* rmnet_force_unassociate_device() - Force a device to unassociate
>+ * @dev:       Device to unassociate
>+ */
>+static void rmnet_force_unassociate_device(struct net_device *dev)
>+{
>+	int i, j;
>+	struct net_device *vndev;
>+	struct rmnet_phys_ep_conf_s *config;
>+	struct rmnet_logical_ep_conf_s *cfg;
>+	struct rmnet_free_vnd_work *vnd_work;
>+
>+	ASSERT_RTNL();
>+	if (!dev)
>+		return;
>+
>+	if (!_rmnet_is_physical_endpoint_associated(dev)) {
>+		LOGM("%s", "Called on unassociated device, skipping");
>+		return;
>+	}
>+
>+	vnd_work = kmalloc(sizeof(*vnd_work), GFP_KERNEL);
>+	if (!vnd_work)
>+		return;
>+
>+	INIT_WORK(&vnd_work->work, _rmnet_free_vnd_later);
>+	vnd_work->count = 0;
>+
>+	/* Check the VNDs for offending mappings */
>+	for (i = 0, j = 0; i < RMNET_MAX_VND &&
>+	     j < RMNET_MAX_VND; i++) {
>+		vndev = rmnet_vnd_get_by_id(i);
>+		if (!vndev) {
>+			LOGL("VND %d not in use; skipping", i);
>+			continue;
>+		}
>+		cfg = rmnet_vnd_get_le_config(vndev);
>+		if (!cfg) {
>+			LOGD("Got NULL config from VND %d", i);
>+			continue;
>+		}
>+		if (cfg->refcount && (cfg->egress_dev == dev)) {
>+			/* Make sure the device is down before clearing any of
>+			 * the mappings. Otherwise we could see a potential
>+			 * race condition if packets are actively being
>+			 * transmitted.
>+			 */
>+			dev_close(vndev);
>+			rmnet_unset_logical_endpoint_config
>+				(vndev, RMNET_LOCAL_LOGICAL_ENDPOINT);
>+			vnd_work->vnd_id[j] = i;
>+			j++;
>+		}
>+	}
>+	if (j > 0) {
>+		vnd_work->count = j;
>+		schedule_work(&vnd_work->work);
>+	} else {
>+		kfree(vnd_work);
>+	}
>+
>+	config = _rmnet_get_phys_ep_config(dev);
>+
>+	if (config) {
>+		cfg = &config->local_ep;
>+
>+		if (cfg && cfg->refcount)
>+			rmnet_unset_logical_endpoint_config
>+			(cfg->egress_dev, RMNET_LOCAL_LOGICAL_ENDPOINT);
>+	}
>+
>+	/* Clear the mappings on the phys ep */
>+	rmnet_unset_logical_endpoint_config(dev, RMNET_LOCAL_LOGICAL_ENDPOINT);
>+	for (i = 0; i < RMNET_MAX_LOGICAL_EP; i++)
>+		rmnet_unset_logical_endpoint_config(dev, i);
>+	rmnet_unassociate_network_device(dev);
>+}
>+
>+/* rmnet_config_notify_cb() - Callback for netdevice notifier chain
>+ * @nb:       Notifier block data
>+ * @event:    Netdevice notifier event ID
>+ * @data:     Contains a net device for which we are getting notified
>+ */
>+static int rmnet_config_notify_cb(struct notifier_block *nb,
>+				  unsigned long event, void *data)
>+{
>+	struct net_device *dev = netdev_notifier_info_to_dev(data);
>+
>+	if (!dev)
>+		return NOTIFY_DONE;
>+
>+	switch (event) {
>+	case NETDEV_UNREGISTER_FINAL:
>+	case NETDEV_UNREGISTER:
>+		LOGM("Kernel is trying to unregister %s", dev->name);
>+		rmnet_force_unassociate_device(dev);
>+		break;
>+
>+	default:
>+		LOGD("Unhandeled event [%lu]", event);
>+		break;
>+	}
>+
>+	return NOTIFY_DONE;
>+}
>+
>+static struct notifier_block rmnet_dev_notifier = {

You should add "__read_mostly"


>+	.notifier_call = rmnet_config_notify_cb,
>+	.next = 0,
>+	.priority = 0

This initialization of 0s is not needed.


>+};
>+
>+static int rmnet_newlink(struct net *src_net, struct net_device *dev,
>+			 struct nlattr *tb[], struct nlattr *data[])
>+{
>+	int ingress_format = RMNET_INGRESS_FORMAT_DEMUXING |
>+			     RMNET_INGRESS_FORMAT_DEAGGREGATION |
>+			     RMNET_INGRESS_FORMAT_MAP;
>+	int egress_format = RMNET_EGRESS_FORMAT_MUXING |
>+			    RMNET_EGRESS_FORMAT_MAP;
>+	struct net_device *real_dev;
>+	int mode = RMNET_EPMODE_VND;
>+	u16 mux_id;
>+
>+	real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
>+	if (!real_dev)
>+		return -ENODEV;
>+
>+	if (!data[IFLA_RMNET_MUX_ID])
>+		return -EINVAL;
>+
>+	mux_id = nla_get_u16(data[IFLA_VLAN_ID]);

This is a bug I believe...             ^^^^^^^
I'm pretty sure that you did not test this code.


>+	if (rmnet_vnd_newlink(mux_id, dev))
>+		return -EINVAL;
>+
>+	rmnet_associate_network_device(real_dev);
>+	rmnet_set_egress_data_format(real_dev, egress_format, 0, 0);
>+	rmnet_set_ingress_data_format(real_dev, ingress_format);
>+	rmnet_set_logical_endpoint_config(real_dev, mux_id, mode, dev);
>+	rmnet_set_logical_endpoint_config(dev, mux_id, mode, real_dev);
>+	return 0;
>+}
>+
>+static void rmnet_delink(struct net_device *dev, struct list_head *head)
>+{
>+	struct rmnet_logical_ep_conf_s *cfg;
>+	int mux_id;
>+
>+	mux_id = rmnet_vnd_is_vnd(dev);
>+	if (!mux_id)
>+		return;
>+
>+/* rmnet_vnd_is_vnd() gives mux_id + 1, so subtract 1 to get the correct mux_id
>+ */

Fix this comment format.


>+	mux_id--;
>+	cfg = rmnet_vnd_get_le_config(dev);
>+
>+	if (cfg && cfg->refcount) {
>+		_rmnet_unset_logical_endpoint_config(cfg->egress_dev, mux_id);
>+		_rmnet_unset_logical_endpoint_config(dev, mux_id);
>+		rmnet_vnd_remove_ref_dev(mux_id);
>+		rmnet_unassociate_network_device(cfg->egress_dev);
>+	}
>+
>+	unregister_netdevice_queue(dev, head);
>+}
>+
>+static int rmnet_rtnl_validate(struct nlattr *tb[], struct nlattr *data[])
>+{
>+	u16 mux_id;
>+
>+	if (!data || !data[IFLA_RMNET_MUX_ID])
>+		return -EINVAL;
>+
>+	mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
>+	if (!mux_id || mux_id > (RMNET_MAX_LOGICAL_EP - 1))
>+		return -ERANGE;
>+
>+	return 0;
>+}
>+
>+static size_t rmnet_get_size(const struct net_device *dev)
>+{
>+	return nla_total_size(2); /* IFLA_RMNET_MUX_ID */
>+}
>+
>+struct rtnl_link_ops rmnet_link_ops __read_mostly = {
>+	.kind		= "rmnet",
>+	.maxtype	= __IFLA_RMNET_MAX,
>+	.priv_size	= sizeof(struct rmnet_vnd_private_s),
>+	.setup		= rmnet_vnd_setup,
>+	.validate	= rmnet_rtnl_validate,
>+	.newlink	= rmnet_newlink,
>+	.dellink	= rmnet_delink,
>+	.get_size	= rmnet_get_size,
>+};
>+
>+int rmnet_config_init(void)
>+{
>+	int rc;
>+
>+	rc = register_netdevice_notifier(&rmnet_dev_notifier);
>+	if (rc != 0) {
>+		LOGE("Failed to register device notifier; rc=%d", rc);
>+		return rc;
>+	}
>+
>+	rc = rtnl_link_register(&rmnet_link_ops);
>+	if (rc != 0) {
>+		unregister_netdevice_notifier(&rmnet_dev_notifier);
>+		LOGE("Failed to register netlink handler; rc=%d", rc);
>+		return rc;
>+	}
>+	return rc;
>+}
>+
>+void rmnet_config_exit(void)
>+{
>+	unregister_netdevice_notifier(&rmnet_dev_notifier);
>+	rtnl_link_unregister(&rmnet_link_ops);
>+}
>diff --git a/drivers/net/rmnet/rmnet_config.h b/drivers/net/rmnet/rmnet_config.h
>new file mode 100644
>index 0000000..0ef58e8
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_config.h
>@@ -0,0 +1,79 @@
>+/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ * RMNET Data configuration engine
>+ *
>+ */
>+
>+#include <linux/types.h>
>+#include <linux/time.h>
>+#include <linux/skbuff.h>
>+
>+#ifndef _RMNET_CONFIG_H_
>+#define _RMNET_CONFIG_H_
>+
>+#define RMNET_MAX_LOGICAL_EP 255
>+
>+/* struct rmnet_logical_ep_conf_s - Logical end-point configuration
>+ *
>+ * @refcount: Reference count for this endpoint. 0 signifies the endpoint is not
>+ *            configured for use
>+ * @rmnet_mode: Specifies how the traffic should be finally delivered. Possible
>+ *            options are available in enum rmnet_config_endpoint_modes_e
>+ * @mux_id: Virtual channel ID used by MAP protocol
>+ * @egress_dev: Next device to deliver the packet to. Exact usage of this
>+ *            parmeter depends on the rmnet_mode
>+ */
>+struct rmnet_logical_ep_conf_s {
>+	u8 refcount;
>+	u8 rmnet_mode;
>+	u8 mux_id;
>+	struct timespec flush_time;
>+	struct net_device *egress_dev;
>+};
>+
>+/* struct rmnet_phys_ep_conf_s - Physical endpoint configuration
>+ * One instance of this structure is instantiated for each net_device associated
>+ * with rmnet.
>+ *
>+ * @dev: The device which is associated with rmnet. Corresponds to this
>+ *       specific instance of rmnet_phys_ep_conf_s
>+ * @local_ep: Default non-muxed endpoint. Used for non-MAP protocols/formats
>+ * @muxed_ep: All multiplexed logical endpoints associated with this device
>+ * @ingress_data_format: RMNET_INGRESS_FORMAT_* flags from rmnet.h
>+ * @egress_data_format: RMNET_EGRESS_FORMAT_* flags from rmnet.h
>+ *
>+ * @egress_agg_size: Maximum size (bytes) of data which should be aggregated
>+ * @egress_agg_count: Maximum count (packets) of data which should be aggregated
>+ *                  Smaller of the two parameters above are chosen for
>+ *                  aggregation
>+ * @tail_spacing: Guaranteed padding (bytes) when de-aggregating ingress frames
>+ * @agg_time: Wall clock time when aggregated frame was created
>+ * @agg_last: Last time the aggregation routing was invoked
>+ */
>+struct rmnet_phys_ep_conf_s {
>+	struct net_device *dev;
>+	struct rmnet_logical_ep_conf_s local_ep;
>+	struct rmnet_logical_ep_conf_s muxed_ep[RMNET_MAX_LOGICAL_EP];
>+	u32 ingress_data_format;
>+	u32 egress_data_format;
>+};
>+
>+int rmnet_config_init(void);
>+void rmnet_config_exit(void);
>+int rmnet_free_vnd(int id);
>+
>+extern struct rtnl_link_ops rmnet_link_ops;
>+
>+struct rmnet_vnd_private_s {
>+	struct rmnet_logical_ep_conf_s local_ep;
>+};
>+#endif /* _RMNET_CONFIG_H_ */
>diff --git a/drivers/net/rmnet/rmnet_handlers.c b/drivers/net/rmnet/rmnet_handlers.c
>new file mode 100644
>index 0000000..bf8b3bb
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_handlers.c
>@@ -0,0 +1,517 @@
>+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ * RMNET Data ingress/egress handler
>+ *
>+ */
>+
>+#include <linux/skbuff.h>
>+#include <linux/netdevice.h>
>+#include <linux/module.h>
>+#include <linux/rmnet.h>
>+#include <linux/netdev_features.h>
>+#include <linux/ip.h>
>+#include <linux/ipv6.h>
>+#include "rmnet_private.h"
>+#include "rmnet_config.h"
>+#include "rmnet_vnd.h"
>+#include "rmnet_map.h"
>+#include "rmnet_stats.h"
>+#include "rmnet_handlers.h"
>+
>+RMNET_LOG_MODULE(RMNET_LOGMASK_HANDLER);
>+
>+#ifdef CONFIG_RMNET_DEBUG
>+unsigned int dump_pkt_rx;
>+module_param(dump_pkt_rx, uint, 0644);
>+MODULE_PARM_DESC(dump_pkt_rx, "Dump packets entering ingress handler");
>+
>+unsigned int dump_pkt_tx;
>+module_param(dump_pkt_tx, uint, 0644);
>+MODULE_PARM_DESC(dump_pkt_tx, "Dump packets exiting egress handler");
>+#endif /* CONFIG_RMNET_DEBUG */
>+
>+#define RMNET_IP_VERSION_4 0x40
>+#define RMNET_IP_VERSION_6 0x60
>+
>+/* Helper Functions */
>+
>+/* __rmnet_set_skb_proto() - Set skb->protocol field
>+ * @skb:      packet being modified
>+ *
>+ * Peek at the first byte of the packet and set the protocol. There is not
>+ * good way to determine if a packet has a MAP header. As of writing this,
>+ * the reserved bit in the MAP frame will prevent it from overlapping with
>+ * IPv4/IPv6 frames. This could change in the future!
>+ */
>+static inline void __rmnet_set_skb_proto(struct sk_buff *skb)
>+{
>+	switch (skb->data[0] & 0xF0) {
>+	case RMNET_IP_VERSION_4:
>+		skb->protocol = htons(ETH_P_IP);
>+		break;
>+	case RMNET_IP_VERSION_6:
>+		skb->protocol = htons(ETH_P_IPV6);
>+		break;
>+	default:
>+		skb->protocol = htons(ETH_P_MAP);
>+		break;
>+	}
>+}
>+
>+#ifdef CONFIG_RMNET_DEBUG
>+/* rmnet_print_packet() - Print packet / diagnostics
>+ * @skb:      Packet to print
>+ * @printlen: Number of bytes to print
>+ * @dev:      Name of interface
>+ * @dir:      Character representing direction (e.g.. 'r' for receive)
>+ *
>+ * This function prints out raw bytes in an SKB. Use of this will have major
>+ * performance impacts and may even trigger watchdog resets if too much is being
>+ * printed. Hence, this should always be compiled out unless absolutely needed.
>+ */
>+void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir)

No reason to have this function. One can use P_ALL tap to get skbs to
userspace.


>+{
>+	char buffer[200];
>+	unsigned int len, printlen;
>+	int i, buffloc = 0;
>+
>+	switch (dir) {
>+	case 'r':
>+		printlen = dump_pkt_rx;
>+		break;
>+
>+	case 't':
>+		printlen = dump_pkt_tx;
>+		break;
>+
>+	default:
>+		printlen = 0;
>+		break;
>+	}
>+
>+	if (!printlen)
>+		return;
>+
>+	pr_err("[%s][%c] - PKT skb->len=%d skb->head=%pK skb->data=%pK\n",
>+	       dev, dir, skb->len, (void *)skb->head, (void *)skb->data);
>+	pr_err("[%s][%c] - PKT skb->tail=%pK skb->end=%pK\n",
>+	       dev, dir, skb_tail_pointer(skb), skb_end_pointer(skb));
>+
>+	if (skb->len > 0)
>+		len = skb->len;
>+	else
>+		len = ((unsigned int)(uintptr_t)skb->end) -
>+		      ((unsigned int)(uintptr_t)skb->data);
>+
>+	pr_err("[%s][%c] - PKT len: %d, printing first %d bytes\n",
>+	       dev, dir, len, printlen);
>+
>+	memset(buffer, 0, sizeof(buffer));
>+	for (i = 0; (i < printlen) && (i < len); i++) {
>+		if ((i % 16) == 0) {
>+			pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer);
>+			memset(buffer, 0, sizeof(buffer));
>+			buffloc = 0;
>+			buffloc += snprintf(&buffer[buffloc],
>+					sizeof(buffer) - buffloc, "%04X:",
>+					i);
>+		}
>+
>+		buffloc += snprintf(&buffer[buffloc], sizeof(buffer) - buffloc,
>+					" %02x", skb->data[i]);
>+	}
>+	pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer);
>+}
>+#else
>+void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir)
>+{
>+}
>+#endif /* CONFIG_RMNET_DEBUG */
>+
>+/* Generic handler */
>+
>+/* rmnet_bridge_handler() - Bridge related functionality
>+ */

Fix the comment format (you have it on multiple places)


>+static rx_handler_result_t rmnet_bridge_handler
>+	(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)

The formatting is incorrect:

static rx_handler_result_t
rmnet_bridge_handler(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)


>+{
>+	if (!ep->egress_dev) {
>+		LOGD("Missing egress device for packet arriving on %s",
>+		     skb->dev->name);
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_BRDG_NO_EGRESS);
>+	} else {
>+		rmnet_egress_handler(skb, ep);
>+	}
>+
>+	return RX_HANDLER_CONSUMED;
>+}
>+
>+#ifdef NET_SKBUFF_DATA_USES_OFFSET
>+static void rmnet_reset_mac_header(struct sk_buff *skb)
>+{
>+	skb->mac_header = 0;
>+	skb->mac_len = 0;
>+}
>+#else
>+static void rmnet_reset_mac_header(struct sk_buff *skb)
>+{
>+	skb->mac_header = skb->network_header;
>+	skb->mac_len = 0;
>+}
>+#endif /*NET_SKBUFF_DATA_USES_OFFSET*/
>+
>+/* __rmnet_deliver_skb() - Deliver skb
>+ *
>+ * Determines where to deliver skb. Options are: consume by network stack,
>+ * pass to bridge handler, or pass to virtual network device
>+ */
>+static rx_handler_result_t __rmnet_deliver_skb
>+	(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)
>+{
>+	switch (ep->rmnet_mode) {
>+	case RMNET_EPMODE_NONE:
>+		return RX_HANDLER_PASS;
>+
>+	case RMNET_EPMODE_BRIDGE:
>+		return rmnet_bridge_handler(skb, ep);
>+
>+	case RMNET_EPMODE_VND:
>+		skb_reset_transport_header(skb);
>+		skb_reset_network_header(skb);
>+		switch (rmnet_vnd_rx_fixup(skb, skb->dev)) {
>+		case RX_HANDLER_CONSUMED:
>+			return RX_HANDLER_CONSUMED;
>+
>+		case RX_HANDLER_PASS:
>+			skb->pkt_type = PACKET_HOST;
>+			rmnet_reset_mac_header(skb);
>+			netif_receive_skb(skb);
>+			return RX_HANDLER_CONSUMED;
>+		}
>+		return RX_HANDLER_PASS;
>+
>+	default:
>+		LOGD("Unknown ep mode %d", ep->rmnet_mode);
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_DELIVER_NO_EP);
>+		return RX_HANDLER_CONSUMED;
>+	}
>+}
>+
>+/* rmnet_ingress_deliver_packet() - Ingress handler for raw IP and bridged
>+ *                                  MAP packets.
>+ * @skb:     Packet needing a destination.
>+ * @config:  Physical end point configuration that the packet arrived on.
>+ */
>+static rx_handler_result_t rmnet_ingress_deliver_packet
>+	(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config)
>+{
>+	if (!config) {
>+		LOGD("%s", "NULL physical EP provided");
>+		kfree_skb(skb);
>+		return RX_HANDLER_CONSUMED;
>+	}
>+
>+	if (!(config->local_ep.refcount)) {
>+		LOGD("Packet on %s has no local endpoint configuration",
>+		     skb->dev->name);
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_IPINGRESS_NO_EP);
>+		return RX_HANDLER_CONSUMED;
>+	}
>+
>+	skb->dev = config->local_ep.egress_dev;
>+
>+	return __rmnet_deliver_skb(skb, &config->local_ep);
>+}
>+
>+/* MAP handler */
>+
>+/* _rmnet_map_ingress_handler() - Actual MAP ingress handler
>+ * @skb:        Packet being received
>+ * @config:     Physical endpoint configuration for the ingress device
>+ *
>+ * Most MAP ingress functions are processed here. Packets are processed
>+ * individually; aggregated packets should use rmnet_map_ingress_handler()
>+ */
>+static rx_handler_result_t _rmnet_map_ingress_handler
>+	(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config)
>+{
>+	struct rmnet_logical_ep_conf_s *ep;
>+	u8 mux_id;
>+	u16 len;
>+
>+	if (RMNET_MAP_GET_CD_BIT(skb)) {
>+		if (config->ingress_data_format
>+		    & RMNET_INGRESS_FORMAT_MAP_COMMANDS)
>+			return rmnet_map_command(skb, config);
>+
>+		LOGM("MAP command packet on %s; %s", skb->dev->name,
>+		     "Not configured for MAP commands");
>+		rmnet_kfree_skb(skb,
>+				RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPC);
>+		return RX_HANDLER_CONSUMED;
>+	}
>+
>+	mux_id = RMNET_MAP_GET_MUX_ID(skb);
>+	len = RMNET_MAP_GET_LENGTH(skb) - RMNET_MAP_GET_PAD(skb);
>+
>+	if (mux_id >= RMNET_MAX_LOGICAL_EP) {
>+		LOGD("Got packet on %s with bad mux id %d",
>+		     skb->dev->name, mux_id);
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_BAD_MUX);
>+			return RX_HANDLER_CONSUMED;
>+	}
>+
>+	ep = &config->muxed_ep[mux_id];
>+
>+	if (!ep->refcount) {
>+		LOGD("Packet on %s:%d; has no logical endpoint config",
>+		     skb->dev->name, mux_id);
>+
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_MUX_NO_EP);
>+		return RX_HANDLER_CONSUMED;
>+	}
>+
>+	if (config->ingress_data_format & RMNET_INGRESS_FORMAT_DEMUXING)
>+		skb->dev = ep->egress_dev;
>+
>+	/* Subtract MAP header */
>+	skb_pull(skb, sizeof(struct rmnet_map_header_s));
>+	skb_trim(skb, len);
>+	__rmnet_set_skb_proto(skb);
>+	return __rmnet_deliver_skb(skb, ep);
>+}
>+
>+/* rmnet_map_ingress_handler() - MAP ingress handler
>+ * @skb:        Packet being received
>+ * @config:     Physical endpoint configuration for the ingress device
>+ *
>+ * Called if and only if MAP is configured in the ingress device's ingress data
>+ * format. Deaggregation is done here, actual MAP processing is done in
>+ * _rmnet_map_ingress_handler().
>+ */
>+static rx_handler_result_t rmnet_map_ingress_handler
>+	(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config)
>+{
>+	struct sk_buff *skbn;
>+	int rc, co = 0;
>+
>+	if (config->ingress_data_format & RMNET_INGRESS_FORMAT_DEAGGREGATION) {
>+		while ((skbn = rmnet_map_deaggregate(skb, config)) != NULL) {
>+			_rmnet_map_ingress_handler(skbn, config);
>+			co++;
>+		}
>+		LOGD("De-aggregated %d packets", co);
>+		rmnet_stats_deagg_pkts(co);
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_AGGBUF);
>+		rc = RX_HANDLER_CONSUMED;
>+	} else {
>+		rc = _rmnet_map_ingress_handler(skb, config);
>+	}
>+
>+	return rc;
>+}
>+
>+/* rmnet_map_egress_handler() - MAP egress handler
>+ * @skb:        Packet being sent
>+ * @config:     Physical endpoint configuration for the egress device
>+ * @ep:         logical endpoint configuration of the packet originator
>+ *              (e.g.. RmNet virtual network device)
>+ * @orig_dev:   The originator vnd device
>+ *
>+ * Called if and only if MAP is configured in the egress device's egress data
>+ * format. Will expand skb if there is insufficient headroom for MAP protocol.
>+ * Note: headroomexpansion will incur a performance penalty.
>+ */
>+static int rmnet_map_egress_handler(struct sk_buff *skb,
>+				    struct rmnet_phys_ep_conf_s *config,
>+				    struct rmnet_logical_ep_conf_s *ep,
>+				    struct net_device *orig_dev)
>+{
>+	int required_headroom, additional_header_length;
>+	struct rmnet_map_header_s *map_header;
>+
>+	additional_header_length = 0;
>+	required_headroom = sizeof(struct rmnet_map_header_s);
>+
>+	LOGD("headroom of %d bytes", required_headroom);
>+
>+	if (skb_headroom(skb) < required_headroom) {
>+		if (pskb_expand_head(skb, required_headroom, 0, GFP_KERNEL)) {
>+			LOGD("Failed to add headroom of %d bytes",
>+			     required_headroom);
>+			return RMNET_MAP_CONSUMED;
>+		}
>+	}
>+
>+	map_header = rmnet_map_add_map_header
>+		(skb, additional_header_length, RMNET_MAP_NO_PAD_BYTES);
>+	if (!map_header) {
>+		LOGD("%s", "Failed to add MAP header to egress packet");
>+		return RMNET_MAP_CONSUMED;
>+	}
>+
>+	if (config->egress_data_format & RMNET_EGRESS_FORMAT_MUXING) {
>+		if (ep->mux_id == 0xff)
>+			map_header->mux_id = 0;
>+		else
>+			map_header->mux_id = ep->mux_id;
>+	}
>+
>+	skb->protocol = htons(ETH_P_MAP);
>+
>+	return RMNET_MAP_SUCCESS;
>+}
>+
>+/* Ingress / Egress Entry Points */
>+
>+/* rmnet_ingress_handler() - Ingress handler entry point
>+ * @skb: Packet being received
>+ *
>+ * Processes packet as per ingress data format for receiving device. Logical
>+ * endpoint is determined from packet inspection. Packet is then sent to the
>+ * egress device listed in the logical endpoint configuration.
>+ */
>+rx_handler_result_t rmnet_ingress_handler(struct sk_buff *skb)
>+{
>+	struct rmnet_phys_ep_conf_s *config;
>+	struct net_device *dev;
>+	int rc;
>+
>+	if (!skb)
>+		return RX_HANDLER_CONSUMED;
>+
>+	dev = skb->dev;
>+	rmnet_print_packet(skb, dev->name, 'r');
>+
>+	config = (struct rmnet_phys_ep_conf_s *)
>+		rcu_dereference(skb->dev->rx_handler_data);
>+
>+	if (!config) {

Cannot happen. Please remove this.


>+		LOGD("%s is not associated with rmnet", skb->dev->name);
>+		kfree_skb(skb);
>+		return RX_HANDLER_CONSUMED;
>+	}
>+
>+	/* Sometimes devices operate in ethernet mode even thouth there is no
>+	 * ethernet header. This causes the skb->protocol to contain a bogus
>+	 * value and the skb->data pointer to be off by 14 bytes. Fix it if
>+	 * configured to do so
>+	 */
>+	if (config->ingress_data_format & RMNET_INGRESS_FIX_ETHERNET) {
>+		skb_push(skb, RMNET_ETHERNET_HEADER_LENGTH);
>+		__rmnet_set_skb_proto(skb);
>+	}
>+
>+	if (config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP) {
>+		rc = rmnet_map_ingress_handler(skb, config);
>+	} else {
>+		switch (ntohs(skb->protocol)) {
>+		case ETH_P_MAP:
>+			if (config->local_ep.rmnet_mode ==
>+				RMNET_EPMODE_BRIDGE) {
>+				rc = rmnet_ingress_deliver_packet(skb, config);
>+			} else {
>+				LOGD("MAP packet on %s; MAP not set",
>+				     dev->name);
>+				rmnet_kfree_skb
>+				(skb,
>+				 RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD);

very odd formatting. Please fix.


>+				rc = RX_HANDLER_CONSUMED;
>+			}
>+			break;
>+
>+		case ETH_P_ARP:
>+		case ETH_P_IP:
>+		case ETH_P_IPV6:
>+			rc = rmnet_ingress_deliver_packet(skb, config);
>+			break;
>+
>+		default:
>+			LOGD("Unknown skb->proto 0x%04X\n",
>+			     ntohs(skb->protocol) & 0xFFFF);
>+			rc = RX_HANDLER_PASS;
>+		}
>+	}
>+
>+	return rc;
>+}
>+
>+/* rmnet_rx_handler() - Rx handler callback registered with kernel
>+ * @pskb: Packet to be processed by rx handler
>+ *
>+ * Standard kernel-expected footprint for rx handlers. Calls
>+ * rmnet_ingress_handler with correctly formatted arguments
>+ */
>+rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb)
>+{
>+	return rmnet_ingress_handler(*pskb);
>+}
>+
>+/* rmnet_egress_handler() - Egress handler entry point
>+ * @skb:        packet to transmit
>+ * @ep:         logical endpoint configuration of the packet originator
>+ *              (e.g.. RmNet virtual network device)
>+ *
>+ * Modifies packet as per logical endpoint configuration and egress data format
>+ * for egress device configured in logical endpoint. Packet is then transmitted
>+ * on the egress device.
>+ */
>+void rmnet_egress_handler(struct sk_buff *skb,
>+			  struct rmnet_logical_ep_conf_s *ep)
>+{
>+	struct rmnet_phys_ep_conf_s *config;
>+	struct net_device *orig_dev;
>+	int rc;
>+
>+	orig_dev = skb->dev;
>+	skb->dev = ep->egress_dev;
>+
>+	config = (struct rmnet_phys_ep_conf_s *)
>+		rcu_dereference(skb->dev->rx_handler_data);

This is certainly a misuse of dev->rx_handler_data. Dev private of a
function arg to carry the pointer around.


>+
>+	if (!config) {
>+		LOGD("%s is not associated with rmnet", skb->dev->name);
>+		kfree_skb(skb);
>+		return;
>+	}
>+
>+	LOGD("Packet going out on %s with egress format 0x%08X",
>+	     skb->dev->name, config->egress_data_format);
>+
>+	if (config->egress_data_format & RMNET_EGRESS_FORMAT_MAP) {
>+		switch (rmnet_map_egress_handler(skb, config, ep, orig_dev)) {
>+		case RMNET_MAP_CONSUMED:
>+			LOGD("%s", "MAP process consumed packet");
>+			return;
>+
>+		case RMNET_MAP_SUCCESS:
>+			break;
>+
>+		default:
>+			LOGD("MAP egress failed on packet on %s",
>+			     skb->dev->name);
>+			rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_EGR_MAPFAIL);
>+			return;
>+		}
>+	}
>+
>+	if (ep->rmnet_mode == RMNET_EPMODE_VND)
>+		rmnet_vnd_tx_fixup(skb, orig_dev);
>+
>+	rmnet_print_packet(skb, skb->dev->name, 't');
>+	rc = dev_queue_xmit(skb);
>+	if (rc != 0) {
>+		LOGD("Failed to queue packet for transmission on [%s]",
>+		     skb->dev->name);
>+	}
>+	rmnet_stats_queue_xmit(rc, RMNET_STATS_QUEUE_XMIT_EGRESS);
>+}
>diff --git a/drivers/net/rmnet/rmnet_handlers.h b/drivers/net/rmnet/rmnet_handlers.h
>new file mode 100644
>index 0000000..43c42c2
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_handlers.h
>@@ -0,0 +1,24 @@
>+/* Copyright (c) 2013, 2016-2017 The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ * RMNET Data ingress/egress handler
>+ *
>+ */
>+
>+#ifndef _RMNET_HANDLERS_H_
>+#define _RMNET_HANDLERS_H_
>+
>+void rmnet_egress_handler(struct sk_buff *skb,
>+			  struct rmnet_logical_ep_conf_s *ep);
>+
>+rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb);
>+
>+#endif /* _RMNET_HANDLERS_H_ */
>diff --git a/drivers/net/rmnet/rmnet_main.c b/drivers/net/rmnet/rmnet_main.c
>new file mode 100644
>index 0000000..f8b7a20
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_main.c
>@@ -0,0 +1,52 @@
>+/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ *
>+ * RMNET Data generic framework
>+ *
>+ */
>+
>+#include <linux/module.h>
>+#include <linux/kernel.h>
>+#include <linux/export.h>
>+#include "rmnet_private.h"
>+#include "rmnet_config.h"
>+#include "rmnet_vnd.h"
>+
>+/* Module Parameters */
>+unsigned int rmnet_log_level = RMNET_LOG_LVL_ERR | RMNET_LOG_LVL_HI;
>+module_param(rmnet_log_level, uint, 0644);
>+MODULE_PARM_DESC(log_level, "Logging level");
>+
>+unsigned int rmnet_log_module_mask;
>+module_param(rmnet_log_module_mask, uint, 0644);
>+MODULE_PARM_DESC(rmnet_log_module_mask, "Logging module mask");

No module options please.



>+
>+/* Startup/Shutdown */
>+
>+static int __init rmnet_init(void)
>+{
>+	rmnet_config_init();
>+	rmnet_vnd_init();
>+
>+	LOGL("%s", "RMNET Data driver loaded successfully");
>+	return 0;
>+}
>+
>+static void __exit rmnet_exit(void)
>+{
>+	rmnet_config_exit();
>+	rmnet_vnd_exit();
>+}
>+
>+module_init(rmnet_init)
>+module_exit(rmnet_exit)
>+MODULE_LICENSE("GPL v2");
>diff --git a/drivers/net/rmnet/rmnet_map.h b/drivers/net/rmnet/rmnet_map.h
>new file mode 100644
>index 0000000..7d533aa
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_map.h
>@@ -0,0 +1,100 @@
>+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ */
>+
>+#include <linux/types.h>
>+#include <linux/spinlock.h>
>+
>+#ifndef _RMNET_MAP_H_
>+#define _RMNET_MAP_H_
>+
>+struct rmnet_map_control_command_s {
>+	u8  command_name;
>+	u8  cmd_type:2;
>+	u8  reserved:6;
>+	u16 reserved2;
>+	u32 transaction_id;
>+	union {
>+		u8  data[65528];
>+		struct {
>+			u16 ip_family:2;
>+			u16 reserved:14;
>+			u16 flow_control_seq_num;
>+			u32 qos_id;
>+		} flow_control;
>+	};
>+}  __aligned(1);
>+
>+enum rmnet_map_results_e {
>+	RMNET_MAP_SUCCESS,
>+	RMNET_MAP_CONSUMED,
>+	RMNET_MAP_GENERAL_FAILURE,
>+	RMNET_MAP_NOT_ENABLED,
>+	RMNET_MAP_FAILED_AGGREGATION,
>+	RMNET_MAP_FAILED_MUX
>+};
>+
>+enum rmnet_map_mux_errors_e {
>+	RMNET_MAP_MUX_SUCCESS,
>+	RMNET_MAP_MUX_INVALID_MUX_ID,
>+	RMNET_MAP_MUX_INVALID_PAD_LENGTH,
>+	RMNET_MAP_MUX_INVALID_PKT_LENGTH,
>+	/* This should always be the last element */
>+	RMNET_MAP_MUX_ENUM_LENGTH
>+};
>+
>+enum rmnet_map_commands_e {
>+	RMNET_MAP_COMMAND_NONE,
>+	RMNET_MAP_COMMAND_FLOW_DISABLE,
>+	RMNET_MAP_COMMAND_FLOW_ENABLE,
>+	/* These should always be the last 2 elements */
>+	RMNET_MAP_COMMAND_UNKNOWN,
>+	RMNET_MAP_COMMAND_ENUM_LENGTH
>+};
>+
>+struct rmnet_map_header_s {
>+	u8  pad_len:6;
>+	u8  reserved_bit:1;
>+	u8  cd_bit:1;
>+	u8  mux_id;
>+	u16 pkt_len;
>+}  __aligned(1);
>+
>+#define RMNET_MAP_GET_MUX_ID(Y) (((struct rmnet_map_header_s *) \
>+				 (Y)->data)->mux_id)
>+#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *) \
>+				(Y)->data)->cd_bit)
>+#define RMNET_MAP_GET_PAD(Y) (((struct rmnet_map_header_s *) \
>+				(Y)->data)->pad_len)
>+#define RMNET_MAP_GET_CMD_START(Y) ((struct rmnet_map_control_command_s *) \
>+				    ((Y)->data + \
>+				      sizeof(struct rmnet_map_header_s)))
>+#define RMNET_MAP_GET_LENGTH(Y) (ntohs(((struct rmnet_map_header_s *) \
>+					(Y)->data)->pkt_len))
>+
>+#define RMNET_MAP_COMMAND_REQUEST     0
>+#define RMNET_MAP_COMMAND_ACK         1
>+#define RMNET_MAP_COMMAND_UNSUPPORTED 2
>+#define RMNET_MAP_COMMAND_INVALID     3
>+
>+#define RMNET_MAP_NO_PAD_BYTES        0
>+#define RMNET_MAP_ADD_PAD_BYTES       1
>+
>+u8 rmnet_map_demultiplex(struct sk_buff *skb);
>+struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
>+				      struct rmnet_phys_ep_conf_s *config);
>+
>+struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb,
>+						    int hdrlen, int pad);
>+rx_handler_result_t rmnet_map_command(struct sk_buff *skb,
>+				      struct rmnet_phys_ep_conf_s *config);
>+
>+#endif /* _RMNET_MAP_H_ */
>diff --git a/drivers/net/rmnet/rmnet_map_command.c b/drivers/net/rmnet/rmnet_map_command.c
>new file mode 100644
>index 0000000..13bcee3
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_map_command.c
>@@ -0,0 +1,180 @@
>+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ */
>+
>+#include <linux/module.h>
>+#include <linux/kernel.h>
>+#include <linux/skbuff.h>
>+#include <linux/netdevice.h>
>+#include <linux/rmnet.h>
>+#include <net/pkt_sched.h>
>+#include "rmnet_config.h"
>+#include "rmnet_map.h"
>+#include "rmnet_private.h"
>+#include "rmnet_vnd.h"
>+#include "rmnet_stats.h"
>+
>+RMNET_LOG_MODULE(RMNET_LOGMASK_MAPC);
>+
>+unsigned long int rmnet_map_command_stats[RMNET_MAP_COMMAND_ENUM_LENGTH];
>+module_param_array(rmnet_map_command_stats, ulong, 0, 0444);
>+MODULE_PARM_DESC(rmnet_map_command_stats, "MAP command statistics");
>+
>+/* rmnet_map_do_flow_control() - Process MAP flow control command
>+ * @skb: Socket buffer containing the MAP flow control message
>+ * @config: Physical end-point configuration of ingress device
>+ * @enable: boolean for enable/disable
>+ *
>+ * Process in-band MAP flow control messages. Assumes mux ID is mapped to a
>+ * RmNet Data vitrual network device.
>+ *
>+ * Return:
>+ *      - RMNET_MAP_COMMAND_UNSUPPORTED on any error
>+ *      - RMNET_MAP_COMMAND_ACK on success
>+ */
>+static u8 rmnet_map_do_flow_control(struct sk_buff *skb,
>+				    struct rmnet_phys_ep_conf_s *config,
>+				    int enable)
>+{
>+	struct rmnet_map_control_command_s *cmd;
>+	struct net_device *vnd;
>+	struct rmnet_logical_ep_conf_s *ep;
>+	u8 mux_id;
>+	u16 ip_family;
>+	u16 fc_seq;
>+	u32 qos_id;
>+	int r;
>+
>+	if (unlikely(!skb || !config))
>+		return RX_HANDLER_CONSUMED;
>+
>+	mux_id = RMNET_MAP_GET_MUX_ID(skb);
>+	cmd = RMNET_MAP_GET_CMD_START(skb);
>+
>+	if (mux_id >= RMNET_MAX_LOGICAL_EP) {
>+		LOGD("Got packet on %s with bad mux id %d",
>+		     skb->dev->name, mux_id);
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_BAD_MUX);
>+		return RX_HANDLER_CONSUMED;
>+	}
>+
>+	ep = &config->muxed_ep[mux_id];
>+
>+	if (!ep->refcount) {
>+		LOGD("Packet on %s:%d; has no logical endpoint config",
>+		     skb->dev->name, mux_id);
>+
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_MUX_NO_EP);
>+			return RX_HANDLER_CONSUMED;
>+	}
>+
>+	vnd = ep->egress_dev;
>+
>+	ip_family = cmd->flow_control.ip_family;
>+	fc_seq = ntohs(cmd->flow_control.flow_control_seq_num);
>+	qos_id = ntohl(cmd->flow_control.qos_id);
>+
>+	/* Ignore the ip family and pass the sequence number for both v4 and v6
>+	 * sequence. User space does not support creating dedicated flows for
>+	 * the 2 protocols
>+	 */
>+	r = rmnet_vnd_do_flow_control(vnd, enable);
>+	LOGD("dev:%s, qos_id:0x%08X, ip_family:%hd, fc_seq %hd, en:%d",
>+	     skb->dev->name, qos_id, ip_family & 3, fc_seq, enable);
>+
>+	if (r) {
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED);
>+		return RMNET_MAP_COMMAND_UNSUPPORTED;
>+	} else {
>+		return RMNET_MAP_COMMAND_ACK;
>+	}
>+}
>+
>+/* rmnet_map_send_ack() - Send N/ACK message for MAP commands
>+ * @skb: Socket buffer containing the MAP command message
>+ * @type: N/ACK message selector
>+ * @config: Physical end-point configuration of ingress device
>+ *
>+ * skb is modified to contain the message type selector. The message is then
>+ * transmitted on skb->dev. Note that this function grabs global Tx lock on
>+ * skb->dev for latency reasons.
>+ *
>+ * Return:
>+ *      - void
>+ */
>+static void rmnet_map_send_ack(struct sk_buff *skb,
>+			       unsigned char type,
>+			       struct rmnet_phys_ep_conf_s *config)
>+{
>+	struct rmnet_map_control_command_s *cmd;
>+	int xmit_status;
>+
>+	if (unlikely(!skb))
>+		return;
>+
>+	skb->protocol = htons(ETH_P_MAP);
>+
>+	cmd = RMNET_MAP_GET_CMD_START(skb);
>+	cmd->cmd_type = type & 0x03;
>+
>+	netif_tx_lock(skb->dev);
>+	xmit_status = skb->dev->netdev_ops->ndo_start_xmit(skb, skb->dev);
>+	netif_tx_unlock(skb->dev);
>+
>+	LOGD("MAP command ACK=%hhu sent with rc: %d", type & 0x03, xmit_status);
>+}
>+
>+/* rmnet_map_command() - Entry point for handling MAP commands
>+ * @skb: Socket buffer containing the MAP command message
>+ * @config: Physical end-point configuration of ingress device
>+ *
>+ * Process MAP command frame and send N/ACK message as appropriate. Message cmd
>+ * name is decoded here and appropriate handler is called.
>+ *
>+ * Return:
>+ *      - RX_HANDLER_CONSUMED. Command frames are always consumed.
>+ */
>+rx_handler_result_t rmnet_map_command(struct sk_buff *skb,
>+				      struct rmnet_phys_ep_conf_s *config)
>+{
>+	struct rmnet_map_control_command_s *cmd;
>+	unsigned char command_name;
>+	unsigned char rc = 0;
>+
>+	if (unlikely(!skb))
>+		return RX_HANDLER_CONSUMED;
>+
>+	cmd = RMNET_MAP_GET_CMD_START(skb);
>+	command_name = cmd->command_name;
>+
>+	if (command_name < RMNET_MAP_COMMAND_ENUM_LENGTH)
>+		rmnet_map_command_stats[command_name]++;
>+
>+	switch (command_name) {
>+	case RMNET_MAP_COMMAND_FLOW_ENABLE:
>+		rc = rmnet_map_do_flow_control(skb, config, 1);
>+		break;
>+
>+	case RMNET_MAP_COMMAND_FLOW_DISABLE:
>+		rc = rmnet_map_do_flow_control(skb, config, 0);
>+		break;
>+
>+	default:
>+		rmnet_map_command_stats[RMNET_MAP_COMMAND_UNKNOWN]++;
>+		LOGM("Uknown MAP command: %d", command_name);
>+		rc = RMNET_MAP_COMMAND_UNSUPPORTED;
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED);
>+		break;
>+	}
>+	if (rc == RMNET_MAP_COMMAND_ACK)
>+		rmnet_map_send_ack(skb, rc, config);
>+	return RX_HANDLER_CONSUMED;
>+}
>diff --git a/drivers/net/rmnet/rmnet_map_data.c b/drivers/net/rmnet/rmnet_map_data.c
>new file mode 100644
>index 0000000..93af3c9
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_map_data.c
>@@ -0,0 +1,145 @@
>+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ * RMNET Data MAP protocol
>+ *
>+ */
>+
>+#include <linux/module.h>
>+#include <linux/kernel.h>
>+#include <linux/skbuff.h>
>+#include <linux/netdevice.h>
>+#include <linux/rmnet.h>
>+#include <linux/spinlock.h>
>+#include <linux/workqueue.h>
>+#include <linux/time.h>
>+#include <linux/ip.h>
>+#include <linux/ipv6.h>
>+#include <linux/udp.h>
>+#include <linux/tcp.h>
>+#include <linux/in.h>
>+#include <net/ip.h>
>+#include <net/checksum.h>
>+#include <net/ip6_checksum.h>
>+#include "rmnet_config.h"
>+#include "rmnet_map.h"
>+#include "rmnet_private.h"
>+#include "rmnet_stats.h"
>+
>+RMNET_LOG_MODULE(RMNET_LOGMASK_MAPD);
>+
>+#define RMNET_MAP_DEAGGR_SPACING  64
>+#define RMNET_MAP_DEAGGR_HEADROOM (RMNET_MAP_DEAGGR_SPACING / 2)
>+
>+/* rmnet_map_add_map_header() - Adds MAP header to front of skb->data
>+ * @skb:        Socket buffer ("packet") to modify
>+ * @hdrlen:     Number of bytes of header data which should not be included in
>+ *              MAP length field
>+ * @pad:        Specify if padding the MAP packet to make it 4 byte aligned is
>+ *              necessary
>+ *
>+ * Padding is calculated and set appropriately in MAP header. Mux ID is
>+ * initialized to 0.
>+ *
>+ * Return:
>+ *      - Pointer to MAP structure
>+ *      - 0 (null) if insufficient headroom
>+ *      - 0 (null) if insufficient tailroom for padding bytes
>+ */
>+struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb,
>+						    int hdrlen, int pad)
>+{
>+	u32 padding, map_datalen;
>+	u8 *padbytes;
>+	struct rmnet_map_header_s *map_header;
>+
>+	if (skb_headroom(skb) < sizeof(struct rmnet_map_header_s))
>+		return 0;
>+
>+	map_datalen = skb->len - hdrlen;
>+	map_header = (struct rmnet_map_header_s *)
>+			skb_push(skb, sizeof(struct rmnet_map_header_s));
>+	memset(map_header, 0, sizeof(struct rmnet_map_header_s));
>+
>+	if (pad == RMNET_MAP_NO_PAD_BYTES) {
>+		map_header->pkt_len = htons(map_datalen);
>+		return map_header;
>+	}
>+
>+	padding = ALIGN(map_datalen, 4) - map_datalen;
>+
>+	if (padding == 0)
>+		goto done;
>+
>+	if (skb_tailroom(skb) < padding)
>+		return 0;
>+
>+	padbytes = (u8 *)skb_put(skb, padding);
>+	LOGD("pad: %d", padding);
>+	memset(padbytes, 0, padding);
>+
>+done:
>+	map_header->pkt_len = htons(map_datalen + padding);
>+	map_header->pad_len = padding & 0x3F;
>+
>+	return map_header;
>+}
>+
>+/* rmnet_map_deaggregate() - Deaggregates a single packet
>+ * @skb:        Source socket buffer containing multiple MAP frames
>+ * @config:     Physical endpoint configuration of the ingress device
>+ *
>+ * A whole new buffer is allocated for each portion of an aggregated frame.
>+ * Caller should keep calling deaggregate() on the source skb until 0 is
>+ * returned, indicating that there are no more packets to deaggregate. Caller
>+ * is responsible for freeing the original skb.
>+ *
>+ * Return:
>+ *     - Pointer to new skb
>+ *     - 0 (null) if no more aggregated packets
>+ */
>+struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
>+				      struct rmnet_phys_ep_conf_s *config)
>+{
>+	struct sk_buff *skbn;
>+	struct rmnet_map_header_s *maph;
>+	u32 packet_len;
>+
>+	if (skb->len == 0)
>+		return 0;
>+
>+	maph = (struct rmnet_map_header_s *)skb->data;
>+	packet_len = ntohs(maph->pkt_len) + sizeof(struct rmnet_map_header_s);
>+
>+	if ((((int)skb->len) - ((int)packet_len)) < 0) {
>+		LOGM("%s", "Got malformed packet. Dropping");
>+		return 0;
>+	}
>+
>+	skbn = alloc_skb(packet_len + RMNET_MAP_DEAGGR_SPACING, GFP_ATOMIC);
>+	if (!skbn)
>+		return 0;
>+
>+	skbn->dev = skb->dev;
>+	skb_reserve(skbn, RMNET_MAP_DEAGGR_HEADROOM);
>+	skb_put(skbn, packet_len);
>+	memcpy(skbn->data, skb->data, packet_len);
>+	skb_pull(skb, packet_len);
>+
>+	/* Some hardware can send us empty frames. Catch them */
>+	if (ntohs(maph->pkt_len) == 0) {
>+		LOGD("Dropping empty MAP frame");
>+		rmnet_kfree_skb(skbn, RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0);
>+		return 0;
>+	}
>+
>+	return skbn;
>+}
>diff --git a/drivers/net/rmnet/rmnet_private.h b/drivers/net/rmnet/rmnet_private.h
>new file mode 100644
>index 0000000..f27e0b3
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_private.h
>@@ -0,0 +1,76 @@
>+/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ */
>+
>+#ifndef _RMNET_PRIVATE_H_
>+#define _RMNET_PRIVATE_H_
>+
>+#define RMNET_MAX_VND              32
>+#define RMNET_MAX_PACKET_SIZE      16384
>+#define RMNET_DFLT_PACKET_SIZE     1500
>+#define RMNET_DEV_NAME_STR         "rmnet"
>+#define RMNET_NEEDED_HEADROOM      16
>+#define RMNET_TX_QUEUE_LEN         1000
>+#define RMNET_ETHERNET_HEADER_LENGTH    14
>+
>+extern unsigned int rmnet_log_level;
>+extern unsigned int rmnet_log_module_mask;
>+
>+#define RMNET_INIT_OK     0
>+#define RMNET_INIT_ERROR  1

Please use common error codes (0/-ENOMEM/-EINVAL/...)


>+
>+#define RMNET_LOG_LVL_DBG BIT(4)
>+#define RMNET_LOG_LVL_LOW BIT(3)
>+#define RMNET_LOG_LVL_MED BIT(2)
>+#define RMNET_LOG_LVL_HI  BIT(1)
>+#define RMNET_LOG_LVL_ERR BIT(0)
>+
>+#define RMNET_LOG_MODULE(X) \
>+	static u32 rmnet_mod_mask = X

Don't use this custom helpers. Use existing loggign facilities.


>+
>+#define RMNET_LOGMASK_CONFIG  BIT(0)
>+#define RMNET_LOGMASK_HANDLER BIT(1)
>+#define RMNET_LOGMASK_VND     BIT(2)
>+#define RMNET_LOGMASK_MAPD    BIT(3)
>+#define RMNET_LOGMASK_MAPC    BIT(4)
>+
>+#define LOGE(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_ERR) \
>+			pr_err("[RMNET:ERR] %s(): " fmt "\n", __func__, \
>+				##__VA_ARGS__); \
>+			} while (0)
>+
>+#define LOGH(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_HI) \
>+			pr_err("[RMNET:HI] %s(): " fmt "\n", __func__, \
>+				##__VA_ARGS__); \
>+			} while (0)
>+
>+#define LOGM(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_MED) \
>+			pr_warn("[RMNET:MED] %s(): " fmt "\n", __func__, \
>+				##__VA_ARGS__); \
>+			} while (0)
>+
>+#define LOGL(fmt, ...) do { if (unlikely \
>+			(rmnet_log_level & RMNET_LOG_LVL_LOW)) \
>+			pr_notice("[RMNET:LOW] %s(): " fmt "\n", __func__, \
>+				##__VA_ARGS__); \
>+			} while (0)

These look scarry. Please use netdev_err, dev_err and others instead.



>+
>+/* Don't use pr_debug as it is compiled out of the kernel. We can be sure of
>+ * minimal impact as LOGD is not enabled by default.
>+ */
>+#define LOGD(fmt, ...) do { if (unlikely( \
>+			    (rmnet_log_level & RMNET_LOG_LVL_DBG) &&\
>+			    (rmnet_log_module_mask & rmnet_mod_mask))) \
>+			pr_notice("[RMNET:DBG] %s(): " fmt "\n", __func__, \
>+				  ##__VA_ARGS__); \
>+			} while (0)
>+
>+#endif /* _RMNET_PRIVATE_H_ */
>diff --git a/drivers/net/rmnet/rmnet_stats.c b/drivers/net/rmnet/rmnet_stats.c
>new file mode 100644
>index 0000000..d53ce38
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_stats.c
>@@ -0,0 +1,86 @@
>+/* Copyright (c) 2014, 2016-2017 The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ *
>+ * RMNET Data statistics
>+ *
>+ */
>+
>+#include <linux/module.h>
>+#include <linux/kernel.h>
>+#include <linux/export.h>
>+#include <linux/skbuff.h>
>+#include <linux/spinlock.h>
>+#include <linux/netdevice.h>
>+#include "rmnet_private.h"
>+#include "rmnet_stats.h"
>+#include "rmnet_config.h"
>+#include "rmnet_map.h"
>+
>+enum rmnet_deagg_e {
>+	RMNET_STATS_AGG_BUFF,
>+	RMNET_STATS_AGG_PKT,
>+	RMNET_STATS_AGG_MAX
>+};
>+
>+static DEFINE_SPINLOCK(rmnet_skb_free_lock);
>+unsigned long int skb_free[RMNET_STATS_SKBFREE_MAX];
>+module_param_array(skb_free, ulong, 0, 0444);
>+MODULE_PARM_DESC(skb_free, "SKBs dropped or freed");
>+
>+static DEFINE_SPINLOCK(rmnet_queue_xmit_lock);
>+unsigned long int queue_xmit[RMNET_STATS_QUEUE_XMIT_MAX * 2];
>+module_param_array(queue_xmit, ulong, 0, 0444);
>+MODULE_PARM_DESC(queue_xmit, "SKBs queued for transmit");
>+
>+static DEFINE_SPINLOCK(rmnet_deagg_count);
>+unsigned long int deagg_count[RMNET_STATS_AGG_MAX];
>+module_param_array(deagg_count, ulong, 0, 0444);
>+MODULE_PARM_DESC(deagg_count, "SKBs De-aggregated");
>+
>+void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason)
>+{
>+	unsigned long flags;
>+
>+	if (reason >= RMNET_STATS_SKBFREE_MAX)
>+		reason = RMNET_STATS_SKBFREE_UNKNOWN;
>+
>+	spin_lock_irqsave(&rmnet_skb_free_lock, flags);
>+	skb_free[reason]++;
>+	spin_unlock_irqrestore(&rmnet_skb_free_lock, flags);
>+
>+	if (skb)
>+		kfree_skb(skb);
>+}
>+
>+void rmnet_stats_queue_xmit(int rc, unsigned int reason)
>+{
>+	unsigned long flags;
>+
>+	if (rc != 0)
>+		reason += RMNET_STATS_QUEUE_XMIT_MAX;
>+	if (reason >= RMNET_STATS_QUEUE_XMIT_MAX * 2)
>+		reason = RMNET_STATS_SKBFREE_UNKNOWN;
>+
>+	spin_lock_irqsave(&rmnet_queue_xmit_lock, flags);
>+	queue_xmit[reason]++;
>+	spin_unlock_irqrestore(&rmnet_queue_xmit_lock, flags);
>+}
>+
>+void rmnet_stats_deagg_pkts(int aggcount)
>+{
>+	unsigned long flags;
>+
>+	spin_lock_irqsave(&rmnet_deagg_count, flags);
>+	deagg_count[RMNET_STATS_AGG_BUFF]++;
>+	deagg_count[RMNET_STATS_AGG_PKT] += aggcount;
>+	spin_unlock_irqrestore(&rmnet_deagg_count, flags);
>+}
>diff --git a/drivers/net/rmnet/rmnet_stats.h b/drivers/net/rmnet/rmnet_stats.h
>new file mode 100644
>index 0000000..c8d0469
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_stats.h
>@@ -0,0 +1,61 @@
>+/* Copyright (c) 2014, 2016-2017 The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ *
>+ * RMNET Data statistics
>+ *
>+ */
>+
>+#ifndef _RMNET_STATS_H_
>+#define _RMNET_STATS_H_
>+
>+enum rmnet_skb_free_e {
>+	RMNET_STATS_SKBFREE_UNKNOWN,
>+	RMNET_STATS_SKBFREE_BRDG_NO_EGRESS,
>+	RMNET_STATS_SKBFREE_DELIVER_NO_EP,
>+	RMNET_STATS_SKBFREE_IPINGRESS_NO_EP,
>+	RMNET_STATS_SKBFREE_MAPINGRESS_BAD_MUX,
>+	RMNET_STATS_SKBFREE_MAPINGRESS_MUX_NO_EP,
>+	RMNET_STATS_SKBFREE_MAPINGRESS_AGGBUF,
>+	RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD,
>+	RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPC,
>+	RMNET_STATS_SKBFREE_EGR_MAPFAIL,
>+	RMNET_STATS_SKBFREE_VND_NO_EGRESS,
>+	RMNET_STATS_SKBFREE_MAPC_BAD_MUX,
>+	RMNET_STATS_SKBFREE_MAPC_MUX_NO_EP,
>+	RMNET_STATS_SKBFREE_AGG_CPY_EXPAND,
>+	RMNET_STATS_SKBFREE_AGG_INTO_BUFF,
>+	RMNET_STATS_SKBFREE_DEAGG_MALFORMED,
>+	RMNET_STATS_SKBFREE_DEAGG_CLONE_FAIL,
>+	RMNET_STATS_SKBFREE_DEAGG_UNKNOWN_IP_TYPE,
>+	RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0,
>+	RMNET_STATS_SKBFREE_INGRESS_BAD_MAP_CKSUM,
>+	RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED,
>+	RMNET_STATS_SKBFREE_MAX
>+};
>+
>+enum rmnet_queue_xmit_e {
>+	RMNET_STATS_QUEUE_XMIT_UNKNOWN,
>+	RMNET_STATS_QUEUE_XMIT_EGRESS,
>+	RMNET_STATS_QUEUE_XMIT_AGG_FILL_BUFFER,
>+	RMNET_STATS_QUEUE_XMIT_AGG_TIMEOUT,
>+	RMNET_STATS_QUEUE_XMIT_AGG_CPY_EXP_FAIL,
>+	RMNET_STATS_QUEUE_XMIT_AGG_SKIP,
>+	RMNET_STATS_QUEUE_XMIT_MAX
>+};
>+
>+void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason);
>+void rmnet_stats_queue_xmit(int rc, unsigned int reason);
>+void rmnet_stats_deagg_pkts(int aggcount);
>+void rmnet_stats_agg_pkts(int aggcount);
>+void rmnet_stats_dl_checksum(unsigned int rc);
>+void rmnet_stats_ul_checksum(unsigned int rc);
>+#endif /* _RMNET_STATS_H_ */
>diff --git a/drivers/net/rmnet/rmnet_vnd.c b/drivers/net/rmnet/rmnet_vnd.c
>new file mode 100644
>index 0000000..a737d0e
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_vnd.c
>@@ -0,0 +1,353 @@
>+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ *
>+ * RMNET Data virtual network driver
>+ *
>+ */
>+
>+#include <linux/types.h>
>+#include <linux/rmnet.h>
>+#include <linux/etherdevice.h>
>+#include <linux/if_arp.h>
>+#include <linux/spinlock.h>
>+#include <net/pkt_sched.h>
>+#include <linux/atomic.h>
>+#include "rmnet_config.h"
>+#include "rmnet_handlers.h"
>+#include "rmnet_private.h"
>+#include "rmnet_map.h"
>+#include "rmnet_vnd.h"
>+#include "rmnet_stats.h"
>+
>+RMNET_LOG_MODULE(RMNET_LOGMASK_VND);
>+
>+struct net_device *rmnet_devices[RMNET_MAX_VND];

Avoid this global variable. 



>+
>+/* RX/TX Fixup */
>+
>+/* rmnet_vnd_rx_fixup() - Virtual Network Device receive fixup hook
>+ * @skb:        Socket buffer ("packet") to modify
>+ * @dev:        Virtual network device
>+ *
>+ * Additional VND specific packet processing for ingress packets
>+ */
>+int rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev)
>+{
>+	if (unlikely(!dev || !skb))
>+		return RX_HANDLER_CONSUMED;
>+
>+	dev->stats.rx_packets++;
>+	dev->stats.rx_bytes += skb->len;
>+
>+	return RX_HANDLER_PASS;
>+}
>+
>+/* rmnet_vnd_tx_fixup() - Virtual Network Device transmic fixup hook
>+ * @skb:      Socket buffer ("packet") to modify
>+ * @dev:      Virtual network device
>+ *
>+ * Additional VND specific packet processing for egress packets
>+ */
>+int rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev)
>+{
>+	struct rmnet_vnd_private_s *dev_conf;
>+
>+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
>+
>+	if (unlikely(!dev || !skb))
>+		return RX_HANDLER_CONSUMED;
>+
>+	dev->stats.tx_packets++;
>+	dev->stats.tx_bytes += skb->len;
>+
>+	return RX_HANDLER_PASS;
>+}
>+
>+/* Network Device Operations */
>+
>+/* rmnet_vnd_start_xmit() - Transmit NDO callback
>+ * @skb:        Socket buffer ("packet") being sent from network stack
>+ * @dev:        Virtual Network Device
>+ *
>+ * Standard network driver operations hook to transmit packets on virtual
>+ * network device. Called by network stack. Packet is not transmitted directly
>+ * from here; instead it is given to the rmnet egress handler.
>+ */
>+static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
>+					struct net_device *dev)
>+{
>+	struct rmnet_vnd_private_s *dev_conf;
>+
>+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
>+	if (dev_conf->local_ep.egress_dev) {
>+		rmnet_egress_handler(skb, &dev_conf->local_ep);
>+	} else {
>+		dev->stats.tx_dropped++;
>+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_VND_NO_EGRESS);
>+	}
>+	return NETDEV_TX_OK;
>+}
>+
>+/* rmnet_vnd_change_mtu() - Change MTU NDO callback
>+ * @dev:         Virtual network device
>+ * @new_mtu:     New MTU value to set (in bytes)
>+ *
>+ * Standard network driver operations hook to set the MTU. Called by kernel to
>+ * set the device MTU. Checks if desired MTU is less than zero or greater than
>+ * RMNET_MAX_PACKET_SIZE;
>+ */
>+static int rmnet_vnd_change_mtu(struct net_device *dev, int new_mtu)
>+{
>+	if (new_mtu < 0 || new_mtu > RMNET_MAX_PACKET_SIZE)
>+		return -EINVAL;
>+
>+	dev->mtu = new_mtu;
>+	return 0;
>+}
>+
>+static const struct net_device_ops rmnet_vnd_ops = {
>+	.ndo_init = 0,
>+	.ndo_start_xmit = rmnet_vnd_start_xmit,
>+	.ndo_change_mtu = rmnet_vnd_change_mtu,
>+	.ndo_set_mac_address = 0,
>+	.ndo_validate_addr = 0,

These are NULL by default. No need to init.


>+};
>+
>+static void rmnet_vnd_free(struct net_device *dev)
>+{
>+	free_netdev(dev);
>+}
>+
>+/* rmnet_vnd_setup() - net_device initialization callback
>+ * @dev:      Virtual network device
>+ *
>+ * Called by kernel whenever a new rmnet<n> device is created. Sets MTU,
>+ * flags, ARP type, needed headroom, etc...
>+ */
>+void rmnet_vnd_setup(struct net_device *dev)
>+{
>+	struct rmnet_vnd_private_s *dev_conf;
>+
>+	LOGM("Setting up device %s", dev->name);
>+
>+	/* Clear out private data */
>+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
>+	memset(dev_conf, 0, sizeof(struct rmnet_vnd_private_s));
>+
>+	dev->netdev_ops = &rmnet_vnd_ops;
>+	dev->mtu = RMNET_DFLT_PACKET_SIZE;
>+	dev->needed_headroom = RMNET_NEEDED_HEADROOM;
>+	random_ether_addr(dev->dev_addr);
>+	dev->tx_queue_len = RMNET_TX_QUEUE_LEN;
>+
>+	/* Raw IP mode */
>+	dev->header_ops = 0;  /* No header */
>+	dev->type = ARPHRD_RAWIP;
>+	dev->hard_header_len = 0;
>+	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
>+
>+	dev->destructor	= rmnet_vnd_free;
>+}
>+
>+/* Exposed API */
>+
>+/* rmnet_vnd_exit() - Shutdown cleanup hook
>+ *
>+ * Called by RmNet main on module unload. Cleans up data structures and
>+ * unregisters/frees net_devices.
>+ */
>+void rmnet_vnd_exit(void)
>+{
>+	int i;
>+
>+	for (i = 0; i < RMNET_MAX_VND; i++)
>+		if (rmnet_devices[i]) {
>+			unregister_netdev(rmnet_devices[i]);
>+			free_netdev(rmnet_devices[i]);
>+	}
>+}
>+
>+/* rmnet_vnd_init() - Init hook
>+ *
>+ * Called by RmNet main on module load. Initializes data structures
>+ */
>+int rmnet_vnd_init(void)
>+{
>+	memset(rmnet_devices, 0, sizeof(struct net_device *) * RMNET_MAX_VND);
>+	return 0;
>+}
>+
>+/* rmnet_vnd_create_dev() - Create a new virtual network device node.
>+ * @id:         Virtual device node id
>+ * @new_device: Pointer to newly created device node
>+ * @prefix:     Device name prefix
>+ *
>+ * Allocates structures for new virtual network devices. Sets the name of the
>+ * new device and registers it with the network stack. Device will appear in
>+ * ifconfig list after this is called. If the prefix is null, then
>+ * RMNET_DEV_NAME_STR will be assumed.
>+ */
>+int rmnet_vnd_newlink(int id, struct net_device *new_device)
>+{
>+	int rc;
>+
>+	if (rmnet_devices[id])
>+		return -EINVAL;
>+
>+	rc = register_netdevice(new_device);
>+	if (!rc) {
>+		rmnet_devices[id] = new_device;
>+		new_device->rtnl_link_ops = &rmnet_link_ops;
>+	}
>+
>+	return rc;
>+}
>+
>+/* rmnet_vnd_free_dev() - free a virtual network device node.
>+ * @id:         Virtual device node id
>+ *
>+ * Unregisters the virtual network device node and frees it.
>+ * unregister_netdev locks the rtnl mutex, so the mutex must not be locked
>+ * by the caller of the function. unregister_netdev enqueues the request to
>+ * unregister the device into a TODO queue. The requests in the TODO queue
>+ * are only done after rtnl mutex is unlocked, therefore free_netdev has to
>+ * called after unlocking rtnl mutex.
>+ */
>+int rmnet_vnd_free_dev(int id)
>+{
>+	struct rmnet_logical_ep_conf_s *epconfig_l;
>+	struct net_device *dev;
>+
>+	rtnl_lock();
>+	if ((id < 0) || (id >= RMNET_MAX_VND) || !rmnet_devices[id]) {
>+		rtnl_unlock();
>+		LOGM("Invalid id [%d]", id);
>+		return -EINVAL;
>+	}
>+
>+	epconfig_l = rmnet_vnd_get_le_config(rmnet_devices[id]);
>+	if (epconfig_l && epconfig_l->refcount) {
>+		rtnl_unlock();
>+		return -EINVAL;
>+	}
>+
>+	dev = rmnet_devices[id];
>+	rmnet_devices[id] = 0;
>+	rtnl_unlock();
>+
>+	if (dev) {
>+		unregister_netdev(dev);
>+		free_netdev(dev);
>+		return 0;
>+	} else {
>+		return -EINVAL;
>+	}
>+}
>+
>+int rmnet_vnd_remove_ref_dev(int id)
>+{
>+	struct rmnet_logical_ep_conf_s *epconfig_l;
>+
>+	if ((id < 0) || (id >= RMNET_MAX_VND) || !rmnet_devices[id])

Unneeded inner "()"s. I see you have it on multiple places.


>+		return -EINVAL;
>+
>+	epconfig_l = rmnet_vnd_get_le_config(rmnet_devices[id]);
>+	if (epconfig_l && epconfig_l->refcount)
>+		return -EBUSY;
>+
>+	rmnet_devices[id] = 0;
>+	return 0;
>+}
>+
>+/* rmnet_vnd_is_vnd() - Determine if net_device is RmNet owned virtual devices
>+ * @dev:        Network device to test
>+ *
>+ * Searches through list of known RmNet virtual devices. This function is O(n)
>+ * and should not be used in the data path.
>+ *
>+ * To get the read id, subtract this result by 1.
>+ */
>+int rmnet_vnd_is_vnd(struct net_device *dev)
>+{
>+	/* This is not an efficient search, but, this will only be called in
>+	 * a configuration context, and the list is small.
>+	 */
>+	int i;
>+
>+	if (!dev)
>+		return 0;
>+
>+	for (i = 0; i < RMNET_MAX_VND; i++)
>+		if (dev == rmnet_devices[i])
>+			return i + 1;
>+
>+	return 0;
>+}
>+
>+/* rmnet_vnd_get_le_config() - Get the logical endpoint configuration
>+ * @dev:      Virtual device node
>+ *
>+ * Gets the logical endpoint configuration for a RmNet virtual network device
>+ * node. Caller should confirm that devices is a RmNet VND before calling.
>+ */
>+struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct net_device *dev)
>+{
>+	struct rmnet_vnd_private_s *dev_conf;
>+
>+	if (!dev)
>+		return 0;
>+
>+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);

The typecast is not needed since netdev_priv is void*. You have it all
over the code.


>+	if (!dev_conf)
>+		return 0;
>+
>+	return &dev_conf->local_ep;
>+}
>+
>+/* rmnet_vnd_do_flow_control() - Process flow control request
>+ * @dev: Virtual network device node to do lookup on
>+ * @enable: boolean to enable/disable flow.
>+ */
>+int rmnet_vnd_do_flow_control(struct net_device *dev, int enable)
>+{
>+	struct rmnet_vnd_private_s *dev_conf;
>+
>+	if (unlikely(!dev) || !rmnet_vnd_is_vnd(dev))
>+		return -EINVAL;
>+
>+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
>+	if (unlikely(!dev_conf))
>+		return -EINVAL;
>+
>+	LOGD("Setting VND TX queue state to %d", enable);
>+	/* Although we expect similar number of enable/disable
>+	 * commands, optimize for the disable. That is more
>+	 * latency sensitive than enable
>+	 */
>+	if (unlikely(enable))
>+		netif_wake_queue(dev);
>+	else
>+		netif_stop_queue(dev);
>+
>+	return 0;
>+}
>+
>+/* rmnet_vnd_get_by_id() - Get VND by array index ID
>+ * @id: Virtual network deice id [0:RMNET_MAX_VND]
>+ */
>+struct net_device *rmnet_vnd_get_by_id(int id)
>+{
>+	if (id < 0 || id >= RMNET_MAX_VND)
>+		return 0;
>+
>+	return rmnet_devices[id];
>+}
>diff --git a/drivers/net/rmnet/rmnet_vnd.h b/drivers/net/rmnet/rmnet_vnd.h
>new file mode 100644
>index 0000000..8095e91
>--- /dev/null
>+++ b/drivers/net/rmnet/rmnet_vnd.h
>@@ -0,0 +1,34 @@
>+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
>+ *
>+ * This program is free software; you can redistribute it and/or modify
>+ * it under the terms of the GNU General Public License version 2 and
>+ * only version 2 as published by the Free Software Foundation.
>+ *
>+ * This program is distributed in the hope that it will be useful,
>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>+ * GNU General Public License for more details.
>+ *
>+ * RMNET Data Virtual Network Device APIs
>+ *
>+ */
>+
>+#include <linux/types.h>
>+
>+#ifndef _RMNET_VND_H_
>+#define _RMNET_VND_H_
>+
>+int rmnet_vnd_do_flow_control(struct net_device *dev, int enable);
>+struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct net_device *dev);
>+int rmnet_vnd_free_dev(int id);
>+int rmnet_vnd_remove_ref_dev(int id);
>+int rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev);
>+int rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev);
>+int rmnet_vnd_is_vnd(struct net_device *dev);
>+int rmnet_vnd_init(void);
>+void rmnet_vnd_exit(void);
>+struct net_device *rmnet_vnd_get_by_id(int id);
>+void rmnet_vnd_setup(struct net_device *dev);
>+int rmnet_vnd_newlink(int id, struct net_device *new_device);
>+
>+#endif /* _RMNET_VND_H_ */
>diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
>index dd9820b..ec29d61 100644
>--- a/include/uapi/linux/Kbuild
>+++ b/include/uapi/linux/Kbuild
>@@ -370,6 +370,7 @@ header-y += resource.h
> header-y += rfkill.h
> header-y += rio_cm_cdev.h
> header-y += rio_mport_cdev.h
>+header-y += rmnet.h
> header-y += romfs_fs.h
> header-y += rose.h
> header-y += route.h
>diff --git a/include/uapi/linux/if_arp.h b/include/uapi/linux/if_arp.h
>index 4d024d7..e762447 100644
>--- a/include/uapi/linux/if_arp.h
>+++ b/include/uapi/linux/if_arp.h
>@@ -59,6 +59,7 @@
> #define ARPHRD_LAPB	516		/* LAPB				*/
> #define ARPHRD_DDCMP    517		/* Digital's DDCMP protocol     */
> #define ARPHRD_RAWHDLC	518		/* Raw HDLC			*/
>+#define ARPHRD_RAWIP	530		/* Raw IP			*/
> 
> #define ARPHRD_TUNNEL	768		/* IPIP tunnel			*/
> #define ARPHRD_TUNNEL6	769		/* IP6IP6 tunnel       		*/
>diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
>index 5bc9bfd..70520da 100644
>--- a/include/uapi/linux/if_ether.h
>+++ b/include/uapi/linux/if_ether.h
>@@ -104,7 +104,9 @@
> #define ETH_P_QINQ3	0x9300		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
> #define ETH_P_EDSA	0xDADA		/* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
> #define ETH_P_AF_IUCV   0xFBFB		/* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
>-
>+#define ETH_P_MAP	0xDA1A		/* Multiplexing and Aggregation Protocol
>+					 *  NOT AN OFFICIALLY REGISTERED ID ]

Please push this and ARPHRD_RAWIP as separate patches, to increase the
visibility.


>+					 */
> #define ETH_P_802_3_MIN	0x0600		/* If the value in the ethernet type is less than this value
> 					 * then the frame is Ethernet II. Else it is 802.3 */
> 
>diff --git a/include/uapi/linux/rmnet.h b/include/uapi/linux/rmnet.h
>new file mode 100644
>index 0000000..dce5763
>--- /dev/null
>+++ b/include/uapi/linux/rmnet.h
>@@ -0,0 +1,34 @@
>+#ifndef _RMNET_DATA_H_
>+#define _RMNET_DATA_H_
>+
>+/* Constants */
>+#define RMNET_EGRESS_FORMAT__RESERVED__         (1<<0)
>+#define RMNET_EGRESS_FORMAT_MAP                 (1<<1)
>+#define RMNET_EGRESS_FORMAT_AGGREGATION         (1<<2)
>+#define RMNET_EGRESS_FORMAT_MUXING              (1<<3)
>+#define RMNET_EGRESS_FORMAT_MAP_CKSUMV3         (1<<4)
>+#define RMNET_EGRESS_FORMAT_MAP_CKSUMV4         (1<<5)
>+
>+#define RMNET_INGRESS_FIX_ETHERNET              (1<<0)
>+#define RMNET_INGRESS_FORMAT_MAP                (1<<1)
>+#define RMNET_INGRESS_FORMAT_DEAGGREGATION      (1<<2)
>+#define RMNET_INGRESS_FORMAT_DEMUXING           (1<<3)
>+#define RMNET_INGRESS_FORMAT_MAP_COMMANDS       (1<<4)
>+#define RMNET_INGRESS_FORMAT_MAP_CKSUMV3        (1<<5)
>+#define RMNET_INGRESS_FORMAT_MAP_CKSUMV4        (1<<6)
>+
>+/* Pass the frame up the stack with no modifications to skb->dev */
>+#define RMNET_EPMODE_NONE (0)
>+/* Replace skb->dev to a virtual rmnet device and pass up the stack */
>+#define	RMNET_EPMODE_VND (1)
>+/* Pass the frame directly to another device with dev_queue_xmit() */
>+#define	RMNET_EPMODE_BRIDGE (2)
>+
>+enum {
>+	IFLA_RMNET_UNSPEC,
>+	IFLA_RMNET_MUX_ID,
>+	__IFLA_RMNET_MAX,
>+};

This belongs to include/uapi/linux/if_link.h
Please see IFLA_BOND_* as example


>+#define __IFLA_RMNET_MAX	(__IFLA_RMNET_MAX - 1)
>+
>+#endif /* _RMNET_DATA_H_ */
>-- 
>1.9.1
>
Stephen Hemminger April 14, 2017, 4:10 p.m. UTC | #2
On Thu, 13 Apr 2017 23:05:29 -0600
Subash Abhinov Kasiviswanathan <subashab@codeaurora.org> wrote:

> RmNet driver provides a transport agnostic MAP (multiplexing and
> aggregation protocol) support in embedded module. Module provides
> virtual network devices which can be attached to any IP-mode
> physical device. This will be used to provide all MAP functionality
> on future hardware in a single consistent location.
> 
> Signed-off-by: Subash Abhinov Kasiviswanathan <subashab@codeaurora.org>

> 
> diff --git a/Documentation/networking/rmnet.txt b/Documentation/networking/rmnet.txt
> new file mode 100644
> index 0000000..58d3ea2
> --- /dev/null
> +++ b/Documentation/networking/rmnet.txt
>
...

> +3. Userspace configuration
> +
> +rmnet userspace configuration is done through netlink library librmnetctl
> +and command line utility rmnetcli. Utility is hosted in codeaurora forum git.
> +The driver uses rtnl_link_ops for communication.
> +
> +https://source.codeaurora.org/quic/la/platform/vendor/qcom-opensource/\
> +dataservices/tree/rmnetctl

Don't split URL better to have long line.


> diff --git a/drivers/net/Makefile b/drivers/net/Makefile
> index 98ed4d9..29b3945 100644
> --- a/drivers/net/Makefile
> +++ b/drivers/net/Makefile
> @@ -74,3 +74,4 @@ obj-$(CONFIG_HYPERV_NET) += hyperv/
>  obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o
>  
>  obj-$(CONFIG_FUJITSU_ES) += fjes/
> +obj-$(CONFIG_RMNET) += rmnet/
> diff --git a/drivers/net/rmnet/Kconfig b/drivers/net/rmnet/Kconfig

Since this is Qualcomm and Ethernet specific, maybe better to put
in drivers/net/ethernet/qualcom/rmnet


> new file mode 100644
> index 0000000..63cd477
> --- /dev/null
> +++ b/drivers/net/rmnet/Kconfig
> @@ -0,0 +1,23 @@
> +#
> +# RMNET MAP driver
> +#
> +
> +menuconfig RMNET
> +	depends on NETDEVICES
> +	bool "RmNet MAP driver"
> +	default n
> +	---help---
> +	  If you say Y here, then the rmnet module will be statically
> +	  compiled into the kernel. The rmnet module provides MAP
> +	  functionality for embedded and bridged traffic.
> +if RMNET
> +
> +config RMNET_DEBUG
> +	bool "RmNet Debug Logging"
> +	default n
> +	---help---
> +	  Say Y here if you want RmNet to be able to log packets in main
> +	  system log. This should not be enabled on production builds as it can
> +	  impact system performance. Note that simply enabling it here will not
> +	  enable the logging; it must be enabled at run-time as well.


Please use network device standard debug mechanism.
netif_msg_XXX

> +endif # RMNET
> diff --git a/drivers/net/rmnet/Makefile b/drivers/net/rmnet/Makefile
> new file mode 100644
> index 0000000..2b6c9cf
> --- /dev/null
> +++ b/drivers/net/rmnet/Makefile
> @@ -0,0 +1,14 @@
> +#
> +# Makefile for the RMNET module
> +#
> +
> +rmnet-y		 := rmnet_main.o
> +rmnet-y		 += rmnet_config.o
> +rmnet-y		 += rmnet_vnd.o
> +rmnet-y		 += rmnet_handlers.o
> +rmnet-y		 += rmnet_map_data.o
> +rmnet-y		 += rmnet_map_command.o
> +rmnet-y		 += rmnet_stats.o
> +obj-$(CONFIG_RMNET) += rmnet.o
> +
> +CFLAGS_rmnet_main.o := -I$(src)
> diff --git a/drivers/net/rmnet/rmnet_config.c b/drivers/net/rmnet/rmnet_config.c
> new file mode 100644
> index 0000000..a4bc76b
> --- /dev/null
> +++ b/drivers/net/rmnet/rmnet_config.c
> @@ -0,0 +1,592 @@
> +/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * RMNET configuration engine
> + *
> + */
> +
> +#include <net/sock.h>
> +#include <linux/module.h>
> +#include <linux/netlink.h>
> +#include <linux/netdevice.h>
> +#include <linux/skbuff.h>
> +#include <linux/spinlock.h>
> +#include <linux/rmnet.h>
> +#include "rmnet_config.h"
> +#include "rmnet_handlers.h"
> +#include "rmnet_vnd.h"
> +#include "rmnet_private.h"
> +
> +RMNET_LOG_MODULE(RMNET_LOGMASK_CONFIG);
> +
> +/* Local Definitions and Declarations */
> +#define RMNET_LOCAL_LOGICAL_ENDPOINT -1
> +
> +/* _rmnet_is_physical_endpoint_associated() - Determines if device is associated
> + * @dev:      Device to get check
> + *
> + * Compares device rx_handler callback pointer against known function
> + */
> +static inline int _rmnet_is_physical_endpoint_associated(struct net_device *dev)
> +{
> +	rx_handler_func_t *rx_handler;
> +
> +	rx_handler = rcu_dereference(dev->rx_handler);
> +
> +	if (rx_handler == rmnet_rx_handler)
> +		return 1;
> +	else
> +		return 0;
> +}

Could just be:

static inline int _rmnet_is_physical_endpoint_associated(const struct net_device *dev)
{
	rx_handler_func_t *rx_handler
		= rcu_dereference(dev->rx_handler);

	return rx_handler == rmet_rx_handler;
}

But standard practice is to use ndo_ops to identify self in network drivers.
I.e
	return dev->netdev_ops == &rmnet_device_ops;


> +/* _rmnet_get_phys_ep_config() - Get physical ep config for an associated device
> + * @dev:      Device to get endpoint configuration from
> + */
> +static inline struct rmnet_phys_ep_conf_s *_rmnet_get_phys_ep_config
> +						(struct net_device *dev)
awkward line break.
dev could be const

> +{
> +	if (_rmnet_is_physical_endpoint_associated(dev))
> +		return (struct rmnet_phys_ep_conf_s *)
> +			rcu_dereference(dev->rx_handler_data);
> +	else
> +		return 0;
> +}
> +
> +struct rmnet_free_vnd_work {
> +	struct work_struct work;
> +	int vnd_id[RMNET_MAX_VND];
> +	int count;
> +};
> +
> +/* _rmnet_get_logical_ep() - Gets the logical end point configuration
> + * structure for a network device
> + * @dev:             Device to get endpoint configuration from
> + * @config_id:       Logical endpoint id on device
> + * Retrieves the logical_endpoint_config structure.
> + */
> +static struct rmnet_logical_ep_conf_s *_rmnet_get_logical_ep
> +	(struct net_device *dev, int config_id)
> +{
> +	struct rmnet_phys_ep_conf_s *config;
> +	struct rmnet_logical_ep_conf_s *epconfig_l;
> +
> +	if (rmnet_vnd_is_vnd(dev)) {
> +		epconfig_l = rmnet_vnd_get_le_config(dev);
> +	} else {
> +		config = _rmnet_get_phys_ep_config(dev);
> +
> +		if (!config)
> +			return NULL;
> +
> +		if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT)
> +			epconfig_l = &config->local_ep;
> +		else
> +			epconfig_l = &config->muxed_ep[config_id];
> +	}
> +
> +	return epconfig_l;
> +}
> +
> +/* rmnet_unassociate_network_device() - Unassociate network device
> + * @dev:      Device to unassociate
> + *
> + * Frees all structures generate for device. Unregisters rx_handler
> + */
> +static int rmnet_unassociate_network_device(struct net_device *dev)
> +{
> +	struct rmnet_phys_ep_conf_s *config;
> +	int config_id = RMNET_LOCAL_LOGICAL_ENDPOINT;
> +	struct rmnet_logical_ep_conf_s *epconfig_l;
> +
> +	ASSERT_RTNL();
> +
> +	LOGL("(%s);", dev->name);
> +
> +	if (!dev || !_rmnet_is_physical_endpoint_associated(dev))
> +		return -EINVAL;
> +
> +	for (; config_id < RMNET_MAX_LOGICAL_EP; config_id++) {
> +		epconfig_l = _rmnet_get_logical_ep(dev, config_id);
> +		if (epconfig_l && epconfig_l->refcount)
> +			return -EINVAL;
> +	}
> +
> +	config = (struct rmnet_phys_ep_conf_s *)
> +		  rcu_dereference(dev->rx_handler_data);

Please don't directly reference rx_handler. There is already functions
like netdev_is_rx_handler_busy() to abstract that API.

> +
> +	if (!config)
> +		return -EINVAL;
> +
> +	kfree(config);
> +
> +	netdev_rx_handler_unregister(dev);
> +
> +	dev_put(dev);
> +	return 0;
> +}
> +
> +/* rmnet_set_ingress_data_format() - Set ingress data format on network device
> + * @dev:                 Device to ingress data format on
> + * @egress_data_format:  32-bit unsigned bitmask of ingress format
> + *
> + * Network device must already have association with RmNet Data driver
> + */
> +static int rmnet_set_ingress_data_format(struct net_device *dev,
> +					 u32 ingress_data_format)
> +{
> +	struct rmnet_phys_ep_conf_s *config;
> +
> +	ASSERT_RTNL();
> +
> +	LOGL("(%s,0x%08X);", dev->name, ingress_data_format);
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	config = _rmnet_get_phys_ep_config(dev);
> +	if (!config)
> +		return -EINVAL;
> +
> +	config->ingress_data_format = ingress_data_format;
> +
> +	return 0;
> +}
> +
> +/* rmnet_set_egress_data_format() - Set egress data format on network device
> + * @dev:                 Device to egress data format on
> + * @egress_data_format:  32-bit unsigned bitmask of egress format
> + *
> + * Network device must already have association with RmNet Data driver
> + */
> +static int rmnet_set_egress_data_format(struct net_device *dev,
> +					u32 egress_data_format,
> +					u16 agg_size,
> +					u16 agg_count)
> +{
> +	struct rmnet_phys_ep_conf_s *config;
> +
> +	ASSERT_RTNL();
> +
> +	LOGL("(%s,0x%08X, %d, %d);",
> +	     dev->name, egress_data_format, agg_size, agg_count);
> +
> +	if (!dev)
> +		return -EINVAL;
> +
> +	config = _rmnet_get_phys_ep_config(dev);
> +	if (!config)
> +		return -EINVAL;
> +
> +	config->egress_data_format = egress_data_format;
> +
> +	return 0;
> +}
> +
> +/* rmnet_associate_network_device() - Associate network device
> + * @dev:      Device to register with RmNet data
> + *
> + * Typically used on physical network devices. Registers RX handler and private
> + * metadata structures.
> + */
> +static int rmnet_associate_network_device(struct net_device *dev)
> +{
> +	struct rmnet_phys_ep_conf_s *config;
> +	int rc;
> +
> +	ASSERT_RTNL();
> +
> +	LOGL("(%s);\n", dev->name);
> +
> +	if (!dev || _rmnet_is_physical_endpoint_associated(dev) ||
> +	    rmnet_vnd_is_vnd(dev)) {
> +		LOGM("cannot register with this dev");
> +		return -EINVAL;
> +	}
> +
> +	config = kmalloc(sizeof(*config), GFP_ATOMIC);
> +	if (!config)
> +		return -ENOMEM;
> +
> +	memset(config, 0, sizeof(struct rmnet_phys_ep_conf_s));
> +	config->dev = dev;
> +
> +	rc = netdev_rx_handler_register(dev, rmnet_rx_handler, config);
> +
> +	if (rc) {
> +		LOGM("netdev_rx_handler_register returns %d", rc);
> +		kfree(config);
> +		return -EBUSY;
> +	}
> +
> +	dev_hold(dev);
> +	return 0;
> +}
> +
> +/* __rmnet_set_logical_endpoint_config() - Set logical endpoing config on device
> + * @dev:         Device to set endpoint configuration on
> + * @config_id:   logical endpoint id on device
> + * @epconfig:    endpoint configuration structure to set
> + */

You are using docbook format here, but this is not a docbook comment.
ie.
/**
 * function - This is a docbook comment
 * @dev: this is a param
 */

Plus these are static functions so there is no point in documentating
internal API with docbook.

> +static int __rmnet_set_logical_endpoint_config
> +	(struct net_device *dev,
> +	 int config_id,
> +	 struct rmnet_logical_ep_conf_s *epconfig)
> +{
> +	struct rmnet_logical_ep_conf_s *epconfig_l;
> +
> +	ASSERT_RTNL();
> +
> +	if (!dev || config_id < RMNET_LOCAL_LOGICAL_ENDPOINT ||
> +	    config_id >= RMNET_MAX_LOGICAL_EP)
> +		return -EINVAL;

For internal API's you should validate parmeters at the external
entry point not in each call. Otherwise you have a multitude of
impossible error checks and dead code paths.

> +
> +	epconfig_l = _rmnet_get_logical_ep(dev, config_id);
> +
> +	if (!epconfig_l || epconfig_l->refcount)
> +		return -EINVAL;
> +
> +	memcpy(epconfig_l, epconfig, sizeof(struct rmnet_logical_ep_conf_s));
> +	if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT)
> +		epconfig_l->mux_id = 0;
> +	else
> +		epconfig_l->mux_id = config_id;
> +
> +	/* Explicitly hold a reference to the egress device */
> +	dev_hold(epconfig_l->egress_dev);
> +	return 0;
> +}
> +
...

> +
> +static struct notifier_block rmnet_dev_notifier = {
> +	.notifier_call = rmnet_config_notify_cb,
> +	.next = 0,
> +	.priority = 0
> +};
Don't initialize fields that are not used or should be zero.


> +
> +static int rmnet_newlink(struct net *src_net, struct net_device *dev,
> +			 struct nlattr *tb[], struct nlattr *data[])
> +{
> +	int ingress_format = RMNET_INGRESS_FORMAT_DEMUXING |
> +			     RMNET_INGRESS_FORMAT_DEAGGREGATION |
> +			     RMNET_INGRESS_FORMAT_MAP;
> +	int egress_format = RMNET_EGRESS_FORMAT_MUXING |
> +			    RMNET_EGRESS_FORMAT_MAP;
> +	struct net_device *real_dev;
> +	int mode = RMNET_EPMODE_VND;
> +	u16 mux_id;
> +
> +	real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
> +	if (!real_dev)
> +		return -ENODEV;
> +
> +	if (!data[IFLA_RMNET_MUX_ID])
> +		return -EINVAL;

So you are inventing private link netlink attributes.
Why? Why can't you use device switch, bridge, or other master/slave model.

> +
> +	mux_id = nla_get_u16(data[IFLA_VLAN_ID]);
> +	if (rmnet_vnd_newlink(mux_id, dev))
> +		return -EINVAL;
> +
> +	rmnet_associate_network_device(real_dev);
> +	rmnet_set_egress_data_format(real_dev, egress_format, 0, 0);
> +	rmnet_set_ingress_data_format(real_dev, ingress_format);
> +	rmnet_set_logical_endpoint_config(real_dev, mux_id, mode, dev);
> +	rmnet_set_logical_endpoint_config(dev, mux_id, mode, real_dev);
> +	return 0;
> +}
> +
> +static void rmnet_delink(struct net_device *dev, struct list_head *head)
> +{
> +	struct rmnet_logical_ep_conf_s *cfg;
> +	int mux_id;
> +
> +	mux_id = rmnet_vnd_is_vnd(dev);
> +	if (!mux_id)
> +		return;
> +
> +/* rmnet_vnd_is_vnd() gives mux_id + 1, so subtract 1 to get the correct mux_id
> + */
> +	mux_id--;
> +	cfg = rmnet_vnd_get_le_config(dev);
> +
> +	if (cfg && cfg->refcount) {
> +		_rmnet_unset_logical_endpoint_config(cfg->egress_dev, mux_id);
> +		_rmnet_unset_logical_endpoint_config(dev, mux_id);
> +		rmnet_vnd_remove_ref_dev(mux_id);
> +		rmnet_unassociate_network_device(cfg->egress_dev);
> +	}
> +
> +	unregister_netdevice_queue(dev, head);
> +}
> +
> +static int rmnet_rtnl_validate(struct nlattr *tb[], struct nlattr *data[])
> +{
> +	u16 mux_id;
> +
> +	if (!data || !data[IFLA_RMNET_MUX_ID])
> +		return -EINVAL;
> +
> +	mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
> +	if (!mux_id || mux_id > (RMNET_MAX_LOGICAL_EP - 1))
> +		return -ERANGE;
> +
> +	return 0;
> +}
> +
> +static size_t rmnet_get_size(const struct net_device *dev)
> +{
> +	return nla_total_size(2); /* IFLA_RMNET_MUX_ID */
> +}
> +
> +struct rtnl_link_ops rmnet_link_ops __read_mostly = {
> +	.kind		= "rmnet",
> +	.maxtype	= __IFLA_RMNET_MAX,
> +	.priv_size	= sizeof(struct rmnet_vnd_private_s),
> +	.setup		= rmnet_vnd_setup,
> +	.validate	= rmnet_rtnl_validate,
> +	.newlink	= rmnet_newlink,
> +	.dellink	= rmnet_delink,
> +	.get_size	= rmnet_get_size,
> +};
> +
> +int rmnet_config_init(void)
> +{
> +	int rc;
> +
> +	rc = register_netdevice_notifier(&rmnet_dev_notifier);
> +	if (rc != 0) {
> +		LOGE("Failed to register device notifier; rc=%d", rc);
> +		return rc;
> +	}
> +
> +	rc = rtnl_link_register(&rmnet_link_ops);
> +	if (rc != 0) {
> +		unregister_netdevice_notifier(&rmnet_dev_notifier);
> +		LOGE("Failed to register netlink handler; rc=%d", rc);
> +		return rc;
> +	}
> +	return rc;
> +}
> +
> +void rmnet_config_exit(void)
> +{
> +	unregister_netdevice_notifier(&rmnet_dev_notifier);
> +	rtnl_link_unregister(&rmnet_link_ops);
> +}
> diff --git a/drivers/net/rmnet/rmnet_config.h b/drivers/net/rmnet/rmnet_config.h
> new file mode 100644
> index 0000000..0ef58e8
> --- /dev/null
> +++ b/drivers/net/rmnet/rmnet_config.h
> @@ -0,0 +1,79 @@
> +/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * RMNET Data configuration engine
> + *
> + */
> +
> +#include <linux/types.h>
> +#include <linux/time.h>
> +#include <linux/skbuff.h>
> +
> +#ifndef _RMNET_CONFIG_H_
> +#define _RMNET_CONFIG_H_
> +
> +#define RMNET_MAX_LOGICAL_EP 255
> +
> +/* struct rmnet_logical_ep_conf_s - Logical end-point configuration
> + *
> + * @refcount: Reference count for this endpoint. 0 signifies the endpoint is not
> + *            configured for use
> + * @rmnet_mode: Specifies how the traffic should be finally delivered. Possible
> + *            options are available in enum rmnet_config_endpoint_modes_e
> + * @mux_id: Virtual channel ID used by MAP protocol
> + * @egress_dev: Next device to deliver the packet to. Exact usage of this
> + *            parmeter depends on the rmnet_mode
> + */
> +struct rmnet_logical_ep_conf_s {
> +	u8 refcount;
> +	u8 rmnet_mode;
> +	u8 mux_id;
> +	struct timespec flush_time;
> +	struct net_device *egress_dev;
> +};
> +
> +/* struct rmnet_phys_ep_conf_s - Physical endpoint configuration
> + * One instance of this structure is instantiated for each net_device associated
> + * with rmnet.
> + *
> + * @dev: The device which is associated with rmnet. Corresponds to this
> + *       specific instance of rmnet_phys_ep_conf_s
> + * @local_ep: Default non-muxed endpoint. Used for non-MAP protocols/formats
> + * @muxed_ep: All multiplexed logical endpoints associated with this device
> + * @ingress_data_format: RMNET_INGRESS_FORMAT_* flags from rmnet.h
> + * @egress_data_format: RMNET_EGRESS_FORMAT_* flags from rmnet.h
> + *
> + * @egress_agg_size: Maximum size (bytes) of data which should be aggregated
> + * @egress_agg_count: Maximum count (packets) of data which should be aggregated
> + *                  Smaller of the two parameters above are chosen for
> + *                  aggregation
> + * @tail_spacing: Guaranteed padding (bytes) when de-aggregating ingress frames
> + * @agg_time: Wall clock time when aggregated frame was created
> + * @agg_last: Last time the aggregation routing was invoked
> + */
> +struct rmnet_phys_ep_conf_s {
> +	struct net_device *dev;
> +	struct rmnet_logical_ep_conf_s local_ep;
> +	struct rmnet_logical_ep_conf_s muxed_ep[RMNET_MAX_LOGICAL_EP];
> +	u32 ingress_data_format;
> +	u32 egress_data_format;
> +};
> +
> +int rmnet_config_init(void);
> +void rmnet_config_exit(void);
> +int rmnet_free_vnd(int id);
> +
> +extern struct rtnl_link_ops rmnet_link_ops;
> +
> +struct rmnet_vnd_private_s {
> +	struct rmnet_logical_ep_conf_s local_ep;
> +};
> +#endif /* _RMNET_CONFIG_H_ */
> diff --git a/drivers/net/rmnet/rmnet_handlers.c b/drivers/net/rmnet/rmnet_handlers.c
> new file mode 100644
> index 0000000..bf8b3bb
> --- /dev/null
> +++ b/drivers/net/rmnet/rmnet_handlers.c
> @@ -0,0 +1,517 @@
> +/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * RMNET Data ingress/egress handler
> + *
> + */
> +
> +#include <linux/skbuff.h>
> +#include <linux/netdevice.h>
> +#include <linux/module.h>
> +#include <linux/rmnet.h>
> +#include <linux/netdev_features.h>
> +#include <linux/ip.h>
> +#include <linux/ipv6.h>
> +#include "rmnet_private.h"
> +#include "rmnet_config.h"
> +#include "rmnet_vnd.h"
> +#include "rmnet_map.h"
> +#include "rmnet_stats.h"
> +#include "rmnet_handlers.h"
> +
> +RMNET_LOG_MODULE(RMNET_LOGMASK_HANDLER);
> +
> +#ifdef CONFIG_RMNET_DEBUG
> +unsigned int dump_pkt_rx;
> +module_param(dump_pkt_rx, uint, 0644);
> +MODULE_PARM_DESC(dump_pkt_rx, "Dump packets entering ingress handler");
> +
> +unsigned int dump_pkt_tx;
> +module_param(dump_pkt_tx, uint, 0644);
> +MODULE_PARM_DESC(dump_pkt_tx, "Dump packets exiting egress handler");
> +#endif /* CONFIG_RMNET_DEBUG */
> +
> +#define RMNET_IP_VERSION_4 0x40
> +#define RMNET_IP_VERSION_6 0x60
> +
> +/* Helper Functions */
> +
> +/* __rmnet_set_skb_proto() - Set skb->protocol field
> + * @skb:      packet being modified
> + *
> + * Peek at the first byte of the packet and set the protocol. There is not
> + * good way to determine if a packet has a MAP header. As of writing this,
> + * the reserved bit in the MAP frame will prevent it from overlapping with
> + * IPv4/IPv6 frames. This could change in the future!
> + */
> +static inline void __rmnet_set_skb_proto(struct sk_buff *skb)
> +{
> +	switch (skb->data[0] & 0xF0) {
> +	case RMNET_IP_VERSION_4:
> +		skb->protocol = htons(ETH_P_IP);
> +		break;
> +	case RMNET_IP_VERSION_6:
> +		skb->protocol = htons(ETH_P_IPV6);
> +		break;
> +	default:
> +		skb->protocol = htons(ETH_P_MAP);
> +		break;
> +	}
> +}
> +
> +#ifdef CONFIG_RMNET_DEBUG
> +/* rmnet_print_packet() - Print packet / diagnostics
> + * @skb:      Packet to print
> + * @printlen: Number of bytes to print
> + * @dev:      Name of interface
> + * @dir:      Character representing direction (e.g.. 'r' for receive)
> + *
> + * This function prints out raw bytes in an SKB. Use of this will have major
> + * performance impacts and may even trigger watchdog resets if too much is being
> + * printed. Hence, this should always be compiled out unless absolutely needed.
> + */
> +void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir)
> +{
> +	char buffer[200];
> +	unsigned int len, printlen;
> +	int i, buffloc = 0;
> +
> +	switch (dir) {
> +	case 'r':
> +		printlen = dump_pkt_rx;
> +		break;
> +
> +	case 't':
> +		printlen = dump_pkt_tx;
> +		break;
> +
> +	default:
> +		printlen = 0;
> +		break;
> +	}
> +
> +	if (!printlen)
> +		return;
> +
> +	pr_err("[%s][%c] - PKT skb->len=%d skb->head=%pK skb->data=%pK\n",
> +	       dev, dir, skb->len, (void *)skb->head, (void *)skb->data);
> +	pr_err("[%s][%c] - PKT skb->tail=%pK skb->end=%pK\n",
> +	       dev, dir, skb_tail_pointer(skb), skb_end_pointer(skb));
> +
> +	if (skb->len > 0)
> +		len = skb->len;
> +	else
> +		len = ((unsigned int)(uintptr_t)skb->end) -
> +		      ((unsigned int)(uintptr_t)skb->data);
> +
> +	pr_err("[%s][%c] - PKT len: %d, printing first %d bytes\n",
> +	       dev, dir, len, printlen);
> +
> +	memset(buffer, 0, sizeof(buffer));
> +	for (i = 0; (i < printlen) && (i < len); i++) {
> +		if ((i % 16) == 0) {
> +			pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer);
> +			memset(buffer, 0, sizeof(buffer));
> +			buffloc = 0;
> +			buffloc += snprintf(&buffer[buffloc],
> +					sizeof(buffer) - buffloc, "%04X:",
> +					i);
> +		}
> +
> +		buffloc += snprintf(&buffer[buffloc], sizeof(buffer) - buffloc,
> +					" %02x", skb->data[i]);

If you really have to do this. Use hex_dump_bytes API.

> +	}
> +	pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer);
> +}
> +#else
> +void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir)
> +{
> +}
> +#endif /* CONFIG_RMNET_DEBUG */
> +
> +/* Generic handler */
> +
> +/* rmnet_bridge_handler() - Bridge related functionality
> + */
> +static rx_handler_result_t rmnet_bridge_handler
> +	(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)
> +{
> +	if (!ep->egress_dev) {
> +		LOGD("Missing egress device for packet arriving on %s",
> +		     skb->dev->name);
> +		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_BRDG_NO_EGRESS);
> +	} else {
> +		rmnet_egress_handler(skb, ep);
> +	}
> +
> +	return RX_HANDLER_CONSUMED;
> +}
> +
> +#ifdef NET_SKBUFF_DATA_USES_OFFSET
> +static void rmnet_reset_mac_header(struct sk_buff *skb)
> +{
> +	skb->mac_header = 0;
> +	skb->mac_len = 0;
> +}

Why not use sbk_set_mac_header(skb, 0)?

> +#else
> +static void rmnet_reset_mac_header(struct sk_buff *skb)
> +{
> +	skb->mac_header = skb->network_header;
> +	skb->mac_len = 0;
> +}
> +#endif /*NET_SKBUFF_DATA_USES_OFFSET*/
> +
> +/* __rmnet_deliver_skb() - Deliver skb
> + *
> + * Determines where to deliver skb. Options are: consume by network stack,
> + * pass to bridge handler, or pass to virtual network device
> + */
> +static rx_handler_result_t __rmnet_deliver_skb
> +	(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)
> +{
> +	switch (ep->rmnet_mode) {
> +	case RMNET_EPMODE_NONE:
> +		return RX_HANDLER_PASS;
> +
> +	case RMNET_EPMODE_BRIDGE:
> +		return rmnet_bridge_handler(skb, ep);
> +
> +	case RMNET_EPMODE_VND:
> +		skb_reset_transport_header(skb);
> +		skb_reset_network_header(skb);
> +		switch (rmnet_vnd_rx_fixup(skb, skb->dev)) {
> +		case RX_HANDLER_CONSUMED:
> +			return RX_HANDLER_CONSUMED;
> +
> +		case RX_HANDLER_PASS:
> +			skb->pkt_type = PACKET_HOST;
> +			rmnet_reset_mac_header(skb);
> +			netif_receive_skb(skb);
> +			return RX_HANDLER_CONSUMED;
> +		}
> +		return RX_HANDLER_PASS;
> +
> +	default:
> +		LOGD("Unknown ep mode %d", ep->rmnet_mode);
> +		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_DELIVER_NO_EP);
> +		return RX_HANDLER_CONSUMED;
> +	}
> +}
> +
> +/* rmnet_ingress_deliver_packet() - Ingress handler for raw IP and bridged
> + *                                  MAP packets.
> + * @skb:     Packet needing a destination.
> + * @config:  Physical end point configuration that the packet arrived on.
> + */
> +static rx_handler_result_t rmnet_ingress_deliver_packet
> +	(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config)
> +{
> +	if (!config) {
> +		LOGD("%s", "NULL physical EP provided");
> +		kfree_skb(skb);
> +		return RX_HANDLER_CONSUMED;
> +	}
> +
> +	if (!(config->local_ep.refcount)) {
> +		LOGD("Packet on %s has no local endpoint configuration",
> +		     skb->dev->name);
> +		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_IPINGRESS_NO_EP);
> +		return RX_HANDLER_CONSUMED;
> +	}
> +
> +	skb->dev = config->local_ep.egress_dev;
> +
> +	return __rmnet_deliver_skb(skb, &config->local_ep);
> +}
> +
> +/* MAP handler */
Subash Abhinov Kasiviswanathan April 14, 2017, 9:57 p.m. UTC | #3
> No, please use standard facilities in order to do debug logging.
> 
>> +static struct rmnet_logical_ep_conf_s *_rmnet_get_logical_ep
> 
> Dont use "_" at the start of a name without purpose
> 
>> +static int rmnet_associate_network_device(struct net_device *dev)
> 
> I would expect to see here you making connection between real_dev and
> rmnet dev. I don't see such thing. Name of the function is misleading.
> 
>> +
>> +	config = kmalloc(sizeof(*config), GFP_ATOMIC);
> 
> kzalloc, and you don't have to zero the memory.
> 
>> +static struct notifier_block rmnet_dev_notifier = {
> 
> You should add "__read_mostly"
> 
>> +	.notifier_call = rmnet_config_notify_cb,
>> +	.next = 0,
>> +	.priority = 0
> 
> This initialization of 0s is not needed.
> 
>> +/* rmnet_vnd_is_vnd() gives mux_id + 1, so subtract 1 to get the 
>> correct mux_id
>> + */
> 
> Fix this comment format.
>> +void rmnet_print_packet(const struct sk_buff *skb, const char *dev, 
>> char dir)
> 
> No reason to have this function. One can use P_ALL tap to get skbs to
> userspace.
> 
>> +/* rmnet_bridge_handler() - Bridge related functionality
>> + */
> 
> Fix the comment format (you have it on multiple places)
> 
>> +static rx_handler_result_t rmnet_bridge_handler
>> +	(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)
> 
> The formatting is incorrect:
> 
> static rx_handler_result_t
> rmnet_bridge_handler(struct sk_buff *skb, struct 
> rmnet_logical_ep_conf_s *ep)
> 
> 
>> +	config = (struct rmnet_phys_ep_conf_s *)
>> +		rcu_dereference(skb->dev->rx_handler_data);
>> +
>> +	if (!config) {
> 
> Cannot happen. Please remove this.
> 
>> +	.ndo_set_mac_address = 0,
>> +	.ndo_validate_addr = 0,
> 
> These are NULL by default. No need to init.
> 
>> +	if ((id < 0) || (id >= RMNET_MAX_VND) || !rmnet_devices[id])
> 
> Unneeded inner "()"s. I see you have it on multiple places.
> 
>> +
>> +	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
> 
> The typecast is not needed since netdev_priv is void*. You have it all
> over the code.
> 
>> +#define ETH_P_MAP	0xDA1A		/* Multiplexing and Aggregation Protocol
>> +					 *  NOT AN OFFICIALLY REGISTERED ID ]
> 
> Please push this and ARPHRD_RAWIP as separate patches, to increase the
> visibility.
> 
>> +enum {
>> +	IFLA_RMNET_UNSPEC,
>> +	IFLA_RMNET_MUX_ID,
>> +	__IFLA_RMNET_MAX,
>> +};
> 
> This belongs to include/uapi/linux/if_link.h
> Please see IFLA_BOND_* as example
>> +#define RMNET_INIT_OK     0
>> +#define RMNET_INIT_ERROR  1
> 
> Please use common error codes (0/-ENOMEM/-EINVAL/...)
> 
>> +	static u32 rmnet_mod_mask = X
> 
> Don't use this custom helpers. Use existing loggign facilities.
> 
>> +			pr_notice("[RMNET:LOW] %s(): " fmt "\n", __func__, \
>> +				##__VA_ARGS__); \
>> +			} while (0)
> 
> These look scarry. Please use netdev_err, dev_err and others instead.
> 
>> +unsigned int rmnet_log_module_mask;
>> +module_param(rmnet_log_module_mask, uint, 0644);
>> +MODULE_PARM_DESC(rmnet_log_module_mask, "Logging module mask");
> 
> No module options please.
> 
>> +	config = (struct rmnet_phys_ep_conf_s *)
>> +		rcu_dereference(skb->dev->rx_handler_data);
> 
> This is certainly a misuse of dev->rx_handler_data. Dev private of a
> function arg to carry the pointer around.
> 
>> +struct net_device *rmnet_devices[RMNET_MAX_VND];
> 
> Avoid this global variable.
> 

Hi Jiri

I'll make these changes.

>> +	if (!data[IFLA_RMNET_MUX_ID])
>> +		return -EINVAL;
>> +
>> +	mux_id = nla_get_u16(data[IFLA_VLAN_ID]);
> 
> This is a bug I believe...             ^^^^^^^
> I'm pretty sure that you did not test this code.
> 

Since both IFLA_VLAN_ID and IFLA_RMNET_MUX_ID are 1 from enum, this 
actually
works. I was using VLAN as a reference, so I missed to change this to
IFLA_RMNET_MUX_ID.

> 
>> +				rmnet_kfree_skb
>> +				(skb,
>> +				 RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD);
> 
> very odd formatting. Please fix.
> 
Checkpatch complains if it is over 80 chars in a line, so I had to do 
this.
I'll change to a single line.

--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
Forum,
a Linux Foundation Collaborative Project
Stephen Hemminger April 14, 2017, 9:59 p.m. UTC | #4
On Fri, 14 Apr 2017 15:57:22 -0600
Subash Abhinov Kasiviswanathan <subashab@codeaurora.org> wrote:

> >> +				rmnet_kfree_skb
> >> +				(skb,
> >> +				 RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD);  
> > 
> > very odd formatting. Please fix.
> >   
> Checkpatch complains if it is over 80 chars in a line, so I had to do 
> this.
> I'll change to a single line.

Either ignore checkpatch, it is only a dumb script; or better yet refactor
the code so it isn't indented so much.
Subash Abhinov Kasiviswanathan April 14, 2017, 11:02 p.m. UTC | #5
>> +https://source.codeaurora.org/quic/la/platform/vendor/qcom-opensource/\
>> +dataservices/tree/rmnetctl
> 
> Don't split URL better to have long line.
>> diff --git a/drivers/net/rmnet/Kconfig b/drivers/net/rmnet/Kconfig
> 
> Since this is Qualcomm and Ethernet specific, maybe better to put
> in drivers/net/ethernet/qualcom/rmnet
> 
>> +config RMNET_DEBUG
> 
> Please use network device standard debug mechanism.
> netif_msg_XXX
> 
>> +		buffloc += snprintf(&buffer[buffloc], sizeof(buffer) - buffloc,
>> +					" %02x", skb->data[i]);
> 
> If you really have to do this. Use hex_dump_bytes API.
> 
>> +	skb->mac_header = 0;
>> +	skb->mac_len = 0;
>> +}
> 
> Why not use sbk_set_mac_header(skb, 0)?
> 
>> +static inline struct rmnet_phys_ep_conf_s *_rmnet_get_phys_ep_config
>> +						(struct net_device *dev)
> awkward line break.
> dev could be const
> 
>> +	config = (struct rmnet_phys_ep_conf_s *)
>> +		  rcu_dereference(dev->rx_handler_data);
> 
> Please don't directly reference rx_handler. There is already functions
> like netdev_is_rx_handler_busy() to abstract that API.
> 
>> + * @epconfig:    endpoint configuration structure to set
>> + */
> 
> You are using docbook format here, but this is not a docbook comment.
> ie.
> /**
>  * function - This is a docbook comment
>  * @dev: this is a param
>  */
> 
> Plus these are static functions so there is no point in documentating
> internal API with docbook.
> 
>> +	ASSERT_RTNL();
>> +
>> +	if (!dev || config_id < RMNET_LOCAL_LOGICAL_ENDPOINT ||
>> +	    config_id >= RMNET_MAX_LOGICAL_EP)
>> +		return -EINVAL;
> 
> For internal API's you should validate parmeters at the external
> entry point not in each call. Otherwise you have a multitude of
> impossible error checks and dead code paths.
> 
>> +	.next = 0,
>> +	.priority = 0
>> +};
> Don't initialize fields that are not used or should be zero.
> 

Hi Stephen

I'll make these changes.

> 
> Could just be:
> 
> static inline int _rmnet_is_physical_endpoint_associated(const struct
> net_device *dev)
> {
> 	rx_handler_func_t *rx_handler
> 		= rcu_dereference(dev->rx_handler);
> 
> 	return rx_handler == rmet_rx_handler;
> }
> 
> But standard practice is to use ndo_ops to identify self in network 
> drivers.
> I.e
> 	return dev->netdev_ops == &rmnet_device_ops;
> 

The netdevice which is associated is the physical (real_dev). This 
device
can vary based on hardware and will have its own netdev_ops associated 
with it.
rmnet_device_ops applies to the rmnet devices only. I'll use the first
option you have suggested.

>> +	if (!data[IFLA_RMNET_MUX_ID])
>> +		return -EINVAL;
> 
> So you are inventing private link netlink attributes.
> Why? Why can't you use device switch, bridge, or other master/slave 
> model.
> 

I would like to eventually add more configuration options for the
ingress & egress data formats as well as the endpoint configuration
(VND mode vs BRIDGE mode). I was able to re-use IFLA_VLAN_ID for
IFLA_RMNET_MUX_ID and test but it wasn't sufficient for the additional
parameters. I'll check the bridge attributes and try to reuse it.

--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
Forum,
a Linux Foundation Collaborative Project
Subash Abhinov Kasiviswanathan Aug. 14, 2017, 11:52 p.m. UTC | #6
>> + */
>> +void rmnet_egress_handler(struct sk_buff *skb,
>> +			  struct rmnet_logical_ep_conf_s *ep)
>> +{
>> +	struct rmnet_phys_ep_conf_s *config;
>> +	struct net_device *orig_dev;
>> +	int rc;
>> +
>> +	orig_dev = skb->dev;
>> +	skb->dev = ep->egress_dev;
>> +
>> +	config = (struct rmnet_phys_ep_conf_s *)
>> +		rcu_dereference(skb->dev->rx_handler_data);
> 
> This is certainly a misuse of dev->rx_handler_data. Dev private of a
> function arg to carry the pointer around.
> 

Hi Jiri

Sorry for the delay in posting a new series.
I have an additional query regarding this comment.

This dev (from skb->dev->rx_handler_data) corresponds to the real_dev to 
which
the rmnet devices are attached to. I had earlier setup a rx_handler on 
this
real_dev netdevice in rmnet_associate_network_device(). Would it still 
be
incorrect to use rx_handler_data of real_dev to have rmnet specific 
config
information?

Bridge is similarly storing the bridge information on the real_dev
rx_handler_data and retrieving it through br_port_get_rcu(). I am using 
that
as a reference.

--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora 
Forum,
a Linux Foundation Collaborative Project
diff mbox

Patch

diff --git a/Documentation/networking/rmnet.txt b/Documentation/networking/rmnet.txt
new file mode 100644
index 0000000..58d3ea2
--- /dev/null
+++ b/Documentation/networking/rmnet.txt
@@ -0,0 +1,83 @@ 
+1. Introduction
+
+rmnet driver is used for supporting the Multiplexing and aggregation
+Protocol (MAP). This protocol is used by all recent chipsets using Qualcomm
+Technologies, Inc. modems.
+
+This driver can be used to register onto any physical network device in
+IP mode. Physical transports include USB, HSIC, PCIe and IP accelerator.
+
+Multiplexing allows for creation of logical netdevices (rmnet devices) to
+handle multiple private data networks (PDN) like a default internet, tethering,
+multimedia messaging service (MMS) or IP media subsystem (IMS). Hardware sends
+packets with MAP headers to rmnet. Based on the multiplexer id, rmnet
+routes to the appropriate PDN after removing the MAP header.
+
+Aggregation is required to achieve high data rates. This involves hardware
+sending aggregated bunch of MAP frames. rmnet driver will de-aggregate
+these MAP frames and send them to appropriate PDN's.
+
+2. Packet format
+
+a. MAP packet (data / control)
+
+MAP header has the same endianness of the IP packet.
+
+Packet format -
+
+Bit             0             1           2-7      8 - 15           16 - 31
+Function   Command / Data   Reserved     Pad   Multiplexer ID    Payload length
+Bit            32 - x
+Function     Raw  Bytes
+
+Command (1)/ Data (0) bit value is to indicate if the packet is a MAP command
+or data packet. Control packet is used for transport level flow control. Data
+packets are standard IP packets.
+
+Reserved bits are usually zeroed out and to be ignored by receiver.
+
+Padding is number of bytes to be added for 4 byte alignment if required by
+hardware.
+
+Multiplexer ID is to indicate the PDN on which data has to be sent.
+
+Payload length includes the padding length but does not include MAP header
+length.
+
+b. MAP packet (command specific)
+
+Bit             0             1           2-7      8 - 15           16 - 31
+Function   Command         Reserved     Pad   Multiplexer ID    Payload length
+Bit          32 - 39        40 - 45    46 - 47       48 - 63
+Function   Command name    Reserved   Command Type   Reserved
+Bit          64 - 95
+Function   Transaction ID
+Bit          96 - 127
+Function   Command data
+
+Command 1 indicates disabling flow while 2 is enabling flow
+
+Command types -
+0 for MAP command request
+1 is to acknowledge the receipt of a command
+2 is for unsupported commands
+3 is for error during processing of commands
+
+c. Aggregation
+
+Aggregation is multiple MAP packets (can be data or command) delivered to
+rmnet in a single linear skb. rmnet will process the individual
+packets and either ACK the MAP command or deliver the IP packet to the
+network stack as needed
+
+MAP header|IP Packet|Optional padding|MAP header|IP Packet|Optional padding....
+MAP header|IP Packet|Optional padding|MAP header|Command Packet|Optional pad...
+
+3. Userspace configuration
+
+rmnet userspace configuration is done through netlink library librmnetctl
+and command line utility rmnetcli. Utility is hosted in codeaurora forum git.
+The driver uses rtnl_link_ops for communication.
+
+https://source.codeaurora.org/quic/la/platform/vendor/qcom-opensource/\
+dataservices/tree/rmnetctl
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 100fbdc..c4ccd6d 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -477,4 +477,6 @@  config FUJITSU_ES
 
 source "drivers/net/hyperv/Kconfig"
 
+source "drivers/net/rmnet/Kconfig"
+
 endif # NETDEVICES
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 98ed4d9..29b3945 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -74,3 +74,4 @@  obj-$(CONFIG_HYPERV_NET) += hyperv/
 obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o
 
 obj-$(CONFIG_FUJITSU_ES) += fjes/
+obj-$(CONFIG_RMNET) += rmnet/
diff --git a/drivers/net/rmnet/Kconfig b/drivers/net/rmnet/Kconfig
new file mode 100644
index 0000000..63cd477
--- /dev/null
+++ b/drivers/net/rmnet/Kconfig
@@ -0,0 +1,23 @@ 
+#
+# RMNET MAP driver
+#
+
+menuconfig RMNET
+	depends on NETDEVICES
+	bool "RmNet MAP driver"
+	default n
+	---help---
+	  If you say Y here, then the rmnet module will be statically
+	  compiled into the kernel. The rmnet module provides MAP
+	  functionality for embedded and bridged traffic.
+if RMNET
+
+config RMNET_DEBUG
+	bool "RmNet Debug Logging"
+	default n
+	---help---
+	  Say Y here if you want RmNet to be able to log packets in main
+	  system log. This should not be enabled on production builds as it can
+	  impact system performance. Note that simply enabling it here will not
+	  enable the logging; it must be enabled at run-time as well.
+endif # RMNET
diff --git a/drivers/net/rmnet/Makefile b/drivers/net/rmnet/Makefile
new file mode 100644
index 0000000..2b6c9cf
--- /dev/null
+++ b/drivers/net/rmnet/Makefile
@@ -0,0 +1,14 @@ 
+#
+# Makefile for the RMNET module
+#
+
+rmnet-y		 := rmnet_main.o
+rmnet-y		 += rmnet_config.o
+rmnet-y		 += rmnet_vnd.o
+rmnet-y		 += rmnet_handlers.o
+rmnet-y		 += rmnet_map_data.o
+rmnet-y		 += rmnet_map_command.o
+rmnet-y		 += rmnet_stats.o
+obj-$(CONFIG_RMNET) += rmnet.o
+
+CFLAGS_rmnet_main.o := -I$(src)
diff --git a/drivers/net/rmnet/rmnet_config.c b/drivers/net/rmnet/rmnet_config.c
new file mode 100644
index 0000000..a4bc76b
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_config.c
@@ -0,0 +1,592 @@ 
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * RMNET configuration engine
+ *
+ */
+
+#include <net/sock.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/rmnet.h>
+#include "rmnet_config.h"
+#include "rmnet_handlers.h"
+#include "rmnet_vnd.h"
+#include "rmnet_private.h"
+
+RMNET_LOG_MODULE(RMNET_LOGMASK_CONFIG);
+
+/* Local Definitions and Declarations */
+#define RMNET_LOCAL_LOGICAL_ENDPOINT -1
+
+/* _rmnet_is_physical_endpoint_associated() - Determines if device is associated
+ * @dev:      Device to get check
+ *
+ * Compares device rx_handler callback pointer against known function
+ */
+static inline int _rmnet_is_physical_endpoint_associated(struct net_device *dev)
+{
+	rx_handler_func_t *rx_handler;
+
+	rx_handler = rcu_dereference(dev->rx_handler);
+
+	if (rx_handler == rmnet_rx_handler)
+		return 1;
+	else
+		return 0;
+}
+
+/* _rmnet_get_phys_ep_config() - Get physical ep config for an associated device
+ * @dev:      Device to get endpoint configuration from
+ */
+static inline struct rmnet_phys_ep_conf_s *_rmnet_get_phys_ep_config
+						(struct net_device *dev)
+{
+	if (_rmnet_is_physical_endpoint_associated(dev))
+		return (struct rmnet_phys_ep_conf_s *)
+			rcu_dereference(dev->rx_handler_data);
+	else
+		return 0;
+}
+
+struct rmnet_free_vnd_work {
+	struct work_struct work;
+	int vnd_id[RMNET_MAX_VND];
+	int count;
+};
+
+/* _rmnet_get_logical_ep() - Gets the logical end point configuration
+ * structure for a network device
+ * @dev:             Device to get endpoint configuration from
+ * @config_id:       Logical endpoint id on device
+ * Retrieves the logical_endpoint_config structure.
+ */
+static struct rmnet_logical_ep_conf_s *_rmnet_get_logical_ep
+	(struct net_device *dev, int config_id)
+{
+	struct rmnet_phys_ep_conf_s *config;
+	struct rmnet_logical_ep_conf_s *epconfig_l;
+
+	if (rmnet_vnd_is_vnd(dev)) {
+		epconfig_l = rmnet_vnd_get_le_config(dev);
+	} else {
+		config = _rmnet_get_phys_ep_config(dev);
+
+		if (!config)
+			return NULL;
+
+		if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT)
+			epconfig_l = &config->local_ep;
+		else
+			epconfig_l = &config->muxed_ep[config_id];
+	}
+
+	return epconfig_l;
+}
+
+/* rmnet_unassociate_network_device() - Unassociate network device
+ * @dev:      Device to unassociate
+ *
+ * Frees all structures generate for device. Unregisters rx_handler
+ */
+static int rmnet_unassociate_network_device(struct net_device *dev)
+{
+	struct rmnet_phys_ep_conf_s *config;
+	int config_id = RMNET_LOCAL_LOGICAL_ENDPOINT;
+	struct rmnet_logical_ep_conf_s *epconfig_l;
+
+	ASSERT_RTNL();
+
+	LOGL("(%s);", dev->name);
+
+	if (!dev || !_rmnet_is_physical_endpoint_associated(dev))
+		return -EINVAL;
+
+	for (; config_id < RMNET_MAX_LOGICAL_EP; config_id++) {
+		epconfig_l = _rmnet_get_logical_ep(dev, config_id);
+		if (epconfig_l && epconfig_l->refcount)
+			return -EINVAL;
+	}
+
+	config = (struct rmnet_phys_ep_conf_s *)
+		  rcu_dereference(dev->rx_handler_data);
+
+	if (!config)
+		return -EINVAL;
+
+	kfree(config);
+
+	netdev_rx_handler_unregister(dev);
+
+	dev_put(dev);
+	return 0;
+}
+
+/* rmnet_set_ingress_data_format() - Set ingress data format on network device
+ * @dev:                 Device to ingress data format on
+ * @egress_data_format:  32-bit unsigned bitmask of ingress format
+ *
+ * Network device must already have association with RmNet Data driver
+ */
+static int rmnet_set_ingress_data_format(struct net_device *dev,
+					 u32 ingress_data_format)
+{
+	struct rmnet_phys_ep_conf_s *config;
+
+	ASSERT_RTNL();
+
+	LOGL("(%s,0x%08X);", dev->name, ingress_data_format);
+
+	if (!dev)
+		return -EINVAL;
+
+	config = _rmnet_get_phys_ep_config(dev);
+	if (!config)
+		return -EINVAL;
+
+	config->ingress_data_format = ingress_data_format;
+
+	return 0;
+}
+
+/* rmnet_set_egress_data_format() - Set egress data format on network device
+ * @dev:                 Device to egress data format on
+ * @egress_data_format:  32-bit unsigned bitmask of egress format
+ *
+ * Network device must already have association with RmNet Data driver
+ */
+static int rmnet_set_egress_data_format(struct net_device *dev,
+					u32 egress_data_format,
+					u16 agg_size,
+					u16 agg_count)
+{
+	struct rmnet_phys_ep_conf_s *config;
+
+	ASSERT_RTNL();
+
+	LOGL("(%s,0x%08X, %d, %d);",
+	     dev->name, egress_data_format, agg_size, agg_count);
+
+	if (!dev)
+		return -EINVAL;
+
+	config = _rmnet_get_phys_ep_config(dev);
+	if (!config)
+		return -EINVAL;
+
+	config->egress_data_format = egress_data_format;
+
+	return 0;
+}
+
+/* rmnet_associate_network_device() - Associate network device
+ * @dev:      Device to register with RmNet data
+ *
+ * Typically used on physical network devices. Registers RX handler and private
+ * metadata structures.
+ */
+static int rmnet_associate_network_device(struct net_device *dev)
+{
+	struct rmnet_phys_ep_conf_s *config;
+	int rc;
+
+	ASSERT_RTNL();
+
+	LOGL("(%s);\n", dev->name);
+
+	if (!dev || _rmnet_is_physical_endpoint_associated(dev) ||
+	    rmnet_vnd_is_vnd(dev)) {
+		LOGM("cannot register with this dev");
+		return -EINVAL;
+	}
+
+	config = kmalloc(sizeof(*config), GFP_ATOMIC);
+	if (!config)
+		return -ENOMEM;
+
+	memset(config, 0, sizeof(struct rmnet_phys_ep_conf_s));
+	config->dev = dev;
+
+	rc = netdev_rx_handler_register(dev, rmnet_rx_handler, config);
+
+	if (rc) {
+		LOGM("netdev_rx_handler_register returns %d", rc);
+		kfree(config);
+		return -EBUSY;
+	}
+
+	dev_hold(dev);
+	return 0;
+}
+
+/* __rmnet_set_logical_endpoint_config() - Set logical endpoing config on device
+ * @dev:         Device to set endpoint configuration on
+ * @config_id:   logical endpoint id on device
+ * @epconfig:    endpoint configuration structure to set
+ */
+static int __rmnet_set_logical_endpoint_config
+	(struct net_device *dev,
+	 int config_id,
+	 struct rmnet_logical_ep_conf_s *epconfig)
+{
+	struct rmnet_logical_ep_conf_s *epconfig_l;
+
+	ASSERT_RTNL();
+
+	if (!dev || config_id < RMNET_LOCAL_LOGICAL_ENDPOINT ||
+	    config_id >= RMNET_MAX_LOGICAL_EP)
+		return -EINVAL;
+
+	epconfig_l = _rmnet_get_logical_ep(dev, config_id);
+
+	if (!epconfig_l || epconfig_l->refcount)
+		return -EINVAL;
+
+	memcpy(epconfig_l, epconfig, sizeof(struct rmnet_logical_ep_conf_s));
+	if (config_id == RMNET_LOCAL_LOGICAL_ENDPOINT)
+		epconfig_l->mux_id = 0;
+	else
+		epconfig_l->mux_id = config_id;
+
+	/* Explicitly hold a reference to the egress device */
+	dev_hold(epconfig_l->egress_dev);
+	return 0;
+}
+
+/* _rmnet_unset_logical_endpoint_config() - Un-set the logical endpoing config
+ * on device
+ * @dev:         Device to set endpoint configuration on
+ * @config_id:   logical endpoint id on device
+ */
+static int _rmnet_unset_logical_endpoint_config(struct net_device *dev,
+						int config_id)
+{
+	struct rmnet_logical_ep_conf_s *epconfig_l = 0;
+
+	ASSERT_RTNL();
+
+	if (!dev || config_id < RMNET_LOCAL_LOGICAL_ENDPOINT ||
+	    config_id >= RMNET_MAX_LOGICAL_EP)
+		return -EINVAL;
+
+	epconfig_l = _rmnet_get_logical_ep(dev, config_id);
+
+	if (!epconfig_l || !epconfig_l->refcount)
+		return -EINVAL;
+
+	/* Explicitly release the reference from the egress device */
+	dev_put(epconfig_l->egress_dev);
+	memset(epconfig_l, 0, sizeof(struct rmnet_logical_ep_conf_s));
+
+	return 0;
+}
+
+/* rmnet_set_logical_endpoint_config() - Set logical endpoint config on a device
+ * @dev:            Device to set endpoint configuration on
+ * @config_id:      logical endpoint id on device
+ * @rmnet_mode:     endpoint mode. Values from: rmnet_config_endpoint_modes_e
+ * @egress_device:  device node to forward packet to once done processing in
+ *                  ingress/egress handlers
+ *
+ * Creates a logical_endpoint_config structure and fills in the information from
+ * function arguments. Calls __rmnet_set_logical_endpoint_config() to finish
+ * configuration. Network device must already have association with RmNet Data
+ * driver
+ */
+static int rmnet_set_logical_endpoint_config(struct net_device *dev,
+					     int config_id,
+					     u8 rmnet_mode,
+					     struct net_device *egress_dev)
+{
+	struct rmnet_logical_ep_conf_s epconfig;
+
+	LOGL("(%s, %d, %d, %s);",
+	     dev->name, config_id, rmnet_mode, egress_dev->name);
+
+	if (!egress_dev ||
+	    ((!_rmnet_is_physical_endpoint_associated(egress_dev)) &&
+	    (!rmnet_vnd_is_vnd(egress_dev)))) {
+		return -EINVAL;
+	}
+
+	memset(&epconfig, 0, sizeof(struct rmnet_logical_ep_conf_s));
+	epconfig.refcount = 1;
+	epconfig.rmnet_mode = rmnet_mode;
+	epconfig.egress_dev = egress_dev;
+
+	return __rmnet_set_logical_endpoint_config(dev, config_id, &epconfig);
+}
+
+/* rmnet_unset_logical_endpoint_config() - Un-set logical endpoing configuration
+ * on a device
+ * @dev:            Device to set endpoint configuration on
+ * @config_id:      logical endpoint id on device
+ *
+ * Retrieves the logical_endpoint_config structure and frees the egress device.
+ * Network device must already have association with RmNet Data driver
+ */
+static int rmnet_unset_logical_endpoint_config(struct net_device *dev,
+					       int config_id)
+{
+	LOGL("(%s, %d);", dev->name, config_id);
+
+	if (!dev || ((!_rmnet_is_physical_endpoint_associated(dev)) &&
+		     (!rmnet_vnd_is_vnd(dev)))) {
+		return -EINVAL;
+	}
+
+	return _rmnet_unset_logical_endpoint_config(dev, config_id);
+}
+
+/* rmnet_free_vnd() - Free virtual network device node
+ * @id:       RmNet virtual device node id
+ */
+int rmnet_free_vnd(int id)
+{
+	LOGL("(%d);", id);
+	return rmnet_vnd_free_dev(id);
+}
+
+static void _rmnet_free_vnd_later(struct work_struct *work)
+{
+	int i;
+	struct rmnet_free_vnd_work *fwork;
+
+	fwork = container_of(work, struct rmnet_free_vnd_work, work);
+
+	for (i = 0; i < fwork->count; i++)
+		rmnet_free_vnd(fwork->vnd_id[i]);
+	kfree(fwork);
+}
+
+/* rmnet_force_unassociate_device() - Force a device to unassociate
+ * @dev:       Device to unassociate
+ */
+static void rmnet_force_unassociate_device(struct net_device *dev)
+{
+	int i, j;
+	struct net_device *vndev;
+	struct rmnet_phys_ep_conf_s *config;
+	struct rmnet_logical_ep_conf_s *cfg;
+	struct rmnet_free_vnd_work *vnd_work;
+
+	ASSERT_RTNL();
+	if (!dev)
+		return;
+
+	if (!_rmnet_is_physical_endpoint_associated(dev)) {
+		LOGM("%s", "Called on unassociated device, skipping");
+		return;
+	}
+
+	vnd_work = kmalloc(sizeof(*vnd_work), GFP_KERNEL);
+	if (!vnd_work)
+		return;
+
+	INIT_WORK(&vnd_work->work, _rmnet_free_vnd_later);
+	vnd_work->count = 0;
+
+	/* Check the VNDs for offending mappings */
+	for (i = 0, j = 0; i < RMNET_MAX_VND &&
+	     j < RMNET_MAX_VND; i++) {
+		vndev = rmnet_vnd_get_by_id(i);
+		if (!vndev) {
+			LOGL("VND %d not in use; skipping", i);
+			continue;
+		}
+		cfg = rmnet_vnd_get_le_config(vndev);
+		if (!cfg) {
+			LOGD("Got NULL config from VND %d", i);
+			continue;
+		}
+		if (cfg->refcount && (cfg->egress_dev == dev)) {
+			/* Make sure the device is down before clearing any of
+			 * the mappings. Otherwise we could see a potential
+			 * race condition if packets are actively being
+			 * transmitted.
+			 */
+			dev_close(vndev);
+			rmnet_unset_logical_endpoint_config
+				(vndev, RMNET_LOCAL_LOGICAL_ENDPOINT);
+			vnd_work->vnd_id[j] = i;
+			j++;
+		}
+	}
+	if (j > 0) {
+		vnd_work->count = j;
+		schedule_work(&vnd_work->work);
+	} else {
+		kfree(vnd_work);
+	}
+
+	config = _rmnet_get_phys_ep_config(dev);
+
+	if (config) {
+		cfg = &config->local_ep;
+
+		if (cfg && cfg->refcount)
+			rmnet_unset_logical_endpoint_config
+			(cfg->egress_dev, RMNET_LOCAL_LOGICAL_ENDPOINT);
+	}
+
+	/* Clear the mappings on the phys ep */
+	rmnet_unset_logical_endpoint_config(dev, RMNET_LOCAL_LOGICAL_ENDPOINT);
+	for (i = 0; i < RMNET_MAX_LOGICAL_EP; i++)
+		rmnet_unset_logical_endpoint_config(dev, i);
+	rmnet_unassociate_network_device(dev);
+}
+
+/* rmnet_config_notify_cb() - Callback for netdevice notifier chain
+ * @nb:       Notifier block data
+ * @event:    Netdevice notifier event ID
+ * @data:     Contains a net device for which we are getting notified
+ */
+static int rmnet_config_notify_cb(struct notifier_block *nb,
+				  unsigned long event, void *data)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(data);
+
+	if (!dev)
+		return NOTIFY_DONE;
+
+	switch (event) {
+	case NETDEV_UNREGISTER_FINAL:
+	case NETDEV_UNREGISTER:
+		LOGM("Kernel is trying to unregister %s", dev->name);
+		rmnet_force_unassociate_device(dev);
+		break;
+
+	default:
+		LOGD("Unhandeled event [%lu]", event);
+		break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block rmnet_dev_notifier = {
+	.notifier_call = rmnet_config_notify_cb,
+	.next = 0,
+	.priority = 0
+};
+
+static int rmnet_newlink(struct net *src_net, struct net_device *dev,
+			 struct nlattr *tb[], struct nlattr *data[])
+{
+	int ingress_format = RMNET_INGRESS_FORMAT_DEMUXING |
+			     RMNET_INGRESS_FORMAT_DEAGGREGATION |
+			     RMNET_INGRESS_FORMAT_MAP;
+	int egress_format = RMNET_EGRESS_FORMAT_MUXING |
+			    RMNET_EGRESS_FORMAT_MAP;
+	struct net_device *real_dev;
+	int mode = RMNET_EPMODE_VND;
+	u16 mux_id;
+
+	real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
+	if (!real_dev)
+		return -ENODEV;
+
+	if (!data[IFLA_RMNET_MUX_ID])
+		return -EINVAL;
+
+	mux_id = nla_get_u16(data[IFLA_VLAN_ID]);
+	if (rmnet_vnd_newlink(mux_id, dev))
+		return -EINVAL;
+
+	rmnet_associate_network_device(real_dev);
+	rmnet_set_egress_data_format(real_dev, egress_format, 0, 0);
+	rmnet_set_ingress_data_format(real_dev, ingress_format);
+	rmnet_set_logical_endpoint_config(real_dev, mux_id, mode, dev);
+	rmnet_set_logical_endpoint_config(dev, mux_id, mode, real_dev);
+	return 0;
+}
+
+static void rmnet_delink(struct net_device *dev, struct list_head *head)
+{
+	struct rmnet_logical_ep_conf_s *cfg;
+	int mux_id;
+
+	mux_id = rmnet_vnd_is_vnd(dev);
+	if (!mux_id)
+		return;
+
+/* rmnet_vnd_is_vnd() gives mux_id + 1, so subtract 1 to get the correct mux_id
+ */
+	mux_id--;
+	cfg = rmnet_vnd_get_le_config(dev);
+
+	if (cfg && cfg->refcount) {
+		_rmnet_unset_logical_endpoint_config(cfg->egress_dev, mux_id);
+		_rmnet_unset_logical_endpoint_config(dev, mux_id);
+		rmnet_vnd_remove_ref_dev(mux_id);
+		rmnet_unassociate_network_device(cfg->egress_dev);
+	}
+
+	unregister_netdevice_queue(dev, head);
+}
+
+static int rmnet_rtnl_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+	u16 mux_id;
+
+	if (!data || !data[IFLA_RMNET_MUX_ID])
+		return -EINVAL;
+
+	mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
+	if (!mux_id || mux_id > (RMNET_MAX_LOGICAL_EP - 1))
+		return -ERANGE;
+
+	return 0;
+}
+
+static size_t rmnet_get_size(const struct net_device *dev)
+{
+	return nla_total_size(2); /* IFLA_RMNET_MUX_ID */
+}
+
+struct rtnl_link_ops rmnet_link_ops __read_mostly = {
+	.kind		= "rmnet",
+	.maxtype	= __IFLA_RMNET_MAX,
+	.priv_size	= sizeof(struct rmnet_vnd_private_s),
+	.setup		= rmnet_vnd_setup,
+	.validate	= rmnet_rtnl_validate,
+	.newlink	= rmnet_newlink,
+	.dellink	= rmnet_delink,
+	.get_size	= rmnet_get_size,
+};
+
+int rmnet_config_init(void)
+{
+	int rc;
+
+	rc = register_netdevice_notifier(&rmnet_dev_notifier);
+	if (rc != 0) {
+		LOGE("Failed to register device notifier; rc=%d", rc);
+		return rc;
+	}
+
+	rc = rtnl_link_register(&rmnet_link_ops);
+	if (rc != 0) {
+		unregister_netdevice_notifier(&rmnet_dev_notifier);
+		LOGE("Failed to register netlink handler; rc=%d", rc);
+		return rc;
+	}
+	return rc;
+}
+
+void rmnet_config_exit(void)
+{
+	unregister_netdevice_notifier(&rmnet_dev_notifier);
+	rtnl_link_unregister(&rmnet_link_ops);
+}
diff --git a/drivers/net/rmnet/rmnet_config.h b/drivers/net/rmnet/rmnet_config.h
new file mode 100644
index 0000000..0ef58e8
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_config.h
@@ -0,0 +1,79 @@ 
+/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * RMNET Data configuration engine
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/time.h>
+#include <linux/skbuff.h>
+
+#ifndef _RMNET_CONFIG_H_
+#define _RMNET_CONFIG_H_
+
+#define RMNET_MAX_LOGICAL_EP 255
+
+/* struct rmnet_logical_ep_conf_s - Logical end-point configuration
+ *
+ * @refcount: Reference count for this endpoint. 0 signifies the endpoint is not
+ *            configured for use
+ * @rmnet_mode: Specifies how the traffic should be finally delivered. Possible
+ *            options are available in enum rmnet_config_endpoint_modes_e
+ * @mux_id: Virtual channel ID used by MAP protocol
+ * @egress_dev: Next device to deliver the packet to. Exact usage of this
+ *            parmeter depends on the rmnet_mode
+ */
+struct rmnet_logical_ep_conf_s {
+	u8 refcount;
+	u8 rmnet_mode;
+	u8 mux_id;
+	struct timespec flush_time;
+	struct net_device *egress_dev;
+};
+
+/* struct rmnet_phys_ep_conf_s - Physical endpoint configuration
+ * One instance of this structure is instantiated for each net_device associated
+ * with rmnet.
+ *
+ * @dev: The device which is associated with rmnet. Corresponds to this
+ *       specific instance of rmnet_phys_ep_conf_s
+ * @local_ep: Default non-muxed endpoint. Used for non-MAP protocols/formats
+ * @muxed_ep: All multiplexed logical endpoints associated with this device
+ * @ingress_data_format: RMNET_INGRESS_FORMAT_* flags from rmnet.h
+ * @egress_data_format: RMNET_EGRESS_FORMAT_* flags from rmnet.h
+ *
+ * @egress_agg_size: Maximum size (bytes) of data which should be aggregated
+ * @egress_agg_count: Maximum count (packets) of data which should be aggregated
+ *                  Smaller of the two parameters above are chosen for
+ *                  aggregation
+ * @tail_spacing: Guaranteed padding (bytes) when de-aggregating ingress frames
+ * @agg_time: Wall clock time when aggregated frame was created
+ * @agg_last: Last time the aggregation routing was invoked
+ */
+struct rmnet_phys_ep_conf_s {
+	struct net_device *dev;
+	struct rmnet_logical_ep_conf_s local_ep;
+	struct rmnet_logical_ep_conf_s muxed_ep[RMNET_MAX_LOGICAL_EP];
+	u32 ingress_data_format;
+	u32 egress_data_format;
+};
+
+int rmnet_config_init(void);
+void rmnet_config_exit(void);
+int rmnet_free_vnd(int id);
+
+extern struct rtnl_link_ops rmnet_link_ops;
+
+struct rmnet_vnd_private_s {
+	struct rmnet_logical_ep_conf_s local_ep;
+};
+#endif /* _RMNET_CONFIG_H_ */
diff --git a/drivers/net/rmnet/rmnet_handlers.c b/drivers/net/rmnet/rmnet_handlers.c
new file mode 100644
index 0000000..bf8b3bb
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_handlers.c
@@ -0,0 +1,517 @@ 
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * RMNET Data ingress/egress handler
+ *
+ */
+
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+#include <linux/rmnet.h>
+#include <linux/netdev_features.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include "rmnet_private.h"
+#include "rmnet_config.h"
+#include "rmnet_vnd.h"
+#include "rmnet_map.h"
+#include "rmnet_stats.h"
+#include "rmnet_handlers.h"
+
+RMNET_LOG_MODULE(RMNET_LOGMASK_HANDLER);
+
+#ifdef CONFIG_RMNET_DEBUG
+unsigned int dump_pkt_rx;
+module_param(dump_pkt_rx, uint, 0644);
+MODULE_PARM_DESC(dump_pkt_rx, "Dump packets entering ingress handler");
+
+unsigned int dump_pkt_tx;
+module_param(dump_pkt_tx, uint, 0644);
+MODULE_PARM_DESC(dump_pkt_tx, "Dump packets exiting egress handler");
+#endif /* CONFIG_RMNET_DEBUG */
+
+#define RMNET_IP_VERSION_4 0x40
+#define RMNET_IP_VERSION_6 0x60
+
+/* Helper Functions */
+
+/* __rmnet_set_skb_proto() - Set skb->protocol field
+ * @skb:      packet being modified
+ *
+ * Peek at the first byte of the packet and set the protocol. There is not
+ * good way to determine if a packet has a MAP header. As of writing this,
+ * the reserved bit in the MAP frame will prevent it from overlapping with
+ * IPv4/IPv6 frames. This could change in the future!
+ */
+static inline void __rmnet_set_skb_proto(struct sk_buff *skb)
+{
+	switch (skb->data[0] & 0xF0) {
+	case RMNET_IP_VERSION_4:
+		skb->protocol = htons(ETH_P_IP);
+		break;
+	case RMNET_IP_VERSION_6:
+		skb->protocol = htons(ETH_P_IPV6);
+		break;
+	default:
+		skb->protocol = htons(ETH_P_MAP);
+		break;
+	}
+}
+
+#ifdef CONFIG_RMNET_DEBUG
+/* rmnet_print_packet() - Print packet / diagnostics
+ * @skb:      Packet to print
+ * @printlen: Number of bytes to print
+ * @dev:      Name of interface
+ * @dir:      Character representing direction (e.g.. 'r' for receive)
+ *
+ * This function prints out raw bytes in an SKB. Use of this will have major
+ * performance impacts and may even trigger watchdog resets if too much is being
+ * printed. Hence, this should always be compiled out unless absolutely needed.
+ */
+void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir)
+{
+	char buffer[200];
+	unsigned int len, printlen;
+	int i, buffloc = 0;
+
+	switch (dir) {
+	case 'r':
+		printlen = dump_pkt_rx;
+		break;
+
+	case 't':
+		printlen = dump_pkt_tx;
+		break;
+
+	default:
+		printlen = 0;
+		break;
+	}
+
+	if (!printlen)
+		return;
+
+	pr_err("[%s][%c] - PKT skb->len=%d skb->head=%pK skb->data=%pK\n",
+	       dev, dir, skb->len, (void *)skb->head, (void *)skb->data);
+	pr_err("[%s][%c] - PKT skb->tail=%pK skb->end=%pK\n",
+	       dev, dir, skb_tail_pointer(skb), skb_end_pointer(skb));
+
+	if (skb->len > 0)
+		len = skb->len;
+	else
+		len = ((unsigned int)(uintptr_t)skb->end) -
+		      ((unsigned int)(uintptr_t)skb->data);
+
+	pr_err("[%s][%c] - PKT len: %d, printing first %d bytes\n",
+	       dev, dir, len, printlen);
+
+	memset(buffer, 0, sizeof(buffer));
+	for (i = 0; (i < printlen) && (i < len); i++) {
+		if ((i % 16) == 0) {
+			pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer);
+			memset(buffer, 0, sizeof(buffer));
+			buffloc = 0;
+			buffloc += snprintf(&buffer[buffloc],
+					sizeof(buffer) - buffloc, "%04X:",
+					i);
+		}
+
+		buffloc += snprintf(&buffer[buffloc], sizeof(buffer) - buffloc,
+					" %02x", skb->data[i]);
+	}
+	pr_err("[%s][%c] - PKT%s\n", dev, dir, buffer);
+}
+#else
+void rmnet_print_packet(const struct sk_buff *skb, const char *dev, char dir)
+{
+}
+#endif /* CONFIG_RMNET_DEBUG */
+
+/* Generic handler */
+
+/* rmnet_bridge_handler() - Bridge related functionality
+ */
+static rx_handler_result_t rmnet_bridge_handler
+	(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)
+{
+	if (!ep->egress_dev) {
+		LOGD("Missing egress device for packet arriving on %s",
+		     skb->dev->name);
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_BRDG_NO_EGRESS);
+	} else {
+		rmnet_egress_handler(skb, ep);
+	}
+
+	return RX_HANDLER_CONSUMED;
+}
+
+#ifdef NET_SKBUFF_DATA_USES_OFFSET
+static void rmnet_reset_mac_header(struct sk_buff *skb)
+{
+	skb->mac_header = 0;
+	skb->mac_len = 0;
+}
+#else
+static void rmnet_reset_mac_header(struct sk_buff *skb)
+{
+	skb->mac_header = skb->network_header;
+	skb->mac_len = 0;
+}
+#endif /*NET_SKBUFF_DATA_USES_OFFSET*/
+
+/* __rmnet_deliver_skb() - Deliver skb
+ *
+ * Determines where to deliver skb. Options are: consume by network stack,
+ * pass to bridge handler, or pass to virtual network device
+ */
+static rx_handler_result_t __rmnet_deliver_skb
+	(struct sk_buff *skb, struct rmnet_logical_ep_conf_s *ep)
+{
+	switch (ep->rmnet_mode) {
+	case RMNET_EPMODE_NONE:
+		return RX_HANDLER_PASS;
+
+	case RMNET_EPMODE_BRIDGE:
+		return rmnet_bridge_handler(skb, ep);
+
+	case RMNET_EPMODE_VND:
+		skb_reset_transport_header(skb);
+		skb_reset_network_header(skb);
+		switch (rmnet_vnd_rx_fixup(skb, skb->dev)) {
+		case RX_HANDLER_CONSUMED:
+			return RX_HANDLER_CONSUMED;
+
+		case RX_HANDLER_PASS:
+			skb->pkt_type = PACKET_HOST;
+			rmnet_reset_mac_header(skb);
+			netif_receive_skb(skb);
+			return RX_HANDLER_CONSUMED;
+		}
+		return RX_HANDLER_PASS;
+
+	default:
+		LOGD("Unknown ep mode %d", ep->rmnet_mode);
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_DELIVER_NO_EP);
+		return RX_HANDLER_CONSUMED;
+	}
+}
+
+/* rmnet_ingress_deliver_packet() - Ingress handler for raw IP and bridged
+ *                                  MAP packets.
+ * @skb:     Packet needing a destination.
+ * @config:  Physical end point configuration that the packet arrived on.
+ */
+static rx_handler_result_t rmnet_ingress_deliver_packet
+	(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config)
+{
+	if (!config) {
+		LOGD("%s", "NULL physical EP provided");
+		kfree_skb(skb);
+		return RX_HANDLER_CONSUMED;
+	}
+
+	if (!(config->local_ep.refcount)) {
+		LOGD("Packet on %s has no local endpoint configuration",
+		     skb->dev->name);
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_IPINGRESS_NO_EP);
+		return RX_HANDLER_CONSUMED;
+	}
+
+	skb->dev = config->local_ep.egress_dev;
+
+	return __rmnet_deliver_skb(skb, &config->local_ep);
+}
+
+/* MAP handler */
+
+/* _rmnet_map_ingress_handler() - Actual MAP ingress handler
+ * @skb:        Packet being received
+ * @config:     Physical endpoint configuration for the ingress device
+ *
+ * Most MAP ingress functions are processed here. Packets are processed
+ * individually; aggregated packets should use rmnet_map_ingress_handler()
+ */
+static rx_handler_result_t _rmnet_map_ingress_handler
+	(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config)
+{
+	struct rmnet_logical_ep_conf_s *ep;
+	u8 mux_id;
+	u16 len;
+
+	if (RMNET_MAP_GET_CD_BIT(skb)) {
+		if (config->ingress_data_format
+		    & RMNET_INGRESS_FORMAT_MAP_COMMANDS)
+			return rmnet_map_command(skb, config);
+
+		LOGM("MAP command packet on %s; %s", skb->dev->name,
+		     "Not configured for MAP commands");
+		rmnet_kfree_skb(skb,
+				RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPC);
+		return RX_HANDLER_CONSUMED;
+	}
+
+	mux_id = RMNET_MAP_GET_MUX_ID(skb);
+	len = RMNET_MAP_GET_LENGTH(skb) - RMNET_MAP_GET_PAD(skb);
+
+	if (mux_id >= RMNET_MAX_LOGICAL_EP) {
+		LOGD("Got packet on %s with bad mux id %d",
+		     skb->dev->name, mux_id);
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_BAD_MUX);
+			return RX_HANDLER_CONSUMED;
+	}
+
+	ep = &config->muxed_ep[mux_id];
+
+	if (!ep->refcount) {
+		LOGD("Packet on %s:%d; has no logical endpoint config",
+		     skb->dev->name, mux_id);
+
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_MUX_NO_EP);
+		return RX_HANDLER_CONSUMED;
+	}
+
+	if (config->ingress_data_format & RMNET_INGRESS_FORMAT_DEMUXING)
+		skb->dev = ep->egress_dev;
+
+	/* Subtract MAP header */
+	skb_pull(skb, sizeof(struct rmnet_map_header_s));
+	skb_trim(skb, len);
+	__rmnet_set_skb_proto(skb);
+	return __rmnet_deliver_skb(skb, ep);
+}
+
+/* rmnet_map_ingress_handler() - MAP ingress handler
+ * @skb:        Packet being received
+ * @config:     Physical endpoint configuration for the ingress device
+ *
+ * Called if and only if MAP is configured in the ingress device's ingress data
+ * format. Deaggregation is done here, actual MAP processing is done in
+ * _rmnet_map_ingress_handler().
+ */
+static rx_handler_result_t rmnet_map_ingress_handler
+	(struct sk_buff *skb, struct rmnet_phys_ep_conf_s *config)
+{
+	struct sk_buff *skbn;
+	int rc, co = 0;
+
+	if (config->ingress_data_format & RMNET_INGRESS_FORMAT_DEAGGREGATION) {
+		while ((skbn = rmnet_map_deaggregate(skb, config)) != NULL) {
+			_rmnet_map_ingress_handler(skbn, config);
+			co++;
+		}
+		LOGD("De-aggregated %d packets", co);
+		rmnet_stats_deagg_pkts(co);
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPINGRESS_AGGBUF);
+		rc = RX_HANDLER_CONSUMED;
+	} else {
+		rc = _rmnet_map_ingress_handler(skb, config);
+	}
+
+	return rc;
+}
+
+/* rmnet_map_egress_handler() - MAP egress handler
+ * @skb:        Packet being sent
+ * @config:     Physical endpoint configuration for the egress device
+ * @ep:         logical endpoint configuration of the packet originator
+ *              (e.g.. RmNet virtual network device)
+ * @orig_dev:   The originator vnd device
+ *
+ * Called if and only if MAP is configured in the egress device's egress data
+ * format. Will expand skb if there is insufficient headroom for MAP protocol.
+ * Note: headroomexpansion will incur a performance penalty.
+ */
+static int rmnet_map_egress_handler(struct sk_buff *skb,
+				    struct rmnet_phys_ep_conf_s *config,
+				    struct rmnet_logical_ep_conf_s *ep,
+				    struct net_device *orig_dev)
+{
+	int required_headroom, additional_header_length;
+	struct rmnet_map_header_s *map_header;
+
+	additional_header_length = 0;
+	required_headroom = sizeof(struct rmnet_map_header_s);
+
+	LOGD("headroom of %d bytes", required_headroom);
+
+	if (skb_headroom(skb) < required_headroom) {
+		if (pskb_expand_head(skb, required_headroom, 0, GFP_KERNEL)) {
+			LOGD("Failed to add headroom of %d bytes",
+			     required_headroom);
+			return RMNET_MAP_CONSUMED;
+		}
+	}
+
+	map_header = rmnet_map_add_map_header
+		(skb, additional_header_length, RMNET_MAP_NO_PAD_BYTES);
+	if (!map_header) {
+		LOGD("%s", "Failed to add MAP header to egress packet");
+		return RMNET_MAP_CONSUMED;
+	}
+
+	if (config->egress_data_format & RMNET_EGRESS_FORMAT_MUXING) {
+		if (ep->mux_id == 0xff)
+			map_header->mux_id = 0;
+		else
+			map_header->mux_id = ep->mux_id;
+	}
+
+	skb->protocol = htons(ETH_P_MAP);
+
+	return RMNET_MAP_SUCCESS;
+}
+
+/* Ingress / Egress Entry Points */
+
+/* rmnet_ingress_handler() - Ingress handler entry point
+ * @skb: Packet being received
+ *
+ * Processes packet as per ingress data format for receiving device. Logical
+ * endpoint is determined from packet inspection. Packet is then sent to the
+ * egress device listed in the logical endpoint configuration.
+ */
+rx_handler_result_t rmnet_ingress_handler(struct sk_buff *skb)
+{
+	struct rmnet_phys_ep_conf_s *config;
+	struct net_device *dev;
+	int rc;
+
+	if (!skb)
+		return RX_HANDLER_CONSUMED;
+
+	dev = skb->dev;
+	rmnet_print_packet(skb, dev->name, 'r');
+
+	config = (struct rmnet_phys_ep_conf_s *)
+		rcu_dereference(skb->dev->rx_handler_data);
+
+	if (!config) {
+		LOGD("%s is not associated with rmnet", skb->dev->name);
+		kfree_skb(skb);
+		return RX_HANDLER_CONSUMED;
+	}
+
+	/* Sometimes devices operate in ethernet mode even thouth there is no
+	 * ethernet header. This causes the skb->protocol to contain a bogus
+	 * value and the skb->data pointer to be off by 14 bytes. Fix it if
+	 * configured to do so
+	 */
+	if (config->ingress_data_format & RMNET_INGRESS_FIX_ETHERNET) {
+		skb_push(skb, RMNET_ETHERNET_HEADER_LENGTH);
+		__rmnet_set_skb_proto(skb);
+	}
+
+	if (config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP) {
+		rc = rmnet_map_ingress_handler(skb, config);
+	} else {
+		switch (ntohs(skb->protocol)) {
+		case ETH_P_MAP:
+			if (config->local_ep.rmnet_mode ==
+				RMNET_EPMODE_BRIDGE) {
+				rc = rmnet_ingress_deliver_packet(skb, config);
+			} else {
+				LOGD("MAP packet on %s; MAP not set",
+				     dev->name);
+				rmnet_kfree_skb
+				(skb,
+				 RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD);
+				rc = RX_HANDLER_CONSUMED;
+			}
+			break;
+
+		case ETH_P_ARP:
+		case ETH_P_IP:
+		case ETH_P_IPV6:
+			rc = rmnet_ingress_deliver_packet(skb, config);
+			break;
+
+		default:
+			LOGD("Unknown skb->proto 0x%04X\n",
+			     ntohs(skb->protocol) & 0xFFFF);
+			rc = RX_HANDLER_PASS;
+		}
+	}
+
+	return rc;
+}
+
+/* rmnet_rx_handler() - Rx handler callback registered with kernel
+ * @pskb: Packet to be processed by rx handler
+ *
+ * Standard kernel-expected footprint for rx handlers. Calls
+ * rmnet_ingress_handler with correctly formatted arguments
+ */
+rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb)
+{
+	return rmnet_ingress_handler(*pskb);
+}
+
+/* rmnet_egress_handler() - Egress handler entry point
+ * @skb:        packet to transmit
+ * @ep:         logical endpoint configuration of the packet originator
+ *              (e.g.. RmNet virtual network device)
+ *
+ * Modifies packet as per logical endpoint configuration and egress data format
+ * for egress device configured in logical endpoint. Packet is then transmitted
+ * on the egress device.
+ */
+void rmnet_egress_handler(struct sk_buff *skb,
+			  struct rmnet_logical_ep_conf_s *ep)
+{
+	struct rmnet_phys_ep_conf_s *config;
+	struct net_device *orig_dev;
+	int rc;
+
+	orig_dev = skb->dev;
+	skb->dev = ep->egress_dev;
+
+	config = (struct rmnet_phys_ep_conf_s *)
+		rcu_dereference(skb->dev->rx_handler_data);
+
+	if (!config) {
+		LOGD("%s is not associated with rmnet", skb->dev->name);
+		kfree_skb(skb);
+		return;
+	}
+
+	LOGD("Packet going out on %s with egress format 0x%08X",
+	     skb->dev->name, config->egress_data_format);
+
+	if (config->egress_data_format & RMNET_EGRESS_FORMAT_MAP) {
+		switch (rmnet_map_egress_handler(skb, config, ep, orig_dev)) {
+		case RMNET_MAP_CONSUMED:
+			LOGD("%s", "MAP process consumed packet");
+			return;
+
+		case RMNET_MAP_SUCCESS:
+			break;
+
+		default:
+			LOGD("MAP egress failed on packet on %s",
+			     skb->dev->name);
+			rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_EGR_MAPFAIL);
+			return;
+		}
+	}
+
+	if (ep->rmnet_mode == RMNET_EPMODE_VND)
+		rmnet_vnd_tx_fixup(skb, orig_dev);
+
+	rmnet_print_packet(skb, skb->dev->name, 't');
+	rc = dev_queue_xmit(skb);
+	if (rc != 0) {
+		LOGD("Failed to queue packet for transmission on [%s]",
+		     skb->dev->name);
+	}
+	rmnet_stats_queue_xmit(rc, RMNET_STATS_QUEUE_XMIT_EGRESS);
+}
diff --git a/drivers/net/rmnet/rmnet_handlers.h b/drivers/net/rmnet/rmnet_handlers.h
new file mode 100644
index 0000000..43c42c2
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_handlers.h
@@ -0,0 +1,24 @@ 
+/* Copyright (c) 2013, 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * RMNET Data ingress/egress handler
+ *
+ */
+
+#ifndef _RMNET_HANDLERS_H_
+#define _RMNET_HANDLERS_H_
+
+void rmnet_egress_handler(struct sk_buff *skb,
+			  struct rmnet_logical_ep_conf_s *ep);
+
+rx_handler_result_t rmnet_rx_handler(struct sk_buff **pskb);
+
+#endif /* _RMNET_HANDLERS_H_ */
diff --git a/drivers/net/rmnet/rmnet_main.c b/drivers/net/rmnet/rmnet_main.c
new file mode 100644
index 0000000..f8b7a20
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_main.c
@@ -0,0 +1,52 @@ 
+/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ *
+ * RMNET Data generic framework
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include "rmnet_private.h"
+#include "rmnet_config.h"
+#include "rmnet_vnd.h"
+
+/* Module Parameters */
+unsigned int rmnet_log_level = RMNET_LOG_LVL_ERR | RMNET_LOG_LVL_HI;
+module_param(rmnet_log_level, uint, 0644);
+MODULE_PARM_DESC(log_level, "Logging level");
+
+unsigned int rmnet_log_module_mask;
+module_param(rmnet_log_module_mask, uint, 0644);
+MODULE_PARM_DESC(rmnet_log_module_mask, "Logging module mask");
+
+/* Startup/Shutdown */
+
+static int __init rmnet_init(void)
+{
+	rmnet_config_init();
+	rmnet_vnd_init();
+
+	LOGL("%s", "RMNET Data driver loaded successfully");
+	return 0;
+}
+
+static void __exit rmnet_exit(void)
+{
+	rmnet_config_exit();
+	rmnet_vnd_exit();
+}
+
+module_init(rmnet_init)
+module_exit(rmnet_exit)
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/rmnet/rmnet_map.h b/drivers/net/rmnet/rmnet_map.h
new file mode 100644
index 0000000..7d533aa
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_map.h
@@ -0,0 +1,100 @@ 
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+
+#ifndef _RMNET_MAP_H_
+#define _RMNET_MAP_H_
+
+struct rmnet_map_control_command_s {
+	u8  command_name;
+	u8  cmd_type:2;
+	u8  reserved:6;
+	u16 reserved2;
+	u32 transaction_id;
+	union {
+		u8  data[65528];
+		struct {
+			u16 ip_family:2;
+			u16 reserved:14;
+			u16 flow_control_seq_num;
+			u32 qos_id;
+		} flow_control;
+	};
+}  __aligned(1);
+
+enum rmnet_map_results_e {
+	RMNET_MAP_SUCCESS,
+	RMNET_MAP_CONSUMED,
+	RMNET_MAP_GENERAL_FAILURE,
+	RMNET_MAP_NOT_ENABLED,
+	RMNET_MAP_FAILED_AGGREGATION,
+	RMNET_MAP_FAILED_MUX
+};
+
+enum rmnet_map_mux_errors_e {
+	RMNET_MAP_MUX_SUCCESS,
+	RMNET_MAP_MUX_INVALID_MUX_ID,
+	RMNET_MAP_MUX_INVALID_PAD_LENGTH,
+	RMNET_MAP_MUX_INVALID_PKT_LENGTH,
+	/* This should always be the last element */
+	RMNET_MAP_MUX_ENUM_LENGTH
+};
+
+enum rmnet_map_commands_e {
+	RMNET_MAP_COMMAND_NONE,
+	RMNET_MAP_COMMAND_FLOW_DISABLE,
+	RMNET_MAP_COMMAND_FLOW_ENABLE,
+	/* These should always be the last 2 elements */
+	RMNET_MAP_COMMAND_UNKNOWN,
+	RMNET_MAP_COMMAND_ENUM_LENGTH
+};
+
+struct rmnet_map_header_s {
+	u8  pad_len:6;
+	u8  reserved_bit:1;
+	u8  cd_bit:1;
+	u8  mux_id;
+	u16 pkt_len;
+}  __aligned(1);
+
+#define RMNET_MAP_GET_MUX_ID(Y) (((struct rmnet_map_header_s *) \
+				 (Y)->data)->mux_id)
+#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *) \
+				(Y)->data)->cd_bit)
+#define RMNET_MAP_GET_PAD(Y) (((struct rmnet_map_header_s *) \
+				(Y)->data)->pad_len)
+#define RMNET_MAP_GET_CMD_START(Y) ((struct rmnet_map_control_command_s *) \
+				    ((Y)->data + \
+				      sizeof(struct rmnet_map_header_s)))
+#define RMNET_MAP_GET_LENGTH(Y) (ntohs(((struct rmnet_map_header_s *) \
+					(Y)->data)->pkt_len))
+
+#define RMNET_MAP_COMMAND_REQUEST     0
+#define RMNET_MAP_COMMAND_ACK         1
+#define RMNET_MAP_COMMAND_UNSUPPORTED 2
+#define RMNET_MAP_COMMAND_INVALID     3
+
+#define RMNET_MAP_NO_PAD_BYTES        0
+#define RMNET_MAP_ADD_PAD_BYTES       1
+
+u8 rmnet_map_demultiplex(struct sk_buff *skb);
+struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
+				      struct rmnet_phys_ep_conf_s *config);
+
+struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb,
+						    int hdrlen, int pad);
+rx_handler_result_t rmnet_map_command(struct sk_buff *skb,
+				      struct rmnet_phys_ep_conf_s *config);
+
+#endif /* _RMNET_MAP_H_ */
diff --git a/drivers/net/rmnet/rmnet_map_command.c b/drivers/net/rmnet/rmnet_map_command.c
new file mode 100644
index 0000000..13bcee3
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_map_command.c
@@ -0,0 +1,180 @@ 
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/rmnet.h>
+#include <net/pkt_sched.h>
+#include "rmnet_config.h"
+#include "rmnet_map.h"
+#include "rmnet_private.h"
+#include "rmnet_vnd.h"
+#include "rmnet_stats.h"
+
+RMNET_LOG_MODULE(RMNET_LOGMASK_MAPC);
+
+unsigned long int rmnet_map_command_stats[RMNET_MAP_COMMAND_ENUM_LENGTH];
+module_param_array(rmnet_map_command_stats, ulong, 0, 0444);
+MODULE_PARM_DESC(rmnet_map_command_stats, "MAP command statistics");
+
+/* rmnet_map_do_flow_control() - Process MAP flow control command
+ * @skb: Socket buffer containing the MAP flow control message
+ * @config: Physical end-point configuration of ingress device
+ * @enable: boolean for enable/disable
+ *
+ * Process in-band MAP flow control messages. Assumes mux ID is mapped to a
+ * RmNet Data vitrual network device.
+ *
+ * Return:
+ *      - RMNET_MAP_COMMAND_UNSUPPORTED on any error
+ *      - RMNET_MAP_COMMAND_ACK on success
+ */
+static u8 rmnet_map_do_flow_control(struct sk_buff *skb,
+				    struct rmnet_phys_ep_conf_s *config,
+				    int enable)
+{
+	struct rmnet_map_control_command_s *cmd;
+	struct net_device *vnd;
+	struct rmnet_logical_ep_conf_s *ep;
+	u8 mux_id;
+	u16 ip_family;
+	u16 fc_seq;
+	u32 qos_id;
+	int r;
+
+	if (unlikely(!skb || !config))
+		return RX_HANDLER_CONSUMED;
+
+	mux_id = RMNET_MAP_GET_MUX_ID(skb);
+	cmd = RMNET_MAP_GET_CMD_START(skb);
+
+	if (mux_id >= RMNET_MAX_LOGICAL_EP) {
+		LOGD("Got packet on %s with bad mux id %d",
+		     skb->dev->name, mux_id);
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_BAD_MUX);
+		return RX_HANDLER_CONSUMED;
+	}
+
+	ep = &config->muxed_ep[mux_id];
+
+	if (!ep->refcount) {
+		LOGD("Packet on %s:%d; has no logical endpoint config",
+		     skb->dev->name, mux_id);
+
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_MUX_NO_EP);
+			return RX_HANDLER_CONSUMED;
+	}
+
+	vnd = ep->egress_dev;
+
+	ip_family = cmd->flow_control.ip_family;
+	fc_seq = ntohs(cmd->flow_control.flow_control_seq_num);
+	qos_id = ntohl(cmd->flow_control.qos_id);
+
+	/* Ignore the ip family and pass the sequence number for both v4 and v6
+	 * sequence. User space does not support creating dedicated flows for
+	 * the 2 protocols
+	 */
+	r = rmnet_vnd_do_flow_control(vnd, enable);
+	LOGD("dev:%s, qos_id:0x%08X, ip_family:%hd, fc_seq %hd, en:%d",
+	     skb->dev->name, qos_id, ip_family & 3, fc_seq, enable);
+
+	if (r) {
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED);
+		return RMNET_MAP_COMMAND_UNSUPPORTED;
+	} else {
+		return RMNET_MAP_COMMAND_ACK;
+	}
+}
+
+/* rmnet_map_send_ack() - Send N/ACK message for MAP commands
+ * @skb: Socket buffer containing the MAP command message
+ * @type: N/ACK message selector
+ * @config: Physical end-point configuration of ingress device
+ *
+ * skb is modified to contain the message type selector. The message is then
+ * transmitted on skb->dev. Note that this function grabs global Tx lock on
+ * skb->dev for latency reasons.
+ *
+ * Return:
+ *      - void
+ */
+static void rmnet_map_send_ack(struct sk_buff *skb,
+			       unsigned char type,
+			       struct rmnet_phys_ep_conf_s *config)
+{
+	struct rmnet_map_control_command_s *cmd;
+	int xmit_status;
+
+	if (unlikely(!skb))
+		return;
+
+	skb->protocol = htons(ETH_P_MAP);
+
+	cmd = RMNET_MAP_GET_CMD_START(skb);
+	cmd->cmd_type = type & 0x03;
+
+	netif_tx_lock(skb->dev);
+	xmit_status = skb->dev->netdev_ops->ndo_start_xmit(skb, skb->dev);
+	netif_tx_unlock(skb->dev);
+
+	LOGD("MAP command ACK=%hhu sent with rc: %d", type & 0x03, xmit_status);
+}
+
+/* rmnet_map_command() - Entry point for handling MAP commands
+ * @skb: Socket buffer containing the MAP command message
+ * @config: Physical end-point configuration of ingress device
+ *
+ * Process MAP command frame and send N/ACK message as appropriate. Message cmd
+ * name is decoded here and appropriate handler is called.
+ *
+ * Return:
+ *      - RX_HANDLER_CONSUMED. Command frames are always consumed.
+ */
+rx_handler_result_t rmnet_map_command(struct sk_buff *skb,
+				      struct rmnet_phys_ep_conf_s *config)
+{
+	struct rmnet_map_control_command_s *cmd;
+	unsigned char command_name;
+	unsigned char rc = 0;
+
+	if (unlikely(!skb))
+		return RX_HANDLER_CONSUMED;
+
+	cmd = RMNET_MAP_GET_CMD_START(skb);
+	command_name = cmd->command_name;
+
+	if (command_name < RMNET_MAP_COMMAND_ENUM_LENGTH)
+		rmnet_map_command_stats[command_name]++;
+
+	switch (command_name) {
+	case RMNET_MAP_COMMAND_FLOW_ENABLE:
+		rc = rmnet_map_do_flow_control(skb, config, 1);
+		break;
+
+	case RMNET_MAP_COMMAND_FLOW_DISABLE:
+		rc = rmnet_map_do_flow_control(skb, config, 0);
+		break;
+
+	default:
+		rmnet_map_command_stats[RMNET_MAP_COMMAND_UNKNOWN]++;
+		LOGM("Uknown MAP command: %d", command_name);
+		rc = RMNET_MAP_COMMAND_UNSUPPORTED;
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED);
+		break;
+	}
+	if (rc == RMNET_MAP_COMMAND_ACK)
+		rmnet_map_send_ack(skb, rc, config);
+	return RX_HANDLER_CONSUMED;
+}
diff --git a/drivers/net/rmnet/rmnet_map_data.c b/drivers/net/rmnet/rmnet_map_data.c
new file mode 100644
index 0000000..93af3c9
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_map_data.c
@@ -0,0 +1,145 @@ 
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * RMNET Data MAP protocol
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/rmnet.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/time.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/udp.h>
+#include <linux/tcp.h>
+#include <linux/in.h>
+#include <net/ip.h>
+#include <net/checksum.h>
+#include <net/ip6_checksum.h>
+#include "rmnet_config.h"
+#include "rmnet_map.h"
+#include "rmnet_private.h"
+#include "rmnet_stats.h"
+
+RMNET_LOG_MODULE(RMNET_LOGMASK_MAPD);
+
+#define RMNET_MAP_DEAGGR_SPACING  64
+#define RMNET_MAP_DEAGGR_HEADROOM (RMNET_MAP_DEAGGR_SPACING / 2)
+
+/* rmnet_map_add_map_header() - Adds MAP header to front of skb->data
+ * @skb:        Socket buffer ("packet") to modify
+ * @hdrlen:     Number of bytes of header data which should not be included in
+ *              MAP length field
+ * @pad:        Specify if padding the MAP packet to make it 4 byte aligned is
+ *              necessary
+ *
+ * Padding is calculated and set appropriately in MAP header. Mux ID is
+ * initialized to 0.
+ *
+ * Return:
+ *      - Pointer to MAP structure
+ *      - 0 (null) if insufficient headroom
+ *      - 0 (null) if insufficient tailroom for padding bytes
+ */
+struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb,
+						    int hdrlen, int pad)
+{
+	u32 padding, map_datalen;
+	u8 *padbytes;
+	struct rmnet_map_header_s *map_header;
+
+	if (skb_headroom(skb) < sizeof(struct rmnet_map_header_s))
+		return 0;
+
+	map_datalen = skb->len - hdrlen;
+	map_header = (struct rmnet_map_header_s *)
+			skb_push(skb, sizeof(struct rmnet_map_header_s));
+	memset(map_header, 0, sizeof(struct rmnet_map_header_s));
+
+	if (pad == RMNET_MAP_NO_PAD_BYTES) {
+		map_header->pkt_len = htons(map_datalen);
+		return map_header;
+	}
+
+	padding = ALIGN(map_datalen, 4) - map_datalen;
+
+	if (padding == 0)
+		goto done;
+
+	if (skb_tailroom(skb) < padding)
+		return 0;
+
+	padbytes = (u8 *)skb_put(skb, padding);
+	LOGD("pad: %d", padding);
+	memset(padbytes, 0, padding);
+
+done:
+	map_header->pkt_len = htons(map_datalen + padding);
+	map_header->pad_len = padding & 0x3F;
+
+	return map_header;
+}
+
+/* rmnet_map_deaggregate() - Deaggregates a single packet
+ * @skb:        Source socket buffer containing multiple MAP frames
+ * @config:     Physical endpoint configuration of the ingress device
+ *
+ * A whole new buffer is allocated for each portion of an aggregated frame.
+ * Caller should keep calling deaggregate() on the source skb until 0 is
+ * returned, indicating that there are no more packets to deaggregate. Caller
+ * is responsible for freeing the original skb.
+ *
+ * Return:
+ *     - Pointer to new skb
+ *     - 0 (null) if no more aggregated packets
+ */
+struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb,
+				      struct rmnet_phys_ep_conf_s *config)
+{
+	struct sk_buff *skbn;
+	struct rmnet_map_header_s *maph;
+	u32 packet_len;
+
+	if (skb->len == 0)
+		return 0;
+
+	maph = (struct rmnet_map_header_s *)skb->data;
+	packet_len = ntohs(maph->pkt_len) + sizeof(struct rmnet_map_header_s);
+
+	if ((((int)skb->len) - ((int)packet_len)) < 0) {
+		LOGM("%s", "Got malformed packet. Dropping");
+		return 0;
+	}
+
+	skbn = alloc_skb(packet_len + RMNET_MAP_DEAGGR_SPACING, GFP_ATOMIC);
+	if (!skbn)
+		return 0;
+
+	skbn->dev = skb->dev;
+	skb_reserve(skbn, RMNET_MAP_DEAGGR_HEADROOM);
+	skb_put(skbn, packet_len);
+	memcpy(skbn->data, skb->data, packet_len);
+	skb_pull(skb, packet_len);
+
+	/* Some hardware can send us empty frames. Catch them */
+	if (ntohs(maph->pkt_len) == 0) {
+		LOGD("Dropping empty MAP frame");
+		rmnet_kfree_skb(skbn, RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0);
+		return 0;
+	}
+
+	return skbn;
+}
diff --git a/drivers/net/rmnet/rmnet_private.h b/drivers/net/rmnet/rmnet_private.h
new file mode 100644
index 0000000..f27e0b3
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_private.h
@@ -0,0 +1,76 @@ 
+/* Copyright (c) 2013-2014, 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _RMNET_PRIVATE_H_
+#define _RMNET_PRIVATE_H_
+
+#define RMNET_MAX_VND              32
+#define RMNET_MAX_PACKET_SIZE      16384
+#define RMNET_DFLT_PACKET_SIZE     1500
+#define RMNET_DEV_NAME_STR         "rmnet"
+#define RMNET_NEEDED_HEADROOM      16
+#define RMNET_TX_QUEUE_LEN         1000
+#define RMNET_ETHERNET_HEADER_LENGTH    14
+
+extern unsigned int rmnet_log_level;
+extern unsigned int rmnet_log_module_mask;
+
+#define RMNET_INIT_OK     0
+#define RMNET_INIT_ERROR  1
+
+#define RMNET_LOG_LVL_DBG BIT(4)
+#define RMNET_LOG_LVL_LOW BIT(3)
+#define RMNET_LOG_LVL_MED BIT(2)
+#define RMNET_LOG_LVL_HI  BIT(1)
+#define RMNET_LOG_LVL_ERR BIT(0)
+
+#define RMNET_LOG_MODULE(X) \
+	static u32 rmnet_mod_mask = X
+
+#define RMNET_LOGMASK_CONFIG  BIT(0)
+#define RMNET_LOGMASK_HANDLER BIT(1)
+#define RMNET_LOGMASK_VND     BIT(2)
+#define RMNET_LOGMASK_MAPD    BIT(3)
+#define RMNET_LOGMASK_MAPC    BIT(4)
+
+#define LOGE(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_ERR) \
+			pr_err("[RMNET:ERR] %s(): " fmt "\n", __func__, \
+				##__VA_ARGS__); \
+			} while (0)
+
+#define LOGH(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_HI) \
+			pr_err("[RMNET:HI] %s(): " fmt "\n", __func__, \
+				##__VA_ARGS__); \
+			} while (0)
+
+#define LOGM(fmt, ...) do { if (rmnet_log_level & RMNET_LOG_LVL_MED) \
+			pr_warn("[RMNET:MED] %s(): " fmt "\n", __func__, \
+				##__VA_ARGS__); \
+			} while (0)
+
+#define LOGL(fmt, ...) do { if (unlikely \
+			(rmnet_log_level & RMNET_LOG_LVL_LOW)) \
+			pr_notice("[RMNET:LOW] %s(): " fmt "\n", __func__, \
+				##__VA_ARGS__); \
+			} while (0)
+
+/* Don't use pr_debug as it is compiled out of the kernel. We can be sure of
+ * minimal impact as LOGD is not enabled by default.
+ */
+#define LOGD(fmt, ...) do { if (unlikely( \
+			    (rmnet_log_level & RMNET_LOG_LVL_DBG) &&\
+			    (rmnet_log_module_mask & rmnet_mod_mask))) \
+			pr_notice("[RMNET:DBG] %s(): " fmt "\n", __func__, \
+				  ##__VA_ARGS__); \
+			} while (0)
+
+#endif /* _RMNET_PRIVATE_H_ */
diff --git a/drivers/net/rmnet/rmnet_stats.c b/drivers/net/rmnet/rmnet_stats.c
new file mode 100644
index 0000000..d53ce38
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_stats.c
@@ -0,0 +1,86 @@ 
+/* Copyright (c) 2014, 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ *
+ * RMNET Data statistics
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/netdevice.h>
+#include "rmnet_private.h"
+#include "rmnet_stats.h"
+#include "rmnet_config.h"
+#include "rmnet_map.h"
+
+enum rmnet_deagg_e {
+	RMNET_STATS_AGG_BUFF,
+	RMNET_STATS_AGG_PKT,
+	RMNET_STATS_AGG_MAX
+};
+
+static DEFINE_SPINLOCK(rmnet_skb_free_lock);
+unsigned long int skb_free[RMNET_STATS_SKBFREE_MAX];
+module_param_array(skb_free, ulong, 0, 0444);
+MODULE_PARM_DESC(skb_free, "SKBs dropped or freed");
+
+static DEFINE_SPINLOCK(rmnet_queue_xmit_lock);
+unsigned long int queue_xmit[RMNET_STATS_QUEUE_XMIT_MAX * 2];
+module_param_array(queue_xmit, ulong, 0, 0444);
+MODULE_PARM_DESC(queue_xmit, "SKBs queued for transmit");
+
+static DEFINE_SPINLOCK(rmnet_deagg_count);
+unsigned long int deagg_count[RMNET_STATS_AGG_MAX];
+module_param_array(deagg_count, ulong, 0, 0444);
+MODULE_PARM_DESC(deagg_count, "SKBs De-aggregated");
+
+void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason)
+{
+	unsigned long flags;
+
+	if (reason >= RMNET_STATS_SKBFREE_MAX)
+		reason = RMNET_STATS_SKBFREE_UNKNOWN;
+
+	spin_lock_irqsave(&rmnet_skb_free_lock, flags);
+	skb_free[reason]++;
+	spin_unlock_irqrestore(&rmnet_skb_free_lock, flags);
+
+	if (skb)
+		kfree_skb(skb);
+}
+
+void rmnet_stats_queue_xmit(int rc, unsigned int reason)
+{
+	unsigned long flags;
+
+	if (rc != 0)
+		reason += RMNET_STATS_QUEUE_XMIT_MAX;
+	if (reason >= RMNET_STATS_QUEUE_XMIT_MAX * 2)
+		reason = RMNET_STATS_SKBFREE_UNKNOWN;
+
+	spin_lock_irqsave(&rmnet_queue_xmit_lock, flags);
+	queue_xmit[reason]++;
+	spin_unlock_irqrestore(&rmnet_queue_xmit_lock, flags);
+}
+
+void rmnet_stats_deagg_pkts(int aggcount)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&rmnet_deagg_count, flags);
+	deagg_count[RMNET_STATS_AGG_BUFF]++;
+	deagg_count[RMNET_STATS_AGG_PKT] += aggcount;
+	spin_unlock_irqrestore(&rmnet_deagg_count, flags);
+}
diff --git a/drivers/net/rmnet/rmnet_stats.h b/drivers/net/rmnet/rmnet_stats.h
new file mode 100644
index 0000000..c8d0469
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_stats.h
@@ -0,0 +1,61 @@ 
+/* Copyright (c) 2014, 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ *
+ * RMNET Data statistics
+ *
+ */
+
+#ifndef _RMNET_STATS_H_
+#define _RMNET_STATS_H_
+
+enum rmnet_skb_free_e {
+	RMNET_STATS_SKBFREE_UNKNOWN,
+	RMNET_STATS_SKBFREE_BRDG_NO_EGRESS,
+	RMNET_STATS_SKBFREE_DELIVER_NO_EP,
+	RMNET_STATS_SKBFREE_IPINGRESS_NO_EP,
+	RMNET_STATS_SKBFREE_MAPINGRESS_BAD_MUX,
+	RMNET_STATS_SKBFREE_MAPINGRESS_MUX_NO_EP,
+	RMNET_STATS_SKBFREE_MAPINGRESS_AGGBUF,
+	RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPD,
+	RMNET_STATS_SKBFREE_INGRESS_NOT_EXPECT_MAPC,
+	RMNET_STATS_SKBFREE_EGR_MAPFAIL,
+	RMNET_STATS_SKBFREE_VND_NO_EGRESS,
+	RMNET_STATS_SKBFREE_MAPC_BAD_MUX,
+	RMNET_STATS_SKBFREE_MAPC_MUX_NO_EP,
+	RMNET_STATS_SKBFREE_AGG_CPY_EXPAND,
+	RMNET_STATS_SKBFREE_AGG_INTO_BUFF,
+	RMNET_STATS_SKBFREE_DEAGG_MALFORMED,
+	RMNET_STATS_SKBFREE_DEAGG_CLONE_FAIL,
+	RMNET_STATS_SKBFREE_DEAGG_UNKNOWN_IP_TYPE,
+	RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0,
+	RMNET_STATS_SKBFREE_INGRESS_BAD_MAP_CKSUM,
+	RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED,
+	RMNET_STATS_SKBFREE_MAX
+};
+
+enum rmnet_queue_xmit_e {
+	RMNET_STATS_QUEUE_XMIT_UNKNOWN,
+	RMNET_STATS_QUEUE_XMIT_EGRESS,
+	RMNET_STATS_QUEUE_XMIT_AGG_FILL_BUFFER,
+	RMNET_STATS_QUEUE_XMIT_AGG_TIMEOUT,
+	RMNET_STATS_QUEUE_XMIT_AGG_CPY_EXP_FAIL,
+	RMNET_STATS_QUEUE_XMIT_AGG_SKIP,
+	RMNET_STATS_QUEUE_XMIT_MAX
+};
+
+void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason);
+void rmnet_stats_queue_xmit(int rc, unsigned int reason);
+void rmnet_stats_deagg_pkts(int aggcount);
+void rmnet_stats_agg_pkts(int aggcount);
+void rmnet_stats_dl_checksum(unsigned int rc);
+void rmnet_stats_ul_checksum(unsigned int rc);
+#endif /* _RMNET_STATS_H_ */
diff --git a/drivers/net/rmnet/rmnet_vnd.c b/drivers/net/rmnet/rmnet_vnd.c
new file mode 100644
index 0000000..a737d0e
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_vnd.c
@@ -0,0 +1,353 @@ 
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ *
+ * RMNET Data virtual network driver
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/rmnet.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/spinlock.h>
+#include <net/pkt_sched.h>
+#include <linux/atomic.h>
+#include "rmnet_config.h"
+#include "rmnet_handlers.h"
+#include "rmnet_private.h"
+#include "rmnet_map.h"
+#include "rmnet_vnd.h"
+#include "rmnet_stats.h"
+
+RMNET_LOG_MODULE(RMNET_LOGMASK_VND);
+
+struct net_device *rmnet_devices[RMNET_MAX_VND];
+
+/* RX/TX Fixup */
+
+/* rmnet_vnd_rx_fixup() - Virtual Network Device receive fixup hook
+ * @skb:        Socket buffer ("packet") to modify
+ * @dev:        Virtual network device
+ *
+ * Additional VND specific packet processing for ingress packets
+ */
+int rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev)
+{
+	if (unlikely(!dev || !skb))
+		return RX_HANDLER_CONSUMED;
+
+	dev->stats.rx_packets++;
+	dev->stats.rx_bytes += skb->len;
+
+	return RX_HANDLER_PASS;
+}
+
+/* rmnet_vnd_tx_fixup() - Virtual Network Device transmic fixup hook
+ * @skb:      Socket buffer ("packet") to modify
+ * @dev:      Virtual network device
+ *
+ * Additional VND specific packet processing for egress packets
+ */
+int rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev)
+{
+	struct rmnet_vnd_private_s *dev_conf;
+
+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
+
+	if (unlikely(!dev || !skb))
+		return RX_HANDLER_CONSUMED;
+
+	dev->stats.tx_packets++;
+	dev->stats.tx_bytes += skb->len;
+
+	return RX_HANDLER_PASS;
+}
+
+/* Network Device Operations */
+
+/* rmnet_vnd_start_xmit() - Transmit NDO callback
+ * @skb:        Socket buffer ("packet") being sent from network stack
+ * @dev:        Virtual Network Device
+ *
+ * Standard network driver operations hook to transmit packets on virtual
+ * network device. Called by network stack. Packet is not transmitted directly
+ * from here; instead it is given to the rmnet egress handler.
+ */
+static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
+					struct net_device *dev)
+{
+	struct rmnet_vnd_private_s *dev_conf;
+
+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
+	if (dev_conf->local_ep.egress_dev) {
+		rmnet_egress_handler(skb, &dev_conf->local_ep);
+	} else {
+		dev->stats.tx_dropped++;
+		rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_VND_NO_EGRESS);
+	}
+	return NETDEV_TX_OK;
+}
+
+/* rmnet_vnd_change_mtu() - Change MTU NDO callback
+ * @dev:         Virtual network device
+ * @new_mtu:     New MTU value to set (in bytes)
+ *
+ * Standard network driver operations hook to set the MTU. Called by kernel to
+ * set the device MTU. Checks if desired MTU is less than zero or greater than
+ * RMNET_MAX_PACKET_SIZE;
+ */
+static int rmnet_vnd_change_mtu(struct net_device *dev, int new_mtu)
+{
+	if (new_mtu < 0 || new_mtu > RMNET_MAX_PACKET_SIZE)
+		return -EINVAL;
+
+	dev->mtu = new_mtu;
+	return 0;
+}
+
+static const struct net_device_ops rmnet_vnd_ops = {
+	.ndo_init = 0,
+	.ndo_start_xmit = rmnet_vnd_start_xmit,
+	.ndo_change_mtu = rmnet_vnd_change_mtu,
+	.ndo_set_mac_address = 0,
+	.ndo_validate_addr = 0,
+};
+
+static void rmnet_vnd_free(struct net_device *dev)
+{
+	free_netdev(dev);
+}
+
+/* rmnet_vnd_setup() - net_device initialization callback
+ * @dev:      Virtual network device
+ *
+ * Called by kernel whenever a new rmnet<n> device is created. Sets MTU,
+ * flags, ARP type, needed headroom, etc...
+ */
+void rmnet_vnd_setup(struct net_device *dev)
+{
+	struct rmnet_vnd_private_s *dev_conf;
+
+	LOGM("Setting up device %s", dev->name);
+
+	/* Clear out private data */
+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
+	memset(dev_conf, 0, sizeof(struct rmnet_vnd_private_s));
+
+	dev->netdev_ops = &rmnet_vnd_ops;
+	dev->mtu = RMNET_DFLT_PACKET_SIZE;
+	dev->needed_headroom = RMNET_NEEDED_HEADROOM;
+	random_ether_addr(dev->dev_addr);
+	dev->tx_queue_len = RMNET_TX_QUEUE_LEN;
+
+	/* Raw IP mode */
+	dev->header_ops = 0;  /* No header */
+	dev->type = ARPHRD_RAWIP;
+	dev->hard_header_len = 0;
+	dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
+
+	dev->destructor	= rmnet_vnd_free;
+}
+
+/* Exposed API */
+
+/* rmnet_vnd_exit() - Shutdown cleanup hook
+ *
+ * Called by RmNet main on module unload. Cleans up data structures and
+ * unregisters/frees net_devices.
+ */
+void rmnet_vnd_exit(void)
+{
+	int i;
+
+	for (i = 0; i < RMNET_MAX_VND; i++)
+		if (rmnet_devices[i]) {
+			unregister_netdev(rmnet_devices[i]);
+			free_netdev(rmnet_devices[i]);
+	}
+}
+
+/* rmnet_vnd_init() - Init hook
+ *
+ * Called by RmNet main on module load. Initializes data structures
+ */
+int rmnet_vnd_init(void)
+{
+	memset(rmnet_devices, 0, sizeof(struct net_device *) * RMNET_MAX_VND);
+	return 0;
+}
+
+/* rmnet_vnd_create_dev() - Create a new virtual network device node.
+ * @id:         Virtual device node id
+ * @new_device: Pointer to newly created device node
+ * @prefix:     Device name prefix
+ *
+ * Allocates structures for new virtual network devices. Sets the name of the
+ * new device and registers it with the network stack. Device will appear in
+ * ifconfig list after this is called. If the prefix is null, then
+ * RMNET_DEV_NAME_STR will be assumed.
+ */
+int rmnet_vnd_newlink(int id, struct net_device *new_device)
+{
+	int rc;
+
+	if (rmnet_devices[id])
+		return -EINVAL;
+
+	rc = register_netdevice(new_device);
+	if (!rc) {
+		rmnet_devices[id] = new_device;
+		new_device->rtnl_link_ops = &rmnet_link_ops;
+	}
+
+	return rc;
+}
+
+/* rmnet_vnd_free_dev() - free a virtual network device node.
+ * @id:         Virtual device node id
+ *
+ * Unregisters the virtual network device node and frees it.
+ * unregister_netdev locks the rtnl mutex, so the mutex must not be locked
+ * by the caller of the function. unregister_netdev enqueues the request to
+ * unregister the device into a TODO queue. The requests in the TODO queue
+ * are only done after rtnl mutex is unlocked, therefore free_netdev has to
+ * called after unlocking rtnl mutex.
+ */
+int rmnet_vnd_free_dev(int id)
+{
+	struct rmnet_logical_ep_conf_s *epconfig_l;
+	struct net_device *dev;
+
+	rtnl_lock();
+	if ((id < 0) || (id >= RMNET_MAX_VND) || !rmnet_devices[id]) {
+		rtnl_unlock();
+		LOGM("Invalid id [%d]", id);
+		return -EINVAL;
+	}
+
+	epconfig_l = rmnet_vnd_get_le_config(rmnet_devices[id]);
+	if (epconfig_l && epconfig_l->refcount) {
+		rtnl_unlock();
+		return -EINVAL;
+	}
+
+	dev = rmnet_devices[id];
+	rmnet_devices[id] = 0;
+	rtnl_unlock();
+
+	if (dev) {
+		unregister_netdev(dev);
+		free_netdev(dev);
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+
+int rmnet_vnd_remove_ref_dev(int id)
+{
+	struct rmnet_logical_ep_conf_s *epconfig_l;
+
+	if ((id < 0) || (id >= RMNET_MAX_VND) || !rmnet_devices[id])
+		return -EINVAL;
+
+	epconfig_l = rmnet_vnd_get_le_config(rmnet_devices[id]);
+	if (epconfig_l && epconfig_l->refcount)
+		return -EBUSY;
+
+	rmnet_devices[id] = 0;
+	return 0;
+}
+
+/* rmnet_vnd_is_vnd() - Determine if net_device is RmNet owned virtual devices
+ * @dev:        Network device to test
+ *
+ * Searches through list of known RmNet virtual devices. This function is O(n)
+ * and should not be used in the data path.
+ *
+ * To get the read id, subtract this result by 1.
+ */
+int rmnet_vnd_is_vnd(struct net_device *dev)
+{
+	/* This is not an efficient search, but, this will only be called in
+	 * a configuration context, and the list is small.
+	 */
+	int i;
+
+	if (!dev)
+		return 0;
+
+	for (i = 0; i < RMNET_MAX_VND; i++)
+		if (dev == rmnet_devices[i])
+			return i + 1;
+
+	return 0;
+}
+
+/* rmnet_vnd_get_le_config() - Get the logical endpoint configuration
+ * @dev:      Virtual device node
+ *
+ * Gets the logical endpoint configuration for a RmNet virtual network device
+ * node. Caller should confirm that devices is a RmNet VND before calling.
+ */
+struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct net_device *dev)
+{
+	struct rmnet_vnd_private_s *dev_conf;
+
+	if (!dev)
+		return 0;
+
+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
+	if (!dev_conf)
+		return 0;
+
+	return &dev_conf->local_ep;
+}
+
+/* rmnet_vnd_do_flow_control() - Process flow control request
+ * @dev: Virtual network device node to do lookup on
+ * @enable: boolean to enable/disable flow.
+ */
+int rmnet_vnd_do_flow_control(struct net_device *dev, int enable)
+{
+	struct rmnet_vnd_private_s *dev_conf;
+
+	if (unlikely(!dev) || !rmnet_vnd_is_vnd(dev))
+		return -EINVAL;
+
+	dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev);
+	if (unlikely(!dev_conf))
+		return -EINVAL;
+
+	LOGD("Setting VND TX queue state to %d", enable);
+	/* Although we expect similar number of enable/disable
+	 * commands, optimize for the disable. That is more
+	 * latency sensitive than enable
+	 */
+	if (unlikely(enable))
+		netif_wake_queue(dev);
+	else
+		netif_stop_queue(dev);
+
+	return 0;
+}
+
+/* rmnet_vnd_get_by_id() - Get VND by array index ID
+ * @id: Virtual network deice id [0:RMNET_MAX_VND]
+ */
+struct net_device *rmnet_vnd_get_by_id(int id)
+{
+	if (id < 0 || id >= RMNET_MAX_VND)
+		return 0;
+
+	return rmnet_devices[id];
+}
diff --git a/drivers/net/rmnet/rmnet_vnd.h b/drivers/net/rmnet/rmnet_vnd.h
new file mode 100644
index 0000000..8095e91
--- /dev/null
+++ b/drivers/net/rmnet/rmnet_vnd.h
@@ -0,0 +1,34 @@ 
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * RMNET Data Virtual Network Device APIs
+ *
+ */
+
+#include <linux/types.h>
+
+#ifndef _RMNET_VND_H_
+#define _RMNET_VND_H_
+
+int rmnet_vnd_do_flow_control(struct net_device *dev, int enable);
+struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct net_device *dev);
+int rmnet_vnd_free_dev(int id);
+int rmnet_vnd_remove_ref_dev(int id);
+int rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev);
+int rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev);
+int rmnet_vnd_is_vnd(struct net_device *dev);
+int rmnet_vnd_init(void);
+void rmnet_vnd_exit(void);
+struct net_device *rmnet_vnd_get_by_id(int id);
+void rmnet_vnd_setup(struct net_device *dev);
+int rmnet_vnd_newlink(int id, struct net_device *new_device);
+
+#endif /* _RMNET_VND_H_ */
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index dd9820b..ec29d61 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -370,6 +370,7 @@  header-y += resource.h
 header-y += rfkill.h
 header-y += rio_cm_cdev.h
 header-y += rio_mport_cdev.h
+header-y += rmnet.h
 header-y += romfs_fs.h
 header-y += rose.h
 header-y += route.h
diff --git a/include/uapi/linux/if_arp.h b/include/uapi/linux/if_arp.h
index 4d024d7..e762447 100644
--- a/include/uapi/linux/if_arp.h
+++ b/include/uapi/linux/if_arp.h
@@ -59,6 +59,7 @@ 
 #define ARPHRD_LAPB	516		/* LAPB				*/
 #define ARPHRD_DDCMP    517		/* Digital's DDCMP protocol     */
 #define ARPHRD_RAWHDLC	518		/* Raw HDLC			*/
+#define ARPHRD_RAWIP	530		/* Raw IP			*/
 
 #define ARPHRD_TUNNEL	768		/* IPIP tunnel			*/
 #define ARPHRD_TUNNEL6	769		/* IP6IP6 tunnel       		*/
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index 5bc9bfd..70520da 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -104,7 +104,9 @@ 
 #define ETH_P_QINQ3	0x9300		/* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_EDSA	0xDADA		/* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
 #define ETH_P_AF_IUCV   0xFBFB		/* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
-
+#define ETH_P_MAP	0xDA1A		/* Multiplexing and Aggregation Protocol
+					 *  NOT AN OFFICIALLY REGISTERED ID ]
+					 */
 #define ETH_P_802_3_MIN	0x0600		/* If the value in the ethernet type is less than this value
 					 * then the frame is Ethernet II. Else it is 802.3 */
 
diff --git a/include/uapi/linux/rmnet.h b/include/uapi/linux/rmnet.h
new file mode 100644
index 0000000..dce5763
--- /dev/null
+++ b/include/uapi/linux/rmnet.h
@@ -0,0 +1,34 @@ 
+#ifndef _RMNET_DATA_H_
+#define _RMNET_DATA_H_
+
+/* Constants */
+#define RMNET_EGRESS_FORMAT__RESERVED__         (1<<0)
+#define RMNET_EGRESS_FORMAT_MAP                 (1<<1)
+#define RMNET_EGRESS_FORMAT_AGGREGATION         (1<<2)
+#define RMNET_EGRESS_FORMAT_MUXING              (1<<3)
+#define RMNET_EGRESS_FORMAT_MAP_CKSUMV3         (1<<4)
+#define RMNET_EGRESS_FORMAT_MAP_CKSUMV4         (1<<5)
+
+#define RMNET_INGRESS_FIX_ETHERNET              (1<<0)
+#define RMNET_INGRESS_FORMAT_MAP                (1<<1)
+#define RMNET_INGRESS_FORMAT_DEAGGREGATION      (1<<2)
+#define RMNET_INGRESS_FORMAT_DEMUXING           (1<<3)
+#define RMNET_INGRESS_FORMAT_MAP_COMMANDS       (1<<4)
+#define RMNET_INGRESS_FORMAT_MAP_CKSUMV3        (1<<5)
+#define RMNET_INGRESS_FORMAT_MAP_CKSUMV4        (1<<6)
+
+/* Pass the frame up the stack with no modifications to skb->dev */
+#define RMNET_EPMODE_NONE (0)
+/* Replace skb->dev to a virtual rmnet device and pass up the stack */
+#define	RMNET_EPMODE_VND (1)
+/* Pass the frame directly to another device with dev_queue_xmit() */
+#define	RMNET_EPMODE_BRIDGE (2)
+
+enum {
+	IFLA_RMNET_UNSPEC,
+	IFLA_RMNET_MUX_ID,
+	__IFLA_RMNET_MAX,
+};
+#define __IFLA_RMNET_MAX	(__IFLA_RMNET_MAX - 1)
+
+#endif /* _RMNET_DATA_H_ */