945 lines
29 KiB
C
945 lines
29 KiB
C
// 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;
|
|
}
|