| /* |
| ************************************************************************** |
| * 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"); |