blob: 89914c3f8f052b255c47da6a40a34ceee548f366 [file] [log] [blame]
/**
* Copyright (c) 2015 Quantenna Communications, 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
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/spinlock.h>
#include <net80211/if_ethersubr.h>
#include <qtn/topaz_tqe_cpuif.h>
#include <qtn/qtn_skb_cb.h>
#include <qtn/qtn_vlan.h>
#include <qtn/lhost_muc_comm.h>
#include <drivers/ruby/emac_lib.h>
__sram_data uint8_t vlan_enabled;
EXPORT_SYMBOL(vlan_enabled);
__sram_data struct qtn_vlan_dev *vdev_tbl_lhost[VLAN_INTERFACE_MAX];
EXPORT_SYMBOL(vdev_tbl_lhost);
__sram_data struct qtn_vlan_dev *vdev_tbl_bus[VLAN_INTERFACE_MAX];
EXPORT_SYMBOL(vdev_tbl_bus);
__sram_data struct qtn_vlan_dev *vport_tbl_lhost[TOPAZ_TQE_NUM_PORTS];
EXPORT_SYMBOL(vport_tbl_lhost);
__sram_data struct qtn_vlan_dev *vport_tbl_bus[TOPAZ_TQE_NUM_PORTS];
EXPORT_SYMBOL(vport_tbl_bus);
struct qtn_vlan_info qtn_vlan_info;
EXPORT_SYMBOL(qtn_vlan_info);
static DEFINE_SPINLOCK(lock);
#define SWITCH_VLAN_LOCAL_INTERFACE_NAME "br0"
#define SWITCH_VLAN_PROC "topaz_vlan"
static inline void __switch_vlan_add_member(struct qtn_vlan_dev *vdev, uint16_t vid)
{
set_bit_a(vdev->u.member_bitmap, vid);
}
static inline void __switch_vlan_del_member(struct qtn_vlan_dev *vdev, uint16_t vid)
{
clr_bit_a(vdev->u.member_bitmap, vid);
}
static inline void __switch_vlan_tag_member(struct qtn_vlan_dev *vdev, uint16_t vid)
{
set_bit_a(vdev->tag_bitmap, vid);
}
static inline void __switch_vlan_untag_member(struct qtn_vlan_dev *vdev, uint16_t vid)
{
clr_bit_a(vdev->tag_bitmap, vid);
}
static inline void
switch_vlan_set_tagrx(struct qtn_vlan_info *vlan_info, uint16_t vlanid, uint8_t tagrx)
{
uint32_t *tagrx_bitmap = vlan_info->vlan_tagrx_bitmap;
tagrx = tagrx & QVLAN_TAGRX_BITMASK;
tagrx_bitmap[qvlan_tagrx_index(vlanid)] &=
~(QVLAN_TAGRX_BITMASK << qvlan_tagrx_shift(vlanid));
tagrx_bitmap[qvlan_tagrx_index(vlanid)] |=
tagrx << (qvlan_tagrx_shift(vlanid));
}
static inline int switch_vlan_manage_tagrx(struct qtn_vlan_dev *vdev,
uint16_t vlanid, uint8_t tag, uint32_t member_quit)
{
struct qtn_vlan_dev *other_dev;
if (vdev->port == TOPAZ_TQE_EMAC_0_PORT)
other_dev = vport_tbl_lhost[TOPAZ_TQE_EMAC_1_PORT];
else if (vdev->port == TOPAZ_TQE_EMAC_1_PORT)
other_dev = vport_tbl_lhost[TOPAZ_TQE_EMAC_0_PORT];
else if (vdev->port == TOPAZ_TQE_PCIE_PORT || vdev->port == TOPAZ_TQE_DSP_PORT) {
other_dev = NULL;
} else {
return qtn_vlan_get_tagrx(qtn_vlan_info.vlan_tagrx_bitmap, vlanid);
}
if (other_dev && !member_quit
&& qtn_vlan_is_member(other_dev, vlanid)
&& qtn_vlan_is_tagged_member(other_dev, vlanid) != !!tag) {
/*
* NOTE: All ethernet ports should have the same tag/untag config
* for one VLAN ID. This is to avoid confusion for multicast packets
* destined for multiple ethernet ports.
*/
printk(KERN_INFO"Warning:port %u forced to %s VLAN %u packets\n",
other_dev->port, tag ? "tag" : "untag", vlanid);
if (tag)
__switch_vlan_tag_member(other_dev, vlanid);
else
__switch_vlan_untag_member(other_dev, vlanid);
} else if (member_quit) {
if (!other_dev || !qtn_vlan_is_member(other_dev, vlanid))
return QVLAN_TAGRX_UNTOUCH;
else
return qtn_vlan_get_tagrx(qtn_vlan_info.vlan_tagrx_bitmap, vlanid);
}
return (tag ? QVLAN_TAGRX_TAG : QVLAN_TAGRX_STRIP);
}
static void switch_vlan_add(struct qtn_vlan_dev *vdev, uint16_t vlanid, uint8_t tag)
{
int tagrx;
if (!qtn_vlan_is_member(vdev, vlanid)) {
__switch_vlan_add_member(vdev, vlanid);
}
/* update tag bitmap */
if (tag)
__switch_vlan_tag_member(vdev, vlanid);
else
__switch_vlan_untag_member(vdev, vlanid);
tagrx = switch_vlan_manage_tagrx(vdev, vlanid, tag, 0);
switch_vlan_set_tagrx(&qtn_vlan_info, vlanid, tagrx);
}
static void switch_vlan_del(struct qtn_vlan_dev *vdev, uint16_t vlanid)
{
int tagrx;
if (!qtn_vlan_is_member(vdev, vlanid))
return;
tagrx = switch_vlan_manage_tagrx(vdev, vlanid, 0, 1);
switch_vlan_set_tagrx(&qtn_vlan_info, vlanid, tagrx);
__switch_vlan_del_member(vdev, vlanid);
__switch_vlan_untag_member(vdev, vlanid);
}
struct qtn_vlan_dev *switch_alloc_vlan_dev(uint8_t port, uint8_t idx, int ifindex)
{
struct qtn_vlan_dev *vdev = NULL;
struct qtn_vlan_user_interface *vintf = NULL;
dma_addr_t bus_addr, bus_addr2;
spin_lock_bh(&lock);
if (vdev_tbl_lhost[idx] != NULL)
goto out;
vdev = (struct qtn_vlan_dev *)dma_alloc_coherent(NULL,
sizeof(struct qtn_vlan_dev), &bus_addr, GFP_ATOMIC);
if (!vdev)
goto out;
memset(vdev, 0, sizeof(*vdev));
vdev->pvid = QVLAN_DEF_PVID;
vdev->bus_addr = (unsigned long)bus_addr;
vdev->port = port;
vdev->idx = idx;
vdev->ifindex = ifindex;
vintf = (struct qtn_vlan_user_interface *)dma_alloc_coherent(NULL,
sizeof(struct qtn_vlan_user_interface), &bus_addr2, GFP_ATOMIC);
if (!vintf)
goto out;
memset(vintf, 0, sizeof(*vintf));
vintf->bus_addr = bus_addr2;
vintf->mode = QVLAN_MODE_ACCESS;
vdev->user_data = (void *)vintf;
arc_write_uncached_32((uint32_t *)&vdev_tbl_lhost[idx], (uint32_t)vdev);
arc_write_uncached_32((uint32_t *)&vdev_tbl_bus[idx], (uint32_t)bus_addr);
if (qtn_vlan_port_indexable(port)) {
arc_write_uncached_32((uint32_t *)&vport_tbl_lhost[port], (uint32_t)vdev);
arc_write_uncached_32((uint32_t *)&vport_tbl_bus[port], (uint32_t)bus_addr);
}
switch_vlan_add(vdev, vdev->pvid, 0);
spin_unlock_bh(&lock);
return vdev;
out:
if (vdev)
dma_free_coherent(NULL, sizeof(struct qtn_vlan_dev), vdev, (dma_addr_t)(vdev->bus_addr));
spin_unlock_bh(&lock);
return NULL;
}
EXPORT_SYMBOL(switch_alloc_vlan_dev);
void switch_free_vlan_dev(struct qtn_vlan_dev *vdev)
{
struct qtn_vlan_user_interface *vintf = (struct qtn_vlan_user_interface *)vdev->user_data;
spin_lock_bh(&lock);
/* vlan_info_tbl[info->idx] = NULL; */
arc_write_uncached_32((uint32_t *)&vdev_tbl_lhost[vdev->idx], (uint32_t)NULL);
arc_write_uncached_32((uint32_t *)&vdev_tbl_bus[vdev->idx], (uint32_t)NULL);
if (qtn_vlan_port_indexable(vdev->port)) {
arc_write_uncached_32((uint32_t *)&vport_tbl_lhost[vdev->idx], (uint32_t)NULL);
arc_write_uncached_32((uint32_t *)&vport_tbl_bus[vdev->idx], (uint32_t)NULL);
}
spin_unlock_bh(&lock);
dma_free_coherent(NULL, sizeof(struct qtn_vlan_dev), vdev, (dma_addr_t)(vdev->bus_addr));
dma_free_coherent(NULL, sizeof(struct qtn_vlan_user_interface), vintf, (dma_addr_t)(vintf->bus_addr));
}
EXPORT_SYMBOL(switch_free_vlan_dev);
void switch_free_vlan_dev_by_idx(uint8_t idx)
{
BUG_ON(idx >= VLAN_INTERFACE_MAX);
switch_free_vlan_dev(vdev_tbl_lhost[idx]);
}
EXPORT_SYMBOL(switch_free_vlan_dev_by_idx);
#ifdef CONFIG_TOPAZ_DBDC_HOST
static enum topaz_tqe_port g_topaz_tqe_pcie_rel_port = TOPAZ_TQE_DUMMY_PORT;
void tqe_register_pcie_rel_port(const enum topaz_tqe_port tqe_port)
{
g_topaz_tqe_pcie_rel_port = tqe_port;
}
EXPORT_SYMBOL(tqe_register_pcie_rel_port);
#endif
struct qtn_vlan_dev*
switch_vlan_dev_get_by_port(uint8_t port)
{
#ifdef CONFIG_TOPAZ_DBDC_HOST
uint8_t dev_id = EXTRACT_DEV_ID_FROM_PORT_ID(port);
port = EXTRACT_PORT_ID_FROM_PORT_ID(port);
if (port == g_topaz_tqe_pcie_rel_port)
return vdev_tbl_lhost[QFP_VDEV_IDX(dev_id)];
#endif
return vport_tbl_lhost[port];
}
EXPORT_SYMBOL(switch_vlan_dev_get_by_port);
struct qtn_vlan_dev*
switch_vlan_dev_get_by_idx(uint8_t idx)
{
return vdev_tbl_lhost[idx];
}
EXPORT_SYMBOL(switch_vlan_dev_get_by_idx);
typedef void (*_fn_vlan_member)(struct qtn_vlan_dev *vdev,
uint16_t vid, uint8_t tag);
static int switch_vlan_member_comm(struct qtn_vlan_dev *vdev, uint16_t vid,
uint8_t tag, _fn_vlan_member handler)
{
if (vid == QVLAN_VID_ALL) {
for (vid = 0; vid < QVLAN_VID_MAX; vid++)
handler(vdev, vid, tag);
} else if (vid < QVLAN_VID_MAX) {
handler(vdev, vid, tag);
} else {
return -EINVAL;
}
return 0;
}
static void _vlan_add_member(struct qtn_vlan_dev *vdev, uint16_t vid, uint8_t tag)
{
spin_lock_bh(&lock);
switch_vlan_add(vdev, vid, tag);
spin_unlock_bh(&lock);
}
int switch_vlan_add_member(struct qtn_vlan_dev *vdev, uint16_t vid, uint8_t tag)
{
return switch_vlan_member_comm(vdev, vid, tag, _vlan_add_member);
}
EXPORT_SYMBOL(switch_vlan_add_member);
static void _vlan_del_member(struct qtn_vlan_dev *vdev, uint16_t vid, uint8_t arg)
{
spin_lock_bh(&lock);
switch_vlan_del(vdev, vid);
spin_unlock_bh(&lock);
}
int switch_vlan_del_member(struct qtn_vlan_dev *vdev, uint16_t vid)
{
return switch_vlan_member_comm(vdev, vid, 0, _vlan_del_member);
}
EXPORT_SYMBOL(switch_vlan_del_member);
static void _vlan_tag_member(struct qtn_vlan_dev *vdev, uint16_t vid, uint8_t arg)
{
int tagrx;
spin_lock_bh(&lock);
if (!qtn_vlan_is_member(vdev, vid))
goto out;
__switch_vlan_tag_member(vdev, vid);
tagrx = switch_vlan_manage_tagrx(vdev, vid, 1, 0);
switch_vlan_set_tagrx(&qtn_vlan_info, vid, tagrx);
out:
spin_unlock_bh(&lock);
}
int switch_vlan_tag_member(struct qtn_vlan_dev *vdev, uint16_t vid)
{
return switch_vlan_member_comm(vdev, vid, 0, _vlan_tag_member);
}
EXPORT_SYMBOL(switch_vlan_tag_member);
static void _vlan_untag_member(struct qtn_vlan_dev *vdev, uint16_t vid, uint8_t arg)
{
int tagrx;
spin_lock_bh(&lock);
if (!qtn_vlan_is_member(vdev, vid))
goto out;
__switch_vlan_untag_member(vdev, vid);
tagrx = switch_vlan_manage_tagrx(vdev, vid, 0, 0);
switch_vlan_set_tagrx(&qtn_vlan_info, vid, tagrx);
out:
spin_unlock_bh(&lock);
}
int switch_vlan_untag_member(struct qtn_vlan_dev *vdev, uint16_t vid)
{
return switch_vlan_member_comm(vdev, vid, 0, _vlan_untag_member);
}
EXPORT_SYMBOL(switch_vlan_untag_member);
int switch_vlan_set_pvid(struct qtn_vlan_dev *vdev, uint16_t vid)
{
if (vid >= QVLAN_VID_MAX)
return -EINVAL;
spin_lock_bh(&lock);
switch_vlan_del(vdev, vdev->pvid);
switch_vlan_add(vdev, vid, 0);
vdev->pvid = vid;
spin_unlock_bh(&lock);
return 0;
}
EXPORT_SYMBOL(switch_vlan_set_pvid);
int switch_vlan_set_mode(struct qtn_vlan_dev *vdev, uint8_t mode)
{
if (mode >= QVLAN_MODE_MAX)
return -EINVAL;
spin_lock_bh(&lock);
if (qtn_vlan_is_mode(vdev, mode))
goto out;
((struct qtn_vlan_user_interface *)vdev->user_data)->mode = mode;
out:
spin_unlock_bh(&lock);
return 0;
}
EXPORT_SYMBOL(switch_vlan_set_mode);
static inline void __switch_vlan_clear_dev(struct qtn_vlan_dev *vdev)
{
memset(&vdev->u, 0, sizeof(vdev->u));
memset(&vdev->tag_bitmap, 0, sizeof(vdev->tag_bitmap));
memset(&vdev->ig_pass, 0, sizeof(vdev->ig_pass));
memset(&vdev->ig_drop, 0, sizeof(vdev->ig_drop));
memset(&vdev->eg_pass, 0, sizeof(vdev->eg_pass));
memset(&vdev->eg_drop, 0, sizeof(vdev->eg_drop));
vdev->pvid = QVLAN_DEF_PVID;
vdev->flags = 0;
}
void switch_vlan_dyn_enable(struct qtn_vlan_dev *vdev)
{
spin_lock_bh(&lock);
__switch_vlan_clear_dev(vdev);
vdev->flags |= QVLAN_DEV_F_DYNAMIC;
spin_unlock_bh(&lock);
}
EXPORT_SYMBOL(switch_vlan_dyn_enable);
void switch_vlan_dyn_disable(struct qtn_vlan_dev *vdev)
{
spin_lock_bh(&lock);
__switch_vlan_clear_dev(vdev);
vdev->pvid = QVLAN_DEF_PVID;
switch_vlan_add(vdev, vdev->pvid, 0);
spin_unlock_bh(&lock);
}
EXPORT_SYMBOL(switch_vlan_dyn_disable);
int switch_vlan_set_node(struct qtn_vlan_dev *vdev, uint16_t ncidx, uint16_t vlanid)
{
int ret = 0;
spin_lock_bh(&lock);
if (!QVLAN_IS_DYNAMIC(vdev)
|| ncidx >= QTN_NCIDX_MAX
|| !qtn_vlan_is_valid(vlanid)
|| vdev->port != TOPAZ_TQE_WMAC_PORT) {
ret = -EINVAL;
goto out;
}
vdev->u.node_vlan[ncidx] = vlanid;
out:
spin_unlock_bh(&lock);
return ret;
}
EXPORT_SYMBOL(switch_vlan_set_node);
int switch_vlan_clr_node(struct qtn_vlan_dev *vdev, uint16_t ncidx)
{
int ret = 0;
spin_lock_bh(&lock);
if (!QVLAN_IS_DYNAMIC(vdev)
|| ncidx >= QTN_NCIDX_MAX
|| vdev->port != TOPAZ_TQE_WMAC_PORT) {
ret = -EINVAL;
goto out;
}
vdev->u.node_vlan[ncidx] = QVLAN_VID_ALL;
out:
spin_unlock_bh(&lock);
return ret;
}
EXPORT_SYMBOL(switch_vlan_clr_node);
int switch_vlan_is_local(const uint8_t *data)
{
const struct ethhdr *eth = (struct ethhdr *)data;
struct net_device *brdev = dev_get_by_name(&init_net, SWITCH_VLAN_LOCAL_INTERFACE_NAME);
int ret = 0;
if (likely(brdev)) {
ret = !compare_ether_addr(eth->h_source, brdev->dev_addr);
dev_put(brdev);
}
return ret;
}
EXPORT_SYMBOL(switch_vlan_is_local);
struct sk_buff *switch_vlan_strip_tag(struct sk_buff *skb, int copy)
{
struct sk_buff *skb2;
struct vlan_ethhdr *veth;
uint16_t vlan_id;
struct net_device *brdev;
BUG_ON(!skb_mac_header_was_set(skb));
if (!vlan_enabled)
return skb;
veth = vlan_eth_hdr(skb);
if (veth->h_vlan_proto != __constant_htons(ETH_P_8021Q))
return skb;
brdev = dev_get_by_name(&init_net, SWITCH_VLAN_LOCAL_INTERFACE_NAME);
if (likely(brdev)) {
vlan_id = ntohs(veth->h_vlan_TCI) & VLAN_VID_MASK;
if (vlan_check_vlan_exist(brdev, vlan_id)) {
dev_put(brdev);
return skb;
}
dev_put(brdev);
}
if (copy) {
skb2 = skb_copy(skb, GFP_ATOMIC);
dev_kfree_skb(skb);
} else {
skb2 = skb;
}
if (!skb2)
return NULL;
veth = vlan_eth_hdr(skb2);
memmove((uint8_t *)veth - QVLAN_PKTCTRL_LEN + VLAN_HLEN,
(uint8_t *)veth - QVLAN_PKTCTRL_LEN,
QVLAN_PKTCTRL_LEN + 2 * VLAN_ETH_ALEN);
skb_push(skb2, ETH_HLEN - VLAN_HLEN);
skb2->protocol = eth_type_trans(skb2, skb2->dev);
M_FLAG_SET(skb2, M_VLAN_TAG_OWE);
return skb2;
}
EXPORT_SYMBOL(switch_vlan_strip_tag);
struct sk_buff *switch_vlan_restore_tag(struct sk_buff *skb, int copy)
{
struct sk_buff *skb2;
struct qtn_vlan_pkt pkt;
if (!M_FLAG_ISSET(skb, M_VLAN_TAG_OWE))
return skb;
if (copy) {
skb2 = skb_copy(skb, GFP_ATOMIC);
dev_kfree_skb(skb);
} else {
skb2 = skb;
}
if (!skb2)
return NULL;
memcpy(&pkt, qtn_vlan_get_info(skb2->data), sizeof(pkt));
BUG_ON(pkt.magic != QVLAN_PKT_MAGIC);
skb2 = __vlan_put_tag(skb2, pkt.vlan_info & QVLAN_MASK_VID);
if (!skb2)
return NULL;
if (unlikely(skb_headroom(skb2) < QVLAN_PKTCTRL_LEN)) {
dev_kfree_skb(skb2);
return NULL;
}
memcpy(qtn_vlan_get_info(skb2->data), &pkt, sizeof(pkt));
return skb2;
}
EXPORT_SYMBOL(switch_vlan_restore_tag);
static int switch_vlan_stats_rd(char *page, char **start, off_t offset,
int count, int *eof, void *data)
{
char *p = page;
struct qtn_vlan_dev *vdev;
struct net_device *ndev;
int i;
spin_lock_bh(&lock);
for (i = 0; i < VLAN_INTERFACE_MAX; i++) {
vdev = vdev_tbl_lhost[i];
if (!vdev)
continue;
ndev = dev_get_by_index(&init_net, vdev->ifindex);
if (unlikely(!ndev))
continue;
p += sprintf(p, "%s\ti-pass\t\te-pass\t\ti-drop\t\te-drop\n", ndev->name);
p += sprintf(p, "Lhost\t%u\t\t%u\t\t%u\t\t%u\n", vdev->ig_pass.lhost, vdev->eg_pass.lhost,
vdev->ig_drop.lhost, vdev->eg_drop.lhost);
p += sprintf(p, "AuC\t%u\t\t%u\t\t%u\t\t%u\n", vdev->ig_pass.auc, vdev->eg_pass.auc,
vdev->ig_drop.auc, vdev->eg_drop.auc);
p += sprintf(p, "MuC\t%u\t\t%u\t\t%u\t\t%u\n", vdev->ig_pass.muc, vdev->eg_pass.muc,
vdev->ig_drop.muc, vdev->eg_drop.muc);
dev_put(ndev);
}
spin_unlock_bh(&lock);
*eof = 1;
return p - page;
}
void switch_vlan_dev_reset(struct qtn_vlan_dev *vdev, uint8_t mode)
{
uint32_t i;
spin_lock_bh(&lock);
for (i = 0; i < QVLAN_VID_MAX; i++) {
if (qtn_vlan_is_member(vdev, i))
switch_vlan_del(vdev, i);
}
memset(vdev->u.member_bitmap, 0, sizeof(vdev->u.member_bitmap));
memset(vdev->tag_bitmap, 0, sizeof(vdev->tag_bitmap));
spin_unlock_bh(&lock);
switch_vlan_set_pvid(vdev, QVLAN_DEF_PVID);
switch_vlan_set_mode(vdev, mode);
}
EXPORT_SYMBOL(switch_vlan_dev_reset);
void switch_vlan_reset(void)
{
uint32_t i;
for (i = 0; i < VLAN_INTERFACE_MAX; i++) {
if (vdev_tbl_lhost[i])
switch_vlan_dev_reset(vdev_tbl_lhost[i], QVLAN_MODE_ACCESS);
}
}
EXPORT_SYMBOL(switch_vlan_reset);
static int __init switch_vlan_module_init(void)
{
if (!create_proc_read_entry(SWITCH_VLAN_PROC, 0,
NULL, switch_vlan_stats_rd, 0))
return -EEXIST;
return 0;
}
static void __exit switch_vlan_module_exit(void)
{
remove_proc_entry(SWITCH_VLAN_PROC, 0);
}
module_init(switch_vlan_module_init);
module_exit(switch_vlan_module_exit);
MODULE_DESCRIPTION("VLAN control panel");
MODULE_AUTHOR("Quantenna");
MODULE_LICENSE("GPL");