| /* |
| * Copyright (c) 2014 Quantenna Communications, Inc. |
| * All rights reserved. |
| */ |
| |
| #ifndef _QTN_VLAN_H_ |
| #define _QTN_VLAN_H_ |
| |
| #include "../common/ruby_mem.h" |
| #include <qtn/qtn_debug.h> |
| #include <qtn/qtn_uc_comm.h> |
| #include <qtn/qtn_net_packet.h> |
| #if defined(__KERNEL__) || defined(MUC_BUILD) || defined(AUC_BUILD) |
| #include <qtn/topaz_tqe_cpuif.h> |
| #endif |
| #if defined(__KERNEL__) |
| #include <qtn/dmautil.h> |
| #endif |
| |
| #define QVLAN_MODE_ACCESS 0 |
| #define QVLAN_MODE_TRUNK 1 |
| #define QVLAN_MODE_HYBRID 2 |
| #define QVLAN_MODE_DYNAMIC 3 |
| #define QVLAN_MODE_MAX QVLAN_MODE_DYNAMIC |
| #define QVLAN_SHIFT_MODE 16 |
| #define QVLAN_MASK_MODE 0xffff0000 |
| #define QVLAN_MASK_VID 0x00000fff |
| |
| #define QVLAN_MODE(x) (uint16_t)((x) >> QVLAN_SHIFT_MODE) |
| #define QVLAN_VID(x) (uint16_t)((x) & QVLAN_MASK_VID) |
| |
| #define QVLAN_MODE_STR_ACCESS "Access mode" |
| #define QVLAN_MODE_STR_TRUNK "Trunk mode" |
| #define QVLAN_MODE_STR_HYBRID "Hybrid mode" |
| #define QVLAN_MODE_STR_DYNAMIC "Dynamic mode" |
| |
| /* default port vlan id */ |
| #define QVLAN_DEF_PVID 1 |
| |
| #define QVLAN_VID_MAX 4096 |
| #define QVLAN_VID_MAX_S 12 |
| #define QVLAN_VID_ALL 0xffff |
| |
| #ifndef NBBY |
| #define NBBY 8 |
| #endif |
| |
| #ifndef NBDW |
| #define NBDW 32 |
| #endif |
| |
| #ifdef CONFIG_TOPAZ_DBDC_HOST |
| #define VLAN_INTERFACE_MAX (QTN_MAX_VAPS + 2 + MAX_QFP_NETDEV) |
| #define QFP_VDEV_IDX(dev_id) (QTN_MAX_VAPS + 2 + (dev_id)) |
| #else |
| #define VLAN_INTERFACE_MAX (QTN_MAX_VAPS + 2) |
| #endif |
| #define WMAC_VDEV_IDX_MAX QTN_MAX_VAPS |
| #define EMAC_VDEV_IDX(port) (QTN_MAX_VAPS + (port)) |
| #define PCIE_VDEV_IDX (QTN_MAX_VAPS + 0) |
| |
| #ifndef howmany |
| #define howmany(x, y) (((x) + ((y) - 1)) / (y)) |
| #endif |
| |
| #define bitsz_var(var) (sizeof(var) * 8) |
| #define bitsz_ptr(ptr) bitsz_var((ptr)[0]) |
| |
| #define set_bit_a(a, i) ((a)[(i) / bitsz_ptr(a)] |= 1 << ((i) % bitsz_ptr(a))) |
| #define clr_bit_a(a, i) ((a)[(i) / bitsz_ptr(a)] &= ~(1 << ((i) % bitsz_ptr(a)))) |
| #define is_set_a(a, i) ((a)[(i) / bitsz_ptr(a)] & (1 << ((i) % bitsz_ptr(a)))) |
| #define is_clr_a(a, i) (is_set_a(a, i) == 0) |
| |
| struct qtn_vlan_stats { |
| uint32_t lhost; |
| uint32_t auc; |
| uint32_t muc; |
| }; |
| |
| struct qtn_vlan_user_interface { |
| unsigned long bus_addr; |
| uint8_t mode; |
| }; |
| |
| struct qtn_vlan_dev { |
| uint8_t idx; |
| uint8_t port; |
| uint16_t pvid; |
| #define QVLAN_DEV_F_DYNAMIC BIT(0) |
| uint32_t flags; |
| unsigned long bus_addr; |
| int ifindex; |
| union { |
| uint32_t member_bitmap[howmany(QVLAN_VID_MAX, NBDW)]; |
| uint16_t node_vlan[QTN_NCIDX_MAX]; |
| }u; |
| uint32_t tag_bitmap[howmany(QVLAN_VID_MAX, NBDW)]; |
| struct qtn_vlan_stats ig_pass; |
| struct qtn_vlan_stats ig_drop; |
| struct qtn_vlan_stats eg_pass; |
| struct qtn_vlan_stats eg_drop; |
| void *user_data; |
| }; |
| #define QVLAN_IS_DYNAMIC(vdev) ((vdev)->flags & QVLAN_DEV_F_DYNAMIC) |
| |
| struct qtn_vlan_pkt { |
| #define QVLAN_PKT_MAGIC 0x1234 |
| uint16_t magic; |
| #define QVLAN_TAGGED 0x8000 |
| #define QVLAN_SKIP_CHECK 0x4000 |
| uint16_t vlan_info; |
| } __packed; |
| |
| #define QVLAN_PKTCTRL_LEN sizeof(struct qtn_vlan_pkt) |
| |
| struct qtn_vlan_info { |
| #define QVLAN_TAGRX_UNTOUCH 0 |
| #define QVLAN_TAGRX_STRIP 1 |
| #define QVLAN_TAGRX_TAG 2 |
| #define QVLAN_TAGRX_BITMASK 0x3 |
| #define QVLAN_TAGRX_BITWIDTH 2 |
| #define QVLAN_TAGRX_BITSHIFT 1 |
| #define QVLAN_TAGRX_NUM_PER_DW (32 / QVLAN_TAGRX_BITWIDTH) |
| #define QVLAN_TAGRX_NUM_PER_DW_S 4 |
| uint32_t vlan_tagrx_bitmap[howmany(QVLAN_VID_MAX * QVLAN_TAGRX_BITWIDTH, NBDW)]; |
| }; |
| |
| RUBY_INLINE int qvlan_tagrx_index(int vid) |
| { |
| return (vid >> QVLAN_TAGRX_NUM_PER_DW_S); |
| } |
| |
| RUBY_INLINE int qvlan_tagrx_shift(int vid) |
| { |
| int shift; |
| |
| shift = vid & (QVLAN_TAGRX_NUM_PER_DW - 1); |
| return (shift << QVLAN_TAGRX_BITSHIFT); |
| } |
| |
| struct qtn_vlan_config { |
| uint32_t vlan_cfg; |
| union { |
| struct vlan_dev_config { |
| uint32_t member_bitmap[howmany(QVLAN_VID_MAX, NBDW)]; |
| uint32_t tag_bitmap[howmany(QVLAN_VID_MAX, NBDW)]; |
| } dev_config; |
| uint32_t tagrx_config[howmany(QVLAN_VID_MAX * QVLAN_TAGRX_BITWIDTH, NBDW)]; |
| } u; |
| }; |
| |
| /* |
| * VLAN forward/drop table |
| *| traffic direction | frame | Access(MBSS/Dynamic mode) | Trunk(Passthrough mode) |
| *|-------------------------------------------------------------------------------------------------------------- |
| *| wifi tx | no vlan | drop | forward |
| *|-------------------------------------------------------------------------------------------------------------- |
| *| | vlan tagged | compare tag with PVID: | compare tag against VID list |
| *| | | 1.equal:untag and forward | 1.Found:forward |
| *| | | 2.not equal:drop | 2.Not found:drop |
| *|-------------------------------------------------------------------------------------------------------------- |
| *| wifi rx | no vlan | Add PVID tag and forward | forward |
| *|-------------------------------------------------------------------------------------------------------------- |
| *| | vlan tagged | Compare tag with PVID: | compare tag against VID list |
| *| | | 1.equal:forward | 1. Found:forward |
| *| | | 2.not equal:drop | 2. Not found:drop |
| *|-------------------------------------------------------------------------------------------------------------- |
| */ |
| |
| #define QVLAN_BYTES_PER_VID ((QTN_MAX_BSS_VAPS + NBBY - 1) / NBBY) |
| #define QVLAN_BYTES_PER_VID_SHIFT 0 |
| |
| RUBY_INLINE int |
| qtn_vlan_is_valid(int vid) |
| { |
| /* VLAN ID 0 is reserved */ |
| return (vid > 0 && vid < QVLAN_VID_MAX); |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_is_member(volatile struct qtn_vlan_dev *vdev, uint16_t vid) |
| { |
| return !!is_set_a(vdev->u.member_bitmap, vid); |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_is_tagged_member(volatile struct qtn_vlan_dev *vdev, uint16_t vid) |
| { |
| return !!is_set_a(vdev->tag_bitmap, vid); |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_is_pvid(volatile struct qtn_vlan_dev *vdev, uint16_t vid) |
| { |
| return vdev->pvid == vid; |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_is_mode(volatile struct qtn_vlan_dev *vdev, uint16_t mode) |
| { |
| return ((struct qtn_vlan_user_interface *)vdev->user_data)->mode == mode; |
| } |
| |
| #if defined(__KERNEL__) || defined(MUC_BUILD) || defined(AUC_BUILD) |
| RUBY_INLINE int |
| qtn_vlan_port_indexable(uint8_t port) |
| { |
| return ((port == TOPAZ_TQE_EMAC_0_PORT) |
| || (port == TOPAZ_TQE_EMAC_1_PORT) |
| || (port == TOPAZ_TQE_PCIE_PORT) |
| || (port == TOPAZ_TQE_DSP_PORT)); |
| } |
| #endif |
| |
| RUBY_INLINE int |
| qtn_vlan_get_tagrx(uint32_t *tagrx_bitmap, uint16_t vlanid) |
| { |
| return (tagrx_bitmap[vlanid >> QVLAN_TAGRX_NUM_PER_DW_S] >> |
| ((vlanid & (QVLAN_TAGRX_NUM_PER_DW - 1)) << QVLAN_TAGRX_BITSHIFT)) & |
| QVLAN_TAGRX_BITMASK; |
| } |
| |
| RUBY_INLINE void |
| qtn_vlan_gen_group_addr(uint8_t *mac, uint16_t vid, uint8_t vapid) |
| { |
| uint16_t encode; |
| |
| mac[0] = 0xff; |
| mac[1] = 0xff; |
| mac[2] = 0xff; |
| mac[3] = 0xff; |
| |
| encode = ((uint16_t)vapid << QVLAN_VID_MAX_S) | vid; |
| mac[4] = encode >> 8; |
| mac[5] = (uint8_t)(encode & 0xff); |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_is_group_addr(const uint8_t *mac) |
| { |
| return (mac[0] == 0xff && mac[1] == 0xff |
| && mac[2] == 0xff && mac[3] == 0xff |
| && mac[4] != 0xff); |
| } |
| |
| #if defined(__KERNEL__) || defined(MUC_BUILD) || defined(AUC_BUILD) |
| RUBY_INLINE struct qtn_vlan_pkt* |
| qtn_vlan_get_info(const void *data) |
| { |
| struct qtn_vlan_pkt *pkt; |
| #if defined(AUC_BUILD) |
| #pragma Off(Behaved) |
| #endif |
| pkt = (struct qtn_vlan_pkt *)((const uint8_t *)data - QVLAN_PKTCTRL_LEN); |
| #if defined(AUC_BUILD) |
| #pragma On(Behaved) |
| #endif |
| return pkt; |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_magic_check(struct qtn_vlan_pkt *pkt) |
| { |
| #define QVLAN_EGRESS_MAGIC_MSG "VLAN-egress: magic number incorrect(0x%04x)\n" |
| |
| if (unlikely(pkt->magic != QVLAN_PKT_MAGIC)) { |
| #if defined (__KERNEL__) |
| printk(KERN_WARNING QVLAN_EGRESS_MAGIC_MSG, pkt->magic); |
| #elif defined(AUC_BUILD) |
| auc_os_printf(QVLAN_EGRESS_MAGIC_MSG, pkt->magic); |
| #elif defined(MUC_BUILD) |
| uc_printk(QVLAN_EGRESS_MAGIC_MSG, pkt->magic); |
| #endif |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| RUBY_INLINE void |
| qtn_vlan_inc_stats(struct qtn_vlan_stats *stats) { |
| #if defined(__KERNEL__) |
| stats->lhost++; |
| #elif defined(AUC_BUILD) |
| stats->auc++; |
| #elif defined(MUC_BUILD) |
| stats->muc++; |
| #endif |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_vlanid_check(struct qtn_vlan_dev *vdev, uint16_t ncidx, uint16_t vlanid) |
| { |
| if (QVLAN_IS_DYNAMIC(vdev)) |
| return (vdev->u.node_vlan[ncidx] == vlanid); |
| else |
| return qtn_vlan_is_member(vdev, vlanid); |
| } |
| |
| RUBY_INLINE int |
| qtn_vlan_egress(struct qtn_vlan_dev *outdev, uint16_t ncidx, void *data) |
| { |
| struct qtn_vlan_pkt *pkt = qtn_vlan_get_info(data); |
| |
| if (!qtn_vlan_magic_check(pkt) |
| || (pkt->vlan_info & QVLAN_SKIP_CHECK) |
| || qtn_vlan_vlanid_check(outdev, ncidx, pkt->vlan_info & QVLAN_MASK_VID)) { |
| qtn_vlan_inc_stats(&outdev->eg_pass); |
| return 1; |
| } |
| |
| qtn_vlan_inc_stats(&outdev->eg_drop); |
| return 0; |
| } |
| #endif |
| |
| #if defined(__KERNEL__) || defined(MUC_BUILD) |
| RUBY_INLINE int |
| qtn_vlan_ingress(struct qtn_vlan_dev *indev, uint16_t ncidx, |
| void *data, uint16_t known_vlanid, uint8_t cache_op) |
| { |
| struct ether_header *eh = (struct ether_header *)data; |
| struct qtn_vlan_pkt *pkt = qtn_vlan_get_info(data); |
| uint16_t vlanid; |
| const uint16_t magic = QVLAN_PKT_MAGIC; |
| |
| if (eh->ether_type == htons(ETHERTYPE_8021Q)) { |
| vlanid = ntohs(*(uint16_t *)(eh + 1)) & QVLAN_MASK_VID; |
| |
| if (!qtn_vlan_vlanid_check(indev, ncidx, vlanid)) { |
| qtn_vlan_inc_stats(&indev->ig_drop); |
| return 0; |
| } |
| |
| vlanid |= QVLAN_TAGGED; |
| } else if (known_vlanid) { |
| vlanid = known_vlanid; |
| |
| if (!qtn_vlan_vlanid_check(indev, ncidx, vlanid)) { |
| qtn_vlan_inc_stats(&indev->ig_drop); |
| return 0; |
| } |
| } else { |
| vlanid = indev->pvid; |
| } |
| |
| pkt->magic = magic; |
| pkt->vlan_info = vlanid; |
| |
| if (cache_op) { |
| #if defined(__KERNEL__) |
| flush_and_inv_dcache_sizerange_safe(pkt, QVLAN_PKTCTRL_LEN); |
| #elif defined(MUC_BUILD) |
| flush_and_inv_dcache_range_safe(pkt, QVLAN_PKTCTRL_LEN); |
| #endif |
| } |
| |
| qtn_vlan_inc_stats(&indev->ig_pass); |
| return 1; |
| } |
| #endif |
| |
| RUBY_INLINE int |
| qtn_vlancfg_reform(struct qtn_vlan_config *vcfg) |
| { |
| /* remove 0,15,16,31 bits to restore vlan_cfg */ |
| vcfg->vlan_cfg &= 0x7ffe7ffe; |
| vcfg->vlan_cfg >>= 1; |
| |
| return ((vcfg->vlan_cfg & QVLAN_MASK_MODE) >> QVLAN_SHIFT_MODE); |
| } |
| |
| #if defined(__KERNEL__) |
| extern uint8_t vlan_enabled; |
| extern struct qtn_vlan_dev *vdev_tbl_lhost[VLAN_INTERFACE_MAX]; |
| extern struct qtn_vlan_dev *vdev_tbl_bus[VLAN_INTERFACE_MAX]; |
| extern struct qtn_vlan_dev *vport_tbl_lhost[TOPAZ_TQE_NUM_PORTS]; |
| extern struct qtn_vlan_dev *vport_tbl_bus[TOPAZ_TQE_NUM_PORTS]; |
| extern struct qtn_vlan_info qtn_vlan_info; |
| |
| extern struct qtn_vlan_dev *switch_alloc_vlan_dev(uint8_t port, uint8_t idx, int ifindex); |
| extern void switch_free_vlan_dev(struct qtn_vlan_dev *dev); |
| extern void switch_free_vlan_dev_by_idx(uint8_t idx); |
| extern struct qtn_vlan_dev *switch_vlan_dev_get_by_port(uint8_t port); |
| extern struct qtn_vlan_dev *switch_vlan_dev_get_by_idx(uint8_t idx); |
| |
| extern int switch_vlan_add_member(struct qtn_vlan_dev *vdev, uint16_t vid, uint8_t tag); |
| extern int switch_vlan_del_member(struct qtn_vlan_dev *vdev, uint16_t vid); |
| extern int switch_vlan_tag_member(struct qtn_vlan_dev *vdev, uint16_t vid); |
| extern int switch_vlan_untag_member(struct qtn_vlan_dev *vdev, uint16_t vid); |
| extern int switch_vlan_set_pvid(struct qtn_vlan_dev *vdev, uint16_t vid); |
| |
| /* dynamic VLAN support */ |
| extern void switch_vlan_dyn_enable(struct qtn_vlan_dev *vdev); |
| extern void switch_vlan_dyn_disable(struct qtn_vlan_dev *vdev); |
| extern int switch_vlan_set_node(struct qtn_vlan_dev *vdev, uint16_t ncidx, uint16_t vlan); |
| extern int switch_vlan_clr_node(struct qtn_vlan_dev *vdev, uint16_t ncidx); |
| |
| extern int switch_vlan_is_local(const uint8_t *data); |
| extern struct sk_buff *switch_vlan_strip_tag(struct sk_buff *, int copy); |
| extern struct sk_buff *switch_vlan_restore_tag(struct sk_buff *, int copy); |
| extern void switch_vlan_reset(void); |
| extern void switch_vlan_dev_reset(struct qtn_vlan_dev *vdev, uint8_t mode); |
| extern void switch_vlan_emac_to_lhost(uint32_t enable); |
| #endif |
| |
| #endif |