From mboxrd@z Thu Jan 1 00:00:00 1970 From: Dan Williams Subject: Re: [PATCH net-next 1/1 v2] net: rmnet_data: Initial implementation Date: Fri, 24 Mar 2017 17:15:00 -0500 Message-ID: <1490393700.3227.7.camel@bigw.org> References: <1489390989-2408-1-git-send-email-subashab@codeaurora.org> <1489390989-2408-2-git-send-email-subashab@codeaurora.org> Mime-Version: 1.0 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit To: Subash Abhinov Kasiviswanathan , netdev@vger.kernel.org, davem@davemloft.net, fengguang.wu@intel.com Return-path: Received: from claudius.bigw.org ([50.244.203.196]:64496 "EHLO claudius.bigw.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S934956AbdCXWVC (ORCPT ); Fri, 24 Mar 2017 18:21:02 -0400 In-Reply-To: <1489390989-2408-2-git-send-email-subashab@codeaurora.org> Sender: netdev-owner@vger.kernel.org List-ID: On Mon, 2017-03-13 at 01:43 -0600, Subash Abhinov Kasiviswanathan wrote: > 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. The first thing that jumps out at me is "why isn't this using rtnl_link_ops?" To me (and perhaps I'm completely wrong) the structure here is a lot like VLAN interfaces. You have a base device (whether that's a netdev or not) and you essentially "ip link add link cdc-wdm0 name rmnet0 type rmnet id 5". Does the aggregation happen only on the downlink (eg, device -> host) or can the host send aggregated packets too? Using rtnl_link_ops would get rid of ASSOC_NET_DEV, UNASSOC_NET_DEV, NEW_VND, NEW_VND_WITH_PREFIX, and FREE_VND. GET_NET_DEV_ASSOC goes away becuase you use normal 'kobject' associations and you can derive the rmnet parent through sysfs links. rmnet_nl_msg_s goes away, because you can use nla_policy. Just a thought; there seems to be a ton of overlap with rtnl_link_ops in the control plane here. Any thoughts on how this plays with net namespaces? Also, I'm not sure if it make sense to provide first class tracepoints for a specific driver, as it's not clear if they are userspace API or not and thus may need to be kept stable. Or are perf probes enough instead? What's RMNET_EPMODE_BRIDGE and how is it used? Dan > Signed-off-by: Subash Abhinov Kasiviswanathan > > --- >  Documentation/networking/rmnet_data.txt |   82 +++ >  include/uapi/linux/Kbuild               |    1 + >  include/uapi/linux/if_arp.h             |    1 + >  include/uapi/linux/if_ether.h           |    4 +- >  include/uapi/linux/rmnet_data.h         |  214 ++++++ >  net/Kconfig                             |    1 + >  net/Makefile                            |    1 + >  net/rmnet_data/Kconfig                  |   21 + >  net/rmnet_data/Makefile                 |   14 + >  net/rmnet_data/rmnet_data_config.c      | 1168 > +++++++++++++++++++++++++++++++ >  net/rmnet_data/rmnet_data_config.h      |  107 +++ >  net/rmnet_data/rmnet_data_handlers.c    |  556 +++++++++++++++ >  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       |  198 ++++++ >  net/rmnet_data/rmnet_data_vnd.c         |  460 ++++++++++++ >  net/rmnet_data/rmnet_data_vnd.h         |   34 + >  net/rmnet_data/rmnet_map.h              |  100 +++ >  net/rmnet_data/rmnet_map_command.c      |  180 +++++ >  net/rmnet_data/rmnet_map_data.c         |  148 ++++ >  23 files changed, 3596 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-opensourc > e/\ > +dataservices/tree/rmnetctl > diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild > index dd9820b..c488c87 100644 > --- a/include/uapi/linux/Kbuild > +++ b/include/uapi/linux/Kbuild > @@ -370,6 +370,7 @@ header-y += resource.h >  header-y += rfkill.h >  header-y += rio_cm_cdev.h >  header-y += rio_mport_cdev.h > +header-y += rmnet_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 5bc9bfd..70520da 100644 > --- a/include/uapi/linux/if_ether.h > +++ b/include/uapi/linux/if_ether.h > @@ -104,7 +104,9 @@ >  #define ETH_P_QINQ3 0x9300 /* deprecated QinQ > VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */ >  #define ETH_P_EDSA 0xDADA /* Ethertype DSA [ > NOT AN OFFICIALLY REGISTERED ID ] */ >  #define ETH_P_AF_IUCV   0xFBFB /* IBM af_iucv [ NOT > AN OFFICIALLY REGISTERED ID ] */ > - > +#define ETH_P_MAP 0xDA1A /* Multiplexing and > Aggregation Protocol > +  *  NOT AN OFFICIALLY > REGISTERED ID ] > +  */ >  #define ETH_P_802_3_MIN 0x0600 /* If the value > in the ethernet type is less than this value >    * then the frame is > Ethernet II. Else it is 802.3 */ >   > diff --git a/include/uapi/linux/rmnet_data.h > b/include/uapi/linux/rmnet_data.h > new file mode 100644 > index 0000000..04b6646 > --- /dev/null > +++ b/include/uapi/linux/rmnet_data.h > @@ -0,0 +1,214 @@ > +/* 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_ > + > +/* 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 > + > +/* Constants */ > +#define RMNET_EGRESS_FORMAT__RESERVED__         (1<<0) > +#define RMNET_EGRESS_FORMAT_MAP                 (1<<1) > +#define RMNET_EGRESS_FORMAT_AGGREGATION         (1<<2) > +#define RMNET_EGRESS_FORMAT_MUXING              (1<<3) > +#define RMNET_EGRESS_FORMAT_MAP_CKSUMV3         (1<<4) > +#define RMNET_EGRESS_FORMAT_MAP_CKSUMV4         (1<<5) > + > +#define RMNET_INGRESS_FIX_ETHERNET              (1<<0) > +#define RMNET_INGRESS_FORMAT_MAP                (1<<1) > +#define RMNET_INGRESS_FORMAT_DEAGGREGATION      (1<<2) > +#define RMNET_INGRESS_FORMAT_DEMUXING           (1<<3) > +#define RMNET_INGRESS_FORMAT_MAP_COMMANDS       (1<<4) > +#define RMNET_INGRESS_FORMAT_MAP_CKSUMV3        (1<<5) > +#define RMNET_INGRESS_FORMAT_MAP_CKSUMV4        (1<<6) > + > +struct rmnet_nl_msg_s { > + __le16 reserved; > + __le16 message_type; > + __le16 reserved2:14; > + __le16 crd:2; > + union { > + __le16 arg_length; > + __le16 return_code; > + }; > + union { > + __u8 data[RMNET_NL_DATA_MAX_LEN]; > + struct { > + __u8   dev[RMNET_MAX_STR_LEN]; > + __le32 flags; > + __le16 agg_size; > + __le16 agg_count; > + __u8   tail_spacing; > + } data_format; > + struct { > + __u8  dev[RMNET_MAX_STR_LEN]; > + __le32 ep_id; > + __u8  operating_mode; > + __u8  next_dev[RMNET_MAX_STR_LEN]; > + } local_ep_config; > + struct { > + __le32 id; > + __u8   vnd_name[RMNET_MAX_STR_LEN]; > + } vnd; > + }; > +}; > + > +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: __le32 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: __le32 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: __le32 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: __le32 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 > +  *     __le32 logical_ep_id, valid values are -1 through 31 > +  *     __u8 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 > +  *       __le32 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 > +  *        __le32 logical_ep_id, valid values are -1 through > 31 > +  * Returns: __u8 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: __le32 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: __le32 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: __le32 node number > +  * Returns: char[] vnd_name > +  */ > + RMNET_NETLINK_GET_VND_NAME, > + > + /* RMNET_NETLINK_FREE_VND - Removes virtual network device > node > +  * Args: __le32 node number > +  * Returns: status code > +  */ > + RMNET_NETLINK_FREE_VND > +}; > + > +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 > +}; > + > +#endif /* _RMNET_DATA_H_ */ > diff --git a/net/Kconfig b/net/Kconfig > index 102f781..77a0d93 100644 > --- a/net/Kconfig > +++ b/net/Kconfig > @@ -239,6 +239,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 9b68155..4a04f56 100644 > --- a/net/Makefile > +++ b/net/Makefile > @@ -84,3 +84,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..38a21d8 > --- /dev/null > +++ b/net/rmnet_data/rmnet_data_config.c > @@ -0,0 +1,1168 @@ > +/* 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#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 */ > +#define RMNET_LOCAL_LOGICAL_ENDPOINT -1 > + > +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 > +}; > + > +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 > function > + * > + * 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; > +} > + > +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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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); > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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 = sizeof(((struct > rmnet_nl_msg_s *)0) > + ->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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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 = sizeof(((struct rmnet_nl_msg_s *)0) > + ->data_format); > + resp_rmnet->data_format.flags = config->egress_data_format; > + resp_rmnet->data_format.agg_count = 0; > + resp_rmnet->data_format.agg_size  = 0; > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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 = sizeof(((struct rmnet_nl_msg_s *)0) > + ->data_format); > + resp_rmnet->data_format.flags = config->ingress_data_format; > + 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; > + > + if (!rmnet_header || !resp_rmnet) > + return; > + > + 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 = sizeof(((struct rmnet_nl_msg_s > *)0)->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) > + return; > + > + 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_head > er->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, > +   u32 ingress_data_format) > +{ > + struct rmnet_phys_ep_conf_s *config; > + > + ASSERT_RTNL(); > + > + LOGL("(%s,0x%08X);", dev->name, ingress_data_format); > + > + if (!dev) > + return 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; > + > + 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, > +  u32 egress_data_format, > +  u16 agg_size, > +  u16 agg_count) > +{ > + struct rmnet_phys_ep_conf_s *config; > + > + ASSERT_RTNL(); > + > + LOGL("(%s,0x%08X, %d, %d);", > +      dev->name, egress_data_format, agg_size, agg_count); > + > + if (!dev) > + return 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; > + > + 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; > + > + 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, > +       u8 rmnet_mode, > +       struct net_device *egress_dev) > +{ > + struct rmnet_logical_ep_conf_s epconfig; > + > + LOGL("(%s, %d, %d, %s);", > +      dev->name, config_id, rmnet_mode, egress_dev->name); > + > + if (!egress_dev || > +     ((!_rmnet_is_physical_endpoint_associated(egress_dev)) > && > +     (!rmnet_vnd_is_vnd(egress_dev)))) { > + return 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, > +       u8 *rmnet_mode, > +       u8 *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) > + return; > + > + 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); > + 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) > + return NOTIFY_DONE; > + > + 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..f9da3c6 > --- /dev/null > +++ b/net/rmnet_data/rmnet_data_config.h > @@ -0,0 +1,107 @@ > +/* 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 > +#include > +#include > + > +#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 { > + u8 refcount; > + u8 rmnet_mode; > + u8 mux_id; > + struct timespec flush_time; > + struct net_device *egress_dev; > +}; > + > +/* struct rmnet_phys_ep_conf_s - Physical endpoint configuration > + * One instance of this structure is instantiated for each > net_device associated > + * with rmnet_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]; > + u32 ingress_data_format; > + u32 egress_data_format; > +}; > + > +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, > +   u32 ingress_data_format); > +int rmnet_set_egress_data_format(struct net_device *dev, > +  u32 egress_data_format, > +  u16 agg_size, > +  u16 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, > +       u8 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, > +       u8 *rmnet_mode, > +       u8 *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..e95d1ef > --- /dev/null > +++ b/net/rmnet_data/rmnet_data_handlers.c > @@ -0,0 +1,556 @@ > +/* 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#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" > +#include "rmnet_data_handlers.h" > + > +RMNET_LOG_MODULE(RMNET_DATA_LOGMASK_HANDLER); > + > +#ifdef CONFIG_RMNET_DATA_DEBUG > +unsigned int dump_pkt_rx; > +module_param(dump_pkt_rx, uint, 0644); > +MODULE_PARM_DESC(dump_pkt_rx, "Dump packets entering ingress > handler"); > + > +unsigned int dump_pkt_tx; > +module_param(dump_pkt_tx, uint, 0644); > +MODULE_PARM_DESC(dump_pkt_tx, "Dump packets exiting egress > handler"); > +#endif /* CONFIG_RMNET_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) > +{ > +} > +#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("Unknown ep mode %d", ep->rmnet_mode); > + rmnet_kfree_skb(skb, > RMNET_STATS_SKBFREE_DELIVER_NO_EP); > + return RX_HANDLER_CONSUMED; > + } > +} > + > +/* rmnet_ingress_deliver_packet() - Ingress handler for raw IP and > bridged > + *                                  MAP packets. > + * @skb:     Packet needing a destination. > + * @config:  Physical end point configuration that the packet > arrived on. > + * > + * 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; > + u8 mux_id; > + u16 len; > + > + if (RMNET_MAP_GET_CD_BIT(skb)) { > + if (config->ingress_data_format > +     & RMNET_INGRESS_FORMAT_MAP_COMMANDS) > + return rmnet_map_command(skb, config); > + > + LOGM("MAP command packet on %s; %s", skb->dev->name, > +      "Not configured for MAP commands"); > + rmnet_kfree_skb(skb, > + RMNET_STATS_SKBFREE_INGRESS_NOT_EXPE > CT_MAPC); > + return RX_HANDLER_CONSUMED; > + } > + > + mux_id = RMNET_MAP_GET_MUX_ID(skb); > + len = RMNET_MAP_GET_LENGTH(skb) - RMNET_MAP_GET_PAD(skb); > + > + if (mux_id >= RMNET_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)) > != NULL) { > + _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) > + return RX_HANDLER_CONSUMED; > + > + 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_EXP > ECT_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..f0e1ec6 > --- /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 > +#include > +#include > +#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, 0644); > +MODULE_PARM_DESC(log_level, "Logging level"); > + > +unsigned int rmnet_data_log_module_mask; > +module_param(rmnet_data_log_module_mask, uint, 0644); > +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..1dfddc1 > --- /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 BIT(4) > +#define RMNET_LOG_LVL_LOW BIT(3) > +#define RMNET_LOG_LVL_MED BIT(2) > +#define RMNET_LOG_LVL_HI  BIT(1) > +#define RMNET_LOG_LVL_ERR BIT(0) > + > +#define RMNET_LOG_MODULE(X) \ > + static u32 rmnet_mod_mask = X > + > +#define RMNET_DATA_LOGMASK_CONFIG  BIT(0) > +#define RMNET_DATA_LOGMASK_HANDLER BIT(1) > +#define RMNET_DATA_LOGMASK_VND     BIT(2) > +#define RMNET_DATA_LOGMASK_MAPD    BIT(3) > +#define RMNET_DATA_LOGMASK_MAPC    BIT(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..4bf7a72 > --- /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 > +#include > +#include > +#include > +#include > +#include > +#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, 0444); > +MODULE_PARM_DESC(skb_free, "SKBs dropped or freed"); > + > +static DEFINE_SPINLOCK(rmnet_queue_xmit_lock); > +unsigned long int queue_xmit[RMNET_STATS_QUEUE_XMIT_MAX * 2]; > +module_param_array(queue_xmit, ulong, 0, 0444); > +MODULE_PARM_DESC(queue_xmit, "SKBs queued for transmit"); > + > +static DEFINE_SPINLOCK(rmnet_deagg_count); > +unsigned long int deagg_count[RMNET_STATS_AGG_MAX]; > +module_param_array(deagg_count, ulong, 0, 0444); > +MODULE_PARM_DESC(deagg_count, "SKBs De-aggregated"); > + > +void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason) > +{ > + unsigned long flags; > + > + if (reason >= RMNET_STATS_SKBFREE_MAX) > + reason = RMNET_STATS_SKBFREE_UNKNOWN; > + > + spin_lock_irqsave(&rmnet_skb_free_lock, flags); > + skb_free[reason]++; > + spin_unlock_irqrestore(&rmnet_skb_free_lock, flags); > + > + if (skb) > + kfree_skb(skb); > +} > + > +void rmnet_stats_queue_xmit(int rc, unsigned int reason) > +{ > + unsigned long flags; > + > + if (rc != 0) > + reason += RMNET_STATS_QUEUE_XMIT_MAX; > + if (reason >= RMNET_STATS_QUEUE_XMIT_MAX * 2) > + reason = RMNET_STATS_SKBFREE_UNKNOWN; > + > + spin_lock_irqsave(&rmnet_queue_xmit_lock, flags); > + queue_xmit[reason]++; > + spin_unlock_irqrestore(&rmnet_queue_xmit_lock, flags); > +} > + > +void rmnet_stats_deagg_pkts(int aggcount) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&rmnet_deagg_count, flags); > + deagg_count[RMNET_STATS_AGG_BUFF]++; > + deagg_count[RMNET_STATS_AGG_PKT] += aggcount; > + spin_unlock_irqrestore(&rmnet_deagg_count, flags); > +} > diff --git a/net/rmnet_data/rmnet_data_stats.h > b/net/rmnet_data/rmnet_data_stats.h > new file mode 100644 > index 0000000..a6f73a2 > --- /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_UNKNOWN_IP_TYPE, > + RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0, > + RMNET_STATS_SKBFREE_INGRESS_BAD_MAP_CKSUM, > + RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED, > + RMNET_STATS_SKBFREE_MAX > +}; > + > +enum rmnet_queue_xmit_e { > + RMNET_STATS_QUEUE_XMIT_UNKNOWN, > + RMNET_STATS_QUEUE_XMIT_EGRESS, > + RMNET_STATS_QUEUE_XMIT_AGG_FILL_BUFFER, > + RMNET_STATS_QUEUE_XMIT_AGG_TIMEOUT, > + RMNET_STATS_QUEUE_XMIT_AGG_CPY_EXP_FAIL, > + RMNET_STATS_QUEUE_XMIT_AGG_SKIP, > + RMNET_STATS_QUEUE_XMIT_MAX > +}; > + > +void rmnet_kfree_skb(struct sk_buff *skb, unsigned int reason); > +void rmnet_stats_queue_xmit(int rc, unsigned int reason); > +void rmnet_stats_deagg_pkts(int aggcount); > +void rmnet_stats_agg_pkts(int aggcount); > +void rmnet_stats_dl_checksum(unsigned int rc); > +void rmnet_stats_ul_checksum(unsigned int rc); > +#endif /* _RMNET_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..264ff61 > --- /dev/null > +++ b/net/rmnet_data/rmnet_data_trace.h > @@ -0,0 +1,198 @@ > +/* 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 > +#include > +#include > + > +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 > + > diff --git a/net/rmnet_data/rmnet_data_vnd.c > b/net/rmnet_data/rmnet_data_vnd.c > new file mode 100644 > index 0000000..d48dc23 > --- /dev/null > +++ b/net/rmnet_data/rmnet_data_vnd.c > @@ -0,0 +1,460 @@ > +/* 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#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)) > + return RX_HANDLER_CONSUMED; > + > + dev->stats.rx_packets++; > + dev->stats.rx_bytes += skb->len; > + > + return RX_HANDLER_PASS; > +} > + > +/* rmnet_vnd_tx_fixup() - Virtual Network Device transmic fixup hook > + * @skb:      Socket buffer ("packet") to modify > + * @dev:      Virtual network device > + * > + * Additional VND specific packet processing for egress packets > + * > + * 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)) > + return RX_HANDLER_CONSUMED; > + > + dev->stats.tx_packets++; > + dev->stats.tx_bytes += skb->len; > + > + return RX_HANDLER_PASS; > +} > + > +/* Network Device Operations */ > + > +/* rmnet_vnd_start_xmit() - Transmit NDO callback > + * @skb:        Socket buffer ("packet") being sent from network > stack > + * @dev:        Virtual Network Device > + * > + * Standard network driver operations hook to transmit packets on > virtual > + * network device. Called by network stack. Packet is not > transmitted directly > + * from here; instead it is given to the rmnet egress handler. > + * > + * 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 const struct net_device_ops rmnet_data_vnd_ops = { > + .ndo_init = 0, > + .ndo_start_xmit = rmnet_vnd_start_xmit, > + .ndo_change_mtu = rmnet_vnd_change_mtu, > + .ndo_set_mac_address = 0, > + .ndo_validate_addr = 0, > +}; > + > +/* rmnet_vnd_setup() - net_device initialization callback > + * @dev:      Virtual network device > + * > + * Called by kernel whenever a new rmnet_data 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]) { > + *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; > + } > + > + rc = register_netdevice(dev); > + if (rc != 0) { > + LOGE("Failed to to register netdev [%s]", dev- > >name); > + free_netdev(dev); > + *new_device = 0; > + rc = 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) > + return 0; > + > + 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) > + return 0; > + > + return &dev_conf->local_ep; > +} > + > +/* rmnet_vnd_do_flow_control() - Process flow control request > + * @dev: Virtual network device node to do lookup on > + * @enable: boolean to enable/disable flow. > + * > + * 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, int enable) > +{ > + struct rmnet_vnd_private_s *dev_conf; > + > + if (unlikely(!dev)) > + return 2; > + > + if (!rmnet_vnd_is_vnd(dev)) > + return 2; > + > + dev_conf = (struct rmnet_vnd_private_s *)netdev_priv(dev); > + > + if (unlikely(!dev_conf)) > + return 2; > + > + 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"); > + 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..2903ffe > --- /dev/null > +++ b/net/rmnet_data/rmnet_data_vnd.h > @@ -0,0 +1,34 @@ > +/* Copyright (c) 2013-2017, The Linux Foundation. All rights > reserved. > + * > + * This program is free software; you can redistribute it and/or > modify > + * it under the terms of the GNU General Public License version 2 > and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the > + * GNU General Public License for more details. > + * > + * RMNET Data Virtual Network Device APIs > + * > + */ > + > +#include > + > +#ifndef _RMNET_DATA_VND_H_ > +#define _RMNET_DATA_VND_H_ > + > +int rmnet_vnd_do_flow_control(struct net_device *dev, int enable); > +struct rmnet_logical_ep_conf_s *rmnet_vnd_get_le_config(struct > net_device *dev); > +int rmnet_vnd_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_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..7d533aa > --- /dev/null > +++ b/net/rmnet_data/rmnet_map.h > @@ -0,0 +1,100 @@ > +/* Copyright (c) 2013-2017, The Linux Foundation. All rights > reserved. > + * > + * This program is free software; you can redistribute it and/or > modify > + * it under the terms of the GNU General Public License version 2 > and > + * only version 2 as published by the Free Software Foundation. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the > + * GNU General Public License for more details. > + */ > + > +#include > +#include > + > +#ifndef _RMNET_MAP_H_ > +#define _RMNET_MAP_H_ > + > +struct rmnet_map_control_command_s { > + u8  command_name; > + u8  cmd_type:2; > + u8  reserved:6; > + u16 reserved2; > + u32 transaction_id; > + union { > + u8  data[65528]; > + struct { > + u16 ip_family:2; > + u16 reserved:14; > + u16 flow_control_seq_num; > + u32 qos_id; > + } flow_control; > + }; > +}  __aligned(1); > + > +enum rmnet_map_results_e { > + RMNET_MAP_SUCCESS, > + RMNET_MAP_CONSUMED, > + RMNET_MAP_GENERAL_FAILURE, > + RMNET_MAP_NOT_ENABLED, > + RMNET_MAP_FAILED_AGGREGATION, > + RMNET_MAP_FAILED_MUX > +}; > + > +enum rmnet_map_mux_errors_e { > + RMNET_MAP_MUX_SUCCESS, > + RMNET_MAP_MUX_INVALID_MUX_ID, > + RMNET_MAP_MUX_INVALID_PAD_LENGTH, > + RMNET_MAP_MUX_INVALID_PKT_LENGTH, > + /* This should always be the last element */ > + RMNET_MAP_MUX_ENUM_LENGTH > +}; > + > +enum rmnet_map_commands_e { > + RMNET_MAP_COMMAND_NONE, > + RMNET_MAP_COMMAND_FLOW_DISABLE, > + RMNET_MAP_COMMAND_FLOW_ENABLE, > + /* These should always be the last 2 elements */ > + RMNET_MAP_COMMAND_UNKNOWN, > + RMNET_MAP_COMMAND_ENUM_LENGTH > +}; > + > +struct rmnet_map_header_s { > + u8  pad_len:6; > + u8  reserved_bit:1; > + u8  cd_bit:1; > + u8  mux_id; > + u16 pkt_len; > +}  __aligned(1); > + > +#define RMNET_MAP_GET_MUX_ID(Y) (((struct rmnet_map_header_s *) \ > +  (Y)->data)->mux_id) > +#define RMNET_MAP_GET_CD_BIT(Y) (((struct rmnet_map_header_s *) \ > + (Y)->data)->cd_bit) > +#define RMNET_MAP_GET_PAD(Y) (((struct rmnet_map_header_s *) \ > + (Y)->data)->pad_len) > +#define RMNET_MAP_GET_CMD_START(Y) ((struct > rmnet_map_control_command_s *) \ > +     ((Y)->data + \ > +       sizeof(struct > rmnet_map_header_s))) > +#define RMNET_MAP_GET_LENGTH(Y) (ntohs(((struct rmnet_map_header_s > *) \ > + (Y)->data)->pkt_len)) > + > +#define RMNET_MAP_COMMAND_REQUEST     0 > +#define RMNET_MAP_COMMAND_ACK         1 > +#define RMNET_MAP_COMMAND_UNSUPPORTED 2 > +#define RMNET_MAP_COMMAND_INVALID     3 > + > +#define RMNET_MAP_NO_PAD_BYTES        0 > +#define RMNET_MAP_ADD_PAD_BYTES       1 > + > +u8 rmnet_map_demultiplex(struct sk_buff *skb); > +struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, > +       struct rmnet_phys_ep_conf_s > *config); > + > +struct rmnet_map_header_s *rmnet_map_add_map_header(struct sk_buff > *skb, > +     int hdrlen, int > pad); > +rx_handler_result_t rmnet_map_command(struct sk_buff *skb, > +       struct rmnet_phys_ep_conf_s > *config); > + > +#endif /* _RMNET_MAP_H_ */ > diff --git a/net/rmnet_data/rmnet_map_command.c > b/net/rmnet_data/rmnet_map_command.c > new file mode 100644 > index 0000000..acb0b11 > --- /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 > +#include > +#include > +#include > +#include > +#include > +#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, 0444); > +MODULE_PARM_DESC(rmnet_map_command_stats, "MAP command statistics"); > + > +/* rmnet_map_do_flow_control() - Process MAP flow control command > + * @skb: Socket buffer containing the MAP flow control message > + * @config: Physical end-point configuration of ingress device > + * @enable: boolean for enable/disable > + * > + * Process in-band MAP flow control messages. Assumes mux ID is > mapped to a > + * RmNet Data vitrual network device. > + * > + * Return: > + *      - RMNET_MAP_COMMAND_UNSUPPORTED on any error > + *      - RMNET_MAP_COMMAND_ACK on success > + */ > +static u8 rmnet_map_do_flow_control(struct sk_buff *skb, > +     struct rmnet_phys_ep_conf_s > *config, > +     int enable) > +{ > + struct rmnet_map_control_command_s *cmd; > + struct net_device *vnd; > + struct rmnet_logical_ep_conf_s *ep; > + u8 mux_id; > + u16 ip_family; > + u16 fc_seq; > + u32 qos_id; > + int r; > + > + if (unlikely(!skb || !config)) > + return RX_HANDLER_CONSUMED; > + > + mux_id = RMNET_MAP_GET_MUX_ID(skb); > + cmd = RMNET_MAP_GET_CMD_START(skb); > + > + if (mux_id >= RMNET_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, enable); > + LOGD("dev:%s, qos_id:0x%08X, ip_family:%hd, fc_seq %hd, > en:%d", > +      skb->dev->name, qos_id, ip_family & 3, fc_seq, enable); > + > + if (r) { > + rmnet_kfree_skb(skb, > RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED); > + return RMNET_MAP_COMMAND_UNSUPPORTED; > + } else { > + return RMNET_MAP_COMMAND_ACK; > + } > +} > + > +/* rmnet_map_send_ack() - Send N/ACK message for MAP commands > + * @skb: Socket buffer containing the MAP command message > + * @type: N/ACK message selector > + * @config: Physical end-point configuration of ingress device > + * > + * skb is modified to contain the message type selector. The message > is then > + * transmitted on skb->dev. Note that this function grabs global Tx > lock on > + * skb->dev for latency reasons. > + * > + * Return: > + *      - void > + */ > +static void rmnet_map_send_ack(struct sk_buff *skb, > +        unsigned char type, > +        struct rmnet_phys_ep_conf_s *config) > +{ > + struct rmnet_map_control_command_s *cmd; > + int xmit_status; > + > + if (unlikely(!skb)) > + return; > + > + skb->protocol = htons(ETH_P_MAP); > + > + cmd = RMNET_MAP_GET_CMD_START(skb); > + cmd->cmd_type = type & 0x03; > + > + netif_tx_lock(skb->dev); > + xmit_status = skb->dev->netdev_ops->ndo_start_xmit(skb, skb- > >dev); > + netif_tx_unlock(skb->dev); > + > + LOGD("MAP command ACK=%hhu sent with rc: %d", type & 0x03, > xmit_status); > +} > + > +/* rmnet_map_command() - Entry point for handling MAP commands > + * @skb: Socket buffer containing the MAP command message > + * @config: Physical end-point configuration of ingress device > + * > + * Process MAP command frame and send N/ACK message as appropriate. > Message cmd > + * name is decoded here and appropriate handler is called. > + * > + * Return: > + *      - RX_HANDLER_CONSUMED. Command frames are always consumed. > + */ > +rx_handler_result_t rmnet_map_command(struct sk_buff *skb, > +       struct rmnet_phys_ep_conf_s > *config) > +{ > + struct rmnet_map_control_command_s *cmd; > + unsigned char command_name; > + unsigned char rc = 0; > + > + if (unlikely(!skb)) > + return RX_HANDLER_CONSUMED; > + > + cmd = RMNET_MAP_GET_CMD_START(skb); > + command_name = cmd->command_name; > + > + if (command_name < RMNET_MAP_COMMAND_ENUM_LENGTH) > + rmnet_map_command_stats[command_name]++; > + > + switch (command_name) { > + case RMNET_MAP_COMMAND_FLOW_ENABLE: > + rc = rmnet_map_do_flow_control(skb, config, 1); > + break; > + > + case RMNET_MAP_COMMAND_FLOW_DISABLE: > + rc = rmnet_map_do_flow_control(skb, config, 0); > + break; > + > + default: > + rmnet_map_command_stats[RMNET_MAP_COMMAND_UNKNOWN]++ > ; > + LOGM("Uknown MAP command: %d", command_name); > + rc = RMNET_MAP_COMMAND_UNSUPPORTED; > + rmnet_kfree_skb(skb, > RMNET_STATS_SKBFREE_MAPC_UNSUPPORTED); > + break; > + } > + if (rc == RMNET_MAP_COMMAND_ACK) > + rmnet_map_send_ack(skb, rc, config); > + return RX_HANDLER_CONSUMED; > +} > diff --git a/net/rmnet_data/rmnet_map_data.c > b/net/rmnet_data/rmnet_map_data.c > new file mode 100644 > index 0000000..33bde72 > --- /dev/null > +++ b/net/rmnet_data/rmnet_map_data.c > @@ -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. > + * > + * RMNET Data MAP protocol > + * > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#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) > +{ > + u32 padding, map_datalen; > + u8 *padbytes; > + struct rmnet_map_header_s *map_header; > + > + if (skb_headroom(skb) < sizeof(struct rmnet_map_header_s)) > + return 0; > + > + map_datalen = skb->len - hdrlen; > + map_header = (struct rmnet_map_header_s *) > + skb_push(skb, sizeof(struct > rmnet_map_header_s)); > + memset(map_header, 0, sizeof(struct rmnet_map_header_s)); > + > + if (pad == RMNET_MAP_NO_PAD_BYTES) { > + map_header->pkt_len = htons(map_datalen); > + return map_header; > + } > + > + padding = ALIGN(map_datalen, 4) - map_datalen; > + > + if (padding == 0) > + goto done; > + > + if (skb_tailroom(skb) < padding) > + return 0; > + > + padbytes = (u8 *)skb_put(skb, padding); > + LOGD("pad: %d", padding); > + memset(padbytes, 0, padding); > + > +done: > + map_header->pkt_len = htons(map_datalen + padding); > + map_header->pad_len = padding & 0x3F; > + > + return map_header; > +} > + > +/* rmnet_map_deaggregate() - Deaggregates a single packet > + * @skb:        Source socket buffer containing multiple MAP frames > + * @config:     Physical endpoint configuration of the ingress > device > + * > + * A whole new buffer is allocated for each portion of an aggregated > frame. > + * Caller should keep calling deaggregate() on the source skb until > 0 is > + * returned, indicating that there are no more packets to > deaggregate. Caller > + * is responsible for freeing the original skb. > + * > + * Return: > + *     - Pointer to new skb > + *     - 0 (null) if no more aggregated packets > + */ > +struct sk_buff *rmnet_map_deaggregate(struct sk_buff *skb, > +       struct rmnet_phys_ep_conf_s > *config) > +{ > + struct sk_buff *skbn; > + struct rmnet_map_header_s *maph; > + u32 packet_len; > + > + if (skb->len == 0) > + return 0; > + > + maph = (struct rmnet_map_header_s *)skb->data; > + packet_len = ntohs(maph->pkt_len) + sizeof(struct > rmnet_map_header_s); > + > + if ((((int)skb->len) - ((int)packet_len)) < 0) { > + LOGM("%s", "Got malformed packet. Dropping"); > + return 0; > + } > + > + skbn = alloc_skb(packet_len + RMNET_MAP_DEAGGR_SPACING, > GFP_ATOMIC); > + if (!skbn) > + return 0; > + > + skbn->dev = skb->dev; > + skb_reserve(skbn, RMNET_MAP_DEAGGR_HEADROOM); > + skb_put(skbn, packet_len); > + memcpy(skbn->data, skb->data, packet_len); > + skb_pull(skb, packet_len); > + > + /* Some hardware can send us empty frames. Catch them */ > + if (ntohs(maph->pkt_len) == 0) { > + LOGD("Dropping empty MAP frame"); > + rmnet_kfree_skb(skbn, > RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0); > + return 0; > + } > + > + return skbn; > +}