diff --git a/drivers/net/dsa/realtek/Makefile b/drivers/net/dsa/realtek/Makefile index 17367bcba496..72bb42e80c95 100644 --- a/drivers/net/dsa/realtek/Makefile +++ b/drivers/net/dsa/realtek/Makefile @@ -16,3 +16,8 @@ ifdef CONFIG_NET_DSA_REALTEK_RTL8366RB_LEDS rtl8366-objs += rtl8366rb-leds.o endif obj-$(CONFIG_NET_DSA_REALTEK_RTL8365MB) += rtl8365mb.o +rtl8365mb-objs := rtl8365mb_main.o \ + rtl8365mb_table.o \ + rtl8365mb_vlan.o \ + rtl8365mb_l2.o \ +# end of rtl8365mb-objs diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h index c03485a80d93..6e0148cee8d8 100644 --- a/drivers/net/dsa/realtek/realtek.h +++ b/drivers/net/dsa/realtek/realtek.h @@ -45,6 +45,12 @@ struct rtl8366_vlan_4k { u8 fid; }; +struct realtek_fdb_entry { + u8 mac_addr[ETH_ALEN]; + u16 vid; + bool is_static; +}; + struct realtek_priv { struct device *dev; struct reset_control *reset_ctl; @@ -54,6 +60,20 @@ struct realtek_priv { struct regmap *map; struct regmap *map_nolock; struct mutex map_lock; + /* vlan_lock protects against concurrent Read-Modify-Write operations + * on the global VLAN 4K and VLANMC tables, such as when adding or + * deleting port VLAN memberships and PVID configurations. + */ + struct mutex vlan_lock; + /* l2_lock is used to prevent concurrent modifications of L2 table + * entries while another function is reading it. l2_(add,del)_mc + * is an example that first read current table entry and then + * create/update it. l2_(add|del)_uc uses a single table op and, + * internally, it might not need this lock. However, altering FDB + * may still collide, as well as l2_flush, with fdb_dump iterating + * over FDB. + */ + struct mutex l2_lock; struct mii_bus *user_mii_bus; struct mii_bus *bus; int mdio_addr; @@ -107,6 +127,32 @@ struct realtek_ops { int (*enable_vlan)(struct realtek_priv *priv, bool enable); int (*enable_vlan4k)(struct realtek_priv *priv, bool enable); int (*enable_port)(struct realtek_priv *priv, int port, bool enable); + int (*port_add_isolation)(struct realtek_priv *priv, int port, + u32 mask); + int (*port_remove_isolation)(struct realtek_priv *priv, int port, + u32 mask); + int (*port_set_efid)(struct realtek_priv *priv, int port, u32 efid); + int (*port_set_learning)(struct realtek_priv *priv, int port, + bool enable); + int (*port_set_ucast_flood)(struct realtek_priv *priv, int port, + bool enable); + int (*port_set_mcast_flood)(struct realtek_priv *priv, int port, + bool enable); + int (*port_set_bcast_flood)(struct realtek_priv *priv, int port, + bool enable); + int (*l2_add_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); + int (*l2_del_uc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], + u16 efid, u16 vid); + int (*l2_get_next_uc)(struct realtek_priv *priv, u16 *addr, + int port, struct realtek_fdb_entry *entry); + int (*l2_add_mc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], u16 vid); + int (*l2_del_mc)(struct realtek_priv *priv, int port, + const unsigned char addr[ETH_ALEN], u16 vid); + int (*l2_flush)(struct realtek_priv *priv, int port, u16 vid); int (*phy_read)(struct realtek_priv *priv, int phy, int regnum); int (*phy_write)(struct realtek_priv *priv, int phy, int regnum, u16 val); diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.c b/drivers/net/dsa/realtek/rtl8365mb_l2.c new file mode 100644 index 000000000000..0494d8ab2578 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.c @@ -0,0 +1,576 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Forwarding and multicast database interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin Šipraga + */ + +#include + +#include "rtl8365mb_l2.h" +#include "rtl8365mb_table.h" +#include + +#define RTL8365MB_L2_ENTRY_SIZE 6 + +#define RTL8365MB_L2_UC_D0_MAC5_MSK GENMASK(7, 0) +#define RTL8365MB_L2_UC_D0_MAC4_MSK GENMASK(15, 8) +#define RTL8365MB_L2_UC_D1_MAC3_MSK GENMASK(7, 0) +#define RTL8365MB_L2_UC_D1_MAC2_MSK GENMASK(15, 8) +#define RTL8365MB_L2_UC_D2_MAC1_MSK GENMASK(7, 0) +#define RTL8365MB_L2_UC_D2_MAC0_MSK GENMASK(15, 8) +#define RTL8365MB_L2_UC_D3_VID_MSK GENMASK(11, 0) +#define RTL8365MB_L2_UC_D3_IVL_MSK GENMASK(13, 13) +#define RTL8365MB_L2_UC_D3_PORT_EXT_MSK GENMASK(15, 15) +#define RTL8365MB_L2_UC_PORT_HI_MSK GENMASK(3, 3) +#define RTL8365MB_L2_UC_D4_EFID_MSK GENMASK(2, 0) +#define RTL8365MB_L2_UC_D4_FID_MSK GENMASK(6, 3) +#define RTL8365MB_L2_UC_D4_SA_PRI_MSK GENMASK(7, 7) +#define RTL8365MB_L2_UC_D4_PORT_MSK GENMASK(10, 8) +#define RTL8365MB_L2_UC_PORT_LO_MSK GENMASK(2, 0) +#define RTL8365MB_L2_UC_D4_AGE_MSK GENMASK(13, 11) +#define RTL8365MB_L2_UC_D4_AUTH_MSK GENMASK(14, 14) +#define RTL8365MB_L2_UC_D4_SA_BLOCK_MSK GENMASK(15, 15) + +#define RTL8365MB_L2_UC_D5_DA_BLOCK_MSK GENMASK(0, 0) +#define RTL8365MB_L2_UC_D5_PRIORITY_MSK GENMASK(3, 1) +#define RTL8365MB_L2_UC_D5_FWD_PRI_MSK GENMASK(4, 4) +#define RTL8365MB_L2_UC_D5_STATIC_MSK GENMASK(5, 5) + +#define RTL8365MB_L2_MC_D0_MAC5_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D0_MAC4_MSK GENMASK(15, 8) +#define RTL8365MB_L2_MC_D1_MAC3_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D1_MAC2_MSK GENMASK(15, 8) +#define RTL8365MB_L2_MC_D2_MAC1_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D2_MAC0_MSK GENMASK(15, 8) +#define RTL8365MB_L2_MC_D3_VID_MSK GENMASK(11, 0) +#define RTL8365MB_L2_MC_D3_IVL_MSK GENMASK(13, 13) +#define RTL8365MB_L2_MC_D3_MBR_HI1_MSK GENMASK(15, 14) +#define RTL8365MB_L2_MC_MBR_HI1_MSK GENMASK(9, 8) + +#define RTL8365MB_L2_MC_D4_MBR_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_MBR_LO_MSK GENMASK(7, 0) +#define RTL8365MB_L2_MC_D4_IGMPIDX_MSK GENMASK(15, 8) + +#define RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK GENMASK(0, 0) +#define RTL8365MB_L2_MC_D5_PRIORITY_MSK GENMASK(3, 1) +#define RTL8365MB_L2_MC_D5_FWD_PRI_MSK GENMASK(4, 4) +#define RTL8365MB_L2_MC_D5_STATIC_MSK GENMASK(5, 5) +#define RTL8365MB_L2_MC_D5_MBR_HI2_MSK GENMASK(7, 7) +#define RTL8365MB_L2_MC_MBR_HI2_MSK GENMASK(10, 10) + +/* Port flush command registers - writing a 1 to the port's MASK bit will + * initiate the flush procedure. Completion is signalled when the corresponding + * BUSY bit is 0. + */ +#define RTL8365MB_L2_FLUSH_PORT_REG 0x0A36 +#define RTL8365MB_L2_FLUSH_PORT_MSK_MSK GENMASK(7, 0) +#define RTL8365MB_L2_FLUSH_PORT_BUSY_MSK GENMASK(15, 8) + +#define RTL8365MB_L2_FLUSH_PORT_EXT_REG 0x0A35 +#define RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK GENMASK(2, 0) +#define RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK GENMASK(5, 3) + +#define RTL8365MB_L2_FLUSH_CTRL1_REG 0x0A37 +#define RTL8365MB_L2_FLUSH_CTRL1_VID_MSK GENMASK(11, 0) +#define RTL8365MB_L2_FLUSH_CTRL1_FID_MSK GENMASK(15, 12) + +#define RTL8365MB_L2_FLUSH_CTRL2_REG 0x0A38 +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK GENMASK(1, 0) +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT 0 +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID 1 +#define RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_FID 2 +#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK GENMASK(2, 2) +#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC 0 +#define RTL8365MB_L2_FLUSH_CTRL2_TYPE_BOTH 1 + +/* This flushes the entire LUT, reading it back it will turn 0 when the + * operation is complete + */ +#define RTL8365MB_L2_FLUSH_CTRL3_REG 0x0A39 +#define RTL8365MB_L2_FLUSH_CTRL3_MSK GENMASK(0, 0) + +struct rtl8365mb_l2_uc_key { + u8 mac_addr[ETH_ALEN]; + u16 vid; + u16 fid; + bool ivl; + u16 efid; +}; + +struct rtl8365mb_l2_uc { + struct rtl8365mb_l2_uc_key key; + u8 port; + u8 age; + u8 priority; + + bool sa_block; + bool da_block; + bool auth; + bool is_static; + bool sa_pri; + bool fwd_pri; +}; + +struct rtl8365mb_l2_mc_key { + u8 mac_addr[ETH_ALEN]; + union { + u16 vid; /* IVL */ + u16 fid; /* SVL */ + }; + bool ivl; +}; + +struct rtl8365mb_l2_mc { + struct rtl8365mb_l2_mc_key key; + u16 member; + u8 priority; + u8 igmpidx; + + bool is_static; + bool fwd_pri; + bool igmp_asic; +}; + +static void rtl8365mb_l2_data_to_uc(const u16 *data, struct rtl8365mb_l2_uc *uc) +{ + u32 val; + + uc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC5_MSK, data[0]); + uc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_UC_D0_MAC4_MSK, data[0]); + uc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC3_MSK, data[1]); + uc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_UC_D1_MAC2_MSK, data[1]); + uc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC1_MSK, data[2]); + uc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_UC_D2_MAC0_MSK, data[2]); + uc->key.efid = FIELD_GET(RTL8365MB_L2_UC_D4_EFID_MSK, data[4]); + uc->key.vid = FIELD_GET(RTL8365MB_L2_UC_D3_VID_MSK, data[3]); + uc->key.ivl = FIELD_GET(RTL8365MB_L2_UC_D3_IVL_MSK, data[3]); + uc->key.fid = FIELD_GET(RTL8365MB_L2_UC_D4_FID_MSK, data[4]); + uc->age = FIELD_GET(RTL8365MB_L2_UC_D4_AGE_MSK, data[4]); + uc->auth = FIELD_GET(RTL8365MB_L2_UC_D4_AUTH_MSK, data[4]); + + val = FIELD_GET(RTL8365MB_L2_UC_D4_PORT_MSK, data[4]); + uc->port = FIELD_PREP(RTL8365MB_L2_UC_PORT_LO_MSK, val); + val = FIELD_GET(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, data[3]); + uc->port |= FIELD_PREP(RTL8365MB_L2_UC_PORT_HI_MSK, val); + + uc->sa_pri = FIELD_GET(RTL8365MB_L2_UC_D4_SA_PRI_MSK, data[4]); + uc->fwd_pri = FIELD_GET(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, data[5]); + uc->sa_block = FIELD_GET(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, data[4]); + uc->da_block = FIELD_GET(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, data[5]); + uc->priority = FIELD_GET(RTL8365MB_L2_UC_D5_PRIORITY_MSK, data[5]); + uc->is_static = FIELD_GET(RTL8365MB_L2_UC_D5_STATIC_MSK, data[5]); +} + +static void rtl8365mb_l2_uc_to_data(const struct rtl8365mb_l2_uc *uc, u16 *data) +{ + u32 val; + + memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); + data[0] |= + FIELD_PREP(RTL8365MB_L2_UC_D0_MAC5_MSK, uc->key.mac_addr[5]); + data[0] |= + FIELD_PREP(RTL8365MB_L2_UC_D0_MAC4_MSK, uc->key.mac_addr[4]); + data[1] |= + FIELD_PREP(RTL8365MB_L2_UC_D1_MAC3_MSK, uc->key.mac_addr[3]); + data[1] |= + FIELD_PREP(RTL8365MB_L2_UC_D1_MAC2_MSK, uc->key.mac_addr[2]); + data[2] |= + FIELD_PREP(RTL8365MB_L2_UC_D2_MAC1_MSK, uc->key.mac_addr[1]); + data[2] |= + FIELD_PREP(RTL8365MB_L2_UC_D2_MAC0_MSK, uc->key.mac_addr[0]); + data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_VID_MSK, uc->key.vid); + data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_IVL_MSK, uc->key.ivl); + + val = FIELD_GET(RTL8365MB_L2_UC_PORT_HI_MSK, uc->port); + data[3] |= FIELD_PREP(RTL8365MB_L2_UC_D3_PORT_EXT_MSK, val); + + data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_FID_MSK, uc->key.fid); + data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_EFID_MSK, uc->key.efid); + data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AGE_MSK, uc->age); + data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_AUTH_MSK, uc->auth); + + val = FIELD_GET(RTL8365MB_L2_UC_PORT_LO_MSK, uc->port); + data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_PORT_MSK, val); + + data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_PRI_MSK, uc->sa_pri); + data[4] |= FIELD_PREP(RTL8365MB_L2_UC_D4_SA_BLOCK_MSK, uc->sa_block); + data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_FWD_PRI_MSK, uc->fwd_pri); + data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_DA_BLOCK_MSK, uc->da_block); + data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_PRIORITY_MSK, uc->priority); + data[5] |= FIELD_PREP(RTL8365MB_L2_UC_D5_STATIC_MSK, uc->is_static); +} + +static void rtl8365mb_l2_data_to_mc(const u16 *data, struct rtl8365mb_l2_mc *mc) +{ + u32 val; + + mc->key.mac_addr[5] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC5_MSK, data[0]); + mc->key.mac_addr[4] = FIELD_GET(RTL8365MB_L2_MC_D0_MAC4_MSK, data[0]); + mc->key.mac_addr[3] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC3_MSK, data[1]); + mc->key.mac_addr[2] = FIELD_GET(RTL8365MB_L2_MC_D1_MAC2_MSK, data[1]); + mc->key.mac_addr[1] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC1_MSK, data[2]); + mc->key.mac_addr[0] = FIELD_GET(RTL8365MB_L2_MC_D2_MAC0_MSK, data[2]); + /* key.vid,key.fid shares the same memory space */ + mc->key.vid = FIELD_GET(RTL8365MB_L2_MC_D3_VID_MSK, data[3]); + mc->key.ivl = FIELD_GET(RTL8365MB_L2_MC_D3_IVL_MSK, data[3]); + mc->priority = FIELD_GET(RTL8365MB_L2_MC_D5_PRIORITY_MSK, data[5]); + mc->fwd_pri = FIELD_GET(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, data[5]); + mc->is_static = FIELD_GET(RTL8365MB_L2_MC_D5_STATIC_MSK, data[5]); + + val = FIELD_GET(RTL8365MB_L2_MC_D4_MBR_MSK, data[4]); + mc->member = FIELD_PREP(RTL8365MB_L2_MC_MBR_LO_MSK, val); + val = FIELD_GET(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, data[3]); + mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI1_MSK, val); + val = FIELD_GET(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, data[5]); + mc->member |= FIELD_PREP(RTL8365MB_L2_MC_MBR_HI2_MSK, val); + + mc->igmpidx = FIELD_GET(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, data[4]); + mc->igmp_asic = FIELD_GET(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, data[5]); +} + +static void rtl8365mb_l2_mc_to_data(const struct rtl8365mb_l2_mc *mc, u16 *data) +{ + u32 val; + + memset(data, 0, RTL8365MB_L2_ENTRY_SIZE * 2); + data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC5_MSK, mc->key.mac_addr[5]); + data[0] |= FIELD_PREP(RTL8365MB_L2_MC_D0_MAC4_MSK, mc->key.mac_addr[4]); + data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC3_MSK, mc->key.mac_addr[3]); + data[1] |= FIELD_PREP(RTL8365MB_L2_MC_D1_MAC2_MSK, mc->key.mac_addr[2]); + data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC1_MSK, mc->key.mac_addr[1]); + data[2] |= FIELD_PREP(RTL8365MB_L2_MC_D2_MAC0_MSK, mc->key.mac_addr[0]); + data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_VID_MSK, mc->key.vid); + data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_IVL_MSK, mc->key.ivl); + + val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI1_MSK, mc->member); + data[3] |= FIELD_PREP(RTL8365MB_L2_MC_D3_MBR_HI1_MSK, val); + + val = FIELD_GET(RTL8365MB_L2_MC_MBR_LO_MSK, mc->member); + data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_MBR_MSK, val); + + data[4] |= FIELD_PREP(RTL8365MB_L2_MC_D4_IGMPIDX_MSK, mc->igmpidx); + data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_IGMP_ASIC_MSK, mc->igmp_asic); + data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_PRIORITY_MSK, mc->priority); + data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_FWD_PRI_MSK, mc->fwd_pri); + data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_STATIC_MSK, mc->is_static); + + val = FIELD_GET(RTL8365MB_L2_MC_MBR_HI2_MSK, mc->member); + data[5] |= FIELD_PREP(RTL8365MB_L2_MC_D5_MBR_HI2_MSK, val); +} + +/* + * rtl8365mb_l2_get_next_uc() - get the next Unicast L2 entry + * @priv: realtek_priv pointer + * @addr: as input, the table index to start the walk + * as output, the found table index + * @port: restrict the walk on entries related to port + * @entry: returned L2 Unicast table entry + * + * This function gets the next unicast L2 table entry starting from @addr + * and checking exclusively entries related to @port. + * + * On success, it returns 0, updates @addr to the index of the found entry, + * and populates @entry. If the search reaches the end of the table and + * wraps around and @addr will be strictly lower than the input @addr. + * Callers must detect this wrap-around condition to prevent infinite loops. + * + * If the table contains no matching entries at all, it returns -ENOENT + * and leaves @addr and @entry unmodified. + * + * Return: Returns 0 on success, a negative error on failure. + **/ +int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port, + struct realtek_fdb_entry *entry) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + struct rtl8365mb_l2_uc uc; + int ret; + + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_READ, addr, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, + port, data, RTL8365MB_L2_ENTRY_SIZE); + if (ret) + return ret; + + rtl8365mb_l2_data_to_uc(data, &uc); + + ether_addr_copy(entry->mac_addr, uc.key.mac_addr); + entry->vid = uc.key.vid; + entry->is_static = uc.is_static; + + return 0; +} + +int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 efid, u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + struct rtl8365mb_l2_uc uc = { 0 }; + u16 addr; + int ret; + + memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); + uc.key.efid = efid; + uc.key.fid = 0; + uc.key.ivl = true; + uc.key.vid = vid; + + uc.port = port; + /* Entries programmed by DSA (including those dynamically learned by + * the software bridge and injected into the CPU port via assisted + * learning) must be static. We do not let HW decrease age behind the + * OS's back. As a trade-off, these will show up as permanent to users. + */ + uc.is_static = true; + /* age greater than 0 adds/updates entries */ + uc.age = 1; + rtl8365mb_l2_uc_to_data(&uc, data); + + /* add the new entry or update an existing one */ + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + + /* Assume the missing new entry as the table is full */ + if (ret == -ENOENT) + return -ENOSPC; + + /* addr will hold the table index, but it is not used here */ + return ret; +} + +int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 efid, u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + struct rtl8365mb_l2_uc uc = { 0 }; + u16 addr; + int ret; + + memcpy(uc.key.mac_addr, mac_addr, ETH_ALEN); + uc.key.efid = efid; + uc.key.fid = 0; + uc.key.ivl = true; + uc.key.vid = vid; + /* age 0 deletes the entry */ + uc.age = 0; + rtl8365mb_l2_uc_to_data(&uc, data); + + /* it looks like the switch will always add/update the entry, + * even when age is 0 or uc.key did not match an existing entry, + * just to immediately drop it because age is zero. You can still + * get the added/updated address from @addr + */ + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + + if (ret == -ENOENT) { + dev_dbg(priv->dev, "%s: %pM vid=%d efid=%d missing\n", + __func__, mac_addr, vid, efid); + /* Silently return success */ + return 0; + } + + /* addr will hold the table index, but it is not used here */ + return ret; +} + +int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid) +{ + int mode = vid ? RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT_VID : + RTL8365MB_L2_FLUSH_CTRL2_MODE_PORT; + u32 val, mask; + int ret; + + mutex_lock(&priv->map_lock); + + /* Configure flushing mode; only flush dynamic entries */ + ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL2_REG, + FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_MODE_MSK, + mode) | + FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL2_TYPE_MSK, + RTL8365MB_L2_FLUSH_CTRL2_TYPE_DYNAMIC)); + if (ret) + goto out; + + ret = regmap_write(priv->map_nolock, RTL8365MB_L2_FLUSH_CTRL1_REG, + FIELD_PREP(RTL8365MB_L2_FLUSH_CTRL1_VID_MSK, vid)); + + if (ret) + goto out; + /* Now issue the flush command and wait for its completion. There are + * two registers for this purpose, and which one to use depends on the + * port number. The _EXT register is for ports 8 or higher. + */ + if (port < 8) { + val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_MSK_MSK, + BIT(port) & 0xFF); + ret = regmap_write(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_REG, val); + if (ret) + goto out; + + mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_BUSY_MSK, + BIT(port) & 0xFF); + ret = regmap_read_poll_timeout(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_REG, + val, !(val & mask), 10, 10000); + if (ret) + goto out; + } else { + val = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_MSK_MSK, + BIT(port) >> 8); + ret = regmap_write(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_EXT_REG, val); + if (ret) + goto out; + + mask = FIELD_PREP(RTL8365MB_L2_FLUSH_PORT_EXT_BUSY_MSK, + BIT(port) >> 8); + ret = regmap_read_poll_timeout(priv->map_nolock, + RTL8365MB_L2_FLUSH_PORT_EXT_REG, + val, !(val & mask), 10, 10000); + if (ret) + goto out; + } + +out: + mutex_unlock(&priv->map_lock); + + return ret; +} + +int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + struct rtl8365mb_l2_mc mc = { 0 }; + u16 addr; + int ret; + + memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); + mc.key.vid = vid; + mc.key.ivl = true; + /* Already set the port and is_static, although not used in OP_READ, + * data will be ready for OP_WRITE if it is a new entry. + */ + mc.member |= BIT(port); + mc.is_static = 1; + rtl8365mb_l2_mc_to_data(&mc, data); + + /* First look for an existing entry (to get existing port members) */ + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_READ, &addr, + RTL8365MB_TABLE_L2_METHOD_MAC, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + if (!ret) { + /* There is already an entry... */ + rtl8365mb_l2_data_to_mc(data, &mc); + dev_dbg(priv->dev, + "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n", + __func__, mac_addr, addr, mc.member, mc.igmpidx, + mc.is_static ? "static" : "dynamic"); + /* the port must be added as a member */ + mc.member |= BIT(port); + + if (!mc.is_static) { + dev_dbg(priv->dev, + "%s: promoting addr=%d group to static\n", + __func__, addr); + mc.is_static = 1; + } + + rtl8365mb_l2_mc_to_data(&mc, data); + } else if (ret == -ENOENT) { + /* New entry, no need to update data again as it already + * includes the member. + * + * Multicast hardware entries do not support EFID (bridge + * isolation). However, traffic isolation is still maintained + * because the hardware applies the port isolation masks + * (pmasks) configured in bridge_join after the L2 lookup. + * Entries from different bridges will collide on the same + * MAC+VID slot with an OR'ed member mask, but packets will + * only exit through ports allowed by the source port's pmask. + */ + } else { + return ret; + } + + /* add the new entry or update an existing one */ + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + + /* Assume the missing new entry as the table is full */ + if (ret == -ENOENT) + return -ENOSPC; + + return ret; +} + +int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid) +{ + u16 data[RTL8365MB_L2_ENTRY_SIZE] = { 0 }; + struct rtl8365mb_l2_mc mc = { 0 }; + u16 addr; + int ret; + + memcpy(mc.key.mac_addr, mac_addr, ETH_ALEN); + mc.key.vid = vid; + mc.key.ivl = true; + rtl8365mb_l2_mc_to_data(&mc, data); + + /* First look for an existing entry (to get existing port members) */ + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_READ, &addr, + RTL8365MB_TABLE_L2_METHOD_MAC, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + if (ret == -ENOENT) { + dev_dbg(priv->dev, "%s: %pM vid=%d missing\n", + __func__, mac_addr, vid); + /* Silently return success */ + return 0; + } + + if (ret) + /* Return on any other error */ + return ret; + + rtl8365mb_l2_data_to_mc(data, &mc); + dev_dbg(priv->dev, + "%s: found %pM addr=%d member=0x%x igmpidx=0x%x %s\n", + __func__, mac_addr, addr, mc.member, mc.igmpidx, + mc.is_static ? "static" : "dynamic"); + /* the port must be removed as a member */ + mc.member &= ~BIT(port); + if (!mc.member) { + /* Multicast entries do not have an age field. Clearing both + * the member portmask and is_static flags is the hardware + * signal to invalidate and reclaim the L2 table slot. + */ + mc.is_static = 0; + mc.igmpidx = 0; + mc.priority = 0; + mc.fwd_pri = 0; + mc.igmp_asic = 0; + } + rtl8365mb_l2_mc_to_data(&mc, data); + + /* update the existing entry. */ + ret = rtl8365mb_table_query(priv, RTL8365MB_TABLE_L2, + RTL8365MB_TABLE_OP_WRITE, &addr, + 0, 0, + data, RTL8365MB_L2_ENTRY_SIZE); + return ret; +} diff --git a/drivers/net/dsa/realtek/rtl8365mb_l2.h b/drivers/net/dsa/realtek/rtl8365mb_l2.h new file mode 100644 index 000000000000..9470cf059ce5 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_l2.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Forwarding and multicast database interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin Šipraga + */ + +#ifndef _REALTEK_RTL8365MB_L2_H +#define _REALTEK_RTL8365MB_L2_H + +#include +#include + +#include "realtek.h" + +int rtl8365mb_l2_get_next_uc(struct realtek_priv *priv, u16 *addr, int port, + struct realtek_fdb_entry *entry); +int rtl8365mb_l2_add_uc(struct realtek_priv *priv, int port, + const unsigned char addr[static ETH_ALEN], + u16 efid, u16 vid); +int rtl8365mb_l2_del_uc(struct realtek_priv *priv, int port, + const unsigned char addr[static ETH_ALEN], + u16 efid, u16 vid); +int rtl8365mb_l2_flush(struct realtek_priv *priv, int port, u16 vid); + +int rtl8365mb_l2_add_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid); +int rtl8365mb_l2_del_mc(struct realtek_priv *priv, int port, + const unsigned char mac_addr[static ETH_ALEN], + u16 vid); + +#endif /* _REALTEK_RTL8365MB_L2_H */ diff --git a/drivers/net/dsa/realtek/rtl8365mb.c b/drivers/net/dsa/realtek/rtl8365mb_main.c similarity index 76% rename from drivers/net/dsa/realtek/rtl8365mb.c rename to drivers/net/dsa/realtek/rtl8365mb_main.c index 0da048da533a..5ac091bf93c9 100644 --- a/drivers/net/dsa/realtek/rtl8365mb.c +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c @@ -104,14 +104,20 @@ #include "realtek-smi.h" #include "realtek-mdio.h" #include "rtl83xx.h" +#include "rtl8365mb_l2.h" +#include "rtl8365mb_vlan.h" /* Family-specific data and limits */ #define RTL8365MB_PHYADDRMAX 7 #define RTL8365MB_NUM_PHYREGS 32 #define RTL8365MB_PHYREGMAX (RTL8365MB_NUM_PHYREGS - 1) #define RTL8365MB_MAX_NUM_PORTS 11 -#define RTL8365MB_MAX_NUM_EXTINTS 3 +/* Valid for the whole family except RTL8370B, which has 4160 entries. + * RTL8370B is mentioned in vendor code but it might not even belong + * to the same RTL8367C family. + */ #define RTL8365MB_LEARN_LIMIT_MAX 2112 +#define RTL8365MB_MAX_NUM_EXTINTS 3 /* Chip identification registers */ #define RTL8365MB_CHIP_ID_REG 0x1300 @@ -284,6 +290,15 @@ (RTL8365MB_PORT_ISOLATION_REG_BASE + (_physport)) #define RTL8365MB_PORT_ISOLATION_MASK 0x07FF +/* Extended filter ID registers - used to key forwarding database with IVL */ +#define RTL8365MB_EFID_MASK GENMASK(2, 0) +#define RTL8365MB_PORT_EFID_REG_BASE 0x0A32 +#define RTL8365MB_PORT_EFID_REG(_p) \ + (RTL8365MB_PORT_EFID_REG_BASE + ((_p) >> 2)) +#define RTL8365MB_PORT_EFID_OFFSET(_p) (((_p) & 0x3) << 2) +#define RTL8365MB_PORT_EFID_MASK(_p) \ + (RTL8365MB_EFID_MASK << RTL8365MB_PORT_EFID_OFFSET(_p)) + /* MSTP port state registers - indexed by tree instance */ #define RTL8365MB_MSTI_CTRL_BASE 0x0A00 #define RTL8365MB_MSTI_CTRL_REG(_msti, _physport) \ @@ -292,6 +307,72 @@ #define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport) \ (0x3 << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET((_physport))) +/* Unknown unicast DA flooding port mask */ +#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG 0x0890 +#define RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_MASK 0x07FF + +/* Unknown multicast DA flooding port mask */ +#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG 0x0891 +#define RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_MASK 0x07FF + +/* Broadcast flooding port mask */ +#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG 0x0892 +#define RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_MASK 0x07FF + +#define RTL8365MB_SUPPORTED_BRIDGE_FLAGS \ + (BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD) + +/* Miscellaneous port configuration register, incl. VLAN egress mode */ +#define RTL8365MB_PORT_MISC_CFG_REG_BASE 0x000E +#define RTL8365MB_PORT_MISC_CFG_REG(_p) \ + (RTL8365MB_PORT_MISC_CFG_REG_BASE + ((_p) << 5)) +#define RTL8365MB_PORT_MISC_CFG_SMALL_TAG_IPG_MASK 0x8000 +#define RTL8365MB_PORT_MISC_CFG_TX_ITFSP_MODE_MASK 0x4000 +#define RTL8365MB_PORT_MISC_CFG_FLOWCTRL_INDEP_MASK 0x2000 +#define RTL8365MB_PORT_MISC_CFG_DOT1Q_REMARK_ENABLE_MASK 0x1000 +#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_FLOWCTRL_MASK 0x0800 +#define RTL8365MB_PORT_MISC_CFG_INGRESSBW_IFG_MASK 0x0400 +#define RTL8365MB_PORT_MISC_CFG_RX_SPC_MASK 0x0200 +#define RTL8365MB_PORT_MISC_CFG_CRC_SKIP_MASK 0x0100 +#define RTL8365MB_PORT_MISC_CFG_PKTGEN_TX_FIRST_MASK 0x0080 +#define RTL8365MB_PORT_MISC_CFG_MAC_LOOPBACK_MASK 0x0040 +/* See &rtl8365mb_vlan_egress_mode */ +#define RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK 0x0030 +#define RTL8365MB_PORT_MISC_CFG_CONGESTION_SUSTAIN_TIME_MASK 0x000F + +/** + * enum rtl8365mb_vlan_egress_mode - port VLAN egress mode + * @RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL: follow untag mask in VLAN4k table entry + * @RTL8365MB_VLAN_EGRESS_MODE_KEEP: the VLAN tag format of egressed packets + * will remain the same as their ingressed format, but the priority and VID + * fields may be altered + * @RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG: always egress with priority tag + * @RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP: the VLAN tag format of egressed + * packets will remain the same as their ingressed format, and neither the + * priority nor VID fields can be altered + */ +enum rtl8365mb_vlan_egress_mode { + RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL = 0, + RTL8365MB_VLAN_EGRESS_MODE_KEEP = 1, + RTL8365MB_VLAN_EGRESS_MODE_PRI_TAG = 2, + RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP = 3, +}; + +/* VLAN control register */ +#define RTL8365MB_VLAN_CTRL_REG 0x07A8 +#define RTL8365MB_VLAN_CTRL_EN_MASK 0x0001 + +/* VLAN ingress filter register */ +#define RTL8365MB_VLAN_INGRESS_REG 0x07A9 +#define RTL8365MB_VLAN_INGRESS_MASK GENMASK(10, 0) +#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_OFFSET(_p) (_p) +#define RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(_p) BIT(_p) + +/* VLAN "transparent" setting registers */ +#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE 0x09D0 +#define RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(_p) \ + (RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG_BASE + (_p)) + /* MIB counter value registers */ #define RTL8365MB_MIB_COUNTER_BASE 0x1000 #define RTL8365MB_MIB_COUNTER_REG(_x) (RTL8365MB_MIB_COUNTER_BASE + (_x)) @@ -803,8 +884,8 @@ static int rtl8365mb_phy_read(struct realtek_priv *priv, int phy, int regnum) ret = rtl8365mb_phy_ocp_read(priv, phy, ocp_addr, &val); if (ret) { dev_err(priv->dev, - "failed to read PHY%d reg %02x @ %04x, ret %d\n", phy, - regnum, ocp_addr, ret); + "failed to read PHY%d reg %02x @ %04x, ret %pe\n", phy, + regnum, ocp_addr, ERR_PTR(ret)); return ret; } @@ -831,8 +912,8 @@ static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum, ret = rtl8365mb_phy_ocp_write(priv, phy, ocp_addr, val); if (ret) { dev_err(priv->dev, - "failed to write PHY%d reg %02x @ %04x, ret %d\n", phy, - regnum, ocp_addr, ret); + "failed to write PHY%d reg %02x @ %04x, ret %pe\n", phy, + regnum, ocp_addr, ERR_PTR(ret)); return ret; } @@ -1082,8 +1163,8 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config, ret = rtl8365mb_ext_config_rgmii(priv, port, state->interface); if (ret) dev_err(priv->dev, - "failed to configure RGMII mode on port %d: %d\n", - port, ret); + "failed to configure RGMII mode on port %d: %pe\n", + port, ERR_PTR(ret)); return; } @@ -1112,8 +1193,8 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config, false, false); if (ret) dev_err(priv->dev, - "failed to reset forced mode on port %d: %d\n", - port, ret); + "failed to reset forced mode on port %d: %pe\n", + port, ERR_PTR(ret)); return; } @@ -1143,8 +1224,8 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config, rx_pause); if (ret) dev_err(priv->dev, - "failed to force mode on port %d: %d\n", port, - ret); + "failed to force mode on port %d: %pe\n", port, + ERR_PTR(ret)); return; } @@ -1210,6 +1291,286 @@ static void rtl8365mb_port_stp_state_set(struct dsa_switch *ds, int port, val << RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(port)); } +static int rtl8365mb_port_set_transparent(struct realtek_priv *priv, + int igr_port, int egr_port, + bool enable) +{ + dev_dbg(priv->dev, "%s transparent VLAN from %d to %d\n", + enable ? "Enable" : "Disable", igr_port, egr_port); + + /* "Transparent" between the two ports means that packets forwarded by + * igr_port and egressed on egr_port will not be filtered by the usual + * VLAN membership settings. + */ + return regmap_update_bits(priv->map, + RTL8365MB_VLAN_EGRESS_TRANSPARENT_REG(egr_port), + BIT(igr_port), enable ? BIT(igr_port) : 0); +} + +static int rtl8365mb_port_set_ingress_filtering(struct realtek_priv *priv, + int port, bool enable) +{ + /* Ingress filtering enabled: Discard VLAN-tagged frames if the port is + * not a member of the VLAN with which the packet is associated. + * Untagged packets will also be discarded unless the port has a PVID + * programmed. Priority-tagged frames are treated as untagged frames. + * + * Ingress filtering disabled: Accept all tagged and untagged frames. + */ + return regmap_update_bits(priv->map, RTL8365MB_VLAN_INGRESS_REG, + RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port), + enable ? + RTL8365MB_VLAN_INGRESS_FILTER_PORT_EN_MASK(port) : + 0); +} + +static int +rtl8365mb_port_set_vlan_egress_mode(struct realtek_priv *priv, int port, + enum rtl8365mb_vlan_egress_mode mode) +{ + u32 val; + + val = FIELD_PREP(RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, mode); + return regmap_update_bits(priv->map, + RTL8365MB_PORT_MISC_CFG_REG(port), + RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, val); +} + +static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering, + struct netlink_ext_ack *extack) +{ + enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame; + enum rtl8365mb_vlan_egress_mode mode; + struct realtek_priv *priv = ds->priv; + u32 configured_ports = 0; + struct dsa_port *dp; + u16 pvid_vid; + int ret; + + dev_dbg(priv->dev, "port %d: %s VLAN filtering\n", port, + vlan_filtering ? "enable" : "disable"); + + ret = rtl8365mb_vlan_port_get_framefilter(priv, port, + &prev_accepted_frame); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to get current framefilter"); + return ret; + } + + /* While filtering, only accepts untagged frames if PVID is enabled */ + if (vlan_filtering) { + ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid); + if (ret) + return ret; + + if (pvid_vid) + accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; + else + accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY; + } else { + accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME; + } + + /* When vlan filter is enable/disabled in a bridge, this function is + * called for all member ports. We need to enable/disable ingress + * VLAN membership check. + */ + ret = rtl8365mb_port_set_ingress_filtering(priv, port, vlan_filtering); + if (ret) + return ret; + + /* However, we also enable/disable egress filtering because the switch + * still consider the egress interface VLAN membership to forward the + * traffic. We enable/disable that check disabling/enabling transparent + * VLAN between the ingress port and all other available ports. + */ + dsa_switch_for_each_available_port(dp, ds) { + /* port isolation will still keep traffic inside the bridge */ + ret = rtl8365mb_port_set_transparent(priv, port, dp->index, + !vlan_filtering); + if (ret) + goto undo_transparent; + + configured_ports |= BIT(dp->index); + } + + if (accepted_frame != prev_accepted_frame) { + ret = rtl8365mb_vlan_port_set_framefilter(priv, port, + accepted_frame); + if (ret) { + NL_SET_ERR_MSG_MOD(extack, + "Failed to set port framefilter"); + goto undo_transparent; + } + } + + /* When VLAN filtering is disabled, preserve frames exactly as received. + * Otherwise, the VLAN egress pipeline may still alter tag state + * according to VLAN membership and untag configuration. + */ + if (vlan_filtering) + mode = RTL8365MB_VLAN_EGRESS_MODE_ORIGINAL; + else + mode = RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP; + + ret = rtl8365mb_port_set_vlan_egress_mode(priv, port, mode); + if (ret) + goto undo_set_framefilter; + + return ret; + +undo_set_framefilter: + if (prev_accepted_frame != accepted_frame) + rtl8365mb_vlan_port_set_framefilter(priv, port, + prev_accepted_frame); +undo_transparent: + /* The DSA core guarantees this callback is only invoked on an actual + * state transition, ensuring the previous hardware state was the + * opposite (!vlan_filtering). It is also called during setup but, in + * that case, any failure here aborts the entire switch initialization. + * + * VLAN_INGRESS and VLAN_EGRESS_TRANSPARENT states are directly derived + * from vlan_filtering. That way, we can simply undo it without + * checking the current HW state as we do with VLAN_EGRESS_MODE. + */ + dsa_switch_for_each_port(dp, ds) { + if (configured_ports & BIT(dp->index)) + rtl8365mb_port_set_transparent(priv, port, dp->index, + vlan_filtering); + } + + rtl8365mb_port_set_ingress_filtering(priv, port, !vlan_filtering); + + return ret; +} + +static int rtl8365mb_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); + bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); + u16 pvid_vid; + struct realtek_priv *priv = ds->priv; + int ret; + + dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n", + vlan->vid, port, untagged ? "untagged" : "tagged", + pvid ? "PVID" : "no PVID"); + + /* VID == 0 is reserved in this driver */ + if (vlan->vid == 0) { + NL_SET_ERR_MSG_MOD(extack, + "VLAN 0 is reserved by this driver"); + return -EOPNOTSUPP; + } + + mutex_lock(&priv->vlan_lock); + + ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid); + if (ret) + goto out_unlock; + + /* Set PVID if needed */ + if (pvid) { + ret = rtl8365mb_vlan_pvid_port_set(ds, port, vlan->vid, + extack); + if (ret) + goto out_unlock; + } else { + /* or try to unset it if not */ + ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid); + if (ret) + goto out_unlock; + } + + /* add port to vlan4k. It knows nothing about PVID */ + ret = rtl8365mb_vlan_4k_port_add(ds, port, vlan, extack); + if (ret) + goto undo_set_pvid; + + ret = 0; + goto out_unlock; + +undo_set_pvid: + /* undo the pvid definition */ + if (pvid != (pvid_vid == vlan->vid)) { + if (pvid_vid) + (void)rtl8365mb_vlan_pvid_port_set(ds, port, pvid_vid, + NULL); + else + (void)rtl8365mb_vlan_pvid_port_clear(ds, port, + vlan->vid); + } +out_unlock: + mutex_unlock(&priv->vlan_lock); + return ret; +} + +static int rtl8365mb_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); + bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID); + struct realtek_priv *priv = ds->priv; + int ret; + + dev_dbg(priv->dev, "del VLAN %d on port %d, %s, %s\n", + vlan->vid, port, untagged ? "untagged" : "tagged", + pvid ? "PVID" : "no PVID"); + + /* VID == 0 is reserved in this driver */ + if (vlan->vid == 0) + return -EOPNOTSUPP; + + mutex_lock(&priv->vlan_lock); + ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid); + if (ret) + goto out_unlock; + + ret = rtl8365mb_vlan_4k_port_del(ds, port, vlan); + /* There is little incentive to try to undo the removal of PVID (if it + * was really in use) as an error here might indicate the ASIC stopped + * to answer. + */ + +out_unlock: + mutex_unlock(&priv->vlan_lock); + return ret; +} + +/* VLAN support is always enabled in the switch. + * + * Standalone forwarding relies on transparent VLAN mode combined with per-port + * isolation masks restricting egress to CPU ports only. + * + */ +static int rtl8365mb_vlan_setup(struct dsa_switch *ds) +{ + struct realtek_priv *priv = ds->priv; + struct dsa_port *dp; + int ret; + + dsa_switch_for_each_available_port(dp, ds) { + /* Disable vlan-filtering for all ports */ + ret = rtl8365mb_port_vlan_filtering(ds, dp->index, false, NULL); + if (ret) { + dev_err(priv->dev, + "Failed to disable vlan filtering on port %d\n", + dp->index); + return ret; + } + } + + /* VLAN is always enabled. */ + ret = regmap_update_bits(priv->map, RTL8365MB_VLAN_CTRL_REG, + RTL8365MB_VLAN_CTRL_EN_MASK, + FIELD_PREP(RTL8365MB_VLAN_CTRL_EN_MASK, 1)); + return ret; +} + static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, bool enable) { @@ -1222,10 +1583,87 @@ static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port, enable ? RTL8365MB_LEARN_LIMIT_MAX : 0); } +static int rtl8365mb_port_set_ucast_flood(struct realtek_priv *priv, int port, + bool enable) +{ + /* Frames with unknown unicast DA will be flooded to a programmable + * port mask that by default includes all ports. Add or remove + * the specified port from this port mask accordingly. + */ + return regmap_update_bits(priv->map, + RTL8365MB_UNKNOWN_UNICAST_FLOODING_PMASK_REG, + BIT(port), enable ? BIT(port) : 0); +} + +static int rtl8365mb_port_set_mcast_flood(struct realtek_priv *priv, int port, + bool enable) +{ + return regmap_update_bits(priv->map, + RTL8365MB_UNKNOWN_MULTICAST_FLOODING_PMASK_REG, + BIT(port), enable ? BIT(port) : 0); +} + +static int rtl8365mb_port_set_bcast_flood(struct realtek_priv *priv, int port, + bool enable) +{ + return regmap_update_bits(priv->map, + RTL8365MB_UNKNOWN_BROADCAST_FLOODING_PMASK_REG, + BIT(port), enable ? BIT(port) : 0); +} + +static int rtl8365mb_port_pre_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + struct realtek_priv *priv = ds->priv; + + dev_dbg(priv->dev, "pre_bridge_flags port:%d flags:%lx supported:%lx\n", + port, flags.mask, RTL8365MB_SUPPORTED_BRIDGE_FLAGS); + + if (flags.mask & ~RTL8365MB_SUPPORTED_BRIDGE_FLAGS) + return -EINVAL; + + return 0; +} + +static int rtl8365mb_port_set_efid(struct realtek_priv *priv, int port, + u32 efid) +{ + return regmap_update_bits(priv->map, RTL8365MB_PORT_EFID_REG(port), + RTL8365MB_PORT_EFID_MASK(port), + efid << RTL8365MB_PORT_EFID_OFFSET(port)); +} + +/* Port isolation manipulation functions. + * + * The port isolation register controls the forwarding mask of a given + * port. The switch will not forward packets ingressed on a given port + * to ports which are not enabled in its forwarding mask. + * + * The port forwarding mask has the highest priority in forwarding + * decisions. The only exception to this rule is when the switch + * receives a packet on its CPU port with ALLOW=0. In that case the TX + * field of the CPU tag will override the forwarding port mask. + */ static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port, u32 mask) { - return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), mask); + return regmap_write(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), + mask); +} + +static int rtl8365mb_port_add_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), + mask, mask); +} + +static int rtl8365mb_port_remove_isolation(struct realtek_priv *priv, int port, + u32 mask) +{ + return regmap_update_bits(priv->map, RTL8365MB_PORT_ISOLATION_REG(port), + mask, 0); } static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port, @@ -1299,8 +1737,8 @@ static void rtl8365mb_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *da mib->length, &data[i]); if (ret) { dev_err(priv->dev, - "failed to read port %d counters: %d\n", port, - ret); + "failed to read port %d counters: %pe\n", port, + ERR_PTR(ret)); break; } } @@ -1554,18 +1992,15 @@ static void rtl8365mb_stats_setup(struct realtek_priv *priv) { struct rtl8365mb *mb = priv->chip_data; struct dsa_switch *ds = &priv->ds; - int i; + struct dsa_port *dp; /* Per-chip global mutex to protect MIB counter access, since doing * so requires accessing a series of registers in a particular order. */ mutex_init(&mb->mib_lock); - for (i = 0; i < priv->num_ports; i++) { - struct rtl8365mb_port *p = &mb->ports[i]; - - if (dsa_is_unused_port(ds, i)) - continue; + dsa_switch_for_each_available_port(dp, ds) { + struct rtl8365mb_port *p = &mb->ports[dp->index]; /* Per-port spinlock to protect the stats64 data */ spin_lock_init(&p->stats_lock); @@ -1581,13 +2016,10 @@ static void rtl8365mb_stats_teardown(struct realtek_priv *priv) { struct rtl8365mb *mb = priv->chip_data; struct dsa_switch *ds = &priv->ds; - int i; + struct dsa_port *dp; - for (i = 0; i < priv->num_ports; i++) { - struct rtl8365mb_port *p = &mb->ports[i]; - - if (dsa_is_unused_port(ds, i)) - continue; + dsa_switch_for_each_available_port(dp, ds) { + struct rtl8365mb_port *p = &mb->ports[dp->index]; cancel_delayed_work_sync(&p->mib_work); } @@ -1646,13 +2078,17 @@ static irqreturn_t rtl8365mb_irq(int irq, void *data) for_each_set_bit(line, &line_changes, priv->num_ports) { int child_irq = irq_find_mapping(priv->irqdomain, line); + if (!child_irq) + continue; + handle_nested_irq(child_irq); } return IRQ_HANDLED; out_error: - dev_err(priv->dev, "failed to read interrupt status: %d\n", ret); + dev_err(priv->dev, "failed to read interrupt status: %pe\n", + ERR_PTR(ret)); out_none: return IRQ_NONE; @@ -1666,10 +2102,14 @@ static struct irq_chip rtl8365mb_irq_chip = { static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) { - irq_set_chip_data(irq, domain->host_data); + struct realtek_priv *priv = domain->host_data; + struct rtl8365mb *mb = priv->chip_data; + + irq_set_chip_data(irq, priv); irq_set_chip_and_handler(irq, &rtl8365mb_irq_chip, handle_simple_irq); irq_set_nested_thread(irq, 1); irq_set_noprobe(irq); + irq_set_parent(irq, mb->irq); return 0; } @@ -1708,13 +2148,14 @@ static int rtl8365mb_irq_disable(struct realtek_priv *priv) static int rtl8365mb_irq_setup(struct realtek_priv *priv) { struct rtl8365mb *mb = priv->chip_data; + struct dsa_switch *ds = &priv->ds; struct device_node *intc; + struct dsa_port *dp; u32 irq_trig; int virq; int irq; u32 val; int ret; - int i; intc = of_get_child_by_name(priv->dev->of_node, "interrupt-controller"); if (!intc) { @@ -1725,13 +2166,19 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv) /* rtl8365mb IRQs cascade off this one */ irq = of_irq_get(intc, 0); if (irq <= 0) { - if (irq != -EPROBE_DEFER) - dev_err(priv->dev, "failed to get parent irq: %d\n", - irq); - ret = irq ? irq : -EINVAL; + if (!irq) { + dev_err(priv->dev, "failed to map IRQ\n"); + ret = -EINVAL; + } else { + ret = dev_err_probe(priv->dev, irq, + "failed to get parent irq\n"); + } goto out_put_node; } + /* Store the irq so that we know to map and free it during teardown */ + mb->irq = irq; + priv->irqdomain = irq_domain_create_linear(of_fwnode_handle(intc), priv->num_ports, &rtl8365mb_irqdomain_ops, priv); if (!priv->irqdomain) { @@ -1740,8 +2187,8 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv) goto out_put_node; } - for (i = 0; i < priv->num_ports; i++) { - virq = irq_create_mapping(priv->irqdomain, i); + dsa_switch_for_each_available_port(dp, ds) { + virq = irq_create_mapping(priv->irqdomain, dp->index); if (!virq) { dev_err(priv->dev, "failed to create irq domain mapping\n"); @@ -1790,13 +2237,11 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv) ret = request_threaded_irq(irq, NULL, rtl8365mb_irq, IRQF_ONESHOT, "rtl8365mb", priv); if (ret) { - dev_err(priv->dev, "failed to request irq: %d\n", ret); + dev_err(priv->dev, "failed to request irq: %pe\n", + ERR_PTR(ret)); goto out_remove_irqdomain; } - /* Store the irq so that we know to free it during teardown */ - mb->irq = irq; - ret = rtl8365mb_irq_enable(priv); if (ret) goto out_free_irq; @@ -1807,18 +2252,20 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv) out_free_irq: free_irq(mb->irq, priv); - mb->irq = 0; out_remove_irqdomain: - for (i = 0; i < priv->num_ports; i++) { - virq = irq_find_mapping(priv->irqdomain, i); - irq_dispose_mapping(virq); + dsa_switch_for_each_port(dp, ds) { + virq = irq_find_mapping(priv->irqdomain, dp->index); + + if (virq) + irq_dispose_mapping(virq); } irq_domain_remove(priv->irqdomain); priv->irqdomain = NULL; out_put_node: + mb->irq = 0; of_node_put(intc); return ret; @@ -1827,8 +2274,9 @@ static int rtl8365mb_irq_setup(struct realtek_priv *priv) static void rtl8365mb_irq_teardown(struct realtek_priv *priv) { struct rtl8365mb *mb = priv->chip_data; + struct dsa_switch *ds = &priv->ds; + struct dsa_port *dp; int virq; - int i; if (mb->irq) { free_irq(mb->irq, priv); @@ -1836,9 +2284,15 @@ static void rtl8365mb_irq_teardown(struct realtek_priv *priv) } if (priv->irqdomain) { - for (i = 0; i < priv->num_ports; i++) { - virq = irq_find_mapping(priv->irqdomain, i); - irq_dispose_mapping(virq); + /* Unused ports with a linked PHY still have an active IRQ + * mapping that must be disposed of during teardown. Loop + * through all ports. + */ + dsa_switch_for_each_port(dp, ds) { + virq = irq_find_mapping(priv->irqdomain, dp->index); + + if (virq) + irq_dispose_mapping(virq); } irq_domain_remove(priv->irqdomain); @@ -1956,24 +2410,27 @@ static int rtl8365mb_setup(struct dsa_switch *ds) { struct realtek_priv *priv = ds->priv; struct rtl8365mb_cpu *cpu; - struct dsa_port *cpu_dp; + u32 downports_mask = 0; + u32 upports_mask = 0; struct rtl8365mb *mb; + struct dsa_port *dp; int ret; - int i; mb = priv->chip_data; cpu = &mb->cpu; ret = rtl8365mb_reset_chip(priv); if (ret) { - dev_err(priv->dev, "failed to reset chip: %d\n", ret); + dev_err(priv->dev, "failed to reset chip: %pe\n", + ERR_PTR(ret)); goto out_error; } /* Configure switch to vendor-defined initial state */ ret = rtl8365mb_switch_init(priv); if (ret) { - dev_err(priv->dev, "failed to initialize switch: %d\n", ret); + dev_err(priv->dev, "failed to initialize switch: %pe\n", + ERR_PTR(ret)); goto out_error; } @@ -1984,50 +2441,125 @@ static int rtl8365mb_setup(struct dsa_switch *ds) else if (ret) dev_info(priv->dev, "no interrupt support\n"); - /* Configure CPU tagging */ - dsa_switch_for_each_cpu_port(cpu_dp, ds) { - cpu->mask |= BIT(cpu_dp->index); - - if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) - cpu->trap_port = cpu_dp->index; + dsa_switch_for_each_port(dp, ds) { + /* Cascading (DSA links) is not supported yet. + * Historically, the driver has always been broken + * without a dedicated CPU port because CPU tagging + * would be disabled, rendering the switch entirely + * non-functional for DSA operations. + */ + if (dsa_port_is_dsa(dp)) { + dev_err(priv->dev, "Cascading (DSA link) not supported\n"); + ret = -EOPNOTSUPP; + goto out_teardown_irq; + } } - cpu->enable = cpu->mask > 0; - ret = rtl8365mb_cpu_config(priv); - if (ret) - goto out_teardown_irq; - /* Configure ports */ - for (i = 0; i < priv->num_ports; i++) { - struct rtl8365mb_port *p = &mb->ports[i]; - - if (dsa_is_unused_port(ds, i)) - continue; - - /* Forward only to the CPU */ - ret = rtl8365mb_port_set_isolation(priv, i, cpu->mask); - if (ret) - goto out_teardown_irq; - - /* Disable learning */ - ret = rtl8365mb_port_set_learning(priv, i, false); - if (ret) - goto out_teardown_irq; + /* Start with all ports blocked, including unused ports */ + dsa_switch_for_each_port(dp, ds) { + struct rtl8365mb_port *p = &mb->ports[dp->index]; /* Set the initial STP state of all ports to DISABLED, otherwise * ports will still forward frames to the CPU despite being * administratively down by default. */ - rtl8365mb_port_stp_state_set(ds, i, BR_STATE_DISABLED); + rtl8365mb_port_stp_state_set(ds, dp->index, BR_STATE_DISABLED); + + /* Start with all port completely isolated */ + ret = rtl8365mb_port_set_isolation(priv, dp->index, 0); + if (ret) + goto out_teardown_irq; + + /* Set the default EFID 0 for standalone mode */ + ret = rtl8365mb_port_set_efid(priv, dp->index, 0); + if (ret) + goto out_teardown_irq; + + /* Disable learning */ + ret = rtl8365mb_port_set_learning(priv, dp->index, false); + if (ret) + goto out_teardown_irq; + + /* Enable all types of flooding */ + ret = rtl83xx_setup_port_flood_control(priv, dp->index); + if (ret) + goto out_teardown_irq; /* Set up per-port private data */ p->priv = priv; - p->index = i; + p->index = dp->index; + + /* Collect CPU ports. If we support cascade switches, it should + * also include the upstream DSA ports. + */ + if (!dsa_port_is_cpu(dp)) + continue; + + upports_mask |= BIT(dp->index); } + /* Configure user ports */ + dsa_switch_for_each_port(dp, ds) { + if (!dsa_port_is_user(dp)) + continue; + + /* Forward only to the CPU */ + ret = rtl8365mb_port_set_isolation(priv, dp->index, + upports_mask); + if (ret) + goto out_teardown_irq; + + /* If we support cascade switches, it should also include the + * downstream DSA ports. + */ + downports_mask |= BIT(dp->index); + } + + /* Configure CPU tagging */ + /* If we support cascade switches, it should also include the upstream + * DSA ports. + */ + dsa_switch_for_each_cpu_port(dp, ds) { + /* Use the first CPU port as trap_port */ + if (cpu->trap_port == RTL8365MB_MAX_NUM_PORTS) + cpu->trap_port = dp->index; + + /* Forward to all user ports */ + ret = rtl8365mb_port_set_isolation(priv, dp->index, + downports_mask); + if (ret) + goto out_teardown_irq; + } + + cpu->mask = upports_mask; + cpu->enable = cpu->mask > 0; + + if (!cpu->enable) { + dev_err(priv->dev, "no CPU port defined\n"); + ret = -EINVAL; + goto out_teardown_irq; + } + + ret = rtl8365mb_cpu_config(priv); + if (ret) + goto out_teardown_irq; + ret = rtl8365mb_port_change_mtu(ds, cpu->trap_port, ETH_DATA_LEN); if (ret) goto out_teardown_irq; + ds->assisted_learning_on_cpu_port = true; + ds->fdb_isolation = true; + /* The EFID is 3 bits, but EFID 0 is reserved for standalone ports */ + ds->max_num_bridges = FIELD_MAX(RTL8365MB_EFID_MASK); + + ds->configure_vlan_while_not_filtering = true; + + /* Set up VLAN */ + ret = rtl8365mb_vlan_setup(ds); + if (ret) + goto out_teardown_irq; + ret = rtl83xx_setup_user_mdio(ds); if (ret) { dev_err(priv->dev, "could not set up MDIO bus\n"); @@ -2091,8 +2623,8 @@ static int rtl8365mb_detect(struct realtek_priv *priv) ret = rtl8365mb_get_chip_id_and_ver(priv->map, &chip_id, &chip_ver); if (ret) { - dev_err(priv->dev, "failed to read chip id and version: %d\n", - ret); + dev_err(priv->dev, "failed to read chip id and version: %pe\n", + ERR_PTR(ret)); return ret; } @@ -2137,7 +2669,20 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = { .setup = rtl8365mb_setup, .teardown = rtl8365mb_teardown, .phylink_get_caps = rtl8365mb_phylink_get_caps, + .port_bridge_join = rtl83xx_port_bridge_join, + .port_bridge_leave = rtl83xx_port_bridge_leave, + .port_pre_bridge_flags = rtl8365mb_port_pre_bridge_flags, + .port_bridge_flags = rtl83xx_port_bridge_flags, .port_stp_state_set = rtl8365mb_port_stp_state_set, + .port_fast_age = rtl83xx_port_fast_age, + .port_fdb_add = rtl83xx_port_fdb_add, + .port_fdb_del = rtl83xx_port_fdb_del, + .port_fdb_dump = rtl83xx_port_fdb_dump, + .port_mdb_add = rtl83xx_port_mdb_add, + .port_mdb_del = rtl83xx_port_mdb_del, + .port_vlan_add = rtl8365mb_port_vlan_add, + .port_vlan_del = rtl8365mb_port_vlan_del, + .port_vlan_filtering = rtl8365mb_port_vlan_filtering, .get_strings = rtl8365mb_get_strings, .get_ethtool_stats = rtl8365mb_get_ethtool_stats, .get_sset_count = rtl8365mb_get_sset_count, @@ -2153,6 +2698,19 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = { static const struct realtek_ops rtl8365mb_ops = { .detect = rtl8365mb_detect, + .port_add_isolation = rtl8365mb_port_add_isolation, + .port_remove_isolation = rtl8365mb_port_remove_isolation, + .port_set_efid = rtl8365mb_port_set_efid, + .port_set_learning = rtl8365mb_port_set_learning, + .port_set_ucast_flood = rtl8365mb_port_set_ucast_flood, + .port_set_mcast_flood = rtl8365mb_port_set_mcast_flood, + .port_set_bcast_flood = rtl8365mb_port_set_bcast_flood, + .l2_add_uc = rtl8365mb_l2_add_uc, + .l2_del_uc = rtl8365mb_l2_del_uc, + .l2_get_next_uc = rtl8365mb_l2_get_next_uc, + .l2_add_mc = rtl8365mb_l2_add_mc, + .l2_del_mc = rtl8365mb_l2_del_mc, + .l2_flush = rtl8365mb_l2_flush, .phy_read = rtl8365mb_phy_read, .phy_write = rtl8365mb_phy_write, }; diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.c b/drivers/net/dsa/realtek/rtl8365mb_table.c new file mode 100644 index 000000000000..f3c8749a2221 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_table.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Look-up table query interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin Šipraga + */ + +#include "rtl8365mb_table.h" +#include + +/* Table access control register */ +#define RTL8365MB_TABLE_CTRL_REG 0x0500 +/* Should be one of rtl8365mb_table enum members */ +#define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0) +/* Should be one of rtl8365mb_table_op enum members */ +#define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3) +/* Should be one of rtl8365mb_table_l2_method enum members */ +#define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4) +#define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8) + +/* Table access address register */ +#define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501 +#define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0) + +/* Table status register */ +#define RTL8365MB_TABLE_STATUS_REG 0x0502 +#define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0) +/* set for L3, unset for L2 */ +#define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11) +#define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12) +#define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13) +#define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14) + +/* Table read/write registers */ +#define RTL8365MB_TABLE_WRITE_BASE 0x0510 +#define RTL8365MB_TABLE_WRITE_REG(_x) \ + (RTL8365MB_TABLE_WRITE_BASE + (_x)) +#define RTL8365MB_TABLE_READ_BASE 0x0520 +#define RTL8365MB_TABLE_READ_REG(_x) \ + (RTL8365MB_TABLE_READ_BASE + (_x)) +#define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0) +#define RTL8365MB_TABLE_WRITE_10TH_REG \ + RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1) + +static int rtl8365mb_table_poll_busy(struct realtek_priv *priv) +{ + u32 val; + + return regmap_read_poll_timeout(priv->map_nolock, + RTL8365MB_TABLE_STATUS_REG, val, + !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val), + 10, 10000); +} + +int rtl8365mb_table_query(struct realtek_priv *priv, + enum rtl8365mb_table table, + enum rtl8365mb_table_op op, u16 *addr, + enum rtl8365mb_table_l2_method method, + u16 port, u16 *data, size_t size) +{ + bool addr_as_input = true; + bool write_data = false; + int ret = 0; + u32 cmd; + u32 val; + u32 hit; + + /* Prepare target table and operation (read or write) */ + cmd = 0; + cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table); + cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op); + if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) { + cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method); + switch (method) { + case RTL8365MB_TABLE_L2_METHOD_MAC: + /* + * Method MAC requires as input the same L2 table format + * you'll get as result. However, it might only use mac + * address and FID/VID fields. + */ + write_data = true; + + /* METHOD_MAC does not use addr as input, but may return + * the matched index. + */ + addr_as_input = false; + + break; + case RTL8365MB_TABLE_L2_METHOD_ADDR: + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: + break; + case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: + cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port); + break; + default: + return -EINVAL; + } + } else if (op == RTL8365MB_TABLE_OP_WRITE) { + write_data = true; + + /* Writing to L2 does not use addr as input, as the table index + * is derived from key fields. + */ + if (table == RTL8365MB_TABLE_L2) + addr_as_input = false; + } + + /* To prevent concurrent access to the look-up tables, take the regmap + * lock manually and access via the map_nolock regmap. + */ + mutex_lock(&priv->map_lock); + + /* Protect from a busy table access (i.e. previous access timeouts) */ + ret = rtl8365mb_table_poll_busy(priv); + if (ret) + goto out; + + /* Write entry data if writing to the table (or L2_METHOD_MAC) */ + if (write_data) { + /* bulk write data up to 9th word */ + ret = regmap_bulk_write(priv->map_nolock, + RTL8365MB_TABLE_WRITE_BASE, + data, + min_t(size_t, size, + RTL8365MB_TABLE_ENTRY_MAX_SIZE - + 1)); + if (ret) + goto out; + + /* 10th register uses only 4 least significant bits */ + if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { + val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK, + data[size - 1]); + ret = regmap_update_bits(priv->map_nolock, + RTL8365MB_TABLE_WRITE_10TH_REG, + RTL8365MB_TABLE_10TH_DATA_MASK, + val); + } + + if (ret) + goto out; + } + + /* Write address (if needed) */ + if (addr_as_input) { + ret = regmap_write(priv->map_nolock, + RTL8365MB_TABLE_ACCESS_ADDR_REG, + FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, + *addr)); + if (ret) + goto out; + } + + /* Execute */ + ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd); + if (ret) + goto out; + + /* Poll for completion */ + ret = rtl8365mb_table_poll_busy(priv); + if (ret) + goto out; + + /* For both reads and writes to the L2 table, check status */ + if (table == RTL8365MB_TABLE_L2) { + ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG, + &val); + if (ret) + goto out; + + /* Did the query find an entry? */ + hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val); + if (!hit) { + ret = -ENOENT; + goto out; + } + + /* If so, extract the address */ + *addr = 0; + *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val); + *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val) + << 11; + /* only set if it is a L3 address */ + *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val) + << 12; + } + + /* Finally, get the table entry if we were reading */ + if (op == RTL8365MB_TABLE_OP_READ) { + ret = regmap_bulk_read(priv->map_nolock, + RTL8365MB_TABLE_READ_BASE, + data, size); + if (ret) + goto out; + + /* For the biggest table entries, the uppermost table + * entry register has space for only one nibble. Mask + * out the remainder bits. Empirically I saw nothing + * wrong with omitting this mask, but it may prevent + * unwanted behaviour. FYI. + */ + if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { + val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK, + data[size - 1]); + data[size - 1] = val; + } + } + +out: + mutex_unlock(&priv->map_lock); + + return ret; +} diff --git a/drivers/net/dsa/realtek/rtl8365mb_table.h b/drivers/net/dsa/realtek/rtl8365mb_table.h new file mode 100644 index 000000000000..41280eb6f8b0 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_table.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Look-up table query interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin Šipraga + */ + +#ifndef _REALTEK_RTL8365MB_TABLE_H +#define _REALTEK_RTL8365MB_TABLE_H + +#include +#include + +#include "realtek.h" + +#define RTL8365MB_TABLE_ENTRY_MAX_SIZE 10 + +/* + * enum rtl8365mb_table - available switch tables + * @RTL8365MB_TABLE_ACL_RULE: ACL rules + * @RTL8365MB_TABLE_ACL_ACTION: ACL actions + * @RTL8365MB_TABLE_CVLAN: VLAN4k configurations + * @RTL8365MB_TABLE_L2: filtering database (2K hash table) + * @RTL8365MB_TABLE_IGMP_GROUP: IGMP group database (readonly) + * + * NOTE: Don't change the enum values. They must concur with the field + * described by @RTL8365MB_TABLE_CTRL_TABLE_MASK. + */ +enum rtl8365mb_table { + RTL8365MB_TABLE_ACL_RULE = 1, + RTL8365MB_TABLE_ACL_ACTION = 2, + RTL8365MB_TABLE_CVLAN = 3, + RTL8365MB_TABLE_L2 = 4, + RTL8365MB_TABLE_IGMP_GROUP = 5, +}; + +/* + * enum rtl8365mb_table_op - table query operation + * @RTL8365MB_TABLE_OP_READ: read an entry from the target table + * @RTL8365MB_TABLE_OP_WRITE: write an entry to the target table + * + * NOTE: Don't change the enum values. They must concur with the field + * described by @RTL8365MB_TABLE_CTRL_OP_MASK. + */ +enum rtl8365mb_table_op { + RTL8365MB_TABLE_OP_READ = 0, + RTL8365MB_TABLE_OP_WRITE = 1, +}; + +/* + * enum rtl8365mb_table_l2_method - look-up method for read queries of L2 table + * @RTL8365MB_TABLE_L2_METHOD_MAC: look-up by source MAC address and FID (or + * VID) + * @RTL8365MB_TABLE_L2_METHOD_ADDR: look-up by entry address + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: look-up next entry starting from the + * supplied address + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: same as ADDR_NEXT but search only + * unicast addresses + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: same as ADDR_NEXT but search only + * multicast addresses + * @RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: same as ADDR_NEXT_UC but + * search only entries with matching source port + * + * NOTE: Don't change the enum values. They must concur with the field + * described by @RTL8365MB_TABLE_CTRL_METHOD_MASK + */ +enum rtl8365mb_table_l2_method { + RTL8365MB_TABLE_L2_METHOD_MAC = 0, + RTL8365MB_TABLE_L2_METHOD_ADDR = 1, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT = 2, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC = 3, + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC = 4, + /* + * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L3 = 5, + * RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC_L2L3 = 6, + */ + RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT = 7, +}; + +/* + * rtl8365mb_table_query() - read from or write to a switch table + * @priv: driver context + * @table: target table, see &enum rtl8365mb_table + * @op: read or write operation, see &enum rtl8365mb_table_op + * @addr: table address. For indexed tables, this selects the entry to access. + * For L2 read queries, it is ignored as input for MAC-based lookup + * methods and used as input for address-based lookup methods. On + * successful L2 queries, it is updated with the matched entry address. + * @method: L2 table lookup method, see &enum rtl8365mb_table_l2_method. + * Ignored for non-L2 tables. + * @port: for L2 read queries using method + * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, restrict the search + * to entries associated with this source port. Ignored otherwise. + * @data: data buffer used to read from or write to the table. For L2 MAC + * lookups, this buffer provides the lookup key and receives the + * matched entry contents on success. + * @size: size of @data in 16-bit words. The caller must ensure that @size + * matches the target table's entry size and does not exceed + * RTL8365MB_TABLE_ENTRY_MAX_SIZE. + * + * This function provides unified access to the internal tables of the switch. + * All tables except the L2 table are simple indexed tables, where @addr + * selects the entry and @op determines whether the access is a read or a + * write operation. + * + * The content of @data is used as input when writing to tables or when + * specifying the lookup key for L2 MAC searches, and as output for all + * successful read operations. It remains unchanged during write operations or + * failed read operations that return %-ENOENT. For other errors during read + * operations, it is undefined. + * + * The L2 table is a hash table and supports multiple lookup methods. For + * %RTL8365MB_TABLE_L2_METHOD_MAC, an entry is searched based on the MAC + * address and FID/VID fields provided in @data, using the same format as + * an L2 table entry. Address-based methods either read a specific entry + * (%RTL8365MB_TABLE_L2_METHOD_ADDR) or iterate over valid entries starting + * from @addr (%RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT and variants). When using + * %RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT, only entries associated with + * the specified @port are considered. + * + * On successful L2 operations, @addr is updated with the matched table address + * or allocated entry address. If no matching entry is found, or if an L2 write + * operation fails (e.g., due to a full table during addition or a missing entry + * during deletion), %-ENOENT is returned and @addr remains unchanged. It is the + * caller's responsibility to map the returned error to the appropriate + * semantic error. + * + * @size must match the size of the target table entry, expressed in 16-bit + * words. + * + * Return: 0 on success, or a negative error code on failure. + */ +int rtl8365mb_table_query(struct realtek_priv *priv, + enum rtl8365mb_table table, + enum rtl8365mb_table_op op, u16 *addr, + enum rtl8365mb_table_l2_method method, + u16 port, u16 *data, size_t size); + +#endif /* _REALTEK_RTL8365MB_TABLE_H */ diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c new file mode 100644 index 000000000000..64dd8d90a5ee --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c @@ -0,0 +1,944 @@ +// 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; +} diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h new file mode 100644 index 000000000000..27a526b61873 --- /dev/null +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* VLAN configuration interface for the rtl8365mb switch family + * + * Copyright (C) 2022 Alvin Šipraga + * + */ + +#ifndef _REALTEK_RTL8365MB_VLAN_H +#define _REALTEK_RTL8365MB_VLAN_H + +#include + +#include "realtek.h" + +enum rtl8365mb_frame_ingress { + RTL8365MB_FRAME_TYPE_ANY_FRAME = 0, + RTL8365MB_FRAME_TYPE_TAGGED_ONLY, + RTL8365MB_FRAME_TYPE_UNTAGGED_ONLY, +}; + +int rtl8365mb_vlan_port_get_pvid(struct realtek_priv *priv, int port, + u16 *pvid); +int +rtl8365mb_vlan_port_get_framefilter(struct realtek_priv *priv, + int port, + enum rtl8365mb_frame_ingress *frame_type); +int +rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv, + int port, + enum rtl8365mb_frame_ingress frame_type); +int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack); +int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan); +int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid, + struct netlink_ext_ack *extack); +int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid); +#endif /* _REALTEK_RTL8365MB_VLAN_H */ diff --git a/drivers/net/dsa/realtek/rtl83xx.c b/drivers/net/dsa/realtek/rtl83xx.c index 2b9bd4462714..71124ecca92f 100644 --- a/drivers/net/dsa/realtek/rtl83xx.c +++ b/drivers/net/dsa/realtek/rtl83xx.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include "realtek.h" #include "rtl83xx.h" @@ -155,6 +157,8 @@ rtl83xx_probe(struct device *dev, return ERR_PTR(-ENOMEM); mutex_init(&priv->map_lock); + mutex_init(&priv->vlan_lock); + mutex_init(&priv->l2_lock); rc.lock_arg = priv; priv->map = devm_regmap_init(dev, NULL, priv, &rc); @@ -325,6 +329,565 @@ void rtl83xx_reset_deassert(struct realtek_priv *priv) gpiod_set_value(priv->reset, false); } +/** + * rtl83xx_port_bridge_join() - join a port to a bridge + * @ds: DSA switch instance + * @port: port index + * @bridge: bridge being joined + * @tx_forward_offload: if the switch can offload TX forwarding + * @extack: netlink extended ack for reporting errors + * + * This function handles joining a port to a bridge. It updates the port + * isolation masks and EFID. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_forward_offload, + struct netlink_ext_ack *extack) +{ + struct realtek_priv *priv = ds->priv; + struct dsa_port *dp; + u32 mask = 0; + int ret; + + if (!priv->ops->port_add_isolation) + return -EOPNOTSUPP; + + if (!priv->ops->port_set_learning) + return -EOPNOTSUPP; + + dev_dbg(priv->dev, "bridge %d join port %d\n", bridge.num, port); + + /* Add this port to the isolation group of every other port + * offloading this bridge. + */ + dsa_switch_for_each_user_port(dp, ds) { + /* Handle this port after */ + if (dp->index == port) + continue; + + /* Skip ports that are not in this bridge */ + if (!dsa_port_offloads_bridge(dp, &bridge)) + continue; + + ret = priv->ops->port_add_isolation(priv, dp->index, BIT(port)); + if (ret) + goto undo_isolation; + + mask |= BIT(dp->index); + } + + /* If we support cascade switches, it should also include the + * downstream DSA ports to the isolation group. + */ + + /* Add those ports to the isolation group of this port */ + ret = priv->ops->port_add_isolation(priv, port, mask); + if (ret) + goto undo_isolation; + + /* Use the bridge number as the EFID for this port */ + if (priv->ops->port_set_efid) { + ret = priv->ops->port_set_efid(priv, port, bridge.num); + if (ret) + goto undo_self_isolation; + } + + ret = priv->ops->port_set_learning(priv, port, true); + if (ret) + goto undo_efid; + + return 0; + +undo_efid: + if (priv->ops->port_set_efid) + priv->ops->port_set_efid(priv, port, 0); + +undo_self_isolation: + priv->ops->port_remove_isolation(priv, port, mask); + +undo_isolation: + dsa_switch_for_each_port(dp, ds) { + if (mask & BIT(dp->index)) + priv->ops->port_remove_isolation(priv, dp->index, + BIT(port)); + } + + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_join, "REALTEK_DSA"); + +/** + * rtl83xx_port_bridge_leave() - leave a bridge + * @ds: DSA switch instance + * @port: port index + * @bridge: bridge being left + * + * This function handles removing a port from a bridge. It updates the port + * isolation masks and EFID. + * + * Context: Can sleep. + * Return: nothing + */ +void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge) +{ + struct realtek_priv *priv = ds->priv; + struct dsa_port *dp; + u32 mask = 0; + int ret; + + if (!priv->ops->port_remove_isolation) + return; + + if (!priv->ops->port_set_learning) + return; + + dev_dbg(priv->dev, "bridge %d leave port %d\n", bridge.num, port); + + /* Remove this port from the isolation group of every other + * port offloading this bridge. + */ + dsa_switch_for_each_user_port(dp, ds) { + /* Handle this port after */ + if (dp->index == port) + continue; + + /* Skip ports that are not in this bridge */ + if (!dsa_port_offloads_bridge(dp, &bridge)) + continue; + + ret = priv->ops->port_remove_isolation(priv, dp->index, + BIT(port)); + if (ret) + dev_err(priv->dev, + "failed to isolate port %d from port %d: %pe\n", + port, dp->index, ERR_PTR(ret)); + + mask |= BIT(dp->index); + } + + /* If we support cascade switches, it should also exclude the + * downstream DSA ports from the isolation group. + */ + + ret = priv->ops->port_set_learning(priv, port, false); + if (ret) + dev_err(priv->dev, + "failed to disable learning on port %d: %pe\n", + port, ERR_PTR(ret)); + + /* Remove those ports from the isolation group of this port */ + ret = priv->ops->port_remove_isolation(priv, port, mask); + if (ret) + dev_err(priv->dev, + "failed to remove isolation mask from port %d: %pe\n", + port, ERR_PTR(ret)); + + /* Revert to the default EFID 0 for standalone mode */ + if (priv->ops->port_set_efid) { + ret = priv->ops->port_set_efid(priv, port, 0); + if (ret) + dev_err(priv->dev, + "failed to clear EFID on port %d: %pe\n", + port, ERR_PTR(ret)); + } +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_leave, "REALTEK_DSA"); + +/** + * rtl83xx_port_fast_age() - flush dynamic FDB entries learned on a port + * @ds: DSA switch instance + * @port: port index + * + * This function requests the switch to age out dynamic FDB entries learned on + * @port. + * + * Context: Can sleep. + * Return: Nothing. + */ +void rtl83xx_port_fast_age(struct dsa_switch *ds, int port) +{ + struct realtek_priv *priv = ds->priv; + int ret; + + if (!priv->ops->l2_flush) { + dev_warn_once(priv->dev, "l2_flush op not defined\n"); + return; + } + + dev_dbg(priv->dev, "fast_age port %d\n", port); + + mutex_lock(&priv->l2_lock); + ret = priv->ops->l2_flush(priv, port, 0); + mutex_unlock(&priv->l2_lock); + if (ret) + dev_err(priv->dev, "failed to fast age on port %d: %d\n", port, + ret); +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fast_age, "REALTEK_DSA"); + +/** + * rtl83xx_port_fdb_add() - add a static FDB entry to a port database + * @ds: DSA switch instance + * @port: port index + * @addr: MAC address to add + * @vid: VLAN ID associated with @addr + * @db: database where the entry should be added + * + * This function adds a static unicast FDB entry to the standalone port + * database or to a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct realtek_priv *priv = ds->priv; + int efid; + int ret; + + if (is_multicast_ether_addr(addr)) + return -EOPNOTSUPP; + + if (!priv->ops->l2_add_uc) + return -EOPNOTSUPP; + + if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + /* Bridge ports use bridge.num as EFID, while standalone ports use + * EFID 0. FDB entries for the CPU port follow the bridge EFID due + * to assisted learning. + */ + efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret = priv->ops->l2_add_uc(priv, port, addr, efid, vid); + + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "fdb_add ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_add, "REALTEK_DSA"); + +/** + * rtl83xx_port_fdb_del() - delete a static FDB entry from a port database + * @ds: DSA switch instance + * @port: port index + * @addr: MAC address to delete + * @vid: VLAN ID associated with @addr + * @db: database where the entry should be removed + * + * This function deletes a static unicast FDB entry from the standalone port + * database or from a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct realtek_priv *priv = ds->priv; + int efid; + int ret; + + if (is_multicast_ether_addr(addr)) + return -EOPNOTSUPP; + + if (!priv->ops->l2_del_uc) + return -EOPNOTSUPP; + + if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + /* + * DSA_DB_BRIDGE ports use bridge number [1..N] as EFID, while + * DSA_DB_PORT use the default EFID (0), not used by any bridge. + */ + efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret = priv->ops->l2_del_uc(priv, port, addr, efid, vid); + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "fdb_del ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_del, "REALTEK_DSA"); + +/** + * rtl83xx_port_fdb_dump() - iterate over FDB entries associated with a port + * @ds: DSA switch instance + * @port: port index + * @cb: callback invoked for each entry + * @data: opaque pointer passed to @cb + * + * This function walks the unicast FDB entries associated with @port and calls + * @cb for each matching entry. + * + * Context: Can sleep. + * Return: 0 on success, or negative value for failure. + */ +int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct realtek_fdb_entry entry = { 0 }; + struct realtek_priv *priv = ds->priv; + u16 start_addr, addr = 0; + int ret = 0; + + if (!priv->ops->l2_get_next_uc) + return -EOPNOTSUPP; + + mutex_lock(&priv->l2_lock); + while (true) { + start_addr = addr; + + dev_dbg(priv->dev, "l2_get_next_uc, addr:%d, port:%d\n", + addr, port); + ret = priv->ops->l2_get_next_uc(priv, &addr, port, &entry); + dev_dbg(priv->dev, + "%s addr:%d mac:%pM vid:%d static:%d ret:%pe\n", + __func__, addr, entry.mac_addr, entry.vid, + entry.is_static, ERR_PTR(ret)); + + if (ret == -ENOENT) { + /* If the table is empty, returns without errors. Note + * that the l2_get_next_uc overflow to the first match + * when it reaches the end of the table. + */ + ret = 0; + break; + } + + if (ret) + break; + + /* When the addr returned is before the requested one, it + * indicates that we reached the end. + */ + if (addr < start_addr) + break; + + ret = cb(entry.mac_addr, entry.vid, entry.is_static, data); + if (ret) + break; + + addr++; + } + mutex_unlock(&priv->l2_lock); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_fdb_dump, "REALTEK_DSA"); + +/** + * rtl83xx_port_mdb_add() - add a multicast database entry to a port database + * @ds: DSA switch instance + * @port: port index + * @mdb: multicast database entry to add + * @db: database where the entry should be added + * + * This function adds a multicast database entry to the standalone port + * database or to a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + struct realtek_priv *priv = ds->priv; + const unsigned char *addr = mdb->addr; + u16 vid = mdb->vid; + int efid; + int ret; + + if (!priv->ops->l2_add_mc) + return -EOPNOTSUPP; + + if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + /* EFID is not used by hardware MDB entries; debugging only */ + efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret = priv->ops->l2_add_mc(priv, port, addr, vid); + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "mdb_add ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_add, "REALTEK_DSA"); + +/** + * rtl83xx_port_mdb_del() - delete a multicast database entry from a port + * database + * @ds: DSA switch instance + * @port: port index + * @mdb: multicast database entry to delete + * @db: database where the entry should be removed + * + * This function deletes a multicast database entry from the standalone port + * database or from a bridge database. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + struct realtek_priv *priv = ds->priv; + const unsigned char *addr = mdb->addr; + u16 vid = mdb->vid; + int efid; + int ret; + + if (!priv->ops->l2_del_mc) + return -EOPNOTSUPP; + + if (db.type != DSA_DB_PORT && db.type != DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + /* EFID is not used by hardware MDB entries; debugging only */ + efid = db.type == DSA_DB_BRIDGE ? db.bridge.num : 0; + + dev_dbg(priv->dev, "%s: port:%d addr:%pM efid:%d vid:%d dbtype:%d\n", + __func__, port, addr, efid, vid, db.type); + + mutex_lock(&priv->l2_lock); + ret = priv->ops->l2_del_mc(priv, port, addr, vid); + mutex_unlock(&priv->l2_lock); + + if (ret) + dev_err(priv->dev, "mdb_del ERROR %pe\n", ERR_PTR(ret)); + return ret; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_mdb_del, "REALTEK_DSA"); + +/** + * rtl83xx_port_bridge_flags() - set port bridge flags + * @ds: DSA switch instance + * @port: port index + * @flags: bridge port flags + * @extack: netlink extended ack for reporting errors + * + * This function handles setting bridge port flags like learning and flooding. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + struct realtek_priv *priv = ds->priv; + bool enable; + int ret; + + if (flags.mask & BR_LEARNING) { + if (!priv->ops->port_set_learning) + return -EOPNOTSUPP; + + enable = !!(flags.val & BR_LEARNING); + ret = priv->ops->port_set_learning(priv, port, enable); + if (ret) + return ret; + } + + if (flags.mask & BR_FLOOD) { + if (!priv->ops->port_set_ucast_flood) + return -EOPNOTSUPP; + + enable = !!(flags.val & BR_FLOOD); + ret = priv->ops->port_set_ucast_flood(priv, port, enable); + if (ret) + return ret; + } + + if (flags.mask & BR_MCAST_FLOOD) { + if (!priv->ops->port_set_mcast_flood) + return -EOPNOTSUPP; + + enable = !!(flags.val & BR_MCAST_FLOOD); + ret = priv->ops->port_set_mcast_flood(priv, port, enable); + if (ret) + return ret; + } + + if (flags.mask & BR_BCAST_FLOOD) { + if (!priv->ops->port_set_bcast_flood) + return -EOPNOTSUPP; + + enable = !!(flags.val & BR_BCAST_FLOOD); + ret = priv->ops->port_set_bcast_flood(priv, port, enable); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_port_bridge_flags, "REALTEK_DSA"); + +/** + * rtl83xx_setup_port_flood_control() - setup default flood control for a port + * @priv: realtek_priv pointer + * @port: port index + * + * This function enables flooding for a given port. + * + * Context: Can sleep. + * Return: 0 on success, negative value for failure. + */ +int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port) +{ + int ret; + + if (priv->ops->port_set_ucast_flood) { + ret = priv->ops->port_set_ucast_flood(priv, port, true); + if (ret) + return ret; + } + + if (priv->ops->port_set_mcast_flood) { + ret = priv->ops->port_set_mcast_flood(priv, port, true); + if (ret) + return ret; + } + + if (priv->ops->port_set_bcast_flood) { + ret = priv->ops->port_set_bcast_flood(priv, port, true); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(rtl83xx_setup_port_flood_control, "REALTEK_DSA"); + MODULE_AUTHOR("Luiz Angelo Daros de Luca "); MODULE_AUTHOR("Linus Walleij "); MODULE_DESCRIPTION("Realtek DSA switches common module"); diff --git a/drivers/net/dsa/realtek/rtl83xx.h b/drivers/net/dsa/realtek/rtl83xx.h index c8a0ff8fd75e..d86447121276 100644 --- a/drivers/net/dsa/realtek/rtl83xx.h +++ b/drivers/net/dsa/realtek/rtl83xx.h @@ -21,4 +21,31 @@ void rtl83xx_remove(struct realtek_priv *priv); void rtl83xx_reset_assert(struct realtek_priv *priv); void rtl83xx_reset_deassert(struct realtek_priv *priv); +int rtl83xx_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_forward_offload, + struct netlink_ext_ack *extack); +void rtl83xx_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge); +int rtl83xx_port_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack); +int rtl83xx_setup_port_flood_control(struct realtek_priv *priv, int port); + +void rtl83xx_port_fast_age(struct dsa_switch *ds, int port); +int rtl83xx_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db); +int rtl83xx_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db); +int rtl83xx_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data); +int rtl83xx_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db); +int rtl83xx_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db); + #endif /* _RTL83XX_H */