Skip to content

Commit

Permalink
net: dsa: felix: enable cut-through forwarding between ports by default
Browse files Browse the repository at this point in the history
The VSC9959 switch embedded within NXP LS1028A (and that version of
Ocelot switches only) supports cut-through forwarding - meaning it can
start the process of looking up the destination ports for a packet, and
forward towards those ports, before the entire packet has been received
(as opposed to the store-and-forward mode).

The up side is having lower forwarding latency for large packets. The
down side is that frames with FCS errors are forwarded instead of being
dropped. However, erroneous frames do not result in incorrect updates of
the FDB or incorrect policer updates, since these processes are deferred
inside the switch to the end of frame. Since the switch starts the
cut-through forwarding process after all packet headers (including IP,
if any) have been processed, packets with large headers and small
payload do not see the benefit of lower forwarding latency.

There are two cases that need special attention.

The first is when a packet is multicast (or flooded) to multiple
destinations, one of which doesn't have cut-through forwarding enabled.
The switch deals with this automatically by disabling cut-through
forwarding for the frame towards all destination ports.

The second is when a packet is forwarded from a port of lower link speed
towards a port of higher link speed. This is not handled by the hardware
and needs software intervention.

Since we practically need to update the cut-through forwarding domain
from paths that aren't serialized by the rtnl_mutex (phylink
mac_link_down/mac_link_up ops), this means we need to serialize physical
link events with user space updates of bonding/bridging domains.

Enabling cut-through forwarding is done per {egress port, traffic class}.
I don't see any reason why this would be a configurable option as long
as it works without issues, and there doesn't appear to be any user
space configuration tool to toggle this on/off, so this patch enables
cut-through forwarding on all eligible ports and traffic classes.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://lore.kernel.org/r/20211125125808.2383984-2-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Vladimir Oltean authored and Jakub Kicinski committed Nov 26, 2021
1 parent a8bd9fa commit 8abe197
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 11 deletions.
12 changes: 10 additions & 2 deletions drivers/net/dsa/ocelot/felix.c
Original file line number Diff line number Diff line change
Expand Up @@ -240,24 +240,32 @@ static int felix_tag_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid)
*/
static void felix_8021q_cpu_port_init(struct ocelot *ocelot, int port)
{
mutex_lock(&ocelot->fwd_domain_lock);

ocelot->ports[port]->is_dsa_8021q_cpu = true;
ocelot->npi = -1;

/* Overwrite PGID_CPU with the non-tagging port */
ocelot_write_rix(ocelot, BIT(port), ANA_PGID_PGID, PGID_CPU);

ocelot_apply_bridge_fwd_mask(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, true);

mutex_unlock(&ocelot->fwd_domain_lock);
}

static void felix_8021q_cpu_port_deinit(struct ocelot *ocelot, int port)
{
mutex_lock(&ocelot->fwd_domain_lock);

ocelot->ports[port]->is_dsa_8021q_cpu = false;

/* Restore PGID_CPU */
ocelot_write_rix(ocelot, BIT(ocelot->num_phys_ports), ANA_PGID_PGID,
PGID_CPU);

ocelot_apply_bridge_fwd_mask(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, true);

mutex_unlock(&ocelot->fwd_domain_lock);
}

/* Set up a VCAP IS2 rule for delivering PTP frames to the CPU port module.
Expand Down
75 changes: 75 additions & 0 deletions drivers/net/dsa/ocelot/felix_vsc9959.c
Original file line number Diff line number Diff line change
Expand Up @@ -2125,6 +2125,80 @@ static void vsc9959_psfp_init(struct ocelot *ocelot)
INIT_LIST_HEAD(&psfp->sgi_list);
}

/* When using cut-through forwarding and the egress port runs at a higher data
* rate than the ingress port, the packet currently under transmission would
* suffer an underrun since it would be transmitted faster than it is received.
* The Felix switch implementation of cut-through forwarding does not check in
* hardware whether this condition is satisfied or not, so we must restrict the
* list of ports that have cut-through forwarding enabled on egress to only be
* the ports operating at the lowest link speed within their respective
* forwarding domain.
*/
static void vsc9959_cut_through_fwd(struct ocelot *ocelot)
{
struct felix *felix = ocelot_to_felix(ocelot);
struct dsa_switch *ds = felix->ds;
int port, other_port;

lockdep_assert_held(&ocelot->fwd_domain_lock);

for (port = 0; port < ocelot->num_phys_ports; port++) {
struct ocelot_port *ocelot_port = ocelot->ports[port];
int min_speed = ocelot_port->speed;
unsigned long mask = 0;
u32 tmp, val = 0;

/* Disable cut-through on ports that are down */
if (ocelot_port->speed <= 0)
goto set;

if (dsa_is_cpu_port(ds, port)) {
/* Ocelot switches forward from the NPI port towards
* any port, regardless of it being in the NPI port's
* forwarding domain or not.
*/
mask = dsa_user_ports(ds);
} else {
mask = ocelot_get_bridge_fwd_mask(ocelot, port);
mask &= ~BIT(port);
if (ocelot->npi >= 0)
mask |= BIT(ocelot->npi);
else
mask |= ocelot_get_dsa_8021q_cpu_mask(ocelot);
}

/* Calculate the minimum link speed, among the ports that are
* up, of this source port's forwarding domain.
*/
for_each_set_bit(other_port, &mask, ocelot->num_phys_ports) {
struct ocelot_port *other_ocelot_port;

other_ocelot_port = ocelot->ports[other_port];
if (other_ocelot_port->speed <= 0)
continue;

if (min_speed > other_ocelot_port->speed)
min_speed = other_ocelot_port->speed;
}

/* Enable cut-through forwarding for all traffic classes. */
if (ocelot_port->speed == min_speed)
val = GENMASK(7, 0);

set:
tmp = ocelot_read_rix(ocelot, ANA_CUT_THRU_CFG, port);
if (tmp == val)
continue;

dev_dbg(ocelot->dev,
"port %d fwd mask 0x%lx speed %d min_speed %d, %s cut-through forwarding\n",
port, mask, ocelot_port->speed, min_speed,
val ? "enabling" : "disabling");

ocelot_write_rix(ocelot, val, ANA_CUT_THRU_CFG, port);
}
}

static const struct ocelot_ops vsc9959_ops = {
.reset = vsc9959_reset,
.wm_enc = vsc9959_wm_enc,
Expand All @@ -2136,6 +2210,7 @@ static const struct ocelot_ops vsc9959_ops = {
.psfp_filter_add = vsc9959_psfp_filter_add,
.psfp_filter_del = vsc9959_psfp_filter_del,
.psfp_stats_get = vsc9959_psfp_stats_get,
.cut_through_fwd = vsc9959_cut_through_fwd,
};

static const struct felix_info felix_info_vsc9959 = {
Expand Down
77 changes: 69 additions & 8 deletions drivers/net/ethernet/mscc/ocelot.c
Original file line number Diff line number Diff line change
Expand Up @@ -663,9 +663,17 @@ void ocelot_phylink_mac_link_down(struct ocelot *ocelot, int port,
struct ocelot_port *ocelot_port = ocelot->ports[port];
int err;

ocelot_port->speed = SPEED_UNKNOWN;

ocelot_port_rmwl(ocelot_port, 0, DEV_MAC_ENA_CFG_RX_ENA,
DEV_MAC_ENA_CFG);

if (ocelot->ops->cut_through_fwd) {
mutex_lock(&ocelot->fwd_domain_lock);
ocelot->ops->cut_through_fwd(ocelot);
mutex_unlock(&ocelot->fwd_domain_lock);
}

ocelot_fields_write(ocelot, port, QSYS_SWITCH_PORT_MODE_PORT_ENA, 0);

err = ocelot_port_flush(ocelot, port);
Expand Down Expand Up @@ -697,6 +705,8 @@ void ocelot_phylink_mac_link_up(struct ocelot *ocelot, int port,
int mac_speed, mode = 0;
u32 mac_fc_cfg;

ocelot_port->speed = speed;

/* The MAC might be integrated in systems where the MAC speed is fixed
* and it's the PCS who is performing the rate adaptation, so we have
* to write "1000Mbps" into the LINK_SPEED field of DEV_CLOCK_CFG
Expand Down Expand Up @@ -769,6 +779,15 @@ void ocelot_phylink_mac_link_up(struct ocelot *ocelot, int port,
ocelot_port_writel(ocelot_port, DEV_MAC_ENA_CFG_RX_ENA |
DEV_MAC_ENA_CFG_TX_ENA, DEV_MAC_ENA_CFG);

/* If the port supports cut-through forwarding, update the masks before
* enabling forwarding on the port.
*/
if (ocelot->ops->cut_through_fwd) {
mutex_lock(&ocelot->fwd_domain_lock);
ocelot->ops->cut_through_fwd(ocelot);
mutex_unlock(&ocelot->fwd_domain_lock);
}

/* Core: Enable port for frame transfer */
ocelot_fields_write(ocelot, port,
QSYS_SWITCH_PORT_MODE_PORT_ENA, 1);
Expand Down Expand Up @@ -1542,7 +1561,7 @@ static u32 ocelot_get_bond_mask(struct ocelot *ocelot, struct net_device *bond,
return mask;
}

static u32 ocelot_get_bridge_fwd_mask(struct ocelot *ocelot, int src_port)
u32 ocelot_get_bridge_fwd_mask(struct ocelot *ocelot, int src_port)
{
struct ocelot_port *ocelot_port = ocelot->ports[src_port];
const struct net_device *bridge;
Expand All @@ -1569,8 +1588,9 @@ static u32 ocelot_get_bridge_fwd_mask(struct ocelot *ocelot, int src_port)

return mask;
}
EXPORT_SYMBOL_GPL(ocelot_get_bridge_fwd_mask);

static u32 ocelot_get_dsa_8021q_cpu_mask(struct ocelot *ocelot)
u32 ocelot_get_dsa_8021q_cpu_mask(struct ocelot *ocelot)
{
u32 mask = 0;
int port;
Expand All @@ -1587,12 +1607,22 @@ static u32 ocelot_get_dsa_8021q_cpu_mask(struct ocelot *ocelot)

return mask;
}
EXPORT_SYMBOL_GPL(ocelot_get_dsa_8021q_cpu_mask);

void ocelot_apply_bridge_fwd_mask(struct ocelot *ocelot)
void ocelot_apply_bridge_fwd_mask(struct ocelot *ocelot, bool joining)
{
unsigned long cpu_fwd_mask;
int port;

lockdep_assert_held(&ocelot->fwd_domain_lock);

/* If cut-through forwarding is supported, update the masks before a
* port joins the forwarding domain, to avoid potential underruns if it
* has the highest speed from the new domain.
*/
if (joining && ocelot->ops->cut_through_fwd)
ocelot->ops->cut_through_fwd(ocelot);

/* If a DSA tag_8021q CPU exists, it needs to be included in the
* regular forwarding path of the front ports regardless of whether
* those are bridged or standalone.
Expand Down Expand Up @@ -1639,6 +1669,16 @@ void ocelot_apply_bridge_fwd_mask(struct ocelot *ocelot)

ocelot_write_rix(ocelot, mask, ANA_PGID_PGID, PGID_SRC + port);
}

/* If cut-through forwarding is supported and a port is leaving, there
* is a chance that cut-through was disabled on the other ports due to
* the port which is leaving (it has a higher link speed). We need to
* update the cut-through masks of the remaining ports no earlier than
* after the port has left, to prevent underruns from happening between
* the cut-through update and the forwarding domain update.
*/
if (!joining && ocelot->ops->cut_through_fwd)
ocelot->ops->cut_through_fwd(ocelot);
}
EXPORT_SYMBOL(ocelot_apply_bridge_fwd_mask);

Expand All @@ -1647,6 +1687,8 @@ void ocelot_bridge_stp_state_set(struct ocelot *ocelot, int port, u8 state)
struct ocelot_port *ocelot_port = ocelot->ports[port];
u32 learn_ena = 0;

mutex_lock(&ocelot->fwd_domain_lock);

ocelot_port->stp_state = state;

if ((state == BR_STATE_LEARNING || state == BR_STATE_FORWARDING) &&
Expand All @@ -1656,7 +1698,9 @@ void ocelot_bridge_stp_state_set(struct ocelot *ocelot, int port, u8 state)
ocelot_rmw_gix(ocelot, learn_ena, ANA_PORT_PORT_CFG_LEARN_ENA,
ANA_PORT_PORT_CFG, port);

ocelot_apply_bridge_fwd_mask(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, state == BR_STATE_FORWARDING);

mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL(ocelot_bridge_stp_state_set);

Expand Down Expand Up @@ -1886,9 +1930,13 @@ void ocelot_port_bridge_join(struct ocelot *ocelot, int port,
{
struct ocelot_port *ocelot_port = ocelot->ports[port];

mutex_lock(&ocelot->fwd_domain_lock);

ocelot_port->bridge = bridge;

ocelot_apply_bridge_fwd_mask(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, true);

mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL(ocelot_port_bridge_join);

Expand All @@ -1897,11 +1945,15 @@ void ocelot_port_bridge_leave(struct ocelot *ocelot, int port,
{
struct ocelot_port *ocelot_port = ocelot->ports[port];

mutex_lock(&ocelot->fwd_domain_lock);

ocelot_port->bridge = NULL;

ocelot_port_set_pvid(ocelot, port, NULL);
ocelot_port_manage_port_tag(ocelot, port);
ocelot_apply_bridge_fwd_mask(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, false);

mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL(ocelot_port_bridge_leave);

Expand Down Expand Up @@ -2023,24 +2075,32 @@ int ocelot_port_lag_join(struct ocelot *ocelot, int port,
if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
return -EOPNOTSUPP;

mutex_lock(&ocelot->fwd_domain_lock);

ocelot->ports[port]->bond = bond;

ocelot_setup_logical_port_ids(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, true);
ocelot_set_aggr_pgids(ocelot);

mutex_unlock(&ocelot->fwd_domain_lock);

return 0;
}
EXPORT_SYMBOL(ocelot_port_lag_join);

void ocelot_port_lag_leave(struct ocelot *ocelot, int port,
struct net_device *bond)
{
mutex_lock(&ocelot->fwd_domain_lock);

ocelot->ports[port]->bond = NULL;

ocelot_setup_logical_port_ids(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot);
ocelot_apply_bridge_fwd_mask(ocelot, false);
ocelot_set_aggr_pgids(ocelot);

mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL(ocelot_port_lag_leave);

Expand Down Expand Up @@ -2331,6 +2391,7 @@ int ocelot_init(struct ocelot *ocelot)
mutex_init(&ocelot->stats_lock);
mutex_init(&ocelot->ptp_lock);
mutex_init(&ocelot->mact_lock);
mutex_init(&ocelot->fwd_domain_lock);
spin_lock_init(&ocelot->ptp_clock_lock);
spin_lock_init(&ocelot->ts_id_lock);
snprintf(queue_name, sizeof(queue_name), "%s-stats",
Expand Down
9 changes: 8 additions & 1 deletion include/soc/mscc/ocelot.h
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ struct ocelot_ops {
int (*psfp_filter_del)(struct ocelot *ocelot, struct flow_cls_offload *f);
int (*psfp_stats_get)(struct ocelot *ocelot, struct flow_cls_offload *f,
struct flow_stats *stats);
void (*cut_through_fwd)(struct ocelot *ocelot);
};

struct ocelot_vcap_policer {
Expand Down Expand Up @@ -655,6 +656,8 @@ struct ocelot_port {

struct net_device *bridge;
u8 stp_state;

int speed;
};

struct ocelot {
Expand Down Expand Up @@ -712,6 +715,8 @@ struct ocelot {

/* Lock for serializing access to the MAC table */
struct mutex mact_lock;
/* Lock for serializing forwarding domain changes */
struct mutex fwd_domain_lock;

struct workqueue_struct *owq;

Expand Down Expand Up @@ -811,7 +816,9 @@ void ocelot_set_ageing_time(struct ocelot *ocelot, unsigned int msecs);
int ocelot_port_vlan_filtering(struct ocelot *ocelot, int port, bool enabled,
struct netlink_ext_ack *extack);
void ocelot_bridge_stp_state_set(struct ocelot *ocelot, int port, u8 state);
void ocelot_apply_bridge_fwd_mask(struct ocelot *ocelot);
u32 ocelot_get_dsa_8021q_cpu_mask(struct ocelot *ocelot);
u32 ocelot_get_bridge_fwd_mask(struct ocelot *ocelot, int src_port);
void ocelot_apply_bridge_fwd_mask(struct ocelot *ocelot, bool joining);
int ocelot_port_pre_bridge_flags(struct ocelot *ocelot, int port,
struct switchdev_brport_flags val);
void ocelot_port_bridge_flags(struct ocelot *ocelot, int port,
Expand Down

0 comments on commit 8abe197

Please sign in to comment.