| // SPDX-License-Identifier: GPL-2.0+ |
| |
| #include "lan966x_main.h" |
| |
| #define VLANACCESS_CMD_IDLE 0 |
| #define VLANACCESS_CMD_READ 1 |
| #define VLANACCESS_CMD_WRITE 2 |
| #define VLANACCESS_CMD_INIT 3 |
| |
| static int lan966x_vlan_get_status(struct lan966x *lan966x) |
| { |
| return lan_rd(lan966x, ANA_VLANACCESS); |
| } |
| |
| static int lan966x_vlan_wait_for_completion(struct lan966x *lan966x) |
| { |
| u32 val; |
| |
| return readx_poll_timeout(lan966x_vlan_get_status, |
| lan966x, val, |
| (val & ANA_VLANACCESS_VLAN_TBL_CMD) == |
| VLANACCESS_CMD_IDLE, |
| TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US); |
| } |
| |
| static void lan966x_vlan_set_mask(struct lan966x *lan966x, u16 vid) |
| { |
| u16 mask = lan966x->vlan_mask[vid]; |
| bool cpu_dis; |
| |
| cpu_dis = !(mask & BIT(CPU_PORT)); |
| |
| /* Set flags and the VID to configure */ |
| lan_rmw(ANA_VLANTIDX_VLAN_PGID_CPU_DIS_SET(cpu_dis) | |
| ANA_VLANTIDX_V_INDEX_SET(vid), |
| ANA_VLANTIDX_VLAN_PGID_CPU_DIS | |
| ANA_VLANTIDX_V_INDEX, |
| lan966x, ANA_VLANTIDX); |
| |
| /* Set the vlan port members mask */ |
| lan_rmw(ANA_VLAN_PORT_MASK_VLAN_PORT_MASK_SET(mask), |
| ANA_VLAN_PORT_MASK_VLAN_PORT_MASK, |
| lan966x, ANA_VLAN_PORT_MASK); |
| |
| /* Issue a write command */ |
| lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_WRITE), |
| ANA_VLANACCESS_VLAN_TBL_CMD, |
| lan966x, ANA_VLANACCESS); |
| |
| if (lan966x_vlan_wait_for_completion(lan966x)) |
| dev_err(lan966x->dev, "Vlan set mask failed\n"); |
| } |
| |
| static void lan966x_vlan_port_add_vlan_mask(struct lan966x_port *port, u16 vid) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| u8 p = port->chip_port; |
| |
| lan966x->vlan_mask[vid] |= BIT(p); |
| lan966x_vlan_set_mask(lan966x, vid); |
| } |
| |
| static void lan966x_vlan_port_del_vlan_mask(struct lan966x_port *port, u16 vid) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| u8 p = port->chip_port; |
| |
| lan966x->vlan_mask[vid] &= ~BIT(p); |
| lan966x_vlan_set_mask(lan966x, vid); |
| } |
| |
| static bool lan966x_vlan_port_any_vlan_mask(struct lan966x *lan966x, u16 vid) |
| { |
| return !!(lan966x->vlan_mask[vid] & ~BIT(CPU_PORT)); |
| } |
| |
| static void lan966x_vlan_cpu_add_vlan_mask(struct lan966x *lan966x, u16 vid) |
| { |
| lan966x->vlan_mask[vid] |= BIT(CPU_PORT); |
| lan966x_vlan_set_mask(lan966x, vid); |
| } |
| |
| static void lan966x_vlan_cpu_del_vlan_mask(struct lan966x *lan966x, u16 vid) |
| { |
| lan966x->vlan_mask[vid] &= ~BIT(CPU_PORT); |
| lan966x_vlan_set_mask(lan966x, vid); |
| } |
| |
| static void lan966x_vlan_cpu_add_cpu_vlan_mask(struct lan966x *lan966x, u16 vid) |
| { |
| __set_bit(vid, lan966x->cpu_vlan_mask); |
| } |
| |
| static void lan966x_vlan_cpu_del_cpu_vlan_mask(struct lan966x *lan966x, u16 vid) |
| { |
| __clear_bit(vid, lan966x->cpu_vlan_mask); |
| } |
| |
| bool lan966x_vlan_cpu_member_cpu_vlan_mask(struct lan966x *lan966x, u16 vid) |
| { |
| return test_bit(vid, lan966x->cpu_vlan_mask); |
| } |
| |
| static u16 lan966x_vlan_port_get_pvid(struct lan966x_port *port) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| |
| if (!(lan966x->bridge_mask & BIT(port->chip_port))) |
| return HOST_PVID; |
| |
| return port->vlan_aware ? port->pvid : UNAWARE_PVID; |
| } |
| |
| int lan966x_vlan_port_set_vid(struct lan966x_port *port, u16 vid, |
| bool pvid, bool untagged) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| |
| /* Egress vlan classification */ |
| if (untagged && port->vid != vid) { |
| if (port->vid) { |
| dev_err(lan966x->dev, |
| "Port already has a native VLAN: %d\n", |
| port->vid); |
| return -EBUSY; |
| } |
| port->vid = vid; |
| } |
| |
| /* Default ingress vlan classification */ |
| if (pvid) |
| port->pvid = vid; |
| |
| return 0; |
| } |
| |
| static void lan966x_vlan_port_remove_vid(struct lan966x_port *port, u16 vid) |
| { |
| if (port->pvid == vid) |
| port->pvid = 0; |
| |
| if (port->vid == vid) |
| port->vid = 0; |
| } |
| |
| void lan966x_vlan_port_set_vlan_aware(struct lan966x_port *port, |
| bool vlan_aware) |
| { |
| port->vlan_aware = vlan_aware; |
| } |
| |
| void lan966x_vlan_port_apply(struct lan966x_port *port) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| u16 pvid; |
| u32 val; |
| |
| pvid = lan966x_vlan_port_get_pvid(port); |
| |
| /* Ingress clasification (ANA_PORT_VLAN_CFG) */ |
| /* Default vlan to classify for untagged frames (may be zero) */ |
| val = ANA_VLAN_CFG_VLAN_VID_SET(pvid); |
| if (port->vlan_aware) |
| val |= ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) | |
| ANA_VLAN_CFG_VLAN_POP_CNT_SET(1); |
| |
| lan_rmw(val, |
| ANA_VLAN_CFG_VLAN_VID | ANA_VLAN_CFG_VLAN_AWARE_ENA | |
| ANA_VLAN_CFG_VLAN_POP_CNT, |
| lan966x, ANA_VLAN_CFG(port->chip_port)); |
| |
| /* Drop frames with multicast source address */ |
| val = ANA_DROP_CFG_DROP_MC_SMAC_ENA_SET(1); |
| if (port->vlan_aware && !pvid) |
| /* If port is vlan-aware and tagged, drop untagged and priority |
| * tagged frames. |
| */ |
| val |= ANA_DROP_CFG_DROP_UNTAGGED_ENA_SET(1) | |
| ANA_DROP_CFG_DROP_PRIO_S_TAGGED_ENA_SET(1) | |
| ANA_DROP_CFG_DROP_PRIO_C_TAGGED_ENA_SET(1); |
| |
| lan_wr(val, lan966x, ANA_DROP_CFG(port->chip_port)); |
| |
| /* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q */ |
| val = REW_TAG_CFG_TAG_TPID_CFG_SET(0); |
| if (port->vlan_aware) { |
| if (port->vid) |
| /* Tag all frames except when VID == DEFAULT_VLAN */ |
| val |= REW_TAG_CFG_TAG_CFG_SET(1); |
| else |
| val |= REW_TAG_CFG_TAG_CFG_SET(3); |
| } |
| |
| /* Update only some bits in the register */ |
| lan_rmw(val, |
| REW_TAG_CFG_TAG_TPID_CFG | REW_TAG_CFG_TAG_CFG, |
| lan966x, REW_TAG_CFG(port->chip_port)); |
| |
| /* Set default VLAN and tag type to 8021Q */ |
| lan_rmw(REW_PORT_VLAN_CFG_PORT_TPID_SET(ETH_P_8021Q) | |
| REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid), |
| REW_PORT_VLAN_CFG_PORT_TPID | |
| REW_PORT_VLAN_CFG_PORT_VID, |
| lan966x, REW_PORT_VLAN_CFG(port->chip_port)); |
| } |
| |
| void lan966x_vlan_port_add_vlan(struct lan966x_port *port, |
| u16 vid, |
| bool pvid, |
| bool untagged) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| |
| /* If the CPU(br) is already part of the vlan then add the fdb |
| * entries in MAC table to copy the frames to the CPU(br). |
| * If the CPU(br) is not part of the vlan then it would |
| * just drop the frames. |
| */ |
| if (lan966x_vlan_cpu_member_cpu_vlan_mask(lan966x, vid)) { |
| lan966x_vlan_cpu_add_vlan_mask(lan966x, vid); |
| lan966x_fdb_write_entries(lan966x, vid); |
| } |
| |
| lan966x_vlan_port_set_vid(port, vid, pvid, untagged); |
| lan966x_vlan_port_add_vlan_mask(port, vid); |
| lan966x_vlan_port_apply(port); |
| } |
| |
| void lan966x_vlan_port_del_vlan(struct lan966x_port *port, u16 vid) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| |
| lan966x_vlan_port_remove_vid(port, vid); |
| lan966x_vlan_port_del_vlan_mask(port, vid); |
| lan966x_vlan_port_apply(port); |
| |
| /* In case there are no other ports in vlan then remove the CPU from |
| * that vlan but still keep it in the mask because it may be needed |
| * again then another port gets added in that vlan |
| */ |
| if (!lan966x_vlan_port_any_vlan_mask(lan966x, vid)) { |
| lan966x_vlan_cpu_del_vlan_mask(lan966x, vid); |
| lan966x_fdb_erase_entries(lan966x, vid); |
| } |
| } |
| |
| void lan966x_vlan_cpu_add_vlan(struct lan966x *lan966x, u16 vid) |
| { |
| /* Add an entry in the MAC table for the CPU |
| * Add the CPU part of the vlan only if there is another port in that |
| * vlan otherwise all the broadcast frames in that vlan will go to CPU |
| * even if none of the ports are in the vlan and then the CPU will just |
| * need to discard these frames. It is required to store this |
| * information so when a front port is added then it would add also the |
| * CPU port. |
| */ |
| if (lan966x_vlan_port_any_vlan_mask(lan966x, vid)) |
| lan966x_vlan_cpu_add_vlan_mask(lan966x, vid); |
| |
| lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, vid); |
| lan966x_fdb_write_entries(lan966x, vid); |
| } |
| |
| void lan966x_vlan_cpu_del_vlan(struct lan966x *lan966x, u16 vid) |
| { |
| /* Remove the CPU part of the vlan */ |
| lan966x_vlan_cpu_del_cpu_vlan_mask(lan966x, vid); |
| lan966x_vlan_cpu_del_vlan_mask(lan966x, vid); |
| lan966x_fdb_erase_entries(lan966x, vid); |
| } |
| |
| void lan966x_vlan_init(struct lan966x *lan966x) |
| { |
| u16 port, vid; |
| |
| /* Clear VLAN table, by default all ports are members of all VLANS */ |
| lan_rmw(ANA_VLANACCESS_VLAN_TBL_CMD_SET(VLANACCESS_CMD_INIT), |
| ANA_VLANACCESS_VLAN_TBL_CMD, |
| lan966x, ANA_VLANACCESS); |
| lan966x_vlan_wait_for_completion(lan966x); |
| |
| for (vid = 1; vid < VLAN_N_VID; vid++) { |
| lan966x->vlan_mask[vid] = 0; |
| lan966x_vlan_set_mask(lan966x, vid); |
| } |
| |
| /* Set all the ports + cpu to be part of HOST_PVID and UNAWARE_PVID */ |
| lan966x->vlan_mask[HOST_PVID] = |
| GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT); |
| lan966x_vlan_set_mask(lan966x, HOST_PVID); |
| |
| lan966x->vlan_mask[UNAWARE_PVID] = |
| GENMASK(lan966x->num_phys_ports - 1, 0) | BIT(CPU_PORT); |
| lan966x_vlan_set_mask(lan966x, UNAWARE_PVID); |
| |
| lan966x_vlan_cpu_add_cpu_vlan_mask(lan966x, UNAWARE_PVID); |
| |
| /* Configure the CPU port to be vlan aware */ |
| lan_wr(ANA_VLAN_CFG_VLAN_VID_SET(0) | |
| ANA_VLAN_CFG_VLAN_AWARE_ENA_SET(1) | |
| ANA_VLAN_CFG_VLAN_POP_CNT_SET(1), |
| lan966x, ANA_VLAN_CFG(CPU_PORT)); |
| |
| /* Set vlan ingress filter mask to all ports */ |
| lan_wr(GENMASK(lan966x->num_phys_ports, 0), |
| lan966x, ANA_VLANMASK); |
| |
| for (port = 0; port < lan966x->num_phys_ports; port++) { |
| lan_wr(0, lan966x, REW_PORT_VLAN_CFG(port)); |
| lan_wr(0, lan966x, REW_TAG_CFG(port)); |
| } |
| } |