diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h index 874d5126f1a4..d3fe124806ae 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h @@ -2131,6 +2131,26 @@ enum mxl862xx_pce_action_cross_state { MXL862XX_PCE_ACTION_CROSS_STATE_CROSS = 2, }; +/** + * enum mxl862xx_pce_action_learning - MAC learning action selector + * + * Controls source address learning for packets matching the rule. + * + * @MXL862XX_PCE_ACTION_LEARNING_DISABLE: Learning action is disabled + * @MXL862XX_PCE_ACTION_LEARNING_REGULAR: Enabled; learning follows the + * regular port and bridge configuration + * @MXL862XX_PCE_ACTION_LEARNING_FORCE_NOT: Enabled; the source address + * of matching packets is never learned + * @MXL862XX_PCE_ACTION_LEARNING_FORCE: Enabled; the source address of + * matching packets is always learned + */ +enum mxl862xx_pce_action_learning { + MXL862XX_PCE_ACTION_LEARNING_DISABLE = 0, + MXL862XX_PCE_ACTION_LEARNING_REGULAR = 1, + MXL862XX_PCE_ACTION_LEARNING_FORCE_NOT = 2, + MXL862XX_PCE_ACTION_LEARNING_FORCE = 3, +}; + /** * struct mxl862xx_pce_action - PCE rule action configuration * @@ -2161,9 +2181,8 @@ enum mxl862xx_pce_action_cross_state { * @traffic_class_action: Traffic class action selector * (0 = disable, 1 = regular CoS, 2 = alternative) * @snooping_type_action: IGMP snooping control selector - * @learning_action: MAC learning action selector - * (0 = disable, 1 = regular, 2 = force no learn, - * 3 = force learn) + * @learning_action: MAC learning action selector. + * See &enum mxl862xx_pce_action_learning * @irq_action: Interrupt action selector * (0 = disable, 1 = regular, 2 = generate interrupt) * @cross_state_action: Cross state action selector. diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c index 5bde382a8290..2f8ec42cbdb3 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -721,6 +721,9 @@ static void mxl862xx_fill_cpu_trap_action(struct dsa_switch *ds, int port, rule->action.cross_state_action = cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS); + rule->action.learning_action = + cpu_to_le32(MXL862XX_PCE_ACTION_LEARNING_FORCE_NOT); + rule->action.fid_enable = 1; rule->action.fid = priv->cpu_trap_fid; } @@ -1491,6 +1494,12 @@ static int mxl862xx_configure_sp_tag_proto(struct dsa_switch *ds, int port, * Per-port host flood control is implemented via egress sub-meters on * the VBP. * + * The bridge port map is written on every update rather than only at + * allocation time. It contains just the user port the VBP belongs to, + * and it is the path by which a CPU-originated frame reassigned onto + * the VBP reaches the wire, so it must never be left to the firmware + * to preserve across an unrelated field update. + * * This is intentionally separate from mxl862xx_set_bridge_port() because * the VBP and the physical bridge port are independent firmware entities: * host flood changes (deferred from atomic context) only need the VBP @@ -1510,8 +1519,10 @@ static int mxl862xx_set_cpu_vbp(struct dsa_switch *ds, int port) vbp_cfg.bridge_port_id = cpu_to_le16(p->bridge_port_cpu); vbp_cfg.mask = cpu_to_le32( MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER); vbp_cfg.bridge_id = cpu_to_le16(p->fid); + mxl862xx_fw_portmap_set_bit(vbp_cfg.bridge_port_map, port); for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) { idx = mxl862xx_flood_meters[i]; @@ -2361,15 +2372,24 @@ static int mxl862xx_mac_portmap_del(struct mxl862xx_priv *priv, * @ds: DSA switch * @addr: MAC address * @vid: VLAN ID - * @bridge: bridge whose members' VBPs to include + * @bridge: bridge the entry is scoped to (used to pick the FID) * * In tag_8021q mode, host FDB/MDB entries in a shared bridge FID must use - * portmap mode targeting ALL bridge members' virtual bridge ports (VBPs). - * The firmware ANDs the entry's portmap with each ingress port's - * bridge_port_map, which contains only that port's own VBP. This - * selects the correct VBP per ingress port, ensuring frames exit - * through the right egress EVLAN (which inserts the per-port management - * VID that identifies the source port to DSA on the CPU side). + * portmap mode targeting virtual bridge ports (VBPs). The firmware ANDs + * the entry's portmap with the ingress port's bridge_port_map, which + * contains only that port's own VBP, so listing every user port's VBP + * unconditionally still leaves exactly the ingress port's VBP after the + * intersection. That selects the correct egress EVLAN, which inserts the + * per-port management VID identifying the source port to DSA on the CPU + * side. + * + * Listing all user ports rather than the current bridge members keeps the + * entry correct without tracking membership changes, and avoids depending + * on every member's VBP having been allocated by the time the entry is + * installed. A port whose VBP is not yet allocated reads back as 0, and + * mxl862xx_fw_portmap_set_bit() would then set bit 0 -- a valid portmap + * bit belonging to another port, not a no-op -- corrupting the portmap of + * an entry the host depends on. Skip unallocated VBPs explicitly. */ static int mxl862xx_mac_add_host_bridge(struct dsa_switch *ds, const unsigned char *addr, u16 vid, @@ -2378,11 +2398,14 @@ static int mxl862xx_mac_add_host_bridge(struct dsa_switch *ds, __le16 add_map[MXL862XX_FW_PORTMAP_WORDS] = {}; struct mxl862xx_priv *priv = ds->priv; u16 fid = priv->bridges[bridge->num]; - struct dsa_port *member_dp; + struct dsa_port *dp; + u16 vbp; - dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev) - mxl862xx_fw_portmap_set_bit(add_map, - priv->ports[member_dp->index].bridge_port_cpu); + dsa_switch_for_each_user_port(dp, ds) { + vbp = priv->ports[dp->index].bridge_port_cpu; + if (vbp) + mxl862xx_fw_portmap_set_bit(add_map, vbp); + } return mxl862xx_mac_portmap_add(priv, addr, fid, vid, add_map); }