// SPDX-License-Identifier: GPL-2.0
/* VLAN configuration interface for the rtl8365mb switch family
*
* Copyright (C) 2022 Alvin Å ipraga <alsi@bang-olufsen.dk>
*
* 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 <linux/if_bridge.h>
#include <linux/lockdep.h>
#include <linux/regmap.h>
/* 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;
}