Commit 3684a23b authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'ocelot-felix-driver-support-for-preemptible-traffic-classes'

Vladimir Oltean says:

====================
Ocelot/Felix driver support for preemptible traffic classes

The series "Add tc-mqprio and tc-taprio support for preemptible traffic
classes" from:
https://lore.kernel.org/netdev/20230220122343.1156614-1-vladimir.oltean@nxp.com/

was eventually submitted in a form without the support for the
Ocelot/Felix switch driver. This patch set picks up that work again,
and presents a fairly modified form compared to the original.
====================

Link: https://lore.kernel.org/r/20230415170551.3939607-1-vladimir.oltean@nxp.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 3b53ada5 403ffc2c
Loading
Loading
Loading
Loading
+32 −11
Original line number Diff line number Diff line
@@ -1424,6 +1424,7 @@ static int vsc9959_qos_port_tas_set(struct ocelot *ocelot, int port,
	mutex_lock(&ocelot->tas_lock);

	if (!taprio->enable) {
		ocelot_port_mqprio(ocelot, port, &taprio->mqprio);
		ocelot_rmw_rix(ocelot, 0, QSYS_TAG_CONFIG_ENABLE,
			       QSYS_TAG_CONFIG, port);

@@ -1436,15 +1437,19 @@ static int vsc9959_qos_port_tas_set(struct ocelot *ocelot, int port,
		return 0;
	}

	ret = ocelot_port_mqprio(ocelot, port, &taprio->mqprio);
	if (ret)
		goto err_unlock;

	if (taprio->cycle_time > NSEC_PER_SEC ||
	    taprio->cycle_time_extension >= NSEC_PER_SEC) {
		ret = -EINVAL;
		goto err;
		goto err_reset_tc;
	}

	if (taprio->num_entries > VSC9959_TAS_GCL_ENTRY_MAX) {
		ret = -ERANGE;
		goto err;
		goto err_reset_tc;
	}

	/* Enable guard band. The switch will schedule frames without taking
@@ -1468,7 +1473,7 @@ static int vsc9959_qos_port_tas_set(struct ocelot *ocelot, int port,
	val = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_8);
	if (val & QSYS_PARAM_STATUS_REG_8_CONFIG_PENDING) {
		ret = -EBUSY;
		goto err;
		goto err_reset_tc;
	}

	ocelot_rmw_rix(ocelot,
@@ -1503,12 +1508,19 @@ static int vsc9959_qos_port_tas_set(struct ocelot *ocelot, int port,
				 !(val & QSYS_TAS_PARAM_CFG_CTRL_CONFIG_CHANGE),
				 10, 100000);
	if (ret)
		goto err;
		goto err_reset_tc;

	ocelot_port->taprio = taprio_offload_get(taprio);
	vsc9959_tas_guard_bands_update(ocelot, port);

err:
	mutex_unlock(&ocelot->tas_lock);

	return 0;

err_reset_tc:
	taprio->mqprio.qopt.num_tc = 0;
	ocelot_port_mqprio(ocelot, port, &taprio->mqprio);
err_unlock:
	mutex_unlock(&ocelot->tas_lock);

	return ret;
@@ -1612,6 +1624,13 @@ static int vsc9959_qos_port_cbs_set(struct dsa_switch *ds, int port,
static int vsc9959_qos_query_caps(struct tc_query_caps_base *base)
{
	switch (base->type) {
	case TC_SETUP_QDISC_MQPRIO: {
		struct tc_mqprio_caps *caps = base->caps;

		caps->validate_queue_counts = true;

		return 0;
	}
	case TC_SETUP_QDISC_TAPRIO: {
		struct tc_taprio_caps *caps = base->caps;

@@ -1635,6 +1654,8 @@ static int vsc9959_port_setup_tc(struct dsa_switch *ds, int port,
		return vsc9959_qos_query_caps(type_data);
	case TC_SETUP_QDISC_TAPRIO:
		return vsc9959_qos_port_tas_set(ocelot, port, type_data);
	case TC_SETUP_QDISC_MQPRIO:
		return ocelot_port_mqprio(ocelot, port, type_data);
	case TC_SETUP_QDISC_CBS:
		return vsc9959_qos_port_cbs_set(ds, port, type_data);
	default:
@@ -2498,6 +2519,7 @@ static void vsc9959_cut_through_fwd(struct ocelot *ocelot)

	for (port = 0; port < ocelot->num_phys_ports; port++) {
		struct ocelot_port *ocelot_port = ocelot->ports[port];
		struct ocelot_mm_state *mm = &ocelot->mm[port];
		int min_speed = ocelot_port->speed;
		unsigned long mask = 0;
		u32 tmp, val = 0;
@@ -2538,10 +2560,12 @@ static void vsc9959_cut_through_fwd(struct ocelot *ocelot)

		/* Enable cut-through forwarding for all traffic classes that
		 * don't have oversized dropping enabled, since this check is
		 * bypassed in cut-through mode.
		 * bypassed in cut-through mode. Also exclude preemptible
		 * traffic classes, since these would hang the port for some
		 * reason, if sent as cut-through.
		 */
		if (ocelot_port->speed == min_speed) {
			val = GENMASK(7, 0);
			val = GENMASK(7, 0) & ~mm->active_preemptible_tcs;

			for (tc = 0; tc < OCELOT_NUM_TC; tc++)
				if (vsc9959_port_qmaxsdu_get(ocelot, port, tc))
@@ -2610,12 +2634,9 @@ static const struct felix_info felix_info_vsc9959 = {
static irqreturn_t felix_irq_handler(int irq, void *data)
{
	struct ocelot *ocelot = (struct ocelot *)data;
	int port;

	ocelot_get_txtstamp(ocelot);

	for (port = 0; port < ocelot->num_phys_ports; port++)
		ocelot_port_mm_irq(ocelot, port);
	ocelot_mm_irq(ocelot);

	return IRQ_HANDLED;
}
+59 −1
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
#include <linux/if_bridge.h>
#include <linux/iopoll.h>
#include <linux/phy/phy.h>
#include <net/pkt_sched.h>
#include <soc/mscc/ocelot_hsio.h>
#include <soc/mscc/ocelot_vcap.h>
#include "ocelot.h"
@@ -1005,7 +1006,12 @@ void ocelot_phylink_mac_link_up(struct ocelot *ocelot, int port,
	 */
	if (ocelot->ops->cut_through_fwd) {
		mutex_lock(&ocelot->fwd_domain_lock);
		ocelot->ops->cut_through_fwd(ocelot);
		/* Workaround for hardware bug - FP doesn't work
		 * at all link speeds for all PHY modes. The function
		 * below also calls ocelot->ops->cut_through_fwd(),
		 * so we don't need to do it twice.
		 */
		ocelot_port_update_active_preemptible_tcs(ocelot, port);
		mutex_unlock(&ocelot->fwd_domain_lock);
	}

@@ -2699,6 +2705,58 @@ void ocelot_port_mirror_del(struct ocelot *ocelot, int from, bool ingress)
}
EXPORT_SYMBOL_GPL(ocelot_port_mirror_del);

static void ocelot_port_reset_mqprio(struct ocelot *ocelot, int port)
{
	struct net_device *dev = ocelot->ops->port_to_netdev(ocelot, port);

	netdev_reset_tc(dev);
	ocelot_port_change_fp(ocelot, port, 0);
}

int ocelot_port_mqprio(struct ocelot *ocelot, int port,
		       struct tc_mqprio_qopt_offload *mqprio)
{
	struct net_device *dev = ocelot->ops->port_to_netdev(ocelot, port);
	struct netlink_ext_ack *extack = mqprio->extack;
	struct tc_mqprio_qopt *qopt = &mqprio->qopt;
	int num_tc = qopt->num_tc;
	int tc, err;

	if (!num_tc) {
		ocelot_port_reset_mqprio(ocelot, port);
		return 0;
	}

	err = netdev_set_num_tc(dev, num_tc);
	if (err)
		return err;

	for (tc = 0; tc < num_tc; tc++) {
		if (qopt->count[tc] != 1) {
			NL_SET_ERR_MSG_MOD(extack,
					   "Only one TXQ per TC supported");
			return -EINVAL;
		}

		err = netdev_set_tc_queue(dev, tc, 1, qopt->offset[tc]);
		if (err)
			goto err_reset_tc;
	}

	err = netif_set_real_num_tx_queues(dev, num_tc);
	if (err)
		goto err_reset_tc;

	ocelot_port_change_fp(ocelot, port, mqprio->preemptible_tcs);

	return 0;

err_reset_tc:
	ocelot_port_reset_mqprio(ocelot, port);
	return err;
}
EXPORT_SYMBOL_GPL(ocelot_port_mqprio);

void ocelot_init_port(struct ocelot *ocelot, int port)
{
	struct ocelot_port *ocelot_port = ocelot->ports[port];
+3 −0
Original line number Diff line number Diff line
@@ -119,6 +119,9 @@ int ocelot_stats_init(struct ocelot *ocelot);
void ocelot_stats_deinit(struct ocelot *ocelot);

int ocelot_mm_init(struct ocelot *ocelot);
void ocelot_port_change_fp(struct ocelot *ocelot, int port,
			   unsigned long preemptible_tcs);
void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port);

extern struct notifier_block ocelot_netdevice_nb;
extern struct notifier_block ocelot_switchdev_nb;
+96 −11
Original line number Diff line number Diff line
@@ -49,14 +49,68 @@ static enum ethtool_mm_verify_status ocelot_mm_verify_status(u32 val)
	}
}

void ocelot_port_mm_irq(struct ocelot *ocelot, int port)
void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port)
{
	struct ocelot_port *ocelot_port = ocelot->ports[port];
	struct ocelot_mm_state *mm = &ocelot->mm[port];
	u32 val = 0;

	lockdep_assert_held(&ocelot->fwd_domain_lock);

	/* Only commit preemptible TCs when MAC Merge is active.
	 * On NXP LS1028A, when using QSGMII, the port hangs if transmitting
	 * preemptible frames at any other link speed than gigabit, so avoid
	 * preemption at lower speeds in this PHY mode.
	 */
	if ((ocelot_port->phy_mode != PHY_INTERFACE_MODE_QSGMII ||
	     ocelot_port->speed == SPEED_1000) && mm->tx_active)
		val = mm->preemptible_tcs;

	/* Cut through switching doesn't work for preemptible priorities,
	 * so first make sure it is disabled.
	 */
	mm->active_preemptible_tcs = val;
	ocelot->ops->cut_through_fwd(ocelot);

	dev_dbg(ocelot->dev,
		"port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n",
		port, phy_modes(ocelot_port->phy_mode),
		phy_speed_to_str(ocelot_port->speed),
		mm->tx_active ? "active" : "inactive", mm->preemptible_tcs,
		mm->active_preemptible_tcs);

	ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_P_QUEUES(val),
		       QSYS_PREEMPTION_CFG_P_QUEUES_M,
		       QSYS_PREEMPTION_CFG, port);
}

void ocelot_port_change_fp(struct ocelot *ocelot, int port,
			   unsigned long preemptible_tcs)
{
	struct ocelot_mm_state *mm = &ocelot->mm[port];

	mutex_lock(&ocelot->fwd_domain_lock);

	if (mm->preemptible_tcs == preemptible_tcs)
		goto out_unlock;

	mm->preemptible_tcs = preemptible_tcs;

	ocelot_port_update_active_preemptible_tcs(ocelot, port);

out_unlock:
	mutex_unlock(&ocelot->fwd_domain_lock);
}

static void ocelot_mm_update_port_status(struct ocelot *ocelot, int port)
{
	struct ocelot_port *ocelot_port = ocelot->ports[port];
	struct ocelot_mm_state *mm = &ocelot->mm[port];
	enum ethtool_mm_verify_status verify_status;
	u32 val;
	u32 val, ack = 0;

	mutex_lock(&mm->lock);
	if (!mm->tx_enabled)
		return;

	val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS);

@@ -73,25 +127,43 @@ void ocelot_port_mm_irq(struct ocelot *ocelot, int port)

		dev_dbg(ocelot->dev, "Port %d TX preemption %s\n",
			port, mm->tx_active ? "active" : "inactive");
		ocelot_port_update_active_preemptible_tcs(ocelot, port);

		ack |= DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY;
	}

	if (val & DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY) {
		dev_err(ocelot->dev,
			"Unexpected P-frame received on port %d while verification was unsuccessful or not yet verified\n",
			port);

		ack |= DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY;
	}

	if (val & DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY) {
		dev_err(ocelot->dev,
			"Unexpected P-frame requested to be transmitted on port %d while verification was unsuccessful or not yet verified, or MM_TX_ENA=0\n",
			port);

		ack |= DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY;
	}

	ocelot_port_writel(ocelot_port, val, DEV_MM_STATUS);
	if (ack)
		ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS);
}

	mutex_unlock(&mm->lock);
void ocelot_mm_irq(struct ocelot *ocelot)
{
	int port;

	mutex_lock(&ocelot->fwd_domain_lock);

	for (port = 0; port < ocelot->num_phys_ports; port++)
		ocelot_mm_update_port_status(ocelot, port);

	mutex_unlock(&ocelot->fwd_domain_lock);
}
EXPORT_SYMBOL_GPL(ocelot_port_mm_irq);
EXPORT_SYMBOL_GPL(ocelot_mm_irq);

int ocelot_port_set_mm(struct ocelot *ocelot, int port,
		       struct ethtool_mm_cfg *cfg,
@@ -121,7 +193,7 @@ int ocelot_port_set_mm(struct ocelot *ocelot, int port,
	if (!cfg->verify_enabled)
		verify_disable = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS;

	mutex_lock(&mm->lock);
	mutex_lock(&ocelot->fwd_domain_lock);

	ocelot_port_rmwl(ocelot_port, mm_enable,
			 DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA |
@@ -140,7 +212,20 @@ int ocelot_port_set_mm(struct ocelot *ocelot, int port,
		       QSYS_PREEMPTION_CFG,
		       port);

	mutex_unlock(&mm->lock);
	/* The switch will emit an IRQ when TX is disabled, to notify that it
	 * has become inactive. We optimize ocelot_mm_update_port_status() to
	 * not bother processing MM IRQs at all for ports with TX disabled,
	 * but we need to ACK this IRQ now, while mm->tx_enabled is still set,
	 * otherwise we get an IRQ storm.
	 */
	if (mm->tx_enabled && !cfg->tx_enabled) {
		ocelot_mm_update_port_status(ocelot, port);
		WARN_ON(mm->tx_active);
	}

	mm->tx_enabled = cfg->tx_enabled;

	mutex_unlock(&ocelot->fwd_domain_lock);

	return 0;
}
@@ -158,7 +243,7 @@ int ocelot_port_get_mm(struct ocelot *ocelot, int port,

	mm = &ocelot->mm[port];

	mutex_lock(&mm->lock);
	mutex_lock(&ocelot->fwd_domain_lock);

	val = ocelot_port_readl(ocelot_port, DEV_MM_ENABLE_CONFIG);
	state->pmac_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA);
@@ -174,10 +259,11 @@ int ocelot_port_get_mm(struct ocelot *ocelot, int port,
	state->tx_min_frag_size = ethtool_mm_frag_size_add_to_min(add_frag_size);
	state->rx_min_frag_size = ETH_ZLEN;

	ocelot_mm_update_port_status(ocelot, port);
	state->verify_status = mm->verify_status;
	state->tx_active = mm->tx_active;

	mutex_unlock(&mm->lock);
	mutex_unlock(&ocelot->fwd_domain_lock);

	return 0;
}
@@ -201,7 +287,6 @@ int ocelot_mm_init(struct ocelot *ocelot)
		u32 val;

		mm = &ocelot->mm[port];
		mutex_init(&mm->lock);
		ocelot_port = ocelot->ports[port];

		/* Update initial status variable for the
+9 −2
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@
#include <linux/regmap.h>
#include <net/dsa.h>

struct tc_mqprio_qopt_offload;

/* Port Group IDs (PGID) are masks of destination ports.
 *
 * For L2 forwarding, the switch performs 3 lookups in the PGID table for each
@@ -744,9 +746,11 @@ struct ocelot_mirror {
};

struct ocelot_mm_state {
	struct mutex lock;
	enum ethtool_mm_verify_status verify_status;
	bool tx_enabled;
	bool tx_active;
	u8 preemptible_tcs;
	u8 active_preemptible_tcs;
};

struct ocelot_port;
@@ -1148,12 +1152,15 @@ int ocelot_vcap_policer_add(struct ocelot *ocelot, u32 pol_ix,
			    struct ocelot_policer *pol);
int ocelot_vcap_policer_del(struct ocelot *ocelot, u32 pol_ix);

void ocelot_port_mm_irq(struct ocelot *ocelot, int port);
void ocelot_mm_irq(struct ocelot *ocelot);
int ocelot_port_set_mm(struct ocelot *ocelot, int port,
		       struct ethtool_mm_cfg *cfg,
		       struct netlink_ext_ack *extack);
int ocelot_port_get_mm(struct ocelot *ocelot, int port,
		       struct ethtool_mm_state *state);
int ocelot_port_mqprio(struct ocelot *ocelot, int port,
		       struct tc_mqprio_qopt_offload *mqprio);
void ocelot_port_update_preemptible_tcs(struct ocelot *ocelot, int port);

#if IS_ENABLED(CONFIG_BRIDGE_MRP)
int ocelot_mrp_add(struct ocelot *ocelot, int port,