Skip to content

Commit e5d1f89

Browse files
vladimirolteankuba-moo
authored andcommitted
net: mscc: ocelot: support L2 multicast entries
There is one main difference in mscc_ocelot between IP multicast and L2 multicast. With IP multicast, destination ports are encoded into the upper bytes of the multicast MAC address. Example: to deliver the address 01:00:5E:11:22:33 to ports 3, 8, and 9, one would need to program the address of 00:03:08:11:22:33 into hardware. Whereas for L2 multicast, the MAC table entry points to a Port Group ID (PGID), and that PGID contains the port mask that the packet will be forwarded to. As to why it is this way, no clue. My guess is that not all port combinations can be supported simultaneously with the limited number of PGIDs, and this was somehow an issue for IP multicast but not for L2 multicast. Anyway. Prior to this change, the raw L2 multicast code was bogus, due to the fact that there wasn't really any way to test it using the bridge code. There were 2 issues: - A multicast PGID was allocated for each MDB entry, but it wasn't in fact programmed to hardware. It was dummy. - In fact we don't want to reserve a multicast PGID for every single MDB entry. That would be odd because we can only have ~60 PGIDs, but thousands of MDB entries. So instead, we want to reserve a multicast PGID for every single port combination for multicast traffic. And since we can have 2 (or more) MDB entries delivered to the same port group (and therefore PGID), we need to reference-count the PGIDs. Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1 parent bb8d53f commit e5d1f89

File tree

3 files changed

+100
-26
lines changed

3 files changed

+100
-26
lines changed

drivers/net/ethernet/mscc/ocelot.c

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -961,10 +961,37 @@ static enum macaccess_entry_type ocelot_classify_mdb(const unsigned char *addr)
961961
return ENTRYTYPE_LOCKED;
962962
}
963963

964-
static int ocelot_mdb_get_pgid(struct ocelot *ocelot,
965-
const struct ocelot_multicast *mc)
964+
static struct ocelot_pgid *ocelot_pgid_alloc(struct ocelot *ocelot, int index,
965+
unsigned long ports)
966966
{
967-
int pgid;
967+
struct ocelot_pgid *pgid;
968+
969+
pgid = kzalloc(sizeof(*pgid), GFP_KERNEL);
970+
if (!pgid)
971+
return ERR_PTR(-ENOMEM);
972+
973+
pgid->ports = ports;
974+
pgid->index = index;
975+
refcount_set(&pgid->refcount, 1);
976+
list_add_tail(&pgid->list, &ocelot->pgids);
977+
978+
return pgid;
979+
}
980+
981+
static void ocelot_pgid_free(struct ocelot *ocelot, struct ocelot_pgid *pgid)
982+
{
983+
if (!refcount_dec_and_test(&pgid->refcount))
984+
return;
985+
986+
list_del(&pgid->list);
987+
kfree(pgid);
988+
}
989+
990+
static struct ocelot_pgid *ocelot_mdb_get_pgid(struct ocelot *ocelot,
991+
const struct ocelot_multicast *mc)
992+
{
993+
struct ocelot_pgid *pgid;
994+
int index;
968995

969996
/* According to VSC7514 datasheet 3.9.1.5 IPv4 Multicast Entries and
970997
* 3.9.1.6 IPv6 Multicast Entries, "Instead of a lookup in the
@@ -973,24 +1000,34 @@ static int ocelot_mdb_get_pgid(struct ocelot *ocelot,
9731000
*/
9741001
if (mc->entry_type == ENTRYTYPE_MACv4 ||
9751002
mc->entry_type == ENTRYTYPE_MACv6)
976-
return 0;
1003+
return ocelot_pgid_alloc(ocelot, 0, mc->ports);
1004+
1005+
list_for_each_entry(pgid, &ocelot->pgids, list) {
1006+
/* When searching for a nonreserved multicast PGID, ignore the
1007+
* dummy PGID of zero that we have for MACv4/MACv6 entries
1008+
*/
1009+
if (pgid->index && pgid->ports == mc->ports) {
1010+
refcount_inc(&pgid->refcount);
1011+
return pgid;
1012+
}
1013+
}
9771014

978-
for_each_nonreserved_multicast_dest_pgid(ocelot, pgid) {
979-
struct ocelot_multicast *mc;
1015+
/* Search for a free index in the nonreserved multicast PGID area */
1016+
for_each_nonreserved_multicast_dest_pgid(ocelot, index) {
9801017
bool used = false;
9811018

982-
list_for_each_entry(mc, &ocelot->multicast, list) {
983-
if (mc->pgid == pgid) {
1019+
list_for_each_entry(pgid, &ocelot->pgids, list) {
1020+
if (pgid->index == index) {
9841021
used = true;
9851022
break;
9861023
}
9871024
}
9881025

9891026
if (!used)
990-
return pgid;
1027+
return ocelot_pgid_alloc(ocelot, index, mc->ports);
9911028
}
9921029

993-
return -1;
1030+
return ERR_PTR(-ENOSPC);
9941031
}
9951032

9961033
static void ocelot_encode_ports_to_mdb(unsigned char *addr,
@@ -1014,6 +1051,7 @@ int ocelot_port_mdb_add(struct ocelot *ocelot, int port,
10141051
struct ocelot_port *ocelot_port = ocelot->ports[port];
10151052
unsigned char addr[ETH_ALEN];
10161053
struct ocelot_multicast *mc;
1054+
struct ocelot_pgid *pgid;
10171055
u16 vid = mdb->vid;
10181056

10191057
if (port == ocelot->npi)
@@ -1025,8 +1063,6 @@ int ocelot_port_mdb_add(struct ocelot *ocelot, int port,
10251063
mc = ocelot_multicast_get(ocelot, mdb->addr, vid);
10261064
if (!mc) {
10271065
/* New entry */
1028-
int pgid;
1029-
10301066
mc = devm_kzalloc(ocelot->dev, sizeof(*mc), GFP_KERNEL);
10311067
if (!mc)
10321068
return -ENOMEM;
@@ -1035,27 +1071,36 @@ int ocelot_port_mdb_add(struct ocelot *ocelot, int port,
10351071
ether_addr_copy(mc->addr, mdb->addr);
10361072
mc->vid = vid;
10371073

1038-
pgid = ocelot_mdb_get_pgid(ocelot, mc);
1039-
1040-
if (pgid < 0) {
1041-
dev_err(ocelot->dev,
1042-
"No more PGIDs available for mdb %pM vid %d\n",
1043-
mdb->addr, vid);
1044-
return -ENOSPC;
1045-
}
1046-
1047-
mc->pgid = pgid;
1048-
10491074
list_add_tail(&mc->list, &ocelot->multicast);
10501075
} else {
1076+
/* Existing entry. Clean up the current port mask from
1077+
* hardware now, because we'll be modifying it.
1078+
*/
1079+
ocelot_pgid_free(ocelot, mc->pgid);
10511080
ocelot_encode_ports_to_mdb(addr, mc);
10521081
ocelot_mact_forget(ocelot, addr, vid);
10531082
}
10541083

10551084
mc->ports |= BIT(port);
1085+
1086+
pgid = ocelot_mdb_get_pgid(ocelot, mc);
1087+
if (IS_ERR(pgid)) {
1088+
dev_err(ocelot->dev,
1089+
"Cannot allocate PGID for mdb %pM vid %d\n",
1090+
mc->addr, mc->vid);
1091+
devm_kfree(ocelot->dev, mc);
1092+
return PTR_ERR(pgid);
1093+
}
1094+
mc->pgid = pgid;
1095+
10561096
ocelot_encode_ports_to_mdb(addr, mc);
10571097

1058-
return ocelot_mact_learn(ocelot, mc->pgid, addr, vid,
1098+
if (mc->entry_type != ENTRYTYPE_MACv4 &&
1099+
mc->entry_type != ENTRYTYPE_MACv6)
1100+
ocelot_write_rix(ocelot, pgid->ports, ANA_PGID_PGID,
1101+
pgid->index);
1102+
1103+
return ocelot_mact_learn(ocelot, pgid->index, addr, vid,
10591104
mc->entry_type);
10601105
}
10611106
EXPORT_SYMBOL(ocelot_port_mdb_add);
@@ -1066,6 +1111,7 @@ int ocelot_port_mdb_del(struct ocelot *ocelot, int port,
10661111
struct ocelot_port *ocelot_port = ocelot->ports[port];
10671112
unsigned char addr[ETH_ALEN];
10681113
struct ocelot_multicast *mc;
1114+
struct ocelot_pgid *pgid;
10691115
u16 vid = mdb->vid;
10701116

10711117
if (port == ocelot->npi)
@@ -1081,16 +1127,28 @@ int ocelot_port_mdb_del(struct ocelot *ocelot, int port,
10811127
ocelot_encode_ports_to_mdb(addr, mc);
10821128
ocelot_mact_forget(ocelot, addr, vid);
10831129

1130+
ocelot_pgid_free(ocelot, mc->pgid);
10841131
mc->ports &= ~BIT(port);
10851132
if (!mc->ports) {
10861133
list_del(&mc->list);
10871134
devm_kfree(ocelot->dev, mc);
10881135
return 0;
10891136
}
10901137

1138+
/* We have a PGID with fewer ports now */
1139+
pgid = ocelot_mdb_get_pgid(ocelot, mc);
1140+
if (IS_ERR(pgid))
1141+
return PTR_ERR(pgid);
1142+
mc->pgid = pgid;
1143+
10911144
ocelot_encode_ports_to_mdb(addr, mc);
10921145

1093-
return ocelot_mact_learn(ocelot, mc->pgid, addr, vid,
1146+
if (mc->entry_type != ENTRYTYPE_MACv4 &&
1147+
mc->entry_type != ENTRYTYPE_MACv6)
1148+
ocelot_write_rix(ocelot, pgid->ports, ANA_PGID_PGID,
1149+
pgid->index);
1150+
1151+
return ocelot_mact_learn(ocelot, pgid->index, addr, vid,
10941152
mc->entry_type);
10951153
}
10961154
EXPORT_SYMBOL(ocelot_port_mdb_del);
@@ -1449,6 +1507,7 @@ int ocelot_init(struct ocelot *ocelot)
14491507
return -ENOMEM;
14501508

14511509
INIT_LIST_HEAD(&ocelot->multicast);
1510+
INIT_LIST_HEAD(&ocelot->pgids);
14521511
ocelot_mact_init(ocelot);
14531512
ocelot_vlan_init(ocelot);
14541513
ocelot_vcap_init(ocelot);

drivers/net/ethernet/mscc/ocelot.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,27 @@ enum macaccess_entry_type {
7979
ENTRYTYPE_MACv6,
8080
};
8181

82+
/* A (PGID) port mask structure, encoding the 2^ocelot->num_phys_ports
83+
* possibilities of egress port masks for L2 multicast traffic.
84+
* For a switch with 9 user ports, there are 512 possible port masks, but the
85+
* hardware only has 46 individual PGIDs that it can forward multicast traffic
86+
* to. So we need a structure that maps the limited PGID indices to the port
87+
* destinations requested by the user for L2 multicast.
88+
*/
89+
struct ocelot_pgid {
90+
unsigned long ports;
91+
int index;
92+
refcount_t refcount;
93+
struct list_head list;
94+
};
95+
8296
struct ocelot_multicast {
8397
struct list_head list;
8498
enum macaccess_entry_type entry_type;
8599
unsigned char addr[ETH_ALEN];
86100
u16 vid;
87101
u16 ports;
88-
int pgid;
102+
struct ocelot_pgid *pgid;
89103
};
90104

91105
int ocelot_port_fdb_do_dump(const unsigned char *addr, u16 vid,

include/soc/mscc/ocelot.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ struct ocelot {
632632
u32 *lags;
633633

634634
struct list_head multicast;
635+
struct list_head pgids;
635636

636637
struct list_head dummy_rules;
637638
struct ocelot_vcap_block block[3];

0 commit comments

Comments
 (0)