diff --git a/qca/nss-ifb/Makefile b/qca/nss-ifb/Makefile new file mode 100644 index 0000000..f8e406a --- /dev/null +++ b/qca/nss-ifb/Makefile @@ -0,0 +1,49 @@ +# +# Copyright (C) 2008-2012 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=nss-ifb +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/nss-ifb + SECTION:=kernel + CATEGORY:=Kernel modules + SUBMENU:=Network Devices + TITLE:=NSS IFB Interface + DEPENDS:=+kmod-qca-nss-drv @!LINUX_3_18 + FILES:=$(PKG_BUILD_DIR)/nss-ifb.ko + KCONFIG:= +endef + +define KernelPackage/nss-ifb/description + Kernel module to register a NSS aware IFB interface. +endef + +EXTRA_KCONFIG:= \ + CONFIG_NET_CLS=y + +EXTRA_CFLAGS:= \ + -I$(STAGING_DIR)/usr/include/qca-nss-drv + +MAKE_OPTS:= \ + $(KERNEL_MAKE_FLAGS) \ + M="$(PKG_BUILD_DIR)" \ + EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \ + $(EXTRA_KCONFIG) + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + $(MAKE_OPTS) \ + modules +endef + +$(eval $(call KernelPackage,nss-ifb)) + diff --git a/qca/nss-ifb/README.md b/qca/nss-ifb/README.md new file mode 100644 index 0000000..a0af7a5 --- /dev/null +++ b/qca/nss-ifb/README.md @@ -0,0 +1,45 @@ +NSS Physical Interface Ingress Driver +===================================== + +This driver redirect NSS physical interface (namely GMACs) ingress traffic to itself +and sends it back to the Linux network stack (as the source GMACs packets) as it's +egress traffic. + +This allows the NSS QDISC drivers to manage the egress traffic of this driver's +NSS virtual interface. + +This driver will create a single network interface named 'nssifb'. The default +source interface is defined as 'eth0'. It can be changed using the following module +parameter path: + +/sys/module/nss-ifb/parameter/nss_src_dev + +To change the source NSS physical interface to 'eth1', use the following command: + +printf eth1 > /sys/module/nss-ifb/parameter/nss_src_dev + +You need to change the source interface first before bringing up the 'nssifb' +interface. Changing it after the interface is up will have no effect. You need +to bring down the interface and bring it back up to have the changes take effect. + +CPU load imposed on the Krait CPUs appears negligible with this driver intercepting +the physical interface's ingress traffic. Full line speed of the GMAC interface +could still be achieved. + +The commands below shows an example to shape ingress traffic to 500 Mbps and egress +to 200 Mbps for the 'eth0' interface. + +# Load the module if it's not loaded +modprobe nss-ifb + +# Bring up the nssifb interface to active ingress redirect +ip link set up nssifb + +# Shape ingress traffic to 500 Mbit with chained NSSFQ_CODEL +tc qdisc add dev nssifb root handle 1: nsstbl rate 500Mbit burst 1Mb +tc qdisc add dev nssifb parent 1: handle 10: nssfq_codel limit 10240 flows 1024 quantum 1514 target 5ms interval 100ms set_default + +# Shape egress traffic to 200 Mbit with chained NSSFQ_CODEL +tc qdisc add dev eth0 root handle 1: nsstbl rate 200Mbit burst 1Mb +tc qdisc add dev eth0 parent 1: handle 10: nssfq_codel limit 10240 flows 1024 quantum 1514 target 5ms interval 100ms set_default + diff --git a/qca/nss-ifb/src/Makefile b/qca/nss-ifb/src/Makefile new file mode 100644 index 0000000..332b9b4 --- /dev/null +++ b/qca/nss-ifb/src/Makefile @@ -0,0 +1,3 @@ +obj-m += nss-ifb.o + +nss-ifb-objs := nss_ifb.o diff --git a/qca/nss-ifb/src/nss_ifb.c b/qca/nss-ifb/src/nss_ifb.c new file mode 100644 index 0000000..18c017f --- /dev/null +++ b/qca/nss-ifb/src/nss_ifb.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * This driver is adapted from the Linux /drivers/net/ifb.c file. + * + * Redirect QCA NSS physical interface ingress traffic to this driver's + * virtual interface. This will allow ingress traffic shaping using the + * QCA NSS shaper. + */ + +#include + +#define TX_Q_LIMIT 32 + +struct nss_ifb_dev_private { + struct nss_virt_if_handle *nssctx; + struct net_device *nss_src_dev; + uint32_t nss_src_if_num; + char nss_src_dev_name[32]; +}; + +char nss_dev_name_array[32] = "eth0"; +char *nss_dev_name = nss_dev_name_array; +module_param(nss_dev_name, charp, 0644); +MODULE_PARM_DESC(nss_dev_name, "NSS physical interface source device name"); + +/* + * Virtual interface egress packet callback. + * + * We send it back to the Linux network stack. + */ +static void nss_ifb_data_cb(struct net_device *netdev, struct sk_buff *skb, struct napi_struct *napi) +{ + struct nss_ifb_dev_private *dp = netdev_priv(netdev); + + skb->protocol = eth_type_trans(skb, dp->nss_src_dev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + napi_gro_receive(napi, skb); +} + +/* + * Virtual interface ingress packet callback. + * + * We just send it back to the NSS firmware to let the shaper work on it. + */ +static void nss_ifb_xmit_cb(struct net_device *netdev, struct sk_buff *skb) +{ + struct nss_ifb_dev_private *dp = netdev_priv(netdev); + int ret; + + ret = nss_virt_if_tx_buf(dp->nssctx, skb); + if (unlikely(ret)) { + pr_warn("Failed [%d] to send skb [len: %d, protocol: 0x%X] to NSS!\n", + ret, skb->len, ntohs(skb->protocol)); + } +} + +static void nss_ifb_stats64(struct net_device *dev, + struct rtnl_link_stats64 *stats) +{ + +} + +static int nss_ifb_dev_init(struct net_device *dev) +{ + struct nss_ifb_dev_private *dp = netdev_priv(dev); + + dp->nssctx = nss_virt_if_create_sync_nexthop(dev, NSS_ETH_RX_INTERFACE, NSS_ETH_RX_INTERFACE); + if (!dp->nssctx) { + dp->nssctx = NULL; + pr_warn("Could not create a NSS virtual interface for dev [%s]\n", + dev->name); + + return -ENODEV; + } + pr_info("Created a NSS virtual interface for dev [%s]\n", dev->name); + + nss_virt_if_register(dp->nssctx, nss_ifb_data_cb, dev); + pr_info("NSS IFB data callback registered\n"); + + nss_virt_if_xmit_callback_register(dp->nssctx, nss_ifb_xmit_cb); + pr_info("NSS IFB transmit callback registered\n"); + + return 0; +} + +static void nss_ifb_dev_uninit(struct net_device *dev) +{ + struct nss_ifb_dev_private *dp = netdev_priv(dev); + int ret; + + nss_virt_if_xmit_callback_unregister(dp->nssctx); + pr_info("NSS IFB transmit callback unregistered\n"); + + ret = nss_virt_if_destroy_sync(dp->nssctx); + if (ret == NSS_TX_SUCCESS) { + pr_info("NSS virtual interface destroyed for dev [%s]\n", dev->name); + } + else { + pr_warn("Unable to destroy NSS virtual interface for dev [%s], error[%d]\n", + dev->name, ret); + } + dp->nssctx = NULL; +} + +static netdev_tx_t nss_ifb_xmit(struct sk_buff *skb, struct net_device *dev) +{ + return NETDEV_TX_OK; +} + +static int nss_ifb_close(struct net_device *dev) +{ + struct nss_ifb_dev_private *dp = netdev_priv(dev); + struct nss_ctx_instance *nss_ctx; + struct net_device *src_dev; + uint32_t src_if_num; + int ret; + + nss_ctx = dp->nssctx->nss_ctx; + src_dev = dp->nss_src_dev; + src_if_num = dp->nss_src_if_num; + + ret = nss_phys_if_set_nexthop(nss_ctx, src_if_num, NSS_ETH_RX_INTERFACE); + if (ret != NSS_TX_SUCCESS) { + pr_warn("%p: Failed to reset next hop for net device [%s].\n", + nss_ctx, src_dev->name); + } + else { + pr_info("%p: Reset nexthop successful for net device [%s].\n", + nss_ctx, src_dev->name); + } + + dev_put(src_dev); + dp->nss_src_dev = NULL; + dp->nss_src_if_num = -1; + + return 0; +} + +static int nss_ifb_open(struct net_device *dev) +{ + struct nss_ifb_dev_private *dp = netdev_priv(dev); + struct net_device *src_dev; + uint32_t src_if_num; + uint32_t nh_if_num; + nss_tx_status_t nss_tx_status; + struct nss_ctx_instance *nss_ctx; + + nss_ctx = dp->nssctx->nss_ctx; + nh_if_num = dp->nssctx->if_num_n2h; + + strcpy(dp->nss_src_dev_name, nss_dev_name); + + src_dev = dev_get_by_name(&init_net, dp->nss_src_dev_name); + if (!src_dev) { + pr_warn("%p: Cannot find the net device [%s]\n", + nss_ctx, dp->nss_src_dev_name); + + return -ENODEV; + } + pr_info("%p: Found net device [%s]\n", nss_ctx, dp->nss_src_dev_name); + + src_if_num = nss_cmn_get_interface_number_by_dev(src_dev); + if (src_if_num < 0) { + pr_warn("%p: Invalid interface number:%d\n", nss_ctx, src_if_num); + dev_put(src_dev); + + return -ENODEV; + } + pr_info("%p: Net device [%s] has NSS intf_num [%d]\n", + nss_ctx, dp->nss_src_dev_name, src_if_num); + + nss_tx_status = nss_phys_if_set_nexthop(nss_ctx, src_if_num, nh_if_num); + if (nss_tx_status != NSS_TX_SUCCESS) { + pr_warn("%p: Sending message failed, cannot change nexthop for [%s]\n", + nss_ctx, dp->nss_src_dev_name); + } + else { + pr_info("Nexthop successfully set for [%s] to [%s]\n", + dp->nss_src_dev_name, dev->name); + } + + dp->nss_src_dev = src_dev; + dp->nss_src_if_num = src_if_num; + + return 0; +} + +static const struct net_device_ops nss_ifb_netdev_ops = { + .ndo_open = nss_ifb_open, + .ndo_stop = nss_ifb_close, + .ndo_get_stats64 = nss_ifb_stats64, + .ndo_start_xmit = nss_ifb_xmit, + .ndo_validate_addr = eth_validate_addr, + .ndo_init = nss_ifb_dev_init, + .ndo_uninit = nss_ifb_dev_uninit, +}; + +#define IFB_FEATURES (NETIF_F_HW_CSUM | NETIF_F_SG | NETIF_F_FRAGLIST | \ + NETIF_F_TSO_ECN | NETIF_F_TSO | NETIF_F_TSO6 | \ + NETIF_F_GSO_ENCAP_ALL | \ + NETIF_F_HIGHDMA | NETIF_F_HW_VLAN_CTAG_TX | \ + NETIF_F_HW_VLAN_STAG_TX) + +static void nss_ifb_dev_free(struct net_device *dev) +{ + +} + +static void nss_ifb_setup(struct net_device *dev) +{ + /* Initialize the device structure. */ + dev->netdev_ops = &nss_ifb_netdev_ops; + + /* Fill in device structure with ethernet-generic values. */ + ether_setup(dev); + dev->tx_queue_len = TX_Q_LIMIT; + + dev->features |= IFB_FEATURES; + dev->hw_features |= dev->features; + dev->hw_enc_features |= dev->features; + dev->vlan_features |= IFB_FEATURES & ~(NETIF_F_HW_VLAN_CTAG_TX | + NETIF_F_HW_VLAN_STAG_TX); + + dev->flags |= IFF_NOARP; + dev->flags &= ~IFF_MULTICAST; + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + netif_keep_dst(dev); + eth_hw_addr_random(dev); + dev->needs_free_netdev = true; + dev->priv_destructor = nss_ifb_dev_free; + + dev->min_mtu = 0; + dev->max_mtu = 0; +} + +static int nss_ifb_validate(struct nlattr *tb[], struct nlattr *data[], + struct netlink_ext_ack *extack) +{ + if (tb[IFLA_ADDRESS]) { + if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN) + return -EINVAL; + if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS]))) + return -EADDRNOTAVAIL; + } + return 0; +} + +static struct rtnl_link_ops nss_ifb_link_ops __read_mostly = { + .kind = "nss_ifb", + .priv_size = sizeof(struct nss_ifb_dev_private), + .setup = nss_ifb_setup, + .validate = nss_ifb_validate, +}; + +static int __init nss_ifb_init_module(void) +{ + struct net_device *dev; + int err; + + down_write(&pernet_ops_rwsem); + rtnl_lock(); + err = __rtnl_link_register(&nss_ifb_link_ops); + if (err < 0) + goto out; + + dev = alloc_netdev(sizeof(struct nss_ifb_dev_private), "nssifb", + NET_NAME_UNKNOWN, nss_ifb_setup); + + if (dev) { + dev->rtnl_link_ops = &nss_ifb_link_ops; + err = register_netdevice(dev); + } + else { + err = -ENOMEM; + } + + if (err) + __rtnl_link_unregister(&nss_ifb_link_ops); + +out: + rtnl_unlock(); + up_write(&pernet_ops_rwsem); + + if (!err) + pr_info("NSS IFB module loaded.\n"); + else + pr_warn("Failed to load NSS IFB module.\n"); + + return err; +} + +static void __exit nss_ifb_cleanup_module(void) +{ + rtnl_link_unregister(&nss_ifb_link_ops); + + pr_info("NSS IFB module unloaded.\n"); +} + +module_init(nss_ifb_init_module); +module_exit(nss_ifb_cleanup_module); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_RTNL_LINK("nss_ifb");