diff --git a/wwan/driver/quectel_MHI/Makefile b/wwan/driver/quectel_MHI/Makefile index 1270cc5..fda7961 100755 --- a/wwan/driver/quectel_MHI/Makefile +++ b/wwan/driver/quectel_MHI/Makefile @@ -2,19 +2,19 @@ include $(TOPDIR)/rules.mk PKG_NAME:=pcie_mhi PKG_VERSION:=1.3.8 -PKG_RELEASE:=1 +PKG_RELEASE:=2 include $(INCLUDE_DIR)/kernel.mk include $(INCLUDE_DIR)/package.mk PKG_BUILD_PARALLEL:=1 -PKG_BUILD_FLAGS:=gc-sections lto define KernelPackage/pcie_mhi SUBMENU:=Network Devices TITLE:=Kernel PCIe driver for MHI device DEPENDS:=@(TARGET_qualcommax_ipq807x||TARGET_qualcommax_ipq50xx) \ - +pciids +pciutils +quectel-cm + +pciids +pciutils +quectel-cm \ + +PACKAGE_kmod-rmnet-nss:kmod-rmnet-nss FILES:=$(PKG_BUILD_DIR)/pcie_mhi.ko AUTOLOAD:=$(call AutoLoad,90,pcie_mhi) endef @@ -24,11 +24,15 @@ define KernelPackage/pcie_mhi/description endef EXTRA_CFLAGS+= \ - -I$(STAGING_DIR)/usr/include/qca-nss-drv \ -Wno-unused-function \ -Wno-missing-prototypes \ -Wno-missing-declarations +ifneq ($(CONFIG_PACKAGE_kmod-rmnet-nss),) +EXTRA_CFLAGS += -I$(STAGING_DIR)/usr/include/qca-nss-rmnet +EXTRA_CFLAGS += -DCONFIG_QCA_NSS_DRV +endif + define Build/Compile +$(KERNEL_MAKE) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" M="$(PKG_BUILD_DIR)" \ modules diff --git a/wwan/driver/quectel_MHI/src/devices/mhi_netdev_quectel.c b/wwan/driver/quectel_MHI/src/devices/mhi_netdev_quectel.c index 8c06ea3..750b123 100644 --- a/wwan/driver/quectel_MHI/src/devices/mhi_netdev_quectel.c +++ b/wwan/driver/quectel_MHI/src/devices/mhi_netdev_quectel.c @@ -31,8 +31,6 @@ #include #include -#include - //#define CONFIG_IPQ5018_RATE_CONTROL //Only used with spf11.5 for IPQ5018 #if defined(CONFIG_IPQ5018_RATE_CONTROL) //#include @@ -59,19 +57,26 @@ static bool netdev_is_rx_handler_busy(struct net_device *dev) } #endif -static struct rmnet_nss_cb __read_mostly *nss_cb = NULL; +#ifdef CONFIG_QCA_NSS_DRV #if defined(CONFIG_PINCTRL_IPQ807x) || defined(CONFIG_PINCTRL_IPQ5018) || defined(CONFIG_PINCTRL_IPQ8074) -//#ifdef CONFIG_RMNET_DATA //spf12.x have no macro defined, just for spf11.x -#define CONFIG_QCA_NSS_DRV #define CONFIG_USE_RMNET_DATA_FOR_SKIP_MEMCPY +#include +//#ifdef CONFIG_RMNET_DATA //spf12.x have no macro defined, just for spf11.x /* define at qca/src/linux-4.4/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c */ //for spf11.x /* define at qsdk/qca/src/datarmnet/core/rmnet_config.c */ //for spf12.x /* set at qsdk/qca/src/data-kernel/drivers/rmnet-nss/rmnet_nss.c */ /* need add DEPENDS:= kmod-rmnet-core in feeds/makefile */ extern struct rmnet_nss_cb *rmnet_nss_callbacks __rcu __read_mostly; -//#endif +#endif +#else +struct rmnet_nss_cb { + int (*nss_create)(void *dev); + int (*nss_free)(void *dev); + int (*nss_tx)(void *skb); +}; #endif +static struct rmnet_nss_cb __read_mostly *nss_cb = NULL; int mhi_netdev_use_xfer_type_dma(unsigned chan) { diff --git a/wwan/driver/quectel_QMI_WWAN/Makefile b/wwan/driver/quectel_QMI_WWAN/Makefile index a96db7b..37f92a4 100644 --- a/wwan/driver/quectel_QMI_WWAN/Makefile +++ b/wwan/driver/quectel_QMI_WWAN/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=qmi_wwan_q PKG_VERSION:=1.2.9 -PKG_RELEASE:=1 +PKG_RELEASE:=2 include $(INCLUDE_DIR)/kernel.mk include $(INCLUDE_DIR)/package.mk @@ -15,17 +15,21 @@ define KernelPackage/qmi_wwan_q DEPENDS:=@(TARGET_qualcommax_ipq807x||TARGET_qualcommax_ipq50xx) \ +kmod-usb-net \ +kmod-usb-wdm \ - +kmod-qca-nss-drv \ - +@NSS_DRV_RMNET_ENABLE \ - +@NSS_DRV_C2C_ENABLE - FILES:=$(PKG_BUILD_DIR)/qmi_wwan_q.ko \ - $(PKG_BUILD_DIR)/rmnet_nss.ko - AUTOLOAD:=$(call AutoLoad,42,rmnet_nss) \ - $(call AutoLoad,81,qmi_wwan_q) + +PACKAGE_kmod-rmnet-nss:kmod-rmnet-nss + FILES:=$(PKG_BUILD_DIR)/qmi_wwan_q.ko + AUTOLOAD:=$(call AutoLoad,81,qmi_wwan_q) endef + EXTRA_CFLAGS+= \ - -I$(STAGING_DIR)/usr/include/qca-nss-drv + -Wno-unused-function \ + -Wno-missing-prototypes \ + -Wno-missing-declarations + +ifneq ($(CONFIG_PACKAGE_kmod-rmnet-nss),) +EXTRA_CFLAGS += -I$(STAGING_DIR)/usr/include/qca-nss-rmnet +EXTRA_CFLAGS += -DCONFIG_QCA_NSS_DRV +endif define Build/Compile +$(KERNEL_MAKE) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" M="$(PKG_BUILD_DIR)" \ diff --git a/wwan/driver/quectel_QMI_WWAN/src/Makefile b/wwan/driver/quectel_QMI_WWAN/src/Makefile index 95fddb7..42bfeb5 100644 --- a/wwan/driver/quectel_QMI_WWAN/src/Makefile +++ b/wwan/driver/quectel_QMI_WWAN/src/Makefile @@ -1,4 +1,4 @@ -obj-m += rmnet_nss.o qmi_wwan_q.o +obj-m += qmi_wwan_q.o PWD := $(shell pwd) diff --git a/wwan/driver/quectel_QMI_WWAN/src/qmi_wwan_q.c b/wwan/driver/quectel_QMI_WWAN/src/qmi_wwan_q.c index ef2a96d..9a6edee 100644 --- a/wwan/driver/quectel_QMI_WWAN/src/qmi_wwan_q.c +++ b/wwan/driver/quectel_QMI_WWAN/src/qmi_wwan_q.c @@ -33,8 +33,6 @@ #include #include -#include "rmnet_nss.h" - #ifndef ETH_P_MAP #define ETH_P_MAP 0xDA1A #endif @@ -48,22 +46,25 @@ #define ARPHRD_RAWIP ARPHRD_NONE #endif -#ifdef CONFIG_PINCTRL_IPQ807x -#define CONFIG_QCA_NSS_DRV -//#define CONFIG_QCA_NSS_PACKET_FILTER -#endif - -static struct rmnet_nss_cb __read_mostly *nss_cb = NULL; +#ifdef CONFIG_QCA_NSS_DRV #if defined(CONFIG_PINCTRL_IPQ807x) || defined(CONFIG_PINCTRL_IPQ5018) || defined(CONFIG_PINCTRL_IPQ8074) -//#ifdef CONFIG_RMNET_DATA //spf12.x none, not effect for spf11.x -#define CONFIG_QCA_NSS_DRV -/* define at qsdk/qca/src/linux-4.4/net/rmnet_data/rmnet_data_main.c */ //for spf11.x +#include +//#ifdef CONFIG_RMNET_DATA //spf12.x have no macro defined, just for spf11.x +/* define at qca/src/linux-4.4/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c */ //for spf11.x /* define at qsdk/qca/src/datarmnet/core/rmnet_config.c */ //for spf12.x /* set at qsdk/qca/src/data-kernel/drivers/rmnet-nss/rmnet_nss.c */ /* need add DEPENDS:= kmod-rmnet-core in feeds/makefile */ extern struct rmnet_nss_cb *rmnet_nss_callbacks __rcu __read_mostly; -//#endif #endif +#else +struct rmnet_nss_cb { + int (*nss_create)(void *dev); + int (*nss_free)(void *dev); + int (*nss_tx)(void *skb); +}; +#endif + +static struct rmnet_nss_cb __read_mostly *nss_cb = NULL; /* This driver supports wwan (3G/LTE/?) devices using a vendor * specific management protocol called Qualcomm MSM Interface (QMI) - diff --git a/wwan/driver/rmnet-nss/Makefile b/wwan/driver/rmnet-nss/Makefile new file mode 100644 index 0000000..8f25e90 --- /dev/null +++ b/wwan/driver/rmnet-nss/Makefile @@ -0,0 +1,38 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=rmnet-nss +PKG_VERSION:=1.0 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/kernel.mk +include $(INCLUDE_DIR)/package.mk + +PKG_BUILD_PARALLEL:=1 + +define KernelPackage/rmnet-nss + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + TITLE:=RmNet QCA NSS support + DEPENDS:=@(TARGET_qualcommax_ipq807x||TARGET_qualcommax_ipq50xx) \ + +kmod-qca-nss-drv \ + +@NSS_DRV_RMNET_ENABLE \ + +@NSS_DRV_C2C_ENABLE + FILES:=$(PKG_BUILD_DIR)/rmnet_nss.ko + AUTOLOAD:=$(call AutoLoad,42,rmnet_nss) +endef + +EXTRA_CFLAGS+= \ + -I$(STAGING_DIR)/usr/include/qca-nss-drv + +define Build/InstallDev + mkdir -p $(1)/usr/include/qca-nss-rmnet + $(CP) $(PKG_BUILD_DIR)/rmnet_nss.h $(1)/usr/include/qca-nss-rmnet/rmnet_nss.h +endef + +define Build/Compile + +$(KERNEL_MAKE) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" M="$(PKG_BUILD_DIR)" \ + modules +endef + +$(eval $(call KernelPackage,rmnet-nss)) diff --git a/wwan/driver/rmnet-nss/src/Makefile b/wwan/driver/rmnet-nss/src/Makefile new file mode 100644 index 0000000..b2c64af --- /dev/null +++ b/wwan/driver/rmnet-nss/src/Makefile @@ -0,0 +1,32 @@ +obj-m += rmnet_nss.o + +KDIR ?= /lib/modules/$(shell uname -r)/build + +KERNEL_MAKE_OPTS := -C $(KDIR) M=$(CURDIR) +ifneq ($(ARCH),) +KERNEL_MAKE_OPTS += ARCH=$(ARCH) +endif +ifneq ($(CROSS_COMPILE),) +KERNEL_MAKE_OPTS += CROSS_COMPILE=$(CROSS_COMPILE) +endif +ifneq ($(INSTALL_MOD_PATH),) +KERNEL_MAKE_OPTS += INSTALL_MOD_PATH=$(INSTALL_MOD_PATH) +endif + +PWD := $(shell pwd) + +default: + $(MAKE) $(KERNEL_MAKE_OPTS) modules + +install: modules_install + +modules_install: + $(MAKE) $(KERNEL_MAKE_OPTS) modules_install + +install: default + install -m 644 $(PWD)/*.ko /lib/modules/$(shell uname -r)/kernel/drivers/net/usb/ + depmod + +clean: + rm -rf *~ .tmp_versions modules.order Module.symvers + find . -type f -name "*~" -o -name "*.o" -o -name "*.ko" -o -name "*.cmd" -o -name "*.mod.c" | xargs rm -rf diff --git a/wwan/driver/rmnet-nss/src/rmnet_nss.c b/wwan/driver/rmnet-nss/src/rmnet_nss.c new file mode 100644 index 0000000..8e50c6f --- /dev/null +++ b/wwan/driver/rmnet-nss/src/rmnet_nss.c @@ -0,0 +1,494 @@ +/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. 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 +#include +#include +#include +#include + +#include "rmnet_nss.h" + +#define RMNET_NSS_HASH_BITS 8 +#define hash_add_ptr(table, node, key) \ + hlist_add_head(node, &table[hash_ptr(key, HASH_BITS(table))]) + +static DEFINE_HASHTABLE(rmnet_nss_ctx_hashtable, RMNET_NSS_HASH_BITS); + +struct rmnet_nss_cb *rmnet_nss_callbacks; +EXPORT_SYMBOL(rmnet_nss_callbacks); + +struct rmnet_nss_ctx { + struct hlist_node hnode; + struct net_device *rmnet_dev; + struct nss_rmnet_rx_handle *nss_ctx; +}; + +enum __rmnet_nss_stat { + RMNET_NSS_RX_ETH, + RMNET_NSS_RX_FAIL, + RMNET_NSS_RX_NON_ETH, + RMNET_NSS_RX_BUSY, + RMNET_NSS_TX_NO_CTX, + RMNET_NSS_TX_SUCCESS, + RMNET_NSS_TX_FAIL, + RMNET_NSS_TX_NONLINEAR, + RMNET_NSS_TX_BAD_IP, + RMNET_NSS_EXCEPTIONS, + RMNET_NSS_EX_BAD_HDR, + RMNET_NSS_EX_BAD_IP, + RMNET_NSS_EX_SUCCESS, + RMNET_NSS_TX_BAD_FRAGS, + RMNET_NSS_TX_LINEARIZE_FAILS, + RMNET_NSS_TX_NON_ZERO_HEADLEN_FRAGS, + RMNET_NSS_TX_BUSY_LOOP, + RMNET_NSS_NUM_STATS, +}; + +static unsigned long rmnet_nss_stats[RMNET_NSS_NUM_STATS]; +extern void qmi_rmnet_mark_skb(struct net_device *dev, struct sk_buff *skb); +static void (*rmnet_mark_skb)(struct net_device *dev, struct sk_buff *skb); + +#define RMNET_NSS_STAT(name, counter, desc) \ + module_param_named(name, rmnet_nss_stats[counter], ulong, 0444); \ + MODULE_PARM_DESC(name, desc) + +RMNET_NSS_STAT(rmnet_nss_rx_ethernet, RMNET_NSS_RX_ETH, + "Number of Ethernet headers successfully removed"); +RMNET_NSS_STAT(rmnet_nss_rx_fail, RMNET_NSS_RX_FAIL, + "Number of Ethernet headers that could not be removed"); +RMNET_NSS_STAT(rmnet_nss_rx_non_ethernet, RMNET_NSS_RX_NON_ETH, + "Number of non-Ethernet packets received"); +RMNET_NSS_STAT(rmnet_nss_rx_busy, RMNET_NSS_RX_BUSY, + "Number of packets dropped decause rmnet_data device was busy"); +RMNET_NSS_STAT(rmnet_nss_tx_slow, RMNET_NSS_TX_NO_CTX, + "Number of packets sent over non-NSS-accelerated rmnet device"); +RMNET_NSS_STAT(rmnet_nss_tx_fast, RMNET_NSS_TX_SUCCESS, + "Number of packets sent over NSS-accelerated rmnet device"); +RMNET_NSS_STAT(rmnet_nss_tx_fail, RMNET_NSS_TX_FAIL, + "Number of packets that NSS could not transmit"); +RMNET_NSS_STAT(rmnet_nss_tx_nonlinear, RMNET_NSS_TX_NONLINEAR, + "Number of non linear sent over NSS-accelerated rmnet device"); +RMNET_NSS_STAT(rmnet_nss_tx_invalid_ip, RMNET_NSS_TX_BAD_IP, + "Number of ingress packets with invalid IP headers"); +RMNET_NSS_STAT(rmnet_nss_tx_invalid_frags, RMNET_NSS_TX_BAD_FRAGS, + "Number of ingress packets with invalid frag format"); +RMNET_NSS_STAT(rmnet_nss_tx_linearize_fail, RMNET_NSS_TX_LINEARIZE_FAILS, + "Number of ingress packets where linearize in tx fails"); +RMNET_NSS_STAT(rmnet_nss_tx_exceptions, RMNET_NSS_EXCEPTIONS, + "Number of times our DL exception handler was invoked"); +RMNET_NSS_STAT(rmnet_nss_exception_non_ethernet, RMNET_NSS_EX_BAD_HDR, + "Number of non-Ethernet exception packets"); +RMNET_NSS_STAT(rmnet_nss_exception_invalid_ip, RMNET_NSS_EX_BAD_IP, + "Number of exception packets with invalid IP headers"); +RMNET_NSS_STAT(rmnet_nss_exception_success, RMNET_NSS_EX_SUCCESS, + "Number of exception packets handled successfully"); +RMNET_NSS_STAT(rmnet_nss_tx_non_zero_headlen_frags, RMNET_NSS_TX_NON_ZERO_HEADLEN_FRAGS, + "Number of packets with non zero headlen"); +RMNET_NSS_STAT(rmnet_nss_tx_busy_loop, RMNET_NSS_TX_BUSY_LOOP, + "Number of times tx packets busy looped"); + +static void rmnet_nss_inc_stat(enum __rmnet_nss_stat stat) +{ + if (stat >= 0 && stat < RMNET_NSS_NUM_STATS) + rmnet_nss_stats[stat]++; +} + +static struct rmnet_nss_ctx *rmnet_nss_find_ctx(struct net_device *dev) +{ + struct rmnet_nss_ctx *ctx; + struct hlist_head *bucket; + u32 hash; + + hash = hash_ptr(dev, HASH_BITS(rmnet_nss_ctx_hashtable)); + bucket = &rmnet_nss_ctx_hashtable[hash]; + hlist_for_each_entry(ctx, bucket, hnode) { + if (ctx->rmnet_dev == dev) + return ctx; + } + + return NULL; +} + +static void rmnet_nss_free_ctx(struct rmnet_nss_ctx *ctx) +{ + if (ctx) { + hash_del(&ctx->hnode); + nss_rmnet_rx_xmit_callback_unregister(ctx->nss_ctx); + nss_rmnet_rx_destroy_sync(ctx->nss_ctx); + kfree(ctx); + } +} + +/* Pull off an ethernet header, if possible */ +static int rmnet_nss_ethhdr_pull(struct sk_buff *skb) +{ + if (!skb->protocol || skb->protocol == htons(ETH_P_802_3)) { + void *ret = skb_pull(skb, sizeof(struct ethhdr)); + + rmnet_nss_inc_stat((ret) ? RMNET_NSS_RX_ETH : + RMNET_NSS_RX_FAIL); + return !ret; + } + + rmnet_nss_inc_stat(RMNET_NSS_RX_NON_ETH); + return -1; +} + +static int rmnet_nss_handle_non_zero_headlen(struct sk_buff *skb) +{ + struct iphdr *iph; + u8 transport; + + if (skb_headlen(skb) < sizeof(struct iphdr)){ + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_IP); + return -EINVAL; + } + + iph = (struct iphdr *)skb->data; + + if (iph->version == 4) { + transport = iph->protocol; + } else if (iph->version == 6) { + struct ipv6hdr *ip6h = (struct ipv6hdr *)iph; + transport = ip6h->nexthdr; + } else { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_IP); + return -EINVAL; + } + +/* Assumption: required headers are copied in case of TCP/UDP by SFE */ +/* In case of TCP/UDP where there are no IP extension headers, the assumption is that SFE copied the IP and Transport header */ + + if (transport != IPPROTO_TCP && transport != IPPROTO_UDP) { + if (skb_linearize(skb)) { + rmnet_nss_inc_stat(RMNET_NSS_TX_LINEARIZE_FAILS); + return -EINVAL; + } + } + else if ((transport == IPPROTO_TCP && skb_headlen(skb) < 40) || (transport == IPPROTO_UDP && skb_headlen(skb) < 28)) { + pr_err_ratelimited("rmnet_nss: error: Partial copy of headers\n"); + return -EINVAL; + } + + return 0; +} + +/* Copy headers to linear section for non linear packets */ +static int rmnet_nss_adjust_header(struct sk_buff *skb) +{ + struct iphdr *iph; + skb_frag_t *frag; + int bytes = 0; + u8 transport; + + if (skb_shinfo(skb)->nr_frags != 1) { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_FRAGS); + return -EINVAL; + } + + if (skb_headlen(skb)) { + rmnet_nss_inc_stat(RMNET_NSS_TX_NON_ZERO_HEADLEN_FRAGS); + return rmnet_nss_handle_non_zero_headlen(skb); + } + + frag = &skb_shinfo(skb)->frags[0]; + + iph = (struct iphdr *)(skb_frag_address(frag)); + + if (iph->version == 4) { + bytes = iph->ihl*4; + transport = iph->protocol; + } else if (iph->version == 6) { + struct ipv6hdr *ip6h = (struct ipv6hdr *)iph; + + bytes = sizeof(struct ipv6hdr); + /* Dont have to account for extension headers yet */ + transport = ip6h->nexthdr; + } else { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_IP); + return -EINVAL; + } + + if (transport == IPPROTO_TCP) { + struct tcphdr *th; + + th = (struct tcphdr *)((u8 *)iph + bytes); + bytes += th->doff * 4; + } else if (transport == IPPROTO_UDP) { + bytes += sizeof(struct udphdr); + } else { + /* cant do anything else here unfortunately so linearize */ + if (skb_linearize(skb)) { + rmnet_nss_inc_stat(RMNET_NSS_TX_LINEARIZE_FAILS); + return -EINVAL; + } else { + return 0; + } + } + + if (bytes > skb_frag_size(frag)) { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_FRAGS); + return -EINVAL; + } + + skb_push(skb, bytes); + memcpy(skb->data, iph, bytes); + + /* subtract to account for skb_push */ + skb->len -= bytes; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 12, 0)) + frag->offset += bytes; +#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0)) + frag->bv_offset += bytes; +#else + frag->page_offset += bytes; +#endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(5, 4, 0)) */ + skb_frag_size_sub(frag, bytes); + + /* subtract to account for skb_frag_size_sub */ + skb->data_len -= bytes; + + return 0; +} + +/* Called by NSS in the DL exception case. + * Since the packet cannot be sent over the accelerated path, we need to + * handle it. Remove the ethernet header and pass it onward to the stack + * if possible. + */ +static void rmnet_nss_receive(struct net_device *dev, struct sk_buff *skb, + struct napi_struct *napi) +{ + rmnet_nss_inc_stat(RMNET_NSS_EXCEPTIONS); + + if (!skb) + return; + + if (rmnet_nss_ethhdr_pull(skb)) { + rmnet_nss_inc_stat(RMNET_NSS_EX_BAD_HDR); + goto drop; + } + + /* reset header pointers */ + skb_reset_transport_header(skb); + skb_reset_network_header(skb); + skb_reset_mac_header(skb); + + /* reset packet type */ + skb->pkt_type = PACKET_HOST; + + skb->dev = dev; + + /* reset protocol type */ + switch (skb->data[0] & 0xF0) { + case 0x40: + skb->protocol = htons(ETH_P_IP); + break; + case 0x60: + skb->protocol = htons(ETH_P_IPV6); + break; + default: + rmnet_nss_inc_stat(RMNET_NSS_EX_BAD_IP); + goto drop; + } + + rmnet_nss_inc_stat(RMNET_NSS_EX_SUCCESS); + + /* Set this so that we dont loop around netif_receive_skb */ + + skb->cb[0] = 1; + + netif_receive_skb(skb); + return; + +drop: + kfree_skb(skb); +} + +/* Main downlink handler + * Looks up NSS contex associated with the device. If the context is found, + * we add a dummy ethernet header with the approriate protocol field set, + * the pass the packet off to NSS for hardware acceleration. + */ +static int rmnet_nss_tx(struct sk_buff *skb) +{ + struct ethhdr *eth; + struct rmnet_nss_ctx *ctx; + struct net_device *dev = skb->dev; + nss_tx_status_t rc; + unsigned int len; + u8 version; + + if (skb_is_nonlinear(skb)) { + if (rmnet_nss_adjust_header(skb)) + goto fail; + else + rmnet_nss_inc_stat(RMNET_NSS_TX_NONLINEAR); + } + + version = ((struct iphdr *)skb->data)->version; + + ctx = rmnet_nss_find_ctx(dev); + if (!ctx) { + rmnet_nss_inc_stat(RMNET_NSS_TX_NO_CTX); + return -EINVAL; + } + + eth = (struct ethhdr *)skb_push(skb, sizeof(*eth)); + memset(eth->h_dest, 0, ETH_ALEN); + memset(eth->h_source, 0, ETH_ALEN); + if (version == 4) { + eth->h_proto = htons(ETH_P_IP); + } else if (version == 6) { + eth->h_proto = htons(ETH_P_IPV6); + } else { + rmnet_nss_inc_stat(RMNET_NSS_TX_BAD_IP); + goto fail; + } + + skb->protocol = htons(ETH_P_802_3); + /* Get length including ethhdr */ + len = skb->len; + +transmit: + rc = nss_rmnet_rx_tx_buf(ctx->nss_ctx, skb); + if (rc == NSS_TX_SUCCESS) { + /* Increment rmnet_data device stats. + * Don't call rmnet_data_vnd_rx_fixup() to do this, as + * there's no guarantee the skb pointer is still valid. + */ + dev->stats.rx_packets++; + dev->stats.rx_bytes += len; + rmnet_nss_inc_stat(RMNET_NSS_TX_SUCCESS); + return 0; + } else if (rc == NSS_TX_FAILURE_QUEUE) { + rmnet_nss_inc_stat(RMNET_NSS_TX_BUSY_LOOP); + goto transmit; + } else if (rc == NSS_TX_FAILURE_NOT_ENABLED) { + /* New stats */ + rmnet_nss_receive(dev, skb, NULL); + return 0; + } + +fail: + rmnet_nss_inc_stat(RMNET_NSS_TX_FAIL); + kfree_skb(skb); + return 1; +} + +/* Called by NSS in the UL acceleration case. + * We are guaranteed to have an ethernet packet here from the NSS hardware, + * We need to pull the header off and invoke our ndo_start_xmit function + * to handle transmitting the packet to the network stack. + */ +static void rmnet_nss_xmit(struct net_device *dev, struct sk_buff *skb) +{ + int rc; + + skb_pull(skb, sizeof(struct ethhdr)); + rmnet_nss_inc_stat(RMNET_NSS_RX_ETH); + + /* Use top-half entry point for the netdev so that we enable QDisc support for RmNet redirect. */ + skb_reset_network_header(skb); + skb->dev = dev; + switch (skb->data[0] & 0xF0) { + case 0x40: + skb->protocol = htons(ETH_P_IP); + break; + case 0x60: + skb->protocol = htons(ETH_P_IPV6); + break; + default: + break; + } + if (rmnet_mark_skb) + rmnet_mark_skb(dev, skb); + + rc = dev_queue_xmit(skb); + if (unlikely(rc != 0)) { + rmnet_nss_inc_stat(RMNET_NSS_RX_BUSY); + } +} + +/* Create and register an NSS context for an rmnet_data device */ +static int rmnet_nss_create_vnd(struct net_device *dev) +{ + struct rmnet_nss_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); + if (!ctx) + return -ENOMEM; + + ctx->rmnet_dev = dev; + ctx->nss_ctx = nss_rmnet_rx_create(dev); + if (!ctx->nss_ctx) { + kfree(ctx); + return -1; + } + + nss_rmnet_rx_register(ctx->nss_ctx, rmnet_nss_receive, dev); + nss_rmnet_rx_xmit_callback_register(ctx->nss_ctx, rmnet_nss_xmit); + hash_add_ptr(rmnet_nss_ctx_hashtable, &ctx->hnode, dev); + return 0; +} + +/* Unregister and destroy the NSS context for an rmnet_data device */ +static int rmnet_nss_free_vnd(struct net_device *dev) +{ + struct rmnet_nss_ctx *ctx; + + ctx = rmnet_nss_find_ctx(dev); + rmnet_nss_free_ctx(ctx); + + return 0; +} + +static const struct rmnet_nss_cb rmnet_nss = { + .nss_create = rmnet_nss_create_vnd, + .nss_free = rmnet_nss_free_vnd, + .nss_tx = rmnet_nss_tx, +}; + +static int __init rmnet_nss_init(void) +{ + pr_err("%s(): initializing rmnet_nss\n", __func__); + RCU_INIT_POINTER(rmnet_nss_callbacks, (struct rmnet_nss_cb *)&rmnet_nss); + rmnet_mark_skb = symbol_get(qmi_rmnet_mark_skb); + return 0; +} + +static void __exit rmnet_nss_exit(void) +{ + struct hlist_node *tmp; + struct rmnet_nss_ctx *ctx; + int bkt; + + pr_err("%s(): exiting rmnet_nss\n", __func__); + RCU_INIT_POINTER(rmnet_nss_callbacks, NULL); + if (rmnet_mark_skb) + symbol_put(qmi_rmnet_mark_skb); + + /* Tear down all NSS contexts */ + hash_for_each_safe(rmnet_nss_ctx_hashtable, bkt, tmp, ctx, hnode) + rmnet_nss_free_ctx(ctx); +} + +MODULE_LICENSE("GPL v2"); +module_init(rmnet_nss_init); +module_exit(rmnet_nss_exit); diff --git a/wwan/driver/rmnet-nss/src/rmnet_nss.h b/wwan/driver/rmnet-nss/src/rmnet_nss.h new file mode 100644 index 0000000..e12a334 --- /dev/null +++ b/wwan/driver/rmnet-nss/src/rmnet_nss.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _RMNET_NSS_H_ +#define _RMNET_NSS_H_ + +#include +#include + +struct rmnet_nss_cb { + int (*nss_create)(struct net_device *dev); + int (*nss_free)(struct net_device *dev); + int (*nss_tx)(struct sk_buff *skb); +}; + +#endif