All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH net-next 0/1 v3] drivers: net: Add support for rmnet driver
@ 2017-04-14  5:05 Subash Abhinov Kasiviswanathan
  2017-04-14  5:05 ` [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation Subash Abhinov Kasiviswanathan
  0 siblings, 1 reply; 8+ messages in thread
From: Subash Abhinov Kasiviswanathan @ 2017-04-14  5:05 UTC (permalink / raw)
  To: netdev, davem, fengguang.wu, dcbw, jiri; +Cc: Subash Abhinov Kasiviswanathan

This patch adds support for the rmnet_data driver which is required to
support recent chipsets using Qualcomm Technologies, Inc. modems. The data
from hardware follows the multiplexing and aggregation protocol (MAP).

This driver can be used to register onto any physical network device in
IP mode. Physical transports include USB, HSIC, PCIe and IP accelerator.

rmnet_data driver helps to decode these packets and queue them to network
stack (and encode and transmit it to the physical device).

--
v1: Same as the RFC patch with some minor fixes for issues reported by
kbuild test robot.

v1->v2: Change datatypes and remove config IOCTL as mentioned by David.
Also fix checkpatch issues and remove some unused code.

v2->v3: Move location to drivers/net and rename to rmnet. Change the
userspace - netlink communication from custom netlink to rtnl_link_ops.
Refactor some code. Use a fixed config for ingress and egress.

Subash Abhinov Kasiviswanathan (1):
  drivers: net: rmnet: Initial implementation

 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

-- 
1.9.1

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation
  2017-04-14  5:05 [PATCH net-next 0/1 v3] drivers: net: Add support for rmnet driver Subash Abhinov Kasiviswanathan
@ 2017-04-14  5:05 ` Subash Abhinov Kasiviswanathan
  2017-04-14  9:07   ` Jiri Pirko
  2017-04-14 16:10   ` Stephen Hemminger
  0 siblings, 2 replies; 8+ messages in thread
From: Subash Abhinov Kasiviswanathan @ 2017-04-14  5:05 UTC (permalink / raw)
  To: netdev, davem, fengguang.wu, dcbw, jiri; +Cc: Subash Abhinov Kasiviswanathan

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"
+	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_ */
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation
  2017-04-14  5:05 ` [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation Subash Abhinov Kasiviswanathan
@ 2017-04-14  9:07   ` Jiri Pirko
  2017-04-14 21:57     ` Subash Abhinov Kasiviswanathan
  2017-08-14 23:52     ` Subash Abhinov Kasiviswanathan
  2017-04-14 16:10   ` Stephen Hemminger
  1 sibling, 2 replies; 8+ messages in thread
From: Jiri Pirko @ 2017-04-14  9:07 UTC (permalink / raw)
  To: Subash Abhinov Kasiviswanathan; +Cc: netdev, davem, fengguang.wu, dcbw

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
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation
  2017-04-14  5:05 ` [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation Subash Abhinov Kasiviswanathan
  2017-04-14  9:07   ` Jiri Pirko
@ 2017-04-14 16:10   ` Stephen Hemminger
  2017-04-14 23:02     ` Subash Abhinov Kasiviswanathan
  1 sibling, 1 reply; 8+ messages in thread
From: Stephen Hemminger @ 2017-04-14 16:10 UTC (permalink / raw)
  To: Subash Abhinov Kasiviswanathan; +Cc: netdev, davem, fengguang.wu, dcbw, jiri

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 */

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation
  2017-04-14  9:07   ` Jiri Pirko
@ 2017-04-14 21:57     ` Subash Abhinov Kasiviswanathan
  2017-04-14 21:59       ` Stephen Hemminger
  2017-08-14 23:52     ` Subash Abhinov Kasiviswanathan
  1 sibling, 1 reply; 8+ messages in thread
From: Subash Abhinov Kasiviswanathan @ 2017-04-14 21:57 UTC (permalink / raw)
  To: Jiri Pirko; +Cc: netdev, davem, fengguang.wu, dcbw

> 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.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation
  2017-04-14 21:57     ` Subash Abhinov Kasiviswanathan
@ 2017-04-14 21:59       ` Stephen Hemminger
  0 siblings, 0 replies; 8+ messages in thread
From: Stephen Hemminger @ 2017-04-14 21:59 UTC (permalink / raw)
  To: Subash Abhinov Kasiviswanathan
  Cc: Jiri Pirko, netdev, davem, fengguang.wu, dcbw

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.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation
  2017-04-14 16:10   ` Stephen Hemminger
@ 2017-04-14 23:02     ` Subash Abhinov Kasiviswanathan
  0 siblings, 0 replies; 8+ messages in thread
From: Subash Abhinov Kasiviswanathan @ 2017-04-14 23:02 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: netdev, davem, fengguang.wu, dcbw, jiri

>> +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.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation
  2017-04-14  9:07   ` Jiri Pirko
  2017-04-14 21:57     ` Subash Abhinov Kasiviswanathan
@ 2017-08-14 23:52     ` Subash Abhinov Kasiviswanathan
  1 sibling, 0 replies; 8+ messages in thread
From: Subash Abhinov Kasiviswanathan @ 2017-08-14 23:52 UTC (permalink / raw)
  To: Jiri Pirko; +Cc: netdev, davem, fengguang.wu, dcbw

>> + */
>> +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.

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2017-08-14 23:52 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-04-14  5:05 [PATCH net-next 0/1 v3] drivers: net: Add support for rmnet driver Subash Abhinov Kasiviswanathan
2017-04-14  5:05 ` [PATCH net-next 1/1 v3] drivers: net: rmnet: Initial implementation Subash Abhinov Kasiviswanathan
2017-04-14  9:07   ` Jiri Pirko
2017-04-14 21:57     ` Subash Abhinov Kasiviswanathan
2017-04-14 21:59       ` Stephen Hemminger
2017-08-14 23:52     ` Subash Abhinov Kasiviswanathan
2017-04-14 16:10   ` Stephen Hemminger
2017-04-14 23:02     ` Subash Abhinov Kasiviswanathan

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.