blob: 80a627750e9a4585d6e86162dfcf4e0bde723d99 [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2015, 2016, 2020, 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.
**************************************************************************
*/
/*
* nss_portifmgr.c
* NSS to HLOS Port Interface manager
*/
#include <linux/module.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <linux/rtnetlink.h>
#include <nss_api_if.h>
#include <nss_gmac_api_if.h>
#include <nss_portifmgr.h>
#define NSS_PORTIFMGR_EXTRA_HEADER_SIZE 4
/*
* Features enabled for portifmgr, note we are not able to enable HW_CSUM
* related features because GMAC HW can't do checksum offloading when
* proprietary header inserted
*/
#define NSS_PORTIFMGR_SUPPORTED_FEATURES (NETIF_F_HIGHDMA | NETIF_F_SG | NETIF_F_FRAGLIST)
#if (NSS_PORTIFMGR_DEBUG_LEVEL < 1)
#define nss_portifmgr_assert(fmt, args...)
#else
#define nss_portifmgr_assert(c) BUG_ON(!(c))
#endif /* NSS_PORTIFMGR_DEBUG_LEVEL */
/*
* Compile messages for dynamic enable/disable
*/
#if defined(CONFIG_DYNAMIC_DEBUG)
#define nss_portifmgr_warn(s, ...) pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#define nss_portifmgr_info(s, ...) pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#define nss_portifmgr_trace(s, ...) pr_debug("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#else /* CONFIG_DYNAMIC_DEBUG */
/*
* Statically compile messages at different levels
*/
#if (NSS_PORTIFMGR_DEBUG_LEVEL < 2)
#define nss_portifmgr_warn(s, ...)
#else
#define nss_portifmgr_warn(s, ...) pr_warn("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#endif
#if (NSS_PORTIFMGR_DEBUG_LEVEL < 3)
#define nss_portifmgr_info(s, ...)
#else
#define nss_portifmgr_info(s, ...) pr_notice("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#endif
#if (NSS_PORTIFMGR_DEBUG_LEVEL < 4)
#define nss_portifmgr_trace(s, ...)
#else
#define nss_portifmgr_trace(s, ...) pr_info("%s[%d]:" s, __func__, __LINE__, ##__VA_ARGS__)
#endif
#endif /* CONFIG_DYNAMIC_DEBUG */
#ifdef NSS_PORTIFMGR_REF_AP148
/*
* This holds all the netdev created on the switch ports
*/
struct net_device *ndev_list[NSS_PORTID_MAX_SWITCH_PORT];
#endif
/*
* nss_portifmgr_receive_pkt()
* Receives a pkt from NSS
*/
static void nss_portifmgr_receive_pkt(struct net_device *dev, struct sk_buff *skb, struct napi_struct *napi)
{
struct nss_portifmgr_priv *priv;
/* SKB NETIF START */
dev_hold(dev);
priv = netdev_priv(dev);
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);
nss_portifmgr_trace("%s: Rx on if_num %d, packet len %d, CSUM %d\n",
__func__, priv->if_num, skb->len, skb->ip_summed);
(void)netif_receive_skb(skb);
/* SKB NETIF END */
dev_put(dev);
}
/*
* nss_portifmgr_open()
* Netdev's open call.
*/
static int nss_portifmgr_open(struct net_device *dev)
{
struct nss_portifmgr_priv *priv = (struct nss_portifmgr_priv *)netdev_priv(dev);
if (!priv->nss_ctx) {
nss_portifmgr_warn("%px: %s registration to NSS not completed yet\n", dev, dev->name);
return -EAGAIN;
}
netif_start_queue(dev);
return 0;
}
/*
* nss_portifmgr_close()
* Netdev's close call.
*/
static int nss_portifmgr_close(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
/*
* nss_portifmgr_start_xmit()
* Transmit skb to NSS FW over portid if_num.
*/
static netdev_tx_t nss_portifmgr_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct net_device_stats *stats = &dev->stats;
struct nss_portifmgr_priv *priv;
const unsigned char *dest = skb->data;
struct sk_buff *tx_skb = skb;
nss_tx_status_t status;
priv = netdev_priv(dev);
/*
* Drop if trying to xmit when registration to NSS is not completed yet.
*/
if (!priv->nss_ctx) {
nss_portifmgr_warn("%px: sending without ctx: if_num %d port_id %d\n",
dev, priv->port_id, priv->if_num);
goto drop;
}
if (skb_headroom(skb) < NSS_PORTIFMGR_EXTRA_HEADER_SIZE) {
nss_portifmgr_warn("%px: headroom not enough skb: %px\n", dev, skb);
goto drop;
}
/*
* For a multicast/broadcast skb, linux is trying to send the same
* skb->data to all interfaces. But we are going to add port header
* inside the eth_header. The 2nd interface will get a skb that eth_hdr
* is already have a portid header. We need to make a copy to avoid this
*/
if (!is_unicast_ether_addr(dest)) {
tx_skb = skb_copy(skb, GFP_KERNEL);
kfree_skb(skb);
if (!tx_skb) {
nss_portifmgr_warn("%px: failed to copy skb\n", dev);
stats->tx_dropped++;
return NETDEV_TX_OK;
}
}
status = nss_portid_if_tx_data(priv->nss_ctx, tx_skb, priv->if_num);
if (likely(status == NSS_TX_SUCCESS)) {
return NETDEV_TX_OK;
}
nss_portifmgr_warn("%px: portid interface failed to xmit the packet : %d\n",
dev, status);
drop:
kfree_skb(tx_skb);
stats->tx_dropped++;
return NETDEV_TX_OK;
}
/*
* nss_portifmgr_get_stats()
* Netdev get stats function to get port stats
*/
static struct rtnl_link_stats64 *nss_portifmgr_get_stats(struct net_device *dev, struct rtnl_link_stats64 *stats)
{
struct nss_portifmgr_priv *priv = (struct nss_portifmgr_priv *)netdev_priv(dev);
BUG_ON(priv == NULL);
nss_portid_get_stats(priv->if_num, stats);
return stats;
}
/*
* nss_portifmgr_change_mtu()
* Netdev change mtu function
*/
int nss_portifmgr_change_mtu(struct net_device *dev, int new_mtu)
{
struct nss_portifmgr_priv *priv = (struct nss_portifmgr_priv *)netdev_priv(dev);
if ((new_mtu + NSS_PORTIFMGR_EXTRA_HEADER_SIZE) > priv->real_dev->mtu) {
return -ERANGE;
}
dev->mtu = new_mtu;
return 0;
}
/*
* nss_portifmgr_netdev_ops
* Netdev operations.
*/
static const struct net_device_ops nss_portifmgr_netdev_ops = {
.ndo_open = nss_portifmgr_open,
.ndo_stop = nss_portifmgr_close,
.ndo_start_xmit = nss_portifmgr_start_xmit,
.ndo_set_mac_address = eth_mac_addr,
.ndo_change_mtu = nss_portifmgr_change_mtu,
.ndo_get_stats64 = nss_portifmgr_get_stats,
};
/*
* nss_portifmgr_create_if()
* API to create netdev and dynamic interfaces that mapped to switch port and gmac
*/
struct net_device *nss_portifmgr_create_if(int port_id, int gmac_id, const char *name)
{
struct net_device *ndev, *real_dev;
struct nss_ctx_instance *nss_ctx;
struct nss_portifmgr_priv *priv;
int err, if_num;
nss_tx_status_t status;
real_dev = nss_gmac_get_netdev_by_macid(gmac_id);
if (!real_dev) {
nss_portifmgr_warn("Underlying gmac%d netdevice does not exist!\n", gmac_id);
return NULL;
}
ndev = alloc_etherdev(sizeof(struct nss_portifmgr_priv));
if (!ndev) {
nss_portifmgr_warn("Error allocating netdev\n");
return NULL;
}
/*
* Setup net_device
*/
ndev->netdev_ops = &nss_portifmgr_netdev_ops;
ndev->needed_headroom = NSS_PORTIFMGR_EXTRA_HEADER_SIZE;
ndev->features |= NSS_PORTIFMGR_SUPPORTED_FEATURES;
ndev->hw_features |= NSS_PORTIFMGR_SUPPORTED_FEATURES;
ndev->vlan_features |= NSS_PORTIFMGR_SUPPORTED_FEATURES;
ndev->wanted_features |= NSS_PORTIFMGR_SUPPORTED_FEATURES;
ndev->mtu = real_dev->mtu - NSS_PORTIFMGR_EXTRA_HEADER_SIZE;
strlcpy(ndev->name, name, IFNAMSIZ);
/*
* Setup temp mac address, this can be changed with ifconfig later
*/
memcpy(ndev->dev_addr, "\x00\x03\x7f\x11\x22\x00", ETH_ALEN);
ndev->dev_addr[5] = 0x10 + port_id;
err = register_netdev(ndev);
if (err) {
nss_portifmgr_warn("Register_netdev() fail with error :%d\n", err);
free_netdev(ndev);
return NULL;
}
if_num = nss_dynamic_interface_alloc_node(NSS_DYNAMIC_INTERFACE_TYPE_PORTID);
if (if_num < 0) {
nss_portifmgr_warn("Dynamic interface alloc failed\n");
goto fail;
}
/*
* Register port interface with NSS
*/
nss_ctx = nss_portid_register_port_if(if_num, port_id, ndev, nss_portifmgr_receive_pkt);
if (!nss_ctx) {
goto fail2;
}
/*
* Fill in information in the netdev priv structure
*/
priv = netdev_priv(ndev);
priv->nss_ctx = nss_ctx;
priv->real_dev = real_dev;
priv->if_num = if_num;
priv->port_id = port_id;
/*
* Send msg to configure port in NSS FW
*/
status = nss_portid_tx_configure_port_if_msg(nss_ctx, if_num, port_id, gmac_id);
if (status == NSS_TX_SUCCESS) {
dev_hold(real_dev);
return ndev;
}
/*
* Failure handling
*/
nss_portid_unregister_port_if(if_num);
fail2:
(void)nss_dynamic_interface_dealloc_node(if_num, NSS_DYNAMIC_INTERFACE_TYPE_PORTID);
fail:
unregister_netdev(ndev);
free_netdev(ndev);
return NULL;
}
EXPORT_SYMBOL(nss_portifmgr_create_if);
/*
* nss_portifmgr_destroy_if()
* API to destroy netdev and the dynamic interfaces associated with it
*/
void nss_portifmgr_destroy_if(struct net_device *ndev)
{
struct nss_portifmgr_priv *priv = netdev_priv(ndev);
nss_tx_status_t status;
/*
* Release ref for the real gmac net_device
*/
dev_put(priv->real_dev);
/*
* Unconfigure port
*/
status = nss_portid_tx_unconfigure_port_if_msg(priv->nss_ctx, priv->if_num, priv->port_id);
if (status != NSS_TX_SUCCESS) {
nss_portifmgr_warn("%px: destroy if_num %d failed\n", priv->nss_ctx, priv->if_num);
}
/*
* Unregister from NSS
*/
nss_portid_unregister_port_if(priv->if_num);
/*
* Release dynamic interface
*/
(void)nss_dynamic_interface_dealloc_node(priv->if_num, NSS_DYNAMIC_INTERFACE_TYPE_PORTID);
/*
* Unregister net_device
*/
rtnl_is_locked() ? unregister_netdevice(ndev) : unregister_netdev(ndev);
}
EXPORT_SYMBOL(nss_portifmgr_destroy_if);
#ifdef NSS_PORTIFMGR_REF_AP148
/*
* nss_portifmgr_create_interfaces_ap148()
* Sample function to create port interfaces on AP148 ref design
*/
void nss_portifmgr_create_interfaces_ap148(void)
{
int i = 0;
ndev_list[i] = nss_portifmgr_create_if(1, 2, "lan%d");
if (!ndev_list[i]) {
nss_portifmgr_warn("Creating lan0 interface on port 1 failed\n");
return;
}
ndev_list[++i] = nss_portifmgr_create_if(2, 2, "lan%d");
if (!ndev_list[i]) {
nss_portifmgr_warn("Creating lan1 interface on port 2 failed\n");
return;
}
ndev_list[++i] = nss_portifmgr_create_if(3, 2, "lan%d");
if (!ndev_list[i]) {
nss_portifmgr_warn("Creating lan2 interface on port 3 failed\n");
return;
}
}
/*
* nss_portifmgr_destroy_all_interfaces()
* Destroy all the created interfaces
*/
void nss_portifmgr_destroy_all_interfaces(void)
{
int i;
for (i = 0; i < NSS_PORTID_MAX_SWITCH_PORT; i++) {
if (ndev_list[i]) {
nss_portifmgr_destroy_if(ndev_list[i]);
ndev_list[i] = NULL;
}
}
}
#endif
/*
* nss_portifmgr_init_module()
* portifmgr module init function
*/
int __init nss_portifmgr_init_module(void)
{
pr_info("module (platform - IPQ806x , Build %s) loaded\n",
NSS_CLIENT_BUILD_ID);
#ifdef NSS_PORTIFMGR_REF_AP148
nss_portifmgr_create_interfaces_ap148();
#endif
return 0;
}
/*
* nss_portifmgr_exit_module()
* portifmgr module exit function
*/
void __exit nss_portifmgr_exit_module(void)
{
#ifdef NSS_PORTIFMGR_REF_AP148
nss_portifmgr_destroy_all_interfaces();
#endif
pr_info("module unloaded\n");
}
module_init(nss_portifmgr_init_module);
module_exit(nss_portifmgr_exit_module);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("NSS port interface manager");