RmNet Data driver provides a transport agnostic MAP (multiplexing and
aggregation protocol) support in embedded and bridge modes. 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 <subas...@codeaurora.org>
---
 Documentation/networking/rmnet_data.txt |   82 +++
 include/uapi/linux/Kbuild               |    2 +
 include/uapi/linux/if_arp.h             |    1 +
 include/uapi/linux/if_ether.h           |    3 +-
 include/uapi/linux/rmnet_data.h         |  416 +++++++++++
 net/Kconfig                             |    1 +
 net/Makefile                            |    1 +
 net/rmnet_data/Kconfig                  |   21 +
 net/rmnet_data/Makefile                 |   14 +
 net/rmnet_data/rmnet_data_config.c      | 1149 +++++++++++++++++++++++++++++++
 net/rmnet_data/rmnet_data_config.h      |  123 ++++
 net/rmnet_data/rmnet_data_handlers.c    |  560 +++++++++++++++
 net/rmnet_data/rmnet_data_handlers.h    |   24 +
 net/rmnet_data/rmnet_data_main.c        |   60 ++
 net/rmnet_data/rmnet_data_private.h     |   76 ++
 net/rmnet_data/rmnet_data_stats.c       |   86 +++
 net/rmnet_data/rmnet_data_stats.h       |   61 ++
 net/rmnet_data/rmnet_data_trace.h       |  183 +++++
 net/rmnet_data/rmnet_data_vnd.c         |  602 ++++++++++++++++
 net/rmnet_data/rmnet_data_vnd.h         |   40 ++
 net/rmnet_data/rmnet_map.h              |  148 ++++
 net/rmnet_data/rmnet_map_command.c      |  180 +++++
 net/rmnet_data/rmnet_map_data.c         |  154 +++++
 23 files changed, 3986 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/networking/rmnet_data.txt
 create mode 100644 include/uapi/linux/rmnet_data.h
 create mode 100644 net/rmnet_data/Kconfig
 create mode 100644 net/rmnet_data/Makefile
 create mode 100644 net/rmnet_data/rmnet_data_config.c
 create mode 100644 net/rmnet_data/rmnet_data_config.h
 create mode 100644 net/rmnet_data/rmnet_data_handlers.c
 create mode 100644 net/rmnet_data/rmnet_data_handlers.h
 create mode 100644 net/rmnet_data/rmnet_data_main.c
 create mode 100644 net/rmnet_data/rmnet_data_private.h
 create mode 100644 net/rmnet_data/rmnet_data_stats.c
 create mode 100644 net/rmnet_data/rmnet_data_stats.h
 create mode 100644 net/rmnet_data/rmnet_data_trace.h
 create mode 100644 net/rmnet_data/rmnet_data_vnd.c
 create mode 100644 net/rmnet_data/rmnet_data_vnd.h
 create mode 100644 net/rmnet_data/rmnet_map.h
 create mode 100644 net/rmnet_data/rmnet_map_command.c
 create mode 100644 net/rmnet_data/rmnet_map_data.c

diff --git a/Documentation/networking/rmnet_data.txt 
b/Documentation/networking/rmnet_data.txt
new file mode 100644
index 0000000..ff6cce8
--- /dev/null
+++ b/Documentation/networking/rmnet_data.txt
@@ -0,0 +1,82 @@
+1. Introduction
+
+rmnet_data 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_data 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_data. Based on the multiplexer id, rmnet_data
+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_data 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_data in a single linear skb. rmnet_data 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_data userspace configuration is done through netlink library librmnetctl
+and command line utility rmnetcli. Utility is hosted in codeaurora forum git
+
+https://source.codeaurora.org/quic/la/platform/vendor/qcom-opensource/\
+dataservices/tree/rmnetctl
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index f330ba4..7283c1d 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -281,6 +281,7 @@ header-y += mroute6.h
 header-y += mroute.h
 header-y += msdos_fs.h
 header-y += msg.h
+header-y += msm_rmnet.h
 header-y += mtio.h
 header-y += nbd.h
 header-y += ncp_fs.h
@@ -366,6 +367,7 @@ header-y += resource.h
 header-y += rfkill.h
 header-y += rio_cm_cdev.h
 header-y += rio_mport_cdev.h
+header-y += rmnet_data.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 3e5185e..75a773f 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -103,7 +103,8 @@
 #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_data.h b/include/uapi/linux/rmnet_data.h
new file mode 100644
index 0000000..56215ad8
--- /dev/null
+++ b/include/uapi/linux/rmnet_data.h
@@ -0,0 +1,416 @@
+/* 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 configuration specification
+ */
+
+#ifndef _RMNET_DATA_H_
+#define _RMNET_DATA_H_
+
+/* Constants */
+#define RMNET_LOCAL_LOGICAL_ENDPOINT -1
+
+#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)
+
+/* Netlink API */
+#define RMNET_NETLINK_PROTO 31
+#define RMNET_MAX_STR_LEN  16
+#define RMNET_NL_DATA_MAX_LEN 64
+
+#define RMNET_NETLINK_MSG_COMMAND    0
+#define RMNET_NETLINK_MSG_RETURNCODE 1
+#define RMNET_NETLINK_MSG_RETURNDATA 2
+
+struct rmnet_nl_msg_s {
+       uint16_t reserved;
+       uint16_t message_type;
+       uint16_t reserved2:14;
+       uint16_t crd:2;
+       union {
+               uint16_t arg_length;
+               uint16_t return_code;
+       };
+       union {
+               uint8_t data[RMNET_NL_DATA_MAX_LEN];
+               struct {
+                       uint8_t  dev[RMNET_MAX_STR_LEN];
+                       uint32_t flags;
+                       uint16_t agg_size;
+                       uint16_t agg_count;
+                       uint8_t  tail_spacing;
+               } data_format;
+               struct {
+                       uint8_t dev[RMNET_MAX_STR_LEN];
+                       int32_t ep_id;
+                       uint8_t operating_mode;
+                       uint8_t next_dev[RMNET_MAX_STR_LEN];
+               } local_ep_config;
+               struct {
+                       uint32_t id;
+                       uint8_t  vnd_name[RMNET_MAX_STR_LEN];
+               } vnd;
+               struct {
+                       uint32_t id;
+                       uint32_t map_flow_id;
+                       uint32_t tc_flow_id;
+               } flow_control;
+       };
+};
+
+enum rmnet_netlink_message_types_e {
+       /* RMNET_NETLINK_ASSOCIATE_NETWORK_DEVICE - Register RMNET data driver
+        *                                          on a particular device.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        * Returns: status code
+        */
+       RMNET_NETLINK_ASSOCIATE_NETWORK_DEVICE,
+
+       /* RMNET_NETLINK_UNASSOCIATE_NETWORK_DEVICE - Unregister RMNET data
+        *                                            driver on a particular
+        *                                            device.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        * Returns: status code
+        */
+       RMNET_NETLINK_UNASSOCIATE_NETWORK_DEVICE,
+
+       /* RMNET_NETLINK_GET_NETWORK_DEVICE_ASSOCIATED - Get if RMNET data
+        *                                            driver is registered on a
+        *                                            particular device.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        * Returns: 1 if registered, 0 if not
+        */
+       RMNET_NETLINK_GET_NETWORK_DEVICE_ASSOCIATED,
+
+       /* RMNET_NETLINK_SET_LINK_EGRESS_DATA_FORMAT - Sets the egress data
+        *                                             format for a particular
+        *                                             link.
+        * Args: uint32_t egress_flags
+        *       char[] dev_name: Null terminated ASCII string, max length: 15
+        * Returns: status code
+        */
+       RMNET_NETLINK_SET_LINK_EGRESS_DATA_FORMAT,
+
+       /* RMNET_NETLINK_GET_LINK_EGRESS_DATA_FORMAT - Gets the egress data
+        *                                             format for a particular
+        *                                             link.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        * Returns: 4-bytes data: uint32_t egress_flags
+        */
+       RMNET_NETLINK_GET_LINK_EGRESS_DATA_FORMAT,
+
+       /* RMNET_NETLINK_SET_LINK_INGRESS_DATA_FORMAT - Sets the ingress data
+        *                                              format for a particular
+        *                                              link.
+        * Args: uint32_t ingress_flags
+        *       char[] dev_name: Null terminated ASCII string, max length: 15
+        * Returns: status code
+        */
+       RMNET_NETLINK_SET_LINK_INGRESS_DATA_FORMAT,
+
+       /* RMNET_NETLINK_GET_LINK_INGRESS_DATA_FORMAT - Gets the ingress data
+        *                                              format for a particular
+        *                                              link.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        * Returns: 4-bytes data: uint32_t ingress_flags
+        */
+       RMNET_NETLINK_GET_LINK_INGRESS_DATA_FORMAT,
+
+       /* RMNET_NETLINK_SET_LOGICAL_EP_CONFIG - Sets the logical endpoint
+        *                                       configuration for a particular
+        *                                       link.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        *     int32_t logical_ep_id, valid values are -1 through 31
+        *     uint8_t rmnet_mode: one of none, vnd, bridged
+        *     char[] egress_dev_name: Egress device if operating in bridge mode
+        * Returns: status code
+        */
+       RMNET_NETLINK_SET_LOGICAL_EP_CONFIG,
+
+       /* RMNET_NETLINK_UNSET_LOGICAL_EP_CONFIG - Un-sets the logical endpoint
+        *                                       configuration for a particular
+        *                                       link.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        *       int32_t logical_ep_id, valid values are -1 through 31
+        * Returns: status code
+        */
+       RMNET_NETLINK_UNSET_LOGICAL_EP_CONFIG,
+
+       /* RMNET_NETLINK_GET_LOGICAL_EP_CONFIG - Gets the logical endpoint
+        *                                       configuration for a particular
+        *                                       link.
+        * Args: char[] dev_name: Null terminated ASCII string, max length: 15
+        *        int32_t logical_ep_id, valid values are -1 through 31
+        * Returns: uint8_t rmnet_mode: one of none, vnd, bridged
+        * char[] egress_dev_name: Egress device
+        */
+       RMNET_NETLINK_GET_LOGICAL_EP_CONFIG,
+
+       /* RMNET_NETLINK_NEW_VND - Creates a new virtual network device node
+        * Args: int32_t node number
+        * Returns: status code
+        */
+       RMNET_NETLINK_NEW_VND,
+
+       /* RMNET_NETLINK_NEW_VND_WITH_PREFIX - Creates a new virtual network
+        *                                     device node with the specified
+        *                                     prefix for the device name
+        * Args: int32_t node number
+        *       char[] vnd_name - Use as prefix
+        * Returns: status code
+        */
+       RMNET_NETLINK_NEW_VND_WITH_PREFIX,
+
+       /* RMNET_NETLINK_GET_VND_NAME - Gets the string name of a VND from ID
+        * Args: int32_t node number
+        * Returns: char[] vnd_name
+        */
+       RMNET_NETLINK_GET_VND_NAME,
+
+       /* RMNET_NETLINK_FREE_VND - Removes virtual network device node
+        * Args: int32_t node number
+        * Returns: status code
+        */
+       RMNET_NETLINK_FREE_VND,
+
+       /* RMNET_NETLINK_ADD_VND_TC_FLOW - Add flow control handle on VND
+        * Args: int32_t node number
+        *       uint32_t MAP Flow Handle
+        *       uint32_t TC Flow Handle
+        * Returns: status code
+        */
+       RMNET_NETLINK_ADD_VND_TC_FLOW,
+
+       /* RMNET_NETLINK_DEL_VND_TC_FLOW - Removes flow control handle on VND
+        * Args: int32_t node number
+        *       uint32_t MAP Flow Handle
+        * Returns: status code
+        */
+       RMNET_NETLINK_DEL_VND_TC_FLOW
+};
+
+enum rmnet_config_endpoint_modes_e {
+       /* Pass the frame up the stack with no modifications to skb->dev      */
+       RMNET_EPMODE_NONE,
+       /* Replace skb->dev to a virtual rmnet device and pass up the stack   */
+       RMNET_EPMODE_VND,
+       /* Pass the frame directly to another device with dev_queue_xmit().   */
+       RMNET_EPMODE_BRIDGE,
+       /* Must be the last item in the list                                  */
+       RMNET_EPMODE_LENGTH
+};
+
+enum rmnet_config_return_codes_e {
+       RMNET_CONFIG_OK,
+       RMNET_CONFIG_UNKNOWN_MESSAGE,
+       RMNET_CONFIG_UNKNOWN_ERROR,
+       RMNET_CONFIG_NOMEM,
+       RMNET_CONFIG_DEVICE_IN_USE,
+       RMNET_CONFIG_INVALID_REQUEST,
+       RMNET_CONFIG_NO_SUCH_DEVICE,
+       RMNET_CONFIG_BAD_ARGUMENTS,
+       RMNET_CONFIG_BAD_EGRESS_DEVICE,
+       RMNET_CONFIG_TC_HANDLE_FULL
+};
+
+
+struct rmnet_map_header_s {
+#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS
+       uint8_t  pad_len:6;
+       uint8_t  reserved_bit:1;
+       uint8_t  cd_bit:1;
+#else
+       uint8_t  cd_bit:1;
+       uint8_t  reserved_bit:1;
+       uint8_t  pad_len:6;
+#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */
+       uint8_t  mux_id;
+       uint16_t 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_IP_VER_MASK 0xF0
+#define RMNET_IPV4        0x40
+#define RMNET_IPV6        0x60
+
+
+/* Bitmap macros for RmNET driver operation mode. */
+#define RMNET_MODE_NONE     (0x00)
+#define RMNET_MODE_LLP_ETH  (0x01)
+#define RMNET_MODE_LLP_IP   (0x02)
+#define RMNET_MODE_QOS      (0x04)
+#define RMNET_MODE_MASK     (RMNET_MODE_LLP_ETH | \
+                            RMNET_MODE_LLP_IP  | \
+                            RMNET_MODE_QOS)
+
+#define RMNET_IS_MODE_QOS(mode)  \
+       ((mode & RMNET_MODE_QOS) == RMNET_MODE_QOS)
+#define RMNET_IS_MODE_IP(mode)   \
+       ((mode & RMNET_MODE_LLP_IP) == RMNET_MODE_LLP_IP)
+
+/* IOCTL command enum
+ * Values chosen to not conflict with other drivers in the ecosystem */
+enum rmnet_ioctl_cmds_e {
+       RMNET_IOCTL_SET_LLP_ETHERNET = 0x000089F1, /* Set Ethernet protocol  */
+       RMNET_IOCTL_SET_LLP_IP       = 0x000089F2, /* Set RAWIP protocol     */
+       RMNET_IOCTL_GET_LLP          = 0x000089F3, /* Get link protocol      */
+       RMNET_IOCTL_SET_QOS_ENABLE   = 0x000089F4, /* Set QoS header enabled */
+       RMNET_IOCTL_SET_QOS_DISABLE  = 0x000089F5, /* Set QoS header disabled*/
+       RMNET_IOCTL_GET_QOS          = 0x000089F6, /* Get QoS header state   */
+       RMNET_IOCTL_GET_OPMODE       = 0x000089F7, /* Get operation mode     */
+       RMNET_IOCTL_OPEN             = 0x000089F8, /* Open transport port    */
+       RMNET_IOCTL_CLOSE            = 0x000089F9, /* Close transport port   */
+       RMNET_IOCTL_FLOW_ENABLE      = 0x000089FA, /* Flow enable            */
+       RMNET_IOCTL_FLOW_DISABLE     = 0x000089FB, /* Flow disable           */
+       RMNET_IOCTL_FLOW_SET_HNDL    = 0x000089FC, /* Set flow handle        */
+       RMNET_IOCTL_EXTENDED         = 0x000089FD, /* Extended IOCTLs        */
+       RMNET_IOCTL_MAX
+};
+
+enum rmnet_ioctl_extended_cmds_e {
+/* RmNet Data Required IOCTLs */
+       RMNET_IOCTL_GET_SUPPORTED_FEATURES     = 0x0000,   /* Get features    */
+       RMNET_IOCTL_SET_MRU                    = 0x0001,   /* Set MRU         */
+       RMNET_IOCTL_GET_MRU                    = 0x0002,   /* Get MRU         */
+       RMNET_IOCTL_GET_EPID                   = 0x0003,   /* Get endpoint ID */
+       RMNET_IOCTL_GET_DRIVER_NAME            = 0x0004,   /* Get driver name */
+       RMNET_IOCTL_ADD_MUX_CHANNEL            = 0x0005,   /* Add MUX ID      */
+       RMNET_IOCTL_SET_EGRESS_DATA_FORMAT     = 0x0006,   /* Set EDF         */
+       RMNET_IOCTL_SET_INGRESS_DATA_FORMAT    = 0x0007,   /* Set IDF         */
+       RMNET_IOCTL_SET_AGGREGATION_COUNT      = 0x0008,   /* Set agg count   */
+       RMNET_IOCTL_GET_AGGREGATION_COUNT      = 0x0009,   /* Get agg count   */
+       RMNET_IOCTL_SET_AGGREGATION_SIZE       = 0x000A,   /* Set agg size    */
+       RMNET_IOCTL_GET_AGGREGATION_SIZE       = 0x000B,   /* Get agg size    */
+       RMNET_IOCTL_FLOW_CONTROL               = 0x000C,   /* Do flow control */
+       RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL   = 0x000D,   /* For legacy use  */
+       RMNET_IOCTL_GET_HWSW_MAP               = 0x000E,   /* Get HW/SW map   */
+       RMNET_IOCTL_SET_RX_HEADROOM            = 0x000F,   /* RX Headroom     */
+       RMNET_IOCTL_GET_EP_PAIR                = 0x0010,   /* Endpoint pair   */
+       RMNET_IOCTL_SET_QOS_VERSION            = 0x0011,   /* 8/6 byte QoS hdr*/
+       RMNET_IOCTL_GET_QOS_VERSION            = 0x0012,   /* 8/6 byte QoS hdr*/
+       RMNET_IOCTL_GET_SUPPORTED_QOS_MODES    = 0x0013,   /* Get QoS modes   */
+       RMNET_IOCTL_SET_SLEEP_STATE            = 0x0014,   /* Set sleep state */
+       RMNET_IOCTL_SET_XLAT_DEV_INFO          = 0x0015,   /* xlat dev name   */
+       RMNET_IOCTL_DEREGISTER_DEV             = 0x0016,   /* Dereg a net dev */
+       RMNET_IOCTL_GET_SG_SUPPORT             = 0x0017,   /* Query sg support*/
+       RMNET_IOCTL_EXTENDED_MAX               = 0x0018
+};
+
+/* Return values for the RMNET_IOCTL_GET_SUPPORTED_FEATURES IOCTL */
+#define RMNET_IOCTL_FEAT_NOTIFY_MUX_CHANNEL              (1<<0)
+#define RMNET_IOCTL_FEAT_SET_EGRESS_DATA_FORMAT          (1<<1)
+#define RMNET_IOCTL_FEAT_SET_INGRESS_DATA_FORMAT         (1<<2)
+#define RMNET_IOCTL_FEAT_SET_AGGREGATION_COUNT           (1<<3)
+#define RMNET_IOCTL_FEAT_GET_AGGREGATION_COUNT           (1<<4)
+#define RMNET_IOCTL_FEAT_SET_AGGREGATION_SIZE            (1<<5)
+#define RMNET_IOCTL_FEAT_GET_AGGREGATION_SIZE            (1<<6)
+#define RMNET_IOCTL_FEAT_FLOW_CONTROL                    (1<<7)
+#define RMNET_IOCTL_FEAT_GET_DFLT_CONTROL_CHANNEL        (1<<8)
+#define RMNET_IOCTL_FEAT_GET_HWSW_MAP                    (1<<9)
+
+/* Input values for the RMNET_IOCTL_SET_EGRESS_DATA_FORMAT IOCTL  */
+#define RMNET_IOCTL_EGRESS_FORMAT_MAP                  (1<<1)
+#define RMNET_IOCTL_EGRESS_FORMAT_AGGREGATION          (1<<2)
+#define RMNET_IOCTL_EGRESS_FORMAT_MUXING               (1<<3)
+#define RMNET_IOCTL_EGRESS_FORMAT_CHECKSUM             (1<<4)
+
+/* Input values for the RMNET_IOCTL_SET_INGRESS_DATA_FORMAT IOCTL */
+#define RMNET_IOCTL_INGRESS_FORMAT_MAP                 (1<<1)
+#define RMNET_IOCTL_INGRESS_FORMAT_DEAGGREGATION       (1<<2)
+#define RMNET_IOCTL_INGRESS_FORMAT_DEMUXING            (1<<3)
+#define RMNET_IOCTL_INGRESS_FORMAT_CHECKSUM            (1<<4)
+#define RMNET_IOCTL_INGRESS_FORMAT_AGG_DATA            (1<<5)
+
+/* User space may not have this defined. */
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+struct rmnet_ioctl_extended_s {
+       uint32_t   extended_ioctl;
+       union {
+               uint32_t data; /* Generic data field for most extended IOCTLs */
+
+               /* Return values for
+                *    RMNET_IOCTL_GET_DRIVER_NAME
+                *    RMNET_IOCTL_GET_DFLT_CONTROL_CHANNEL */
+               int8_t if_name[IFNAMSIZ];
+
+               /* Input values for the RMNET_IOCTL_ADD_MUX_CHANNEL IOCTL */
+               struct {
+                       uint32_t  mux_id;
+                       int8_t    vchannel_name[IFNAMSIZ];
+               } rmnet_mux_val;
+
+               /* Input values for the RMNET_IOCTL_FLOW_CONTROL IOCTL */
+               struct {
+                       uint8_t   flow_mode;
+                       uint8_t   mux_id;
+               } flow_control_prop;
+
+               /* Return values for RMNET_IOCTL_GET_EP_PAIR */
+               struct {
+                       uint32_t   consumer_pipe_num;
+                       uint32_t   producer_pipe_num;
+               } ipa_ep_pair;
+
+               struct {
+                       uint32_t __data; /* Placeholder for legacy data*/
+                       uint32_t agg_size;
+                       uint32_t agg_count;
+               } ingress_format;
+       } u;
+};
+
+struct rmnet_ioctl_data_s {
+       union {
+               uint32_t        operation_mode;
+               uint32_t        tcm_handle;
+       } u;
+};
+
+#define RMNET_IOCTL_QOS_MODE_6   (1<<0)
+#define RMNET_IOCTL_QOS_MODE_8   (1<<1)
+
+/* QMI QoS header definition */
+#define QMI_QOS_HDR_S  __attribute((__packed__)) qmi_qos_hdr_s
+struct QMI_QOS_HDR_S {
+       unsigned char    version;
+       unsigned char    flags;
+       uint32_t         flow_id;
+};
+
+/* QMI QoS 8-byte header. */
+struct qmi_qos_hdr8_s {
+       struct QMI_QOS_HDR_S   hdr;
+       uint8_t                reserved[2];
+} __attribute((__packed__));
+
+#endif /* _RMNET_DATA_H_ */
diff --git a/net/Kconfig b/net/Kconfig
index a29bb4b..3ac7634 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -238,6 +238,7 @@ source "net/switchdev/Kconfig"
 source "net/l3mdev/Kconfig"
 source "net/qrtr/Kconfig"
 source "net/ncsi/Kconfig"
+source "net/rmnet_data/Kconfig"
 
 config RPS
        bool
diff --git a/net/Makefile b/net/Makefile
index 4cafaa2..1003739 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -81,3 +81,4 @@ obj-y                         += l3mdev/
 endif
 obj-$(CONFIG_QRTR)             += qrtr/
 obj-$(CONFIG_NET_NCSI)         += ncsi/
+obj-$(CONFIG_RMNET_DATA)       += rmnet_data/
diff --git a/net/rmnet_data/Kconfig b/net/rmnet_data/Kconfig
new file mode 100644
index 0000000..4748a6f
--- /dev/null
+++ b/net/rmnet_data/Kconfig
@@ -0,0 +1,21 @@
+#
+# RMNET Data and MAP driver
+#
+
+menuconfig RMNET_DATA
+       depends on NETDEVICES
+       bool "RmNet Data and MAP driver"
+       ---help---
+         If you say Y here, then the rmnet_data module will be statically
+         compiled into the kernel. The rmnet data module provides MAP
+         functionality for embedded and bridged traffic.
+if RMNET_DATA
+
+config RMNET_DATA_DEBUG
+       bool "RmNet Data Debug Logging"
+       ---help---
+         Say Y here if you want RmNet data 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_DATA
diff --git a/net/rmnet_data/Makefile b/net/rmnet_data/Makefile
new file mode 100644
index 0000000..ccb8b5b
--- /dev/null
+++ b/net/rmnet_data/Makefile
@@ -0,0 +1,14 @@
+#
+# Makefile for the RMNET Data module
+#
+
+rmnet_data-y            := rmnet_data_main.o
+rmnet_data-y            += rmnet_data_config.o
+rmnet_data-y            += rmnet_data_vnd.o
+rmnet_data-y            += rmnet_data_handlers.o
+rmnet_data-y            += rmnet_map_data.o
+rmnet_data-y            += rmnet_map_command.o
+rmnet_data-y            += rmnet_data_stats.o
+obj-$(CONFIG_RMNET_DATA) += rmnet_data.o
+
+CFLAGS_rmnet_data_main.o := -I$(src)
diff --git a/net/rmnet_data/rmnet_data_config.c 
b/net/rmnet_data/rmnet_data_config.c
new file mode 100644
index 0000000..558f2f9
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_config.c
@@ -0,0 +1,1149 @@
+/* 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 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_data.h>
+#include "rmnet_data_config.h"
+#include "rmnet_data_handlers.h"
+#include "rmnet_data_vnd.h"
+#include "rmnet_data_private.h"
+#include "rmnet_data_trace.h"
+
+RMNET_LOG_MODULE(RMNET_DATA_LOGMASK_CONFIG);
+
+/* Local Definitions and Declarations */
+static struct sock *nl_socket_handle;
+
+static struct netlink_kernel_cfg rmnet_netlink_cfg = {
+       .input = rmnet_config_netlink_msg_handler
+};
+
+static struct notifier_block rmnet_dev_notifier = {
+       .notifier_call = rmnet_config_notify_cb,
+       .next = 0,
+       .priority = 0
+};
+
+#define RMNET_NL_MSG_SIZE(Y) (sizeof(((struct rmnet_nl_msg_s *)0)->Y))
+
+struct rmnet_free_vnd_work {
+       struct work_struct work;
+       int vnd_id[RMNET_DATA_MAX_VND];
+       int count;
+};
+
+/* Init and Cleanup */
+
+static struct sock *_rmnet_config_start_netlink(void)
+{
+       return netlink_kernel_create(&init_net,
+                                    RMNET_NETLINK_PROTO,
+                                    &rmnet_netlink_cfg);
+}
+
+/* rmnet_config_init() - Startup init
+ *
+ * Registers netlink protocol with kernel and opens socket. Netlink handler is
+ * registered with kernel.
+ */
+int rmnet_config_init(void)
+{
+       int rc;
+       nl_socket_handle = _rmnet_config_start_netlink();
+       if (!nl_socket_handle) {
+               LOGE("%s", "Failed to init netlink socket");
+               return RMNET_INIT_ERROR;
+       }
+
+       rc = register_netdevice_notifier(&rmnet_dev_notifier);
+       if (rc != 0) {
+               LOGE("Failed to register device notifier; rc=%d", rc);
+               /* TODO: Cleanup the nl socket */
+               return RMNET_INIT_ERROR;
+       }
+
+       return 0;
+}
+
+/* rmnet_config_exit() - Cleans up all netlink related resources
+ */
+void rmnet_config_exit(void)
+{
+       int rc;
+       netlink_kernel_release(nl_socket_handle);
+       rc = unregister_netdevice_notifier(&rmnet_dev_notifier);
+       if (rc != 0)
+               LOGE("Failed to unregister device notifier; rc=%d", rc);
+}
+
+/* Helper Functions */
+
+/* _rmnet_is_physical_endpoint_associated() - Determines if device is 
associated
+ * @dev:      Device to get check
+ *
+ * Compares device rx_handler callback pointer against known funtion
+ *
+ * Return:
+ *      - 1 if associated
+ *      - 0 if NOT associated
+ */
+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
+ *
+ * Return:
+ *     - pointer to configuration if successful
+ *     - 0 (null) if device is not associated
+ */
+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;
+}
+
+/* _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.
+ *
+ * Return:
+ *      - End point configuration structure
+ *      - NULL in case of an error
+ */
+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;
+}
+
+/* Netlink Handler */
+#define _RMNET_NETLINK_NULL_CHECKS() do { if (!rmnet_header || !resp_rmnet) \
+                       BUG(); \
+               } while (0)
+
+static void _rmnet_netlink_set_link_egress_data_format
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       _RMNET_NETLINK_NULL_CHECKS();
+
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+       dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev);
+
+       if (!dev) {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       resp_rmnet->return_code =
+               rmnet_set_egress_data_format(dev,
+                                            rmnet_header->data_format.flags,
+                                            rmnet_header->data_format.agg_size,
+                                            rmnet_header->data_format.agg_count
+                                            );
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_set_link_ingress_data_format
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       _RMNET_NETLINK_NULL_CHECKS();
+
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+
+       dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev);
+       if (!dev) {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       resp_rmnet->return_code = rmnet_set_ingress_data_format(
+                                       dev,
+                                       rmnet_header->data_format.flags,
+                                       rmnet_header->data_format.tail_spacing);
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_set_logical_ep_config
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev, *dev2;
+       _RMNET_NETLINK_NULL_CHECKS();
+
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+       if (rmnet_header->local_ep_config.ep_id < -1
+           || rmnet_header->local_ep_config.ep_id > 254) {
+               resp_rmnet->return_code = RMNET_CONFIG_BAD_ARGUMENTS;
+               return;
+       }
+
+       dev = dev_get_by_name(&init_net,
+                               rmnet_header->local_ep_config.dev);
+
+       dev2 = dev_get_by_name(&init_net,
+                               rmnet_header->local_ep_config.next_dev);
+
+
+       if (dev && dev2)
+               resp_rmnet->return_code =
+                       rmnet_set_logical_endpoint_config(
+                               dev,
+                               rmnet_header->local_ep_config.ep_id,
+                               rmnet_header->local_ep_config.operating_mode,
+                               dev2);
+       else
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       if (dev)
+               dev_put(dev);
+       if (dev2)
+               dev_put(dev2);
+}
+
+static void _rmnet_netlink_unset_logical_ep_config
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       _RMNET_NETLINK_NULL_CHECKS();
+
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+       if (rmnet_header->local_ep_config.ep_id < -1
+           || rmnet_header->local_ep_config.ep_id > 254) {
+               resp_rmnet->return_code = RMNET_CONFIG_BAD_ARGUMENTS;
+               return;
+       }
+
+       dev = dev_get_by_name(&init_net,
+                               rmnet_header->local_ep_config.dev);
+
+       if (dev) {
+               resp_rmnet->return_code =
+                       rmnet_unset_logical_endpoint_config(
+                               dev,
+                               rmnet_header->local_ep_config.ep_id);
+               dev_put(dev);
+       } else {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+       }
+}
+
+static void _rmnet_netlink_get_logical_ep_config
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       _RMNET_NETLINK_NULL_CHECKS();
+
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+       if (rmnet_header->local_ep_config.ep_id < -1
+           || rmnet_header->local_ep_config.ep_id > 254) {
+               resp_rmnet->return_code = RMNET_CONFIG_BAD_ARGUMENTS;
+               return;
+       }
+
+       dev = dev_get_by_name(&init_net,
+                               rmnet_header->local_ep_config.dev);
+
+       if (dev)
+               resp_rmnet->return_code =
+                       rmnet_get_logical_endpoint_config(
+                               dev,
+                               rmnet_header->local_ep_config.ep_id,
+                               &resp_rmnet->local_ep_config.operating_mode,
+                               resp_rmnet->local_ep_config.next_dev,
+                               sizeof(resp_rmnet->local_ep_config.next_dev));
+       else {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       if (resp_rmnet->return_code == RMNET_CONFIG_OK) {
+               /* Begin Data */
+               resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA;
+               resp_rmnet->arg_length = RMNET_NL_MSG_SIZE(local_ep_config);
+       }
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_associate_network_device
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       _RMNET_NETLINK_NULL_CHECKS();
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+
+       dev = dev_get_by_name(&init_net, rmnet_header->data);
+       if (!dev) {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       resp_rmnet->return_code = rmnet_associate_network_device(dev);
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_unassociate_network_device
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       _RMNET_NETLINK_NULL_CHECKS();
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+
+       dev = dev_get_by_name(&init_net, rmnet_header->data);
+       if (!dev) {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       resp_rmnet->return_code = rmnet_unassociate_network_device(dev);
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_get_network_device_associated
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+
+       _RMNET_NETLINK_NULL_CHECKS();
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+
+       dev = dev_get_by_name(&init_net, rmnet_header->data);
+       if (!dev) {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       resp_rmnet->return_code = _rmnet_is_physical_endpoint_associated(dev);
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA;
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_get_link_egress_data_format
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       struct rmnet_phys_ep_conf_s *config;
+       _RMNET_NETLINK_NULL_CHECKS();
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+
+
+       dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev);
+       if (!dev) {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       config = _rmnet_get_phys_ep_config(dev);
+       if (!config) {
+               resp_rmnet->return_code = RMNET_CONFIG_INVALID_REQUEST;
+               dev_put(dev);
+               return;
+       }
+
+       /* Begin Data */
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA;
+       resp_rmnet->arg_length = RMNET_NL_MSG_SIZE(data_format);
+       resp_rmnet->data_format.flags = config->egress_data_format;
+       resp_rmnet->data_format.agg_count = config->egress_agg_count;
+       resp_rmnet->data_format.agg_size  = config->egress_agg_size;
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_get_link_ingress_data_format
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       struct net_device *dev;
+       struct rmnet_phys_ep_conf_s *config;
+       _RMNET_NETLINK_NULL_CHECKS();
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+
+
+       dev = dev_get_by_name(&init_net, rmnet_header->data_format.dev);
+       if (!dev) {
+               resp_rmnet->return_code = RMNET_CONFIG_NO_SUCH_DEVICE;
+               return;
+       }
+
+       config = _rmnet_get_phys_ep_config(dev);
+       if (!config) {
+               resp_rmnet->return_code = RMNET_CONFIG_INVALID_REQUEST;
+               dev_put(dev);
+               return;
+       }
+
+       /* Begin Data */
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA;
+       resp_rmnet->arg_length = RMNET_NL_MSG_SIZE(data_format);
+       resp_rmnet->data_format.flags = config->ingress_data_format;
+       resp_rmnet->data_format.tail_spacing = config->tail_spacing;
+       dev_put(dev);
+}
+
+static void _rmnet_netlink_get_vnd_name
+                                       (struct rmnet_nl_msg_s *rmnet_header,
+                                        struct rmnet_nl_msg_s *resp_rmnet)
+{
+       int r;
+       _RMNET_NETLINK_NULL_CHECKS();
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+
+       r = rmnet_vnd_get_name(rmnet_header->vnd.id, resp_rmnet->vnd.vnd_name,
+                              RMNET_MAX_STR_LEN);
+
+       if (r != 0) {
+               resp_rmnet->return_code = RMNET_CONFIG_INVALID_REQUEST;
+               return;
+       }
+
+       /* Begin Data */
+       resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNDATA;
+       resp_rmnet->arg_length = RMNET_NL_MSG_SIZE(vnd);
+}
+
+/* rmnet_config_netlink_msg_handler() - Netlink message handler callback
+ * @skb:      Packet containing netlink messages
+ *
+ * Standard kernel-expected format for a netlink message handler. Processes 
SKBs
+ * which contain RmNet data specific netlink messages.
+ */
+void rmnet_config_netlink_msg_handler(struct sk_buff *skb)
+{
+       struct nlmsghdr *nlmsg_header, *resp_nlmsg;
+       struct rmnet_nl_msg_s *rmnet_header, *resp_rmnet;
+       int return_pid, response_data_length;
+       struct sk_buff *skb_response;
+
+       response_data_length = 0;
+       nlmsg_header = (struct nlmsghdr *) skb->data;
+       rmnet_header = (struct rmnet_nl_msg_s *) nlmsg_data(nlmsg_header);
+
+       if (!nlmsg_header->nlmsg_pid ||
+           (nlmsg_header->nlmsg_len < sizeof(struct nlmsghdr) +
+                                      sizeof(struct rmnet_nl_msg_s)))
+               return;
+
+       LOGL("Netlink message pid=%d, seq=%d, length=%d, rmnet_type=%d",
+               nlmsg_header->nlmsg_pid,
+               nlmsg_header->nlmsg_seq,
+               nlmsg_header->nlmsg_len,
+               rmnet_header->message_type);
+
+       return_pid = nlmsg_header->nlmsg_pid;
+
+       skb_response = nlmsg_new(sizeof(struct nlmsghdr)
+                                + sizeof(struct rmnet_nl_msg_s),
+                                GFP_KERNEL);
+
+       if (!skb_response) {
+               LOGH("%s", "Failed to allocate response buffer");
+               return;
+       }
+
+       resp_nlmsg = nlmsg_put(skb_response,
+                              0,
+                              nlmsg_header->nlmsg_seq,
+                              NLMSG_DONE,
+                              sizeof(struct rmnet_nl_msg_s),
+                              0);
+
+       resp_rmnet = nlmsg_data(resp_nlmsg);
+
+       if (!resp_rmnet)
+               BUG();
+
+       resp_rmnet->message_type = rmnet_header->message_type;
+       rtnl_lock();
+       switch (rmnet_header->message_type) {
+       case RMNET_NETLINK_ASSOCIATE_NETWORK_DEVICE:
+               _rmnet_netlink_associate_network_device
+                                               (rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_UNASSOCIATE_NETWORK_DEVICE:
+               _rmnet_netlink_unassociate_network_device
+                                               (rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_GET_NETWORK_DEVICE_ASSOCIATED:
+               _rmnet_netlink_get_network_device_associated
+                                               (rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_SET_LINK_EGRESS_DATA_FORMAT:
+               _rmnet_netlink_set_link_egress_data_format
+                                               (rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_GET_LINK_EGRESS_DATA_FORMAT:
+               _rmnet_netlink_get_link_egress_data_format
+                                               (rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_SET_LINK_INGRESS_DATA_FORMAT:
+               _rmnet_netlink_set_link_ingress_data_format
+                                               (rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_GET_LINK_INGRESS_DATA_FORMAT:
+               _rmnet_netlink_get_link_ingress_data_format
+                                               (rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_SET_LOGICAL_EP_CONFIG:
+               _rmnet_netlink_set_logical_ep_config(rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_UNSET_LOGICAL_EP_CONFIG:
+               _rmnet_netlink_unset_logical_ep_config(rmnet_header,
+                                                      resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_GET_LOGICAL_EP_CONFIG:
+               _rmnet_netlink_get_logical_ep_config(rmnet_header, resp_rmnet);
+               break;
+
+       case RMNET_NETLINK_NEW_VND:
+               resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+               resp_rmnet->return_code =
+                                        rmnet_create_vnd(rmnet_header->vnd.id);
+               break;
+
+       case RMNET_NETLINK_NEW_VND_WITH_PREFIX:
+               resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+               resp_rmnet->return_code = rmnet_create_vnd_prefix(
+                                               rmnet_header->vnd.id,
+                                               rmnet_header->vnd.vnd_name);
+               break;
+
+       case RMNET_NETLINK_FREE_VND:
+               resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+               /* Please check rmnet_vnd_free_dev documentation regarding
+                  the below locking sequence
+               */
+               rtnl_unlock();
+               resp_rmnet->return_code = rmnet_free_vnd(rmnet_header->vnd.id);
+               rtnl_lock();
+               break;
+
+       case RMNET_NETLINK_GET_VND_NAME:
+               _rmnet_netlink_get_vnd_name(rmnet_header, resp_rmnet);
+               break;
+
+       default:
+               resp_rmnet->crd = RMNET_NETLINK_MSG_RETURNCODE;
+               resp_rmnet->return_code = RMNET_CONFIG_UNKNOWN_MESSAGE;
+               break;
+       }
+       rtnl_unlock();
+       nlmsg_unicast(nl_socket_handle, skb_response, return_pid);
+       LOGD("%s", "Done processing command");
+
+}
+
+/* Configuration API */
+
+/* rmnet_unassociate_network_device() - Unassociate network device
+ * @dev:      Device to unassociate
+ *
+ * Frees all structures generate for device. Unregisters rx_handler
+ * todo: needs to do some sanity verification first (is device in use, etc...)
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE dev is null
+ *      - RMNET_CONFIG_INVALID_REQUEST if device is not already associated
+ *      - RMNET_CONFIG_DEVICE_IN_USE if device has logical ep that wasn't unset
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ */
+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)
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       if (!_rmnet_is_physical_endpoint_associated(dev))
+               return RMNET_CONFIG_INVALID_REQUEST;
+
+       for (; config_id < RMNET_DATA_MAX_LOGICAL_EP; config_id++) {
+               epconfig_l = _rmnet_get_logical_ep(dev, config_id);
+               if (epconfig_l && epconfig_l->refcount)
+                       return RMNET_CONFIG_DEVICE_IN_USE;
+       }
+
+       config = (struct rmnet_phys_ep_conf_s *)
+               rcu_dereference(dev->rx_handler_data);
+
+       if (!config)
+               return RMNET_CONFIG_UNKNOWN_ERROR;
+
+       kfree(config);
+
+       netdev_rx_handler_unregister(dev);
+
+       /* Explicitly release the reference from the device */
+       dev_put(dev);
+       trace_rmnet_unassociate(dev);
+       return RMNET_CONFIG_OK;
+}
+
+/* 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
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE dev is null
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ */
+int rmnet_set_ingress_data_format(struct net_device *dev,
+                                 uint32_t ingress_data_format,
+                                 uint8_t  tail_spacing)
+{
+       struct rmnet_phys_ep_conf_s *config;
+       ASSERT_RTNL();
+
+       LOGL("(%s,0x%08X);", dev->name, ingress_data_format);
+
+       if (!dev)
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       config = _rmnet_get_phys_ep_config(dev);
+
+       if (!config)
+               return RMNET_CONFIG_INVALID_REQUEST;
+
+       config->ingress_data_format = ingress_data_format;
+       config->tail_spacing = tail_spacing;
+
+       return RMNET_CONFIG_OK;
+}
+
+/* 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
+ * todo: Bounds check on agg_*
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE dev is null
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ */
+int rmnet_set_egress_data_format(struct net_device *dev,
+                                uint32_t egress_data_format,
+                                uint16_t agg_size,
+                                uint16_t 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 RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       config = _rmnet_get_phys_ep_config(dev);
+
+       if (!config)
+               return RMNET_CONFIG_UNKNOWN_ERROR;
+
+       config->egress_data_format = egress_data_format;
+       config->egress_agg_size = agg_size;
+       config->egress_agg_count = agg_count;
+
+       return RMNET_CONFIG_OK;
+}
+
+/* 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.
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE dev is null
+ *      - RMNET_CONFIG_INVALID_REQUEST if the device to be associated is a vnd
+ *      - RMNET_CONFIG_DEVICE_IN_USE if dev rx_handler is already filled
+ *      - RMNET_CONFIG_DEVICE_IN_USE if netdev_rx_handler_register() fails
+ */
+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)
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       if (_rmnet_is_physical_endpoint_associated(dev)) {
+               LOGM("%s is already regestered", dev->name);
+               return RMNET_CONFIG_DEVICE_IN_USE;
+       }
+
+       if (rmnet_vnd_is_vnd(dev)) {
+               LOGM("%s is a vnd", dev->name);
+               return RMNET_CONFIG_INVALID_REQUEST;
+       }
+
+       config = kmalloc(sizeof(*config), GFP_ATOMIC);
+
+       if (!config)
+               return RMNET_CONFIG_NOMEM;
+
+       memset(config, 0, sizeof(struct rmnet_phys_ep_conf_s));
+       config->dev = dev;
+       spin_lock_init(&config->agg_lock);
+
+       rc = netdev_rx_handler_register(dev, rmnet_rx_handler, config);
+
+       if (rc) {
+               LOGM("netdev_rx_handler_register returns %d", rc);
+               kfree(config);
+               return RMNET_CONFIG_DEVICE_IN_USE;
+       }
+
+       /* Explicitly hold a reference to the device */
+       dev_hold(dev);
+       trace_rmnet_associate(dev);
+       return RMNET_CONFIG_OK;
+}
+
+/* _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:    endpoing configuration structure to set
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE if device to set config on is null
+ *      - RMNET_CONFIG_DEVICE_IN_USE if device already has a logical ep
+ *      - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range
+ */
+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)
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       if (config_id < RMNET_LOCAL_LOGICAL_ENDPOINT
+               || config_id >= RMNET_DATA_MAX_LOGICAL_EP)
+               return RMNET_CONFIG_BAD_ARGUMENTS;
+
+       epconfig_l = _rmnet_get_logical_ep(dev, config_id);
+
+       if (!epconfig_l)
+                       return RMNET_CONFIG_UNKNOWN_ERROR;
+
+       if (epconfig_l->refcount)
+               return RMNET_CONFIG_DEVICE_IN_USE;
+
+       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 RMNET_CONFIG_OK;
+}
+
+/* _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
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE if device to set config on is null
+ *      - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range
+ */
+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)
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       if (config_id < RMNET_LOCAL_LOGICAL_ENDPOINT
+               || config_id >= RMNET_DATA_MAX_LOGICAL_EP)
+               return RMNET_CONFIG_BAD_ARGUMENTS;
+
+       epconfig_l = _rmnet_get_logical_ep(dev, config_id);
+
+       if (!epconfig_l || !epconfig_l->refcount)
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       /* 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 RMNET_CONFIG_OK;
+}
+
+/* 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
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_BAD_EGRESS_DEVICE if egress device is null
+ *      - RMNET_CONFIG_BAD_EGRESS_DEVICE if egress device is not handled by
+ *                                       RmNet data module
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE if device to set config on is null
+ *      - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range
+ */
+int rmnet_set_logical_endpoint_config(struct net_device *dev,
+                                     int config_id,
+                                     uint8_t 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 RMNET_CONFIG_BAD_EGRESS_DEVICE;
+       }
+
+       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
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE device is not associated
+ *      - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range
+ */
+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 RMNET_CONFIG_NO_SUCH_DEVICE;
+       }
+
+       return _rmnet_unset_logical_endpoint_config(dev, config_id);
+}
+
+/* rmnet_get_logical_endpoint_config() - Gets logical endpoing configuration
+ * for a device
+ * @dev:                  Device to get endpoint configuration on
+ * @config_id:            logical endpoint id on device
+ * @rmnet_mode:           (I/O) logical endpoint mode
+ * @egress_dev_name:      (I/O) logical endpoint egress device name
+ * @egress_dev_name_size: The maximal size of the I/O egress_dev_name
+ *
+ * Retrieves the logical_endpoint_config structure.
+ * Network device must already have association with RmNet Data driver
+ *
+ * Return:
+ *      - RMNET_CONFIG_OK if successful
+ *      - RMNET_CONFIG_UNKNOWN_ERROR net_device private section is null
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE device is not associated
+ *      - RMNET_CONFIG_BAD_ARGUMENTS if logical endpoint id is out of range or
+ *        if the provided buffer size for egress dev name is too short
+ */
+int rmnet_get_logical_endpoint_config(struct net_device *dev,
+                                     int config_id,
+                                     uint8_t *rmnet_mode,
+                                     uint8_t *egress_dev_name,
+                                     size_t egress_dev_name_size)
+{
+       struct rmnet_logical_ep_conf_s *epconfig_l = 0;
+       size_t strlcpy_res = 0;
+
+       LOGL("(%s, %d);", dev->name, config_id);
+
+       if (!egress_dev_name || !rmnet_mode)
+               return RMNET_CONFIG_BAD_ARGUMENTS;
+       if (config_id < RMNET_LOCAL_LOGICAL_ENDPOINT
+               || config_id >= RMNET_DATA_MAX_LOGICAL_EP)
+               return RMNET_CONFIG_BAD_ARGUMENTS;
+
+       epconfig_l = _rmnet_get_logical_ep(dev, config_id);
+
+       if (!epconfig_l || !epconfig_l->refcount)
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+
+       *rmnet_mode = epconfig_l->rmnet_mode;
+
+       strlcpy_res = strlcpy(egress_dev_name, epconfig_l->egress_dev->name,
+                             egress_dev_name_size);
+
+       if (strlcpy_res >= egress_dev_name_size)
+               return RMNET_CONFIG_BAD_ARGUMENTS;
+
+       return RMNET_CONFIG_OK;
+}
+
+/* rmnet_create_vnd() - Create virtual network device node
+ * @id:       RmNet virtual device node id
+ *
+ * Return:
+ *      - result of rmnet_vnd_create_dev()
+ */
+int rmnet_create_vnd(int id)
+{
+       struct net_device *dev;
+       ASSERT_RTNL();
+       LOGL("(%d);", id);
+       return rmnet_vnd_create_dev(id, &dev, NULL);
+}
+
+/* rmnet_create_vnd() - Create virtual network device node
+ * @id:       RmNet virtual device node id
+ * @prefix:   String prefix for device name
+ *
+ * Return:
+ *      - result of rmnet_vnd_create_dev()
+ */
+int rmnet_create_vnd_prefix(int id, const char *prefix)
+{
+       struct net_device *dev;
+       ASSERT_RTNL();
+       LOGL("(%d, \"%s\");", id, prefix);
+       return rmnet_vnd_create_dev(id, &dev, prefix);
+}
+
+/* rmnet_free_vnd() - Free virtual network device node
+ * @id:       RmNet virtual device node id
+ *
+ * Return:
+ *      - result of rmnet_vnd_free_dev()
+ */
+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
+ *
+ * Return:
+ *      - void
+ */
+static void rmnet_force_unassociate_device(struct net_device *dev)
+{
+       int i, j;
+       struct net_device *vndev;
+       struct rmnet_logical_ep_conf_s *cfg;
+       struct rmnet_free_vnd_work *vnd_work;
+       ASSERT_RTNL();
+
+       if (!dev)
+               BUG();
+
+       if (!_rmnet_is_physical_endpoint_associated(dev)) {
+               LOGM("%s", "Called on unassociated device, skipping");
+               return;
+       }
+
+       trace_rmnet_unregister_cb_clear_vnds(dev);
+       vnd_work = kmalloc(sizeof(*vnd_work), GFP_KERNEL);
+       if (!vnd_work) {
+               LOGH("%s", "Out of Memory");
+               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_DATA_MAX_VND &&
+                               j < RMNET_DATA_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) {
+                       LOGH("Got NULL config from VND %d", i);
+                       BUG();
+                       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);
+       }
+
+
+       /* Clear the mappings on the phys ep */
+       trace_rmnet_unregister_cb_clear_lepcs(dev);
+       rmnet_unset_logical_endpoint_config(dev, RMNET_LOCAL_LOGICAL_ENDPOINT);
+       for (i = 0; i < RMNET_DATA_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
+ *
+ * Return:
+ *      - result of NOTIFY_DONE()
+ */
+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)
+               BUG();
+
+       switch (event) {
+       case NETDEV_UNREGISTER_FINAL:
+       case NETDEV_UNREGISTER:
+               trace_rmnet_unregister_cb_entry(dev);
+               LOGH("Kernel is trying to unregister %s", dev->name);
+               rmnet_force_unassociate_device(dev);
+               trace_rmnet_unregister_cb_exit(dev);
+               break;
+
+       default:
+               trace_rmnet_unregister_cb_unhandled(dev);
+               LOGD("Unhandeled event [%lu]", event);
+               break;
+       }
+
+       return NOTIFY_DONE;
+}
diff --git a/net/rmnet_data/rmnet_data_config.h 
b/net/rmnet_data/rmnet_data_config.h
new file mode 100644
index 0000000..6645d9d
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_config.h
@@ -0,0 +1,123 @@
+/* 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/spinlock.h>
+
+#ifndef _RMNET_DATA_CONFIG_H_
+#define _RMNET_DATA_CONFIG_H_
+
+#define RMNET_DATA_MAX_LOGICAL_EP 256
+
+/* 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 {
+       uint8_t refcount;
+       uint8_t rmnet_mode;
+       uint8_t 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_data.
+ *
+ * @dev: The device which is associated with rmnet_data. 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_data.h
+ * @egress_data_format: RMNET_EGRESS_FORMAT_* flags from rmnet_data.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_DATA_MAX_LOGICAL_EP];
+       uint32_t ingress_data_format;
+       uint32_t egress_data_format;
+
+       /* MAP specific */
+       uint16_t egress_agg_size;
+       uint16_t egress_agg_count;
+       uint8_t tail_spacing;
+       /* MAP aggregation state machine
+        *  - This is not sctrictly configuration and is updated at runtime
+        *    Make sure all of these are protected by the agg_lock
+        */
+       spinlock_t agg_lock;
+       struct sk_buff *agg_skb;
+       uint8_t agg_state;
+       uint8_t agg_count;
+       struct timespec agg_time;
+       struct timespec agg_last;
+};
+
+int rmnet_config_init(void);
+void rmnet_config_exit(void);
+
+int rmnet_unassociate_network_device(struct net_device *dev);
+int rmnet_set_ingress_data_format(struct net_device *dev,
+                                 uint32_t ingress_data_format,
+                                 uint8_t  tail_spacing);
+int rmnet_set_egress_data_format(struct net_device *dev,
+                                uint32_t egress_data_format,
+                                uint16_t agg_size,
+                                uint16_t agg_count);
+int rmnet_associate_network_device(struct net_device *dev);
+int _rmnet_set_logical_endpoint_config(struct net_device *dev,
+                                      int config_id,
+                                     struct rmnet_logical_ep_conf_s *epconfig);
+int rmnet_set_logical_endpoint_config(struct net_device *dev,
+                                     int config_id,
+                                     uint8_t rmnet_mode,
+                                     struct net_device *egress_dev);
+int _rmnet_unset_logical_endpoint_config(struct net_device *dev,
+                                        int config_id);
+int rmnet_unset_logical_endpoint_config(struct net_device *dev,
+                                       int config_id);
+int _rmnet_get_logical_endpoint_config(struct net_device *dev,
+                                      int config_id,
+                                     struct rmnet_logical_ep_conf_s *epconfig);
+int rmnet_get_logical_endpoint_config(struct net_device *dev,
+                                     int config_id,
+                                     uint8_t *rmnet_mode,
+                                     uint8_t *egress_dev_name,
+                                     size_t egress_dev_name_size);
+void rmnet_config_netlink_msg_handler (struct sk_buff *skb);
+int rmnet_config_notify_cb(struct notifier_block *nb,
+                                 unsigned long event, void *data);
+int rmnet_create_vnd(int id);
+int rmnet_create_vnd_prefix(int id, const char *name);
+int rmnet_free_vnd(int id);
+
+#endif /* _RMNET_DATA_CONFIG_H_ */
diff --git a/net/rmnet_data/rmnet_data_handlers.c 
b/net/rmnet_data/rmnet_data_handlers.c
new file mode 100644
index 0000000..d284557
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_handlers.c
@@ -0,0 +1,560 @@
+/* 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_data.h>
+#include <linux/netdev_features.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include "rmnet_data_private.h"
+#include "rmnet_data_config.h"
+#include "rmnet_data_vnd.h"
+#include "rmnet_map.h"
+#include "rmnet_data_stats.h"
+#include "rmnet_data_trace.h"
+
+RMNET_LOG_MODULE(RMNET_DATA_LOGMASK_HANDLER);
+
+void rmnet_egress_handler(struct sk_buff *skb,
+                         struct rmnet_logical_ep_conf_s *ep);
+
+#ifdef CONFIG_RMNET_DATA_DEBUG
+unsigned int dump_pkt_rx;
+module_param(dump_pkt_rx, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dump_pkt_rx, "Dump packets entering ingress handler");
+
+unsigned int dump_pkt_tx;
+module_param(dump_pkt_tx, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dump_pkt_tx, "Dump packets exiting egress handler");
+#endif /* CONFIG_RMNET_DATA_DEBUG */
+
+#define RMNET_DATA_IP_VERSION_4 0x40
+#define RMNET_DATA_IP_VERSION_6 0x60
+
+/* Helper Functions */
+
+/* __rmnet_data_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_data_set_skb_proto(struct sk_buff *skb)
+{
+       switch (skb->data[0] & 0xF0) {
+       case RMNET_DATA_IP_VERSION_4:
+               skb->protocol = htons(ETH_P_IP);
+               break;
+       case RMNET_DATA_IP_VERSION_6:
+               skb->protocol = htons(ETH_P_IPV6);
+               break;
+       default:
+               skb->protocol = htons(ETH_P_MAP);
+               break;
+       }
+}
+
+#ifdef CONFIG_RMNET_DATA_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)
+{
+       return;
+}
+#endif /* CONFIG_RMNET_DATA_DEBUG */
+
+/* Generic handler */
+
+/* rmnet_bridge_handler() - Bridge related functionality
+ *
+ * Return:
+ *      - RX_HANDLER_CONSUMED in all cases
+ */
+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
+ *
+ * Return:
+ *      - RX_HANDLER_CONSUMED if packet forwarded or dropped
+ *      - RX_HANDLER_PASS if packet is to be consumed by network stack as-is
+ */
+static rx_handler_result_t __rmnet_deliver_skb(struct sk_buff *skb,
+                                        struct rmnet_logical_ep_conf_s *ep)
+{
+       trace___rmnet_deliver_skb(skb);
+       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("Unkown 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.
+ *
+ * Return:
+ *      - RX_HANDLER_CONSUMED if packet forwarded/dropped
+ *      - RX_HANDLER_PASS if packet should be passed up the stack by caller
+ */
+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()
+ *
+ * Return:
+ *      - RX_HANDLER_CONSUMED if packet is dropped
+ *      - result of __rmnet_deliver_skb() for all other cases
+ */
+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;
+       uint8_t mux_id;
+       uint16_t 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)
+                       - config->tail_spacing;
+
+       if (mux_id >= RMNET_DATA_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_data_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().
+ *
+ * Return:
+ *      - RX_HANDLER_CONSUMED for aggregated packets
+ *      - RX_HANDLER_CONSUMED for dropped packets
+ *      - result of _rmnet_map_ingress_handler() for all other cases
+ */
+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) {
+               trace_rmnet_start_deaggregation(skb);
+               while ((skbn = rmnet_map_deaggregate(skb, config)) != 0) {
+                       _rmnet_map_ingress_handler(skbn, config);
+                       co++;
+               }
+               trace_rmnet_end_deaggregation(skb, 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.
+ *
+ * Return:
+ *      - 0 on success
+ *      - 1 on failure
+ */
+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.
+ *
+ * Return:
+ *      - RX_HANDLER_PASS if packet is not processed by handler (caller must
+ *        deal with the packet)
+ *      - RX_HANDLER_CONSUMED if packet is forwarded or processed by MAP
+ */
+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)
+               BUG();
+
+       dev = skb->dev;
+       trace_rmnet_ingress_handler(skb);
+       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_data", 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_data_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
+ *
+ * Return:
+ *      - Whatever rmnet_ingress_handler() returns
+ */
+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_data", 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');
+       trace_rmnet_egress_handler(skb);
+       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/net/rmnet_data/rmnet_data_handlers.h 
b/net/rmnet_data/rmnet_data_handlers.h
new file mode 100644
index 0000000..85a590c
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_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_DATA_HANDLERS_H_
+#define _RMNET_DATA_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_DATA_HANDLERS_H_ */
diff --git a/net/rmnet_data/rmnet_data_main.c b/net/rmnet_data/rmnet_data_main.c
new file mode 100644
index 0000000..a2318081
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_main.c
@@ -0,0 +1,60 @@
+/* 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_data_private.h"
+#include "rmnet_data_config.h"
+#include "rmnet_data_vnd.h"
+
+/* Trace Points */
+#define CREATE_TRACE_POINTS
+#include "rmnet_data_trace.h"
+
+/* Module Parameters */
+unsigned int rmnet_data_log_level = RMNET_LOG_LVL_ERR | RMNET_LOG_LVL_HI;
+module_param(rmnet_data_log_level, uint,  S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(log_level, "Logging level");
+
+unsigned int rmnet_data_log_module_mask;
+module_param(rmnet_data_log_module_mask, uint,  S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(rmnet_data_log_module_mask, "Logging module mask");
+
+/* Startup/Shutdown */
+
+/* rmnet_init() - Module initialization
+ *
+ * todo: check for (and init) startup errors
+ */
+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/net/rmnet_data/rmnet_data_private.h 
b/net/rmnet_data/rmnet_data_private.h
new file mode 100644
index 0000000..906c7e8
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_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_DATA_PRIVATE_H_
+#define _RMNET_DATA_PRIVATE_H_
+
+#define RMNET_DATA_MAX_VND              32
+#define RMNET_DATA_MAX_PACKET_SIZE      16384
+#define RMNET_DATA_DFLT_PACKET_SIZE     1500
+#define RMNET_DATA_DEV_NAME_STR         "rmnet_data"
+#define RMNET_DATA_NEEDED_HEADROOM      16
+#define RMNET_DATA_TX_QUEUE_LEN         1000
+#define RMNET_ETHERNET_HEADER_LENGTH    14
+
+extern unsigned int rmnet_data_log_level;
+extern unsigned int rmnet_data_log_module_mask;
+
+#define RMNET_INIT_OK     0
+#define RMNET_INIT_ERROR  1
+
+#define RMNET_LOG_LVL_DBG (1<<4)
+#define RMNET_LOG_LVL_LOW (1<<3)
+#define RMNET_LOG_LVL_MED (1<<2)
+#define RMNET_LOG_LVL_HI  (1<<1)
+#define RMNET_LOG_LVL_ERR (1<<0)
+
+#define RMNET_LOG_MODULE(X) \
+       static uint32_t rmnet_mod_mask = X
+
+#define RMNET_DATA_LOGMASK_CONFIG  (1<<0)
+#define RMNET_DATA_LOGMASK_HANDLER (1<<1)
+#define RMNET_DATA_LOGMASK_VND     (1<<2)
+#define RMNET_DATA_LOGMASK_MAPD    (1<<3)
+#define RMNET_DATA_LOGMASK_MAPC    (1<<4)
+
+#define LOGE(fmt, ...) do { if (rmnet_data_log_level & RMNET_LOG_LVL_ERR) \
+                       pr_err("[RMNET:ERR] %s(): " fmt "\n", __func__, \
+                               ##__VA_ARGS__); \
+                       } while (0)
+
+#define LOGH(fmt, ...) do { if (rmnet_data_log_level & RMNET_LOG_LVL_HI) \
+                       pr_err("[RMNET:HI] %s(): " fmt "\n" , __func__, \
+                               ##__VA_ARGS__); \
+                       } while (0)
+
+#define LOGM(fmt, ...) do { if (rmnet_data_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_data_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_data_log_level & RMNET_LOG_LVL_DBG) \
+                           && (rmnet_data_log_module_mask & rmnet_mod_mask))) \
+                       pr_notice("[RMNET:DBG] %s(): " fmt "\n", __func__, \
+                                 ##__VA_ARGS__); \
+                       } while (0)
+
+#endif /* _RMNET_DATA_PRIVATE_H_ */
diff --git a/net/rmnet_data/rmnet_data_stats.c 
b/net/rmnet_data/rmnet_data_stats.c
new file mode 100644
index 0000000..7382edc
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_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_data_private.h"
+#include "rmnet_data_stats.h"
+#include "rmnet_data_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, S_IRUGO);
+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, S_IRUGO);
+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, S_IRUGO);
+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/net/rmnet_data/rmnet_data_stats.h 
b/net/rmnet_data/rmnet_data_stats.h
new file mode 100644
index 0000000..947e406
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_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_DATA_STATS_H_
+#define _RMNET_DATA_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_UNKOWN_IP_TYP,
+       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_DATA_STATS_H_ */
diff --git a/net/rmnet_data/rmnet_data_trace.h 
b/net/rmnet_data/rmnet_data_trace.h
new file mode 100644
index 0000000..e1fdf01
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_trace.h
@@ -0,0 +1,183 @@
+/* Copyright (c) 2014-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.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM rmnet_data
+#define TRACE_INCLUDE_FILE rmnet_data_trace
+
+#if !defined(_RMNET_DATA_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
+#define _RMNET_DATA_TRACE_H_
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/tracepoint.h>
+
+DECLARE_EVENT_CLASS(rmnet_handler_template,
+
+       TP_PROTO(struct sk_buff *skb),
+
+       TP_ARGS(skb),
+
+       TP_STRUCT__entry(
+               __field(void *, skbaddr)
+               __field(unsigned int, len)
+               __string(name, skb->dev->name)
+       ),
+
+       TP_fast_assign(
+               __entry->skbaddr = skb;
+               __entry->len = skb->len;
+               __assign_str(name, skb->dev->name);
+       ),
+
+       TP_printk("dev=%s skbaddr=%pK len=%u",
+                 __get_str(name), __entry->skbaddr, __entry->len)
+)
+
+DEFINE_EVENT(rmnet_handler_template, rmnet_egress_handler,
+
+       TP_PROTO(struct sk_buff *skb),
+
+       TP_ARGS(skb)
+);
+
+DEFINE_EVENT(rmnet_handler_template, rmnet_ingress_handler,
+
+       TP_PROTO(struct sk_buff *skb),
+
+       TP_ARGS(skb)
+);
+
+DEFINE_EVENT(rmnet_handler_template, rmnet_vnd_start_xmit,
+
+       TP_PROTO(struct sk_buff *skb),
+
+       TP_ARGS(skb)
+);
+
+DEFINE_EVENT(rmnet_handler_template, __rmnet_deliver_skb,
+
+       TP_PROTO(struct sk_buff *skb),
+
+       TP_ARGS(skb)
+);
+
+TRACE_EVENT(rmnet_start_deaggregation,
+
+       TP_PROTO(struct sk_buff *skb),
+
+       TP_ARGS(skb),
+
+       TP_STRUCT__entry(
+               __string(name, skb->dev->name)
+       ),
+
+       TP_fast_assign(
+               __assign_str(name, skb->dev->name);
+       ),
+
+       TP_printk("dev: %s, deaggregated first packet", __get_str(name))
+)
+
+TRACE_EVENT(rmnet_end_deaggregation,
+
+       TP_PROTO(struct sk_buff *skb, int num_deagg_packets),
+
+       TP_ARGS(skb, num_deagg_packets),
+
+       TP_STRUCT__entry(
+               __string(name, skb->dev->name)
+               __field(int, num)
+       ),
+
+       TP_fast_assign(
+               __assign_str(name, skb->dev->name);
+               __entry->num = num_deagg_packets;
+       ),
+
+       TP_printk("dev: %s, deaggregate end count: %d",
+               __get_str(name), __entry->num)
+)
+
+DECLARE_EVENT_CLASS(rmnet_physdev_action_template,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev),
+
+       TP_STRUCT__entry(
+               __string(name, dev->name)
+       ),
+
+       TP_fast_assign(
+               __assign_str(name, dev->name);
+       ),
+
+       TP_printk("Physical dev=%s", __get_str(name))
+)
+
+DEFINE_EVENT(rmnet_physdev_action_template, rmnet_unregister_cb_unhandled,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev)
+);
+
+DEFINE_EVENT(rmnet_physdev_action_template, rmnet_unregister_cb_entry,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev)
+);
+
+DEFINE_EVENT(rmnet_physdev_action_template, rmnet_unregister_cb_exit,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev)
+);
+
+DEFINE_EVENT(rmnet_physdev_action_template, rmnet_unregister_cb_clear_vnds,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev)
+);
+
+DEFINE_EVENT(rmnet_physdev_action_template, rmnet_unregister_cb_clear_lepcs,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev)
+);
+
+DEFINE_EVENT(rmnet_physdev_action_template, rmnet_associate,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev)
+);
+
+DEFINE_EVENT(rmnet_physdev_action_template, rmnet_unassociate,
+
+       TP_PROTO(struct net_device *dev),
+
+       TP_ARGS(dev)
+);
+
+#endif /* _RMNET_DATA_TRACE_H_ */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#include <trace/define_trace.h>
+
diff --git a/net/rmnet_data/rmnet_data_vnd.c b/net/rmnet_data/rmnet_data_vnd.c
new file mode 100644
index 0000000..5016e07
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_vnd.c
@@ -0,0 +1,602 @@
+/* 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_data.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_data_config.h"
+#include "rmnet_data_handlers.h"
+#include "rmnet_data_private.h"
+#include "rmnet_map.h"
+#include "rmnet_data_vnd.h"
+#include "rmnet_data_stats.h"
+#include "rmnet_data_trace.h"
+
+RMNET_LOG_MODULE(RMNET_DATA_LOGMASK_VND);
+
+struct net_device *rmnet_devices[RMNET_DATA_MAX_VND];
+
+struct rmnet_vnd_private_s {
+       struct rmnet_logical_ep_conf_s local_ep;
+};
+
+/* ***************** 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
+ *
+ * Return:
+ *      - RX_HANDLER_PASS if packet should continue to process in stack
+ *      - RX_HANDLER_CONSUMED if packet should not be processed in stack
+ *
+ */
+int rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev)
+{
+       if (unlikely(!dev || !skb))
+               BUG();
+
+       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
+ *
+ * Return:
+ *      - RX_HANDLER_PASS if packet should continue to be transmitted
+ *      - RX_HANDLER_CONSUMED if packet should not be transmitted by stack
+ */
+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))
+               BUG();
+
+       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.
+ *
+ * Return:
+ *      - NETDEV_TX_OK under all cirumstances (cannot block/fail)
+ */
+static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
+                                       struct net_device *dev)
+{
+       struct rmnet_vnd_private_s *dev_conf;
+       trace_rmnet_vnd_start_xmit(skb);
+       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_DATA_MAX_PACKET_SIZE;
+ *
+ * Return:
+ *      - 0 if successful
+ *      - -EINVAL if new_mtu is out of range
+ */
+static int rmnet_vnd_change_mtu(struct net_device *dev, int new_mtu)
+{
+       if (new_mtu < 0 || new_mtu > RMNET_DATA_MAX_PACKET_SIZE)
+               return -EINVAL;
+
+       dev->mtu = new_mtu;
+       return 0;
+}
+
+static int rmnet_vnd_ioctl_extended(struct net_device *dev, struct ifreq *ifr)
+{
+       struct rmnet_vnd_private_s *dev_conf;
+       struct rmnet_ioctl_extended_s ext_cmd;
+       int rc = 0;
+       dev_conf = (struct rmnet_vnd_private_s *) netdev_priv(dev);
+
+       rc = copy_from_user(&ext_cmd, ifr->ifr_ifru.ifru_data,
+                           sizeof(struct rmnet_ioctl_extended_s));
+       if (rc) {
+               LOGM("%s(): copy_from_user() failed\n", __func__);
+               return rc;
+       }
+
+       switch (ext_cmd.extended_ioctl) {
+       case RMNET_IOCTL_GET_SUPPORTED_FEATURES:
+               ext_cmd.u.data = 0;
+               break;
+
+       case RMNET_IOCTL_GET_DRIVER_NAME:
+               strlcpy(ext_cmd.u.if_name, "rmnet_data",
+                       sizeof(ext_cmd.u.if_name));
+               break;
+
+       case RMNET_IOCTL_GET_SUPPORTED_QOS_MODES:
+               ext_cmd.u.data = RMNET_IOCTL_QOS_MODE_6
+                                | RMNET_IOCTL_QOS_MODE_8;
+               break;
+
+       default:
+               rc = -EINVAL;
+               goto done;
+               break;
+       }
+
+       rc = copy_to_user(ifr->ifr_ifru.ifru_data, &ext_cmd,
+                         sizeof(struct rmnet_ioctl_extended_s));
+       if (rc)
+               LOGM("%s(): copy_to_user() failed\n", __func__);
+
+done:
+       return rc;
+}
+
+
+/**
+ * rmnet_vnd_ioctl() - IOCTL NDO callback
+ * @dev:         Virtual network device
+ * @ifreq:       User data
+ * @cmd:         IOCTL command value
+ *
+ * Standard network driver operations hook to process IOCTLs. Called by kernel
+ * to process non-stanard IOCTLs for device
+ *
+ * Return:
+ *      - 0 if successful
+ *      - -EINVAL if unknown IOCTL
+ */
+static int rmnet_vnd_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+       struct rmnet_vnd_private_s *dev_conf;
+       int rc;
+       struct rmnet_ioctl_data_s ioctl_data;
+       rc = 0;
+       dev_conf = (struct rmnet_vnd_private_s *) netdev_priv(dev);
+
+       switch (cmd) {
+
+       case RMNET_IOCTL_OPEN: /* Do nothing. Support legacy behavior */
+               LOGM("RMNET_IOCTL_OPEN on %s (ignored)", dev->name);
+               break;
+
+       case RMNET_IOCTL_CLOSE: /* Do nothing. Support legacy behavior */
+               LOGM("RMNET_IOCTL_CLOSE on %s (ignored)", dev->name);
+               break;
+
+       case RMNET_IOCTL_SET_LLP_ETHERNET:
+               LOGM("RMNET_IOCTL_SET_LLP_ETHERNET on %s (no support)",
+                    dev->name);
+               rc = -EINVAL;
+               break;
+
+       case RMNET_IOCTL_SET_LLP_IP: /* Do nothing. Support legacy behavior */
+               LOGM("RMNET_IOCTL_SET_LLP_IP on %s (ignored)", dev->name);
+               break;
+
+       case RMNET_IOCTL_GET_LLP: /* Always return IP mode */
+               LOGM("RMNET_IOCTL_GET_LLP on %s", dev->name);
+               ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
+               if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
+                       sizeof(struct rmnet_ioctl_data_s)))
+                       rc = -EFAULT;
+               break;
+
+       case RMNET_IOCTL_EXTENDED:
+               rc = rmnet_vnd_ioctl_extended(dev, ifr);
+               break;
+
+       default:
+               LOGM("Unknown IOCTL 0x%08X", cmd);
+               rc = -EINVAL;
+       }
+
+       return rc;
+}
+
+static const struct net_device_ops rmnet_data_vnd_ops = {
+       .ndo_init = 0,
+       .ndo_start_xmit = rmnet_vnd_start_xmit,
+       .ndo_do_ioctl = rmnet_vnd_ioctl,
+       .ndo_change_mtu = rmnet_vnd_change_mtu,
+       .ndo_set_mac_address = 0,
+       .ndo_validate_addr = 0,
+};
+
+/**
+ * rmnet_vnd_setup() - net_device initialization callback
+ * @dev:      Virtual network device
+ *
+ * Called by kernel whenever a new rmnet_data<n> device is created. Sets MTU,
+ * flags, ARP type, needed headroom, etc...
+ */
+static 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_data_vnd_ops;
+       dev->mtu = RMNET_DATA_DFLT_PACKET_SIZE;
+       dev->needed_headroom = RMNET_DATA_NEEDED_HEADROOM;
+       random_ether_addr(dev->dev_addr);
+       dev->tx_queue_len = RMNET_DATA_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);
+}
+
+/* ***************** 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_DATA_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_DATA_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_DATA_DEV_NAME_STR will be assumed.
+ *
+ * Return:
+ *      - 0 if successful
+ *      - RMNET_CONFIG_BAD_ARGUMENTS if id is out of range or prefix is too 
long
+ *      - RMNET_CONFIG_DEVICE_IN_USE if id already in use
+ *      - RMNET_CONFIG_NOMEM if net_device allocation failed
+ *      - RMNET_CONFIG_UNKNOWN_ERROR if register_netdevice() fails
+ */
+int rmnet_vnd_create_dev(int id, struct net_device **new_device,
+                        const char *prefix)
+{
+       struct net_device *dev;
+       char dev_prefix[IFNAMSIZ];
+       int p, rc = 0;
+
+       if (id < 0 || id >= RMNET_DATA_MAX_VND) {
+               *new_device = 0;
+               return RMNET_CONFIG_BAD_ARGUMENTS;
+       }
+
+       if (rmnet_devices[id] != 0) {
+               *new_device = 0;
+               return RMNET_CONFIG_DEVICE_IN_USE;
+       }
+
+       if (!prefix)
+               p = scnprintf(dev_prefix, IFNAMSIZ, "%s%%d",
+                         RMNET_DATA_DEV_NAME_STR);
+       else
+               p = scnprintf(dev_prefix, IFNAMSIZ, "%s%%d",
+                         prefix);
+       if (p >= (IFNAMSIZ-1)) {
+               LOGE("Specified prefix longer than IFNAMSIZ");
+               return RMNET_CONFIG_BAD_ARGUMENTS;
+       }
+
+       dev = alloc_netdev(sizeof(struct rmnet_vnd_private_s),
+                          dev_prefix,
+                          NET_NAME_ENUM,
+                          rmnet_vnd_setup);
+       if (!dev) {
+               LOGE("Failed to to allocate netdev for id %d", id);
+               *new_device = 0;
+               return RMNET_CONFIG_NOMEM;
+       }
+
+       if (!prefix) {
+               /* Configuring DL checksum offload on rmnet_data interfaces */
+               dev->hw_features = NETIF_F_RXCSUM;
+               /* Configuring UL checksum offload on rmnet_data interfaces */
+               dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM;
+               /* Configuring GRO on rmnet_data interfaces */
+               dev->hw_features |= NETIF_F_GRO;
+               /* Configuring Scatter-Gather on rmnet_data interfaces */
+               dev->hw_features |= NETIF_F_SG;
+               /* Configuring GSO on rmnet_data interfaces */
+               dev->hw_features |= NETIF_F_GSO;
+               dev->hw_features |= NETIF_F_GSO_UDP_TUNNEL;
+               dev->hw_features |= NETIF_F_GSO_UDP_TUNNEL_CSUM;
+       }
+
+       rc = register_netdevice(dev);
+       if (rc != 0) {
+               LOGE("Failed to to register netdev [%s]", dev->name);
+               free_netdev(dev);
+               *new_device = 0;
+               return RMNET_CONFIG_UNKNOWN_ERROR;
+       } else {
+               rmnet_devices[id] = dev;
+               *new_device = dev;
+       }
+
+       LOGM("Registered device %s", dev->name);
+       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.
+ *
+ * Return:
+ *      - 0 if successful
+ *      - RMNET_CONFIG_NO_SUCH_DEVICE if id is invalid or not in range
+ *      - RMNET_CONFIG_DEVICE_IN_USE if device has logical ep that wasn't unset
+ */
+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_DATA_MAX_VND) || !rmnet_devices[id]) {
+               rtnl_unlock();
+               LOGM("Invalid id [%d]", id);
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+       }
+
+       epconfig_l = rmnet_vnd_get_le_config(rmnet_devices[id]);
+       if (epconfig_l && epconfig_l->refcount) {
+               rtnl_unlock();
+               return RMNET_CONFIG_DEVICE_IN_USE;
+       }
+
+       dev = rmnet_devices[id];
+       rmnet_devices[id] = 0;
+       rtnl_unlock();
+
+       if (dev) {
+               unregister_netdev(dev);
+               free_netdev(dev);
+               return 0;
+       } else {
+               return RMNET_CONFIG_NO_SUCH_DEVICE;
+       }
+}
+
+/**
+ * rmnet_vnd_get_name() - Gets the string name of a VND based on ID
+ * @id:         Virtual device node id
+ * @name:       Buffer to store name of virtual device node
+ * @name_len:   Length of name buffer
+ *
+ * Copies the name of the virtual device node into the users buffer. Will throw
+ * an error if the buffer is null, or too small to hold the device name.
+ *
+ * Return:
+ *      - 0 if successful
+ *      - -EINVAL if name is null
+ *      - -EINVAL if id is invalid or not in range
+ *      - -EINVAL if name is too small to hold things
+ */
+int rmnet_vnd_get_name(int id, char *name, int name_len)
+{
+       int p;
+
+       if (!name) {
+               LOGM("%s", "Bad arguments; name buffer null");
+               return -EINVAL;
+       }
+
+       if ((id < 0) || (id >= RMNET_DATA_MAX_VND) || !rmnet_devices[id]) {
+               LOGM("Invalid id [%d]", id);
+               return -EINVAL;
+       }
+
+       p = strlcpy(name, rmnet_devices[id]->name, name_len);
+       if (p >= name_len) {
+               LOGM("Buffer to small (%d) to fit device name", name_len);
+               return -EINVAL;
+       }
+       LOGL("Found mapping [%d]->\"%s\"", id, name);
+
+       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.
+ *
+ * Return:
+ *      - 0 if device is not RmNet virtual device
+ *      - 1 if device is RmNet virtual device
+ */
+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)
+               BUG();
+
+       for (i = 0; i < RMNET_DATA_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.
+ *
+ * Return:
+ *      - Pointer to logical endpoint configuration structure
+ *      - 0 (null) if dev is null
+ */
+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)
+               BUG();
+
+       return &dev_conf->local_ep;
+}
+
+/**
+ * rmnet_vnd_do_flow_control() - Process flow control request
+ * @dev: Virtual network device node to do lookup on
+ * @map_flow_id: Flow ID from MAP message
+ * @v4_seq: pointer to IPv4 indication sequence number
+ * @v6_seq: pointer to IPv6 indication sequence number
+ * @enable: boolean to enable/disable flow.
+ *
+ * Return:
+ *      - 0 if successful
+ *      - 1 if no mapping is found
+ *      - 2 if dev is not RmNet virtual network device node
+ */
+int rmnet_vnd_do_flow_control(struct net_device *dev,
+                              uint32_t map_flow_id,
+                              uint16_t v4_seq,
+                              uint16_t v6_seq,
+                              int enable)
+{
+       struct rmnet_vnd_private_s *dev_conf;
+
+       if (unlikely(!dev))
+               BUG();
+
+       if (!rmnet_vnd_is_vnd(dev)) {
+               return 2;
+       } else {
+               dev_conf = (struct rmnet_vnd_private_s *) netdev_priv(dev);
+       }
+
+       if (unlikely(!dev_conf))
+               BUG();
+
+       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_DATA_MAX_VND]
+ *
+ * Return:
+ *      - 0 if no device or ID out of range
+ *      - otherwise return pointer to VND net_device struct
+ */
+struct net_device *rmnet_vnd_get_by_id(int id)
+{
+       if (id < 0 || id >= RMNET_DATA_MAX_VND) {
+               pr_err("Bug; VND ID out of bounds");
+               BUG();
+               return 0;
+       }
+       return rmnet_devices[id];
+}
diff --git a/net/rmnet_data/rmnet_data_vnd.h b/net/rmnet_data/rmnet_data_vnd.h
new file mode 100644
index 0000000..6eeaf65
--- /dev/null
+++ b/net/rmnet_data/rmnet_data_vnd.h
@@ -0,0 +1,40 @@
+/* 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_DATA_VND_H_
+#define _RMNET_DATA_VND_H_
+
+int rmnet_vnd_do_flow_control(struct net_device *dev,
+                              uint32_t map_flow_id,
+                              uint16_t v4_seq,
+                              uint16_t v6_seq,
+                              int enable);
+struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct net_device 
*dev);
+int rmnet_vnd_get_name(int id, char *name, int name_len);
+int rmnet_vnd_create_dev(int id, struct net_device **new_device,
+                        const char *prefix);
+int rmnet_vnd_free_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_add_tc_flow(uint32_t id, uint32_t map_flow, uint32_t tc_flow);
+int rmnet_vnd_del_tc_flow(uint32_t id, uint32_t map_flow, uint32_t tc_flow);
+int rmnet_vnd_init(void);
+void rmnet_vnd_exit(void);
+struct net_device *rmnet_vnd_get_by_id(int id);
+
+#endif /* _RMNET_DATA_VND_H_ */
diff --git a/net/rmnet_data/rmnet_map.h b/net/rmnet_data/rmnet_map.h
new file mode 100644
index 0000000..d52eef7
--- /dev/null
+++ b/net/rmnet_data/rmnet_map.h
@@ -0,0 +1,148 @@
+/* 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 {
+       uint8_t command_name;
+#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS
+       uint8_t  cmd_type:2;
+       uint8_t  reserved:6;
+#else
+       uint8_t  reserved:6;
+       uint8_t  cmd_type:2;
+#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */
+       uint16_t reserved2;
+       uint32_t   transaction_id;
+       union {
+               uint8_t  data[65528];
+               struct {
+#ifndef RMNET_USE_BIG_ENDIAN_STRUCTS
+                       uint16_t  ip_family:2;
+                       uint16_t  reserved:14;
+#else
+                       uint16_t  reserved:14;
+                       uint16_t  ip_family:2;
+#endif /* RMNET_USE_BIG_ENDIAN_STRUCTS */
+                       uint16_t  flow_control_seq_num;
+                       uint32_t  qos_id;
+               } flow_control;
+       };
+}  __aligned(1);
+
+struct rmnet_map_dl_checksum_trailer_s {
+       unsigned char  reserved_h;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+       unsigned char  valid:1;
+       unsigned char  reserved_l:7;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+       unsigned char  reserved_l:7;
+       unsigned char  valid:1;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+       unsigned short checksum_start_offset;
+       unsigned short checksum_length;
+       unsigned short checksum_value;
+} __aligned(1);
+
+struct rmnet_map_ul_checksum_header_s {
+       unsigned short checksum_start_offset;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+       unsigned short checksum_insert_offset:14;
+       unsigned short udp_ip4_ind:1;
+       unsigned short cks_en:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+       unsigned short cks_en:1;
+       unsigned short udp_ip4_ind:1;
+       unsigned short checksum_insert_offset:14;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+} __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_checksum_errors_e {
+       RMNET_MAP_CHECKSUM_OK,
+       RMNET_MAP_CHECKSUM_VALID_FLAG_NOT_SET,
+       RMNET_MAP_CHECKSUM_VALIDATION_FAILED,
+       RMNET_MAP_CHECKSUM_ERR_UNKOWN,
+       RMNET_MAP_CHECKSUM_ERR_NOT_DATA_PACKET,
+       RMNET_MAP_CHECKSUM_ERR_BAD_BUFFER,
+       RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION,
+       RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT,
+       RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET,
+       RMNET_MAP_CHECKSUM_SKIPPED,
+       RMNET_MAP_CHECKSUM_SW,
+       /* This should always be the last element */
+       RMNET_MAP_CHECKSUM_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
+};
+
+enum rmnet_map_agg_state_e {
+       RMNET_MAP_AGG_IDLE,
+       RMNET_MAP_TXFER_SCHEDULED
+};
+
+#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
+
+uint8_t 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);
+void rmnet_map_aggregate(struct sk_buff *skb,
+                        struct rmnet_phys_ep_conf_s *config);
+
+int rmnet_map_checksum_downlink_packet(struct sk_buff *skb);
+int rmnet_map_checksum_uplink_packet(struct sk_buff *skb,
+       struct net_device *orig_dev, uint32_t egress_data_format);
+
+#endif /* _RMNET_MAP_H_ */
diff --git a/net/rmnet_data/rmnet_map_command.c 
b/net/rmnet_data/rmnet_map_command.c
new file mode 100644
index 0000000..54e2761
--- /dev/null
+++ b/net/rmnet_data/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_data.h>
+#include <net/pkt_sched.h>
+#include "rmnet_data_config.h"
+#include "rmnet_map.h"
+#include "rmnet_data_private.h"
+#include "rmnet_data_vnd.h"
+#include "rmnet_data_stats.h"
+
+RMNET_LOG_MODULE(RMNET_DATA_LOGMASK_MAPC);
+
+unsigned long int rmnet_map_command_stats[RMNET_MAP_COMMAND_ENUM_LENGTH];
+module_param_array(rmnet_map_command_stats, ulong, 0, S_IRUGO);
+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 uint8_t 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;
+       uint8_t mux_id;
+       uint16_t  ip_family;
+       uint16_t  fc_seq;
+       uint32_t  qos_id;
+       int r;
+
+       if (unlikely(!skb || !config))
+               BUG();
+
+       mux_id = RMNET_MAP_GET_MUX_ID(skb);
+       cmd = RMNET_MAP_GET_CMD_START(skb);
+
+       if (mux_id >= RMNET_DATA_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, qos_id, fc_seq, fc_seq, 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))
+               BUG();
+
+       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))
+               BUG();
+
+       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/net/rmnet_data/rmnet_map_data.c b/net/rmnet_data/rmnet_map_data.c
new file mode 100644
index 0000000..03cacdf
--- /dev/null
+++ b/net/rmnet_data/rmnet_map_data.c
@@ -0,0 +1,154 @@
+/* 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_data.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_data_config.h"
+#include "rmnet_map.h"
+#include "rmnet_data_private.h"
+#include "rmnet_data_stats.h"
+#include "rmnet_data_trace.h"
+
+RMNET_LOG_MODULE(RMNET_DATA_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
+ *
+ * todo: Parameterize skb alignment
+ */
+struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff *skb,
+                                                   int hdrlen, int pad)
+{
+       uint32_t padding, map_datalen;
+       uint8_t *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 = (uint8_t *) 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;
+       uint32_t 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 ((config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP_CKSUMV3) ||
+           (config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP_CKSUMV4))
+               packet_len += sizeof(struct rmnet_map_dl_checksum_trailer_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;
+}
-- 
1.9.1

Reply via email to