// SPDX-License-Identifier: GPL-2.0 /* VLAN configuration interface for the rtl8365mb switch family * * Copyright (C) 2022 Alvin Šipraga * * VLAN configuration takes place in two separate domains of the switch: the * VLAN4k table and the VLAN membership configuration (MC) database. While the * VLAN4k table is exhaustive and can be fully populated with 4096 VLAN * configurations, the same does not hold for the VLAN membership configuration * database, which is limited to 32 entries. * * The switch will normally only use the VLAN4k table when making forwarding * decisions. The VLAN membership configuration database is a vestigial ASIC * design and is only used for a few specific features in the rtl8365mb * family. This means that the limit of 32 entries should not hinder us in * programming a huge number of VLANs into the switch. * * One necessary use of the VLAN membership configuration database is for the * programming of a port-based VLAN ID (PVID). The PVID is programmed on a * per-port basis via register field, which refers to a specific VLAN membership * configuration via an index 0~31. In order to maintain coherent behaviour on a * port with a PVID, it is necessary to keep the VLAN configuration synchronized * between the VLAN4k table and the VLAN membership configuration database. * * Since VLAN membership configs are a scarce resource, it will only be used * when strictly needed (i.e. a VLAN with members using PVID). Otherwise, the * VLAN4k will be enough. * * With some exceptions, the entries in both the VLAN4k table and the VLAN * membership configuration database offer the same configuration options. The * differences are as follows: * * 1. VLAN4k entries can specify whether to use Independent or Shared VLAN * Learning (IVL or SVL respectively). VLAN membership config entries * cannot. This underscores the fact that VLAN membership configs are not * involved in the learning process of the ASIC. * * 2. VLAN membership config entries use an "enhanced VLAN ID" (efid), which has * a range 0~8191 compared with the standard 0~4095 range of the VLAN4k * table. This underscores the fact that VLAN membership configs can be used * to group ports on a layer beyond the standard VLAN configuration, which * may be useful for ACL rules which specify alternative forwarding * decisions. * * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports. * */ #include "rtl8365mb_vlan.h" #include "rtl8365mb_table.h" #include #include #include /* CVLAN (i.e. VLAN4k) table entry layout, u16[3] */ #define RTL8365MB_CVLAN_ENTRY_SIZE 3 /* 48-bits */ #define RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK GENMASK(7, 0) #define RTL8365MB_CVLAN_MBR_LO_MASK GENMASK(7, 0) #define RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK GENMASK(15, 8) #define RTL8365MB_CVLAN_UNTAG_LO_MASK GENMASK(7, 0) #define RTL8365MB_CVLAN_ENTRY_D1_FID_MASK GENMASK(3, 0) #define RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK GENMASK(4, 4) #define RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK GENMASK(7, 5) #define RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK GENMASK(8, 8) #define RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK GENMASK(13, 9) #define RTL8365MB_CVLAN_METERIDX_LO_MASK GENMASK(4, 0) #define RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK GENMASK(14, 14) /* extends RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK */ #define RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK GENMASK(2, 0) #define RTL8365MB_CVLAN_MBR_HI_MASK GENMASK(10, 8) /* extends RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK */ #define RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK GENMASK(5, 3) #define RTL8365MB_CVLAN_UNTAG_HI_MASK GENMASK(10, 8) /* extends RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK */ #define RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK GENMASK(6, 6) #define RTL8365MB_CVLAN_METERIDX_HI_MASK GENMASK(5, 5) /* VLAN member configuration registers 0~31, u16[3] */ #define RTL8365MB_VLAN_MC_BASE 0x0728 #define RTL8365MB_VLAN_MC_ENTRY_SIZE 4 /* 64-bit */ #define RTL8365MB_VLAN_MC_REG(index) \ (RTL8365MB_VLAN_MC_BASE + \ (RTL8365MB_VLAN_MC_ENTRY_SIZE * (index))) #define RTL8365MB_VLAN_MC_D0_MBR_MASK GENMASK(10, 0) #define RTL8365MB_VLAN_MC_D1_FID_MASK GENMASK(3, 0) #define RTL8365MB_VLAN_MC_D2_VBPEN_MASK GENMASK(0, 0) #define RTL8365MB_VLAN_MC_D2_VBPRI_MASK GENMASK(3, 1) #define RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK GENMASK(4, 4) #define RTL8365MB_VLAN_MC_D2_METERIDX_MASK GENMASK(10, 5) #define RTL8365MB_VLAN_MC_D3_EVID_MASK GENMASK(12, 0) /* Some limits for VLAN4k/VLAN membership config entries */ #define RTL8365MB_PRIORITYMAX 7 #define RTL8365MB_FIDMAX 15 #define RTL8365MB_METERMAX 63 #define RTL8365MB_VLAN_MCMAX 31 /* RTL8367S supports 4k vlans (vid<=4095) and 32 enhanced vlans * for VIDs up to 8191 */ #define RTL8365MB_MAX_4K_VID 0x0FFF /* 4095 */ #define RTL8365MB_MAX_MC_VID 0x1FFF /* 8191 */ /* Port-based VID registers 0~5 - each one holds an MC index for two ports */ #define RTL8365MB_VLAN_PVID_CTRL_BASE 0x0700 #define RTL8365MB_VLAN_PVID_CTRL_REG(_p) \ (RTL8365MB_VLAN_PVID_CTRL_BASE + ((_p) >> 1)) #define RTL8365MB_VLAN_PVID_CTRL_PORT0_MCIDX_MASK 0x001F #define RTL8365MB_VLAN_PVID_CTRL_PORT1_MCIDX_MASK 0x1F00 #define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p) \ (((_p) & 1) << 3) #define RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(_p) \ (0x1F << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(_p)) /* Frame type filtering registers */ #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE 0x07aa #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port) \ (RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_BASE + ((port) >> 3)) /* required as FIELD_PREP cannot use non-constant masks */ #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port) \ (0x3 << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port)) #define RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port) \ (((port) & 0x7) << 1) /* * struct rtl8365mb_vlan4k - VLAN4k table entry * @vid: VLAN ID (0~4095) * @member: port mask of ports in this VLAN * @untag: port mask of ports which untag on egress * @fid: filter ID - only used with SVL (unused) * @priority: priority classification (unused) * @priority_en: enable priority (unused) * @policing_en: enable policing (unused) * @ivl_en: enable IVL instead of default SVL * @meteridx: metering index (unused) * * This structure is used to get/set entries in the VLAN4k table. The * VLAN4k table dictates the VLAN configuration for the switch for the * vast majority of features. */ struct rtl8365mb_vlan4k { u16 vid; u16 member; u16 untag; u8 fid : 4; u8 priority : 3; u8 priority_en : 1; u8 policing_en : 1; u8 ivl_en : 1; u8 meteridx : 6; }; /* * struct rtl8365mb_vlanmc - VLAN membership config * @evid: Enhanced VLAN ID (0~8191) * @member: port mask of ports in this VLAN * @fid: filter ID - only used with SVL (unused) * @priority: priority classification (unused) * @priority_en: enable priority (unused) * @policing_en: enable policing (unused) * @meteridx: metering index (unused) * * This structure is used to get/set entries in the VLAN membership * configuration database. This feature is largely vestigial, but * still needed for at least the following features: * - PVID configuration * - ACL configuration * - selection of VLAN by the CPU tag when VSEL=1, although the switch * can also select VLAN based on the VLAN tag if VSEL=0 * * This is a low-level structure and it is recommended to interface with * the VLAN membership config database via &struct rtl8365mb_vlanmc_entry. */ struct rtl8365mb_vlanmc { u16 evid; u16 member; u8 fid : 4; u8 priority : 3; u8 priority_en : 1; u8 policing_en : 1; u8 meteridx : 6; }; static int rtl8365mb_vlan_4k_read(struct realtek_priv *priv, u16 vid, struct rtl8365mb_vlan4k *vlan4k) { u16 data[RTL8365MB_CVLAN_ENTRY_SIZE]; int val; int ret; ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, RTL8365MB_TABLE_OP_READ, &vid, 0, 0, data, ARRAY_SIZE(data)); if (ret) return ret; /* Unpack table entry */ memset(vlan4k, 0, sizeof(*vlan4k)); vlan4k->vid = vid; val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, data[0]); vlan4k->member = FIELD_PREP(RTL8365MB_CVLAN_MBR_LO_MASK, val); val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, data[2]); vlan4k->member |= FIELD_PREP(RTL8365MB_CVLAN_MBR_HI_MASK, val); val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, data[0]); vlan4k->untag = FIELD_PREP(RTL8365MB_CVLAN_UNTAG_LO_MASK, val); val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, data[2]); vlan4k->untag |= FIELD_PREP(RTL8365MB_CVLAN_UNTAG_HI_MASK, val); vlan4k->fid = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, data[1]); vlan4k->priority_en = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, data[1]); vlan4k->priority = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, data[1]); vlan4k->policing_en = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, data[1]); val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, data[1]); val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); vlan4k->meteridx = val; val = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, data[2]); val = FIELD_PREP(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); vlan4k->meteridx |= val; vlan4k->ivl_en = FIELD_GET(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, data[1]); return 0; } static int rtl8365mb_vlan_4k_write(struct realtek_priv *priv, const struct rtl8365mb_vlan4k *vlan4k) { u16 data[RTL8365MB_CVLAN_ENTRY_SIZE] = { 0 }; u16 vid; int val; /* Pack table entry value */ val = FIELD_GET(RTL8365MB_CVLAN_MBR_LO_MASK, vlan4k->member); data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_MBR_MASK, val); val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_LO_MASK, vlan4k->untag); data[0] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D0_UNTAG_MASK, val); data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_FID_MASK, vlan4k->fid); data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPEN_MASK, vlan4k->priority_en); data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_VBPRI_MASK, vlan4k->priority); data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_ENVLANPOL_MASK, vlan4k->policing_en); /* FIELD_* does not play nice with struct bitfield. */ val = vlan4k->meteridx; val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_LO_MASK, val); data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_METERIDX_MASK, val); data[1] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D1_IVL_SVL_MASK, vlan4k->ivl_en); val = FIELD_GET(RTL8365MB_CVLAN_MBR_HI_MASK, vlan4k->member); data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_MBR_EXT_MASK, val); val = FIELD_GET(RTL8365MB_CVLAN_UNTAG_HI_MASK, vlan4k->untag); data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_UNTAG_EXT_MASK, val); val = vlan4k->meteridx; val = FIELD_GET(RTL8365MB_CVLAN_METERIDX_HI_MASK, val); data[2] |= FIELD_PREP(RTL8365MB_CVLAN_ENTRY_D2_METERIDX_EXT_MASK, val); vid = vlan4k->vid; return rtl8365mb_table_query(priv, RTL8365MB_TABLE_CVLAN, RTL8365MB_TABLE_OP_WRITE, &vid, 0, 0, data, ARRAY_SIZE(data)); } static int rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack, bool include) { struct realtek_priv *priv = ds->priv; struct rtl8365mb_vlan4k vlan4k = {0}; int ret; dev_dbg(priv->dev, "%s VLAN %d 4K on port %d\n", include ? "add" : "del", vlan->vid, port); if (vlan->vid > RTL8365MB_MAX_4K_VID) { NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than " __stringify(RTL8365MB_MAX_4K_VID)); return -EINVAL; } ret = rtl8365mb_vlan_4k_read(priv, vlan->vid, &vlan4k); if (ret) { dev_err(priv->dev, "Failed to read VLAN 4k table\n"); return ret; } if (include) vlan4k.member |= BIT(port); else vlan4k.member &= ~BIT(port); if (include && (vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED)) vlan4k.untag |= BIT(port); else vlan4k.untag &= ~BIT(port); vlan4k.ivl_en = true; /* always use Independent VLAN Learning */ ret = rtl8365mb_vlan_4k_write(priv, &vlan4k); if (ret) { dev_err(priv->dev, "Failed to write VLAN 4k table\n"); return ret; } return 0; } /* * rtl8365mb_vlan_4k_port_add() - Add a port to a VLAN 4K table entry * @ds: dsa switch instance * @port: port index * @vlan: switchdev VLAN object containing the target VID and flags * @extack: netlink extended ACK for error reporting * * Adds the specified port to the hardware VLAN 4K membership table. * * Context: Can sleep. Must be called with &priv->vlan_lock held. * Takes and releases &priv->map_lock. * Return: 0 on success, or a negative error code on failure. */ int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan, struct netlink_ext_ack *extack) { struct realtek_priv *priv = ds->priv; lockdep_assert_held(&priv->vlan_lock); return rtl8365mb_vlan_4k_port_set(ds, port, vlan, extack, true); } /* * rtl8365mb_vlan_4k_port_del() - Remove a port from a VLAN 4K table entry * @ds: dsa switch instance * @port: port index * @vlan: switchdev VLAN object containing the target VID * * Removes the specified port from the hardware VLAN 4K membership table. * * Context: Can sleep. Must be called with &priv->vlan_lock held. * Takes and releases &priv->map_lock. * Return: 0 on success, or a negative error code on failure. */ int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan) { struct realtek_priv *priv = ds->priv; lockdep_assert_held(&priv->vlan_lock); return rtl8365mb_vlan_4k_port_set(ds, port, vlan, NULL, false); } static int rtl8365mb_vlan_mc_read(struct realtek_priv *priv, u32 index, struct rtl8365mb_vlanmc *vlanmc) { u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE]; int ret; ret = regmap_bulk_read(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, RTL8365MB_VLAN_MC_ENTRY_SIZE); if (ret) return ret; vlanmc->member = FIELD_GET(RTL8365MB_VLAN_MC_D0_MBR_MASK, data[0]); vlanmc->fid = FIELD_GET(RTL8365MB_VLAN_MC_D1_FID_MASK, data[1]); vlanmc->meteridx = FIELD_GET(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, data[2]); vlanmc->policing_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, data[2]); vlanmc->priority = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, data[2]); vlanmc->priority_en = FIELD_GET(RTL8365MB_VLAN_MC_D2_VBPEN_MASK, data[2]); vlanmc->evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, data[3]); return 0; } static int rtl8365mb_vlan_mc_write(struct realtek_priv *priv, u32 index, const struct rtl8365mb_vlanmc *vlanmc) { u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 }; int ret; data[0] |= FIELD_PREP(RTL8365MB_VLAN_MC_D0_MBR_MASK, vlanmc->member); data[1] |= FIELD_PREP(RTL8365MB_VLAN_MC_D1_FID_MASK, vlanmc->fid); data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_METERIDX_MASK, vlanmc->meteridx); data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_ENVLANPOL_MASK, vlanmc->policing_en); data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPRI_MASK, vlanmc->priority); data[2] |= FIELD_PREP(RTL8365MB_VLAN_MC_D2_VBPEN_MASK, vlanmc->priority_en); data[3] |= FIELD_PREP(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlanmc->evid); ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, RTL8365MB_VLAN_MC_ENTRY_SIZE); return ret; } static int rtl8365mb_vlan_mc_erase(struct realtek_priv *priv, u32 index) { u16 data[RTL8365MB_VLAN_MC_ENTRY_SIZE] = { 0 }; int ret; ret = regmap_bulk_write(priv->map, RTL8365MB_VLAN_MC_REG(index), &data, RTL8365MB_VLAN_MC_ENTRY_SIZE); return ret; } /* * rtl8365mb_vlan_mc_find() - find VLANMC index by VID or the first free index * * @priv: realtek_priv pointer * @vid: VLAN ID * @index: found index * @first_free: found free index * * If a VLAN MC entry using @vid was found, @index will return the matched index * and @first_free is undefined. If not found, @index will return 0 and * @first_free will return the first found free index in VLAN MC or 0 if the * table is full. * * Although 0 is a valid VLAN MC index, it is reserved for ports without PVID, * including standalone, non-member ports. It uses VID == 0. * * Both @index and @first_free will be in the * 1..@RTL8365MB_VLAN_MCMAX range. * * Return: Returns 0 on success, a negative error on failure. */ static int rtl8365mb_vlan_mc_find(struct realtek_priv *priv, u16 vid, u8 *index, u8 *first_free) { u32 vlan_entry_d3; u8 vlanmc_idx; u16 evid; int ret; *index = 0; *first_free = 0; /* look for existing entry or an empty one */ /* By design, VlanMC[0] is reserved as a neutral PVID value for * standalone ports. It always has EVID == 0. That way, we assume that * all entries after index 0 with VID == 0 are empty. **/ for (vlanmc_idx = 1; vlanmc_idx <= RTL8365MB_VLAN_MCMAX; vlanmc_idx++) { /* just read the 4th word, where the evid is */ ret = regmap_read(priv->map, RTL8365MB_VLAN_MC_REG(vlanmc_idx) + 3, &vlan_entry_d3); if (ret) return ret; evid = FIELD_GET(RTL8365MB_VLAN_MC_D3_EVID_MASK, vlan_entry_d3); if (evid == vid) { *index = vlanmc_idx; return 0; } if (evid == 0x0 && *first_free < 1) *first_free = vlanmc_idx; } return 0; } static int rtl8365mb_vlan_port_get_pvid_idx(struct realtek_priv *priv, int port, u8 *vlanmc_idx) { u32 data; int ret; ret = regmap_read(priv->map, RTL8365MB_VLAN_PVID_CTRL_REG(port), &data); if (ret) return ret; *vlanmc_idx = (data & RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port)) >> RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); return 0; } /* * rtl8365mb_vlan_mc_port_set() - include or exclude a port from VlanMC * @ds: dsa switch * @port: the port number * @vid: the vlan VID to include/exclude @port * @pvid: inform if vid is used as pvid in @port * @extack: optional extack to return errors * @include: whether to include or exclude @port * * This function is used to include/exclude ports to the VlanMC table. * * VlanMC stands for VLAN membership config and it is used exclusively for * PVID. If @vlan members are not using PVID, this function will either * remove or not create a new VlanMC entry. * * VlanMC members are used as a reference port map, cleaning the entry once * no port is using it. * * Port PVID and accepted frame type are updated as well. * * Context: Can sleep. Must be called with &priv->vlan_lock held. * Takes and releases &priv->map_lock. * Return: Returns 0 on success, a negative error on failure. */ static int rtl8365mb_vlan_mc_port_set(struct dsa_switch *ds, int port, u16 vid, bool pvid, struct netlink_ext_ack *extack, bool include) { struct realtek_priv *priv = ds->priv; struct rtl8365mb_vlanmc vlanmc = {0}; u8 first_unused = 0; u8 vlanmc_idx = 0; int ret; dev_dbg(priv->dev, "%s VLAN %d MC on port %d\n", include ? "add" : "del", vid, port); if (vid > RTL8365MB_MAX_MC_VID) { NL_SET_ERR_MSG_MOD(extack, "VLAN ID greater than " __stringify(RTL8365MB_MAX_MC_VID)); return -EINVAL; } /* look for existing entry or an empty slot */ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx, &first_unused); if (ret) { dev_err(priv->dev, "Failed to find a VLAN MC table index\n"); return ret; } if (vlanmc_idx) { ret = rtl8365mb_vlan_mc_read(priv, vlanmc_idx, &vlanmc); if (ret) { dev_err(priv->dev, "Failed to read VLAN MC table\n"); return ret; } } else if (include) { /* for now, vlan_mc is only required for PVID. Defer allocation * until at least one port uses PVID. */ if (!pvid) { dev_dbg(priv->dev, "Not creating VlanMC for vlan %d until a port uses PVID (%d does not)\n", vid, port); return 0; } if (!first_unused) { NL_SET_ERR_MSG_MOD(extack, "All VLAN MC entries (0.." __stringify(RTL8365MB_VLAN_MCMAX) ") are in use."); return -ENOSPC; } vlanmc_idx = first_unused; vlanmc.evid = vid; } else /* excluding and VLANMC not found */ { return 0; } dev_dbg(priv->dev, "VLAN %d (idx: %d) PVID curr members: %08x\n", vid, vlanmc_idx, vlanmc.member); /* here we either have an existing VLANMC (with PVID members) or the * added port is using this VLAN as PVID */ if (include) vlanmc.member |= BIT(port); else vlanmc.member &= ~BIT(port); /* just like we don't need to create a VLAN_MC when there is no port * using it as PVID, we can erase it when there is no more port using * it as PVID. */ if (!vlanmc.member) { dev_dbg(priv->dev, "Clearing VlanMC index %d previously used by VID %d\n", vlanmc_idx, vid); ret = rtl8365mb_vlan_mc_erase(priv, vlanmc_idx); } else { dev_dbg(priv->dev, "Saving VlanMC index %d with VID %d\n", vlanmc_idx, vid); ret = rtl8365mb_vlan_mc_write(priv, vlanmc_idx, &vlanmc); } if (ret) { dev_err(priv->dev, "Failed to write vlan MC entry\n"); return ret; } return 0; } static int rtl8365mb_vlan_port_set_pvid(struct realtek_priv *priv, int port, u16 vlanmc_idx) { int ret; u32 val; dev_dbg(priv->dev, "set PVID IDX %d on port %d\n", vlanmc_idx, port); val = vlanmc_idx << RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_OFFSET(port); ret = regmap_update_bits(priv->map, RTL8365MB_VLAN_PVID_CTRL_REG(port), RTL8365MB_VLAN_PVID_CTRL_PORT_MCIDX_MASK(port), val); if (ret) return ret; return 0; } static int rtl8365mb_vlan_get_pvid_mc(struct realtek_priv *priv, int port, u8 *vlanmc_idx, struct rtl8365mb_vlanmc *vlanmc) { int ret; ret = rtl8365mb_vlan_port_get_pvid_idx(priv, port, vlanmc_idx); if (ret) return ret; memset(vlanmc, 0, sizeof(*vlanmc)); if (!*vlanmc_idx) return 0; ret = rtl8365mb_vlan_mc_read(priv, *vlanmc_idx, vlanmc); if (ret) return ret; return 0; } /* * rtl8365mb_vlan_port_get_pvid - Retrieve the port PVID * @priv: realtek switch private structure * @port: port index * @pvid: pointer to store the retrieved VLAN ID * * Returns the port PVID if defined or 0 if not. * * Context: Can sleep. Takes and releases &priv->map_lock. * Return: 0 on success or a negative error code on failure. */ int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, u16 *pvid) { struct rtl8365mb_vlanmc vlanmc; u8 vlanmc_idx; int ret; ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc); if (ret) return ret; *pvid = vlanmc.evid; return 0; } /* * rtl8365mb_vlan_port_get_framefilter() - Get the ingress frame filtering mode * for a port * @priv: realtek switch private structure * @port: port index * @frame_type: pointer to store the retrieved ingress frame filter type * * Context: Can sleep. Takes and releases &priv->map_lock. * Return: 0 on success, or a negative error code on failure. */ int rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv, int port, enum rtl8365mb_frame_ingress *frame_type) { u32 val; int ret; /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the * port is a member of vlan PVID */ ret = regmap_read(priv->map, RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port), &val); if (ret) return ret; *frame_type = field_get(RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port), val); return 0; } /* * rtl8365mb_vlan_port_set_framefilter() - Set the ingress frame filtering mode * for a port * @priv: realtek switch private structure * @port: port index * @frame_type: the ingress frame filter type to configure * * Context: Can sleep. Takes and releases &priv->map_lock. * Return: 0 on success, or a negative error code on failure. */ int rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, int port, enum rtl8365mb_frame_ingress frame_type) { u32 val; /* Even if ACCEPT_FRAME_TYPE_ANY, the switch will still check if the * port is a member of vlan PVID */ val = frame_type << RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_OFFSET(port); return regmap_update_bits(priv->map, RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_REG(port), RTL8365MB_VLAN_ACCEPT_FRAME_TYPE_MASK(port), val); } /* * rtl8365mb_vlan_pvid_port_set() - Configure a port's PVID and associated * VLANMC entry * @ds: dsa switch instance * @port: port index * @vid: target VID * @extack: netlink extended ACK for error reporting * * Allocates or reuses a hardware VLANMC entry to map the given port to its new * PVID. Gracefully unwinds and restores previous configuration if a hardware * write operation fails during execution. * * Context: Can sleep. Must be called with &priv->vlan_lock held. * Takes and releases &priv->map_lock. * Return: 0 on success, or a negative error code on failure. */ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid, struct netlink_ext_ack *extack) { enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; struct realtek_priv *priv = ds->priv; struct rtl8365mb_vlanmc prev_vlanmc = {0}; u8 _unused_first_free_idx; u8 prev_vlanmc_idx; u8 vlanmc_idx; int ret; lockdep_assert_held(&priv->vlan_lock); /* Read the old PVID exclusively to undo in case of error */ ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &prev_vlanmc_idx, &prev_vlanmc); if (ret) { dev_err(priv->dev, "Failed to read current VLAN MC\n"); return ret; } ret = rtl8365mb_vlan_port_get_framefilter(priv, port, &prev_accepted_frame); if (ret) { dev_err(priv->dev, "Failed to get current framefilter\n"); return ret; } /* Find or allocate a new vlan MC and add port to members, * although members are not checked by the HW in vlan MC. */ ret = rtl8365mb_vlan_mc_port_set(ds, port, vid, true, extack, true); if (ret) return ret; /* look for existing entry */ ret = rtl8365mb_vlan_mc_find(priv, vid, &vlanmc_idx, &_unused_first_free_idx); if (ret) { dev_err(priv->dev, "Failed to find a VLAN MC table index\n"); goto undo_vlan_mc_port_set; } if (!vlanmc_idx) { dev_err(priv->dev, "VLAN should already exist in VLAN MC\n"); ret = -ENOENT; goto undo_vlan_mc_port_set; } ret = rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx); if (ret) { dev_err(priv->dev, "Failed to set port PVID\n"); goto undo_vlan_mc_port_set; } /* Changing accept frame is what enables PVID (if not enabled before) */ accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; ret = rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame); if (ret) { dev_err(priv->dev, "Failed to set port frame filter\n"); goto undo_vlan_port_set_pvid; } /* A VLAN can be added with PVID without removing from the old * PVID VLAN. Clear PVID from the old VLAN MC (if needed). */ if (prev_vlanmc_idx && (prev_vlanmc.evid != vid)) { ret = rtl8365mb_vlan_mc_port_set(ds, port, prev_vlanmc.evid, false, NULL, false); if (ret) { dev_err(priv->dev, "Failed to clear old VLAN MC\n"); goto undo_set_framefilter; } } return 0; undo_set_framefilter: (void)rtl8365mb_vlan_port_set_framefilter(priv, port, prev_accepted_frame); undo_vlan_port_set_pvid: (void)rtl8365mb_vlan_port_set_pvid(priv, port, prev_vlanmc_idx); undo_vlan_mc_port_set: if (prev_vlanmc.evid != vid) (void)rtl8365mb_vlan_mc_port_set(ds, port, vid, false, NULL, false); return ret; } /* * rtl8365mb_vlan_pvid_port_clear() - Remove a port's PVID configuration * @ds: dsa switch instance * @port: port index * @vid: VLAN VID for PVID * * Resets the target port's hardware PVID allocation to 0. Cleans up and frees * the associated VLANMC entry if no other ports are referencing it. * * Context: Can sleep. Must be called with &priv->vlan_lock held. * Takes and releases &priv->map_lock. * Return: 0 on success, or a negative error code on failure. */ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid) { enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; struct realtek_priv *priv = ds->priv; struct rtl8365mb_vlanmc vlanmc = {0}; u8 vlanmc_idx; int ret; lockdep_assert_held(&priv->vlan_lock); ret = rtl8365mb_vlan_get_pvid_mc(priv, port, &vlanmc_idx, &vlanmc); if (ret) { dev_err(priv->dev, "Failed to read current VLAN MC\n"); return ret; } /* Port is not using PVID. Nothing to remove. */ if (!vlanmc_idx) return 0; /* We are leaving a non PVID vlan, Nothing to remove. */ if (vlanmc.evid != vid) return 0; ret = rtl8365mb_vlan_port_get_framefilter(priv, port, &prev_accepted_frame); if (ret) { dev_err(priv->dev, "Failed to get current framefilter\n"); return ret; } /* Changing accept frame is what really removes PVID. But only do * that if we are filtering vlan */ if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) { accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY; ret = rtl8365mb_vlan_port_set_framefilter(priv, port, accepted_frame); if (ret) { dev_err(priv->dev, "Failed to set port frame filter\n"); return ret; } } else { /* skip undo_set_framefilter */ accepted_frame = prev_accepted_frame; } ret = rtl8365mb_vlan_port_set_pvid(priv, port, 0); if (ret) { dev_err(priv->dev, "Failed to set port PVID to 0\n"); goto undo_set_framefilter; } /* Clears the VLAN MC membership and maybe VLAN MC entry if empty */ ret = rtl8365mb_vlan_mc_port_set(ds, port, vlanmc.evid, false, NULL, false); if (ret) goto undo_port_set_pvid; return 0; undo_port_set_pvid: (void)rtl8365mb_vlan_port_set_pvid(priv, port, vlanmc_idx); undo_set_framefilter: if (prev_accepted_frame != accepted_frame) (void)rtl8365mb_vlan_port_set_framefilter(priv, port, prev_accepted_frame); return ret; }