Commit 92346bde authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'add-tc-taprio-support-for-queuemaxsdu'

Vladimir Oltean says:

====================
Add tc-taprio support for queueMaxSDU

The tc-taprio offload mode supported by the Felix DSA driver has
limitations surrounding its guard bands.

The initial discussion was at:
https://lore.kernel.org/netdev/c7618025da6723418c56a54fe4683bd7@walle.cc/

with the latest status being that we now have a vsc9959_tas_guard_bands_update()
method which makes a best-guess attempt at how much useful space to
reserve for packet scheduling in a taprio interval, and how much to
reserve for guard bands.

IEEE 802.1Q actually does offer a tunable variable (queueMaxSDU) which
can determine the max MTU supported per traffic class. In turn we can
determine the size we need for the guard bands, depending on the
queueMaxSDU. This way we can make the guard band of small taprio
intervals smaller than one full MTU worth of transmission time, if we
know that said traffic class will transport only smaller packets.

As discussed with Gerhard Engleder, the queueMaxSDU may also be useful
in limiting the latency on an endpoint, if some of the TX queues are
outside of the control of the Linux driver.
https://patchwork.kernel.org/project/netdevbpf/patch/20220914153303.1792444-11-vladimir.oltean@nxp.com/

Allow input of queueMaxSDU through netlink into tc-taprio, offload it to
the hardware I have access to (LS1028A), and (implicitly) deny
non-default values to everyone else. Kurt Kanzenbach has also kindly
tested and shared a patch to offload this to hellcreek.

v3 at:
https://patchwork.kernel.org/project/netdevbpf/cover/20220927234746.1823648-1-vladimir.oltean@nxp.com/
v2 at:
https://patchwork.kernel.org/project/netdevbpf/list/?series=679954&state=*
v1 at:
https://patchwork.kernel.org/project/netdevbpf/cover/20220914153303.1792444-1-vladimir.oltean@nxp.com/
====================

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


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 8af535b6 dfc7175d
Loading
Loading
Loading
Loading
+86 −10
Original line number Diff line number Diff line
@@ -128,6 +128,16 @@ static void hellcreek_select_prio(struct hellcreek *hellcreek, int prio)
	hellcreek_write(hellcreek, val, HR_PSEL);
}

static void hellcreek_select_port_prio(struct hellcreek *hellcreek, int port,
				       int prio)
{
	u16 val = port << HR_PSEL_PTWSEL_SHIFT;

	val |= prio << HR_PSEL_PRTCWSEL_SHIFT;

	hellcreek_write(hellcreek, val, HR_PSEL);
}

static void hellcreek_select_counter(struct hellcreek *hellcreek, int counter)
{
	u16 val = counter << HR_CSEL_SHIFT;
@@ -1537,6 +1547,45 @@ hellcreek_port_prechangeupper(struct dsa_switch *ds, int port,
	return ret;
}

static void hellcreek_setup_maxsdu(struct hellcreek *hellcreek, int port,
				   const struct tc_taprio_qopt_offload *schedule)
{
	int tc;

	for (tc = 0; tc < 8; ++tc) {
		u32 max_sdu = schedule->max_sdu[tc] + VLAN_ETH_HLEN - ETH_FCS_LEN;
		u16 val;

		if (!schedule->max_sdu[tc])
			continue;

		dev_dbg(hellcreek->dev, "Configure max-sdu %u for tc %d on port %d\n",
			max_sdu, tc, port);

		hellcreek_select_port_prio(hellcreek, port, tc);

		val = (max_sdu & HR_PTPRTCCFG_MAXSDU_MASK) << HR_PTPRTCCFG_MAXSDU_SHIFT;

		hellcreek_write(hellcreek, val, HR_PTPRTCCFG);
	}
}

static void hellcreek_reset_maxsdu(struct hellcreek *hellcreek, int port)
{
	int tc;

	for (tc = 0; tc < 8; ++tc) {
		u16 val;

		hellcreek_select_port_prio(hellcreek, port, tc);

		val = (HELLCREEK_DEFAULT_MAX_SDU & HR_PTPRTCCFG_MAXSDU_MASK)
			<< HR_PTPRTCCFG_MAXSDU_SHIFT;

		hellcreek_write(hellcreek, val, HR_PTPRTCCFG);
	}
}

static void hellcreek_setup_gcl(struct hellcreek *hellcreek, int port,
				const struct tc_taprio_qopt_offload *schedule)
{
@@ -1720,7 +1769,10 @@ static int hellcreek_port_set_schedule(struct dsa_switch *ds, int port,
	}
	hellcreek_port->current_schedule = taprio_offload_get(taprio);

	/* Then select port */
	/* Configure max sdu */
	hellcreek_setup_maxsdu(hellcreek, port, hellcreek_port->current_schedule);

	/* Select tdg */
	hellcreek_select_tgd(hellcreek, port);

	/* Enable gating and keep defaults */
@@ -1772,7 +1824,10 @@ static int hellcreek_port_del_schedule(struct dsa_switch *ds, int port)
		hellcreek_port->current_schedule = NULL;
	}

	/* Then select port */
	/* Reset max sdu */
	hellcreek_reset_maxsdu(hellcreek, port);

	/* Select tgd */
	hellcreek_select_tgd(hellcreek, port);

	/* Disable gating and return to regular switching flow */
@@ -1809,14 +1864,31 @@ static bool hellcreek_validate_schedule(struct hellcreek *hellcreek,
	return true;
}

static int hellcreek_tc_query_caps(struct tc_query_caps_base *base)
{
	switch (base->type) {
	case TC_SETUP_QDISC_TAPRIO: {
		struct tc_taprio_caps *caps = base->caps;

		caps->supports_queue_max_sdu = true;

		return 0;
	}
	default:
		return -EOPNOTSUPP;
	}
}

static int hellcreek_port_setup_tc(struct dsa_switch *ds, int port,
				   enum tc_setup_type type, void *type_data)
{
	struct tc_taprio_qopt_offload *taprio = type_data;
	struct hellcreek *hellcreek = ds->priv;

	if (type != TC_SETUP_QDISC_TAPRIO)
		return -EOPNOTSUPP;
	switch (type) {
	case TC_QUERY_CAPS:
		return hellcreek_tc_query_caps(type_data);
	case TC_SETUP_QDISC_TAPRIO: {
		struct tc_taprio_qopt_offload *taprio = type_data;

		if (!hellcreek_validate_schedule(hellcreek, taprio))
			return -EOPNOTSUPP;
@@ -1826,6 +1898,10 @@ static int hellcreek_port_setup_tc(struct dsa_switch *ds, int port,

		return hellcreek_port_del_schedule(ds, port);
	}
	default:
		return -EOPNOTSUPP;
	}
}

static const struct dsa_switch_ops hellcreek_ds_ops = {
	.devlink_info_get      = hellcreek_devlink_info_get,
+7 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@
#define HELLCREEK_VLAN_UNTAGGED_MEMBER	0x1
#define HELLCREEK_VLAN_TAGGED_MEMBER	0x3
#define HELLCREEK_NUM_EGRESS_QUEUES	8
#define HELLCREEK_DEFAULT_MAX_SDU	1536

/* Register definitions */
#define HR_MODID_C			(0 * 2)
@@ -72,6 +73,12 @@
#define HR_PRTCCFG_PCP_TC_MAP_SHIFT	0
#define HR_PRTCCFG_PCP_TC_MAP_MASK	GENMASK(2, 0)

#define HR_PTPRTCCFG			(0xa9 * 2)
#define HR_PTPRTCCFG_SET_QTRACK		BIT(15)
#define HR_PTPRTCCFG_REJECT		BIT(14)
#define HR_PTPRTCCFG_MAXSDU_SHIFT	0
#define HR_PTPRTCCFG_MAXSDU_MASK	GENMASK(10, 0)

#define HR_CSEL				(0x8d * 2)
#define HR_CSEL_SHIFT			0
#define HR_CSEL_MASK			GENMASK(7, 0)
+35 −2
Original line number Diff line number Diff line
@@ -1194,6 +1194,14 @@ static u32 vsc9959_port_qmaxsdu_get(struct ocelot *ocelot, int port, int tc)
	}
}

static u32 vsc9959_tas_tc_max_sdu(struct tc_taprio_qopt_offload *taprio, int tc)
{
	if (!taprio || !taprio->max_sdu[tc])
		return 0;

	return taprio->max_sdu[tc] + ETH_HLEN + 2 * VLAN_HLEN + ETH_FCS_LEN;
}

/* Update QSYS_PORT_MAX_SDU to make sure the static guard bands added by the
 * switch (see the ALWAYS_GUARD_BAND_SCH_Q comment) are correct at all MTU
 * values (the default value is 1518). Also, for traffic class windows smaller
@@ -1203,6 +1211,7 @@ static u32 vsc9959_port_qmaxsdu_get(struct ocelot *ocelot, int port, int tc)
static void vsc9959_tas_guard_bands_update(struct ocelot *ocelot, int port)
{
	struct ocelot_port *ocelot_port = ocelot->ports[port];
	struct tc_taprio_qopt_offload *taprio;
	u64 min_gate_len[OCELOT_NUM_TC];
	int speed, picos_per_byte;
	u64 needed_bit_time_ps;
@@ -1212,6 +1221,8 @@ static void vsc9959_tas_guard_bands_update(struct ocelot *ocelot, int port)

	lockdep_assert_held(&ocelot->tas_lock);

	taprio = ocelot_port->taprio;

	val = ocelot_read_rix(ocelot, QSYS_TAG_CONFIG, port);
	tas_speed = QSYS_TAG_CONFIG_LINK_SPEED_X(val);

@@ -1248,11 +1259,12 @@ static void vsc9959_tas_guard_bands_update(struct ocelot *ocelot, int port)
		"port %d: max frame size %d needs %llu ps at speed %d\n",
		port, maxlen, needed_bit_time_ps, speed);

	vsc9959_tas_min_gate_lengths(ocelot_port->taprio, min_gate_len);
	vsc9959_tas_min_gate_lengths(taprio, min_gate_len);

	mutex_lock(&ocelot->fwd_domain_lock);

	for (tc = 0; tc < OCELOT_NUM_TC; tc++) {
		u32 requested_max_sdu = vsc9959_tas_tc_max_sdu(taprio, tc);
		u64 remaining_gate_len_ps;
		u32 max_sdu;

@@ -1263,7 +1275,7 @@ static void vsc9959_tas_guard_bands_update(struct ocelot *ocelot, int port)
			/* Setting QMAXSDU_CFG to 0 disables oversized frame
			 * dropping.
			 */
			max_sdu = 0;
			max_sdu = requested_max_sdu;
			dev_dbg(ocelot->dev,
				"port %d tc %d min gate len %llu"
				", sending all frames\n",
@@ -1294,6 +1306,10 @@ static void vsc9959_tas_guard_bands_update(struct ocelot *ocelot, int port)
			 */
			if (max_sdu > 20)
				max_sdu -= 20;

			if (requested_max_sdu && requested_max_sdu < max_sdu)
				max_sdu = requested_max_sdu;

			dev_info(ocelot->dev,
				 "port %d tc %d min gate length %llu"
				 " ns not enough for max frame size %d at %d"
@@ -1583,6 +1599,21 @@ static int vsc9959_qos_port_cbs_set(struct dsa_switch *ds, int port,
	return 0;
}

static int vsc9959_qos_query_caps(struct tc_query_caps_base *base)
{
	switch (base->type) {
	case TC_SETUP_QDISC_TAPRIO: {
		struct tc_taprio_caps *caps = base->caps;

		caps->supports_queue_max_sdu = true;

		return 0;
	}
	default:
		return -EOPNOTSUPP;
	}
}

static int vsc9959_port_setup_tc(struct dsa_switch *ds, int port,
				 enum tc_setup_type type,
				 void *type_data)
@@ -1590,6 +1621,8 @@ static int vsc9959_port_setup_tc(struct dsa_switch *ds, int port,
	struct ocelot *ocelot = ds->priv;

	switch (type) {
	case TC_QUERY_CAPS:
		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_CBS:
+17 −11
Original line number Diff line number Diff line
@@ -2116,13 +2116,14 @@ static void enetc_setup_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring)

static void enetc_setup_bdrs(struct enetc_ndev_priv *priv)
{
	struct enetc_hw *hw = &priv->si->hw;
	int i;

	for (i = 0; i < priv->num_tx_rings; i++)
		enetc_setup_txbdr(&priv->si->hw, priv->tx_ring[i]);
		enetc_setup_txbdr(hw, priv->tx_ring[i]);

	for (i = 0; i < priv->num_rx_rings; i++)
		enetc_setup_rxbdr(&priv->si->hw, priv->rx_ring[i]);
		enetc_setup_rxbdr(hw, priv->rx_ring[i]);
}

static void enetc_clear_rxbdr(struct enetc_hw *hw, struct enetc_bdr *rx_ring)
@@ -2155,13 +2156,14 @@ static void enetc_clear_txbdr(struct enetc_hw *hw, struct enetc_bdr *tx_ring)

static void enetc_clear_bdrs(struct enetc_ndev_priv *priv)
{
	struct enetc_hw *hw = &priv->si->hw;
	int i;

	for (i = 0; i < priv->num_tx_rings; i++)
		enetc_clear_txbdr(&priv->si->hw, priv->tx_ring[i]);
		enetc_clear_txbdr(hw, priv->tx_ring[i]);

	for (i = 0; i < priv->num_rx_rings; i++)
		enetc_clear_rxbdr(&priv->si->hw, priv->rx_ring[i]);
		enetc_clear_rxbdr(hw, priv->rx_ring[i]);

	udelay(1);
}
@@ -2169,13 +2171,13 @@ static void enetc_clear_bdrs(struct enetc_ndev_priv *priv)
static int enetc_setup_irqs(struct enetc_ndev_priv *priv)
{
	struct pci_dev *pdev = priv->si->pdev;
	struct enetc_hw *hw = &priv->si->hw;
	int i, j, err;

	for (i = 0; i < priv->bdr_int_num; i++) {
		int irq = pci_irq_vector(pdev, ENETC_BDR_INT_BASE_IDX + i);
		struct enetc_int_vector *v = priv->int_vector[i];
		int entry = ENETC_BDR_INT_BASE_IDX + i;
		struct enetc_hw *hw = &priv->si->hw;

		snprintf(v->name, sizeof(v->name), "%s-rxtx%d",
			 priv->ndev->name, i);
@@ -2263,13 +2265,14 @@ static void enetc_setup_interrupts(struct enetc_ndev_priv *priv)

static void enetc_clear_interrupts(struct enetc_ndev_priv *priv)
{
	struct enetc_hw *hw = &priv->si->hw;
	int i;

	for (i = 0; i < priv->num_tx_rings; i++)
		enetc_txbdr_wr(&priv->si->hw, i, ENETC_TBIER, 0);
		enetc_txbdr_wr(hw, i, ENETC_TBIER, 0);

	for (i = 0; i < priv->num_rx_rings; i++)
		enetc_rxbdr_wr(&priv->si->hw, i, ENETC_RBIER, 0);
		enetc_rxbdr_wr(hw, i, ENETC_RBIER, 0);
}

static int enetc_phylink_connect(struct net_device *ndev)
@@ -2436,6 +2439,7 @@ int enetc_setup_tc_mqprio(struct net_device *ndev, void *type_data)
{
	struct enetc_ndev_priv *priv = netdev_priv(ndev);
	struct tc_mqprio_qopt *mqprio = type_data;
	struct enetc_hw *hw = &priv->si->hw;
	struct enetc_bdr *tx_ring;
	int num_stack_tx_queues;
	u8 num_tc;
@@ -2452,7 +2456,7 @@ int enetc_setup_tc_mqprio(struct net_device *ndev, void *type_data)
		/* Reset all ring priorities to 0 */
		for (i = 0; i < priv->num_tx_rings; i++) {
			tx_ring = priv->tx_ring[i];
			enetc_set_bdr_prio(&priv->si->hw, tx_ring->index, 0);
			enetc_set_bdr_prio(hw, tx_ring->index, 0);
		}

		return 0;
@@ -2471,7 +2475,7 @@ int enetc_setup_tc_mqprio(struct net_device *ndev, void *type_data)
	 */
	for (i = 0; i < num_tc; i++) {
		tx_ring = priv->tx_ring[i];
		enetc_set_bdr_prio(&priv->si->hw, tx_ring->index, i);
		enetc_set_bdr_prio(hw, tx_ring->index, i);
	}

	/* Reset the number of netdev queues based on the TC count */
@@ -2584,19 +2588,21 @@ static int enetc_set_rss(struct net_device *ndev, int en)
static void enetc_enable_rxvlan(struct net_device *ndev, bool en)
{
	struct enetc_ndev_priv *priv = netdev_priv(ndev);
	struct enetc_hw *hw = &priv->si->hw;
	int i;

	for (i = 0; i < priv->num_rx_rings; i++)
		enetc_bdr_enable_rxvlan(&priv->si->hw, i, en);
		enetc_bdr_enable_rxvlan(hw, i, en);
}

static void enetc_enable_txvlan(struct net_device *ndev, bool en)
{
	struct enetc_ndev_priv *priv = netdev_priv(ndev);
	struct enetc_hw *hw = &priv->si->hw;
	int i;

	for (i = 0; i < priv->num_tx_rings; i++)
		enetc_bdr_enable_txvlan(&priv->si->hw, i, en);
		enetc_bdr_enable_txvlan(hw, i, en);
}

void enetc_set_features(struct net_device *ndev, netdev_features_t features)
+10 −4
Original line number Diff line number Diff line
@@ -453,7 +453,11 @@ static inline void enetc_cbd_free_data_mem(struct enetc_si *si, int size,
			  data, *dma);
}

void enetc_reset_ptcmsdur(struct enetc_hw *hw);
void enetc_set_ptcmsdur(struct enetc_hw *hw, u32 *queue_max_sdu);

#ifdef CONFIG_FSL_ENETC_QOS
int enetc_qos_query_caps(struct net_device *ndev, void *type_data);
int enetc_setup_tc_taprio(struct net_device *ndev, void *type_data);
void enetc_sched_speed_set(struct enetc_ndev_priv *priv, int speed);
int enetc_setup_tc_cbs(struct net_device *ndev, void *type_data);
@@ -467,19 +471,20 @@ int enetc_set_psfp(struct net_device *ndev, bool en);

static inline void enetc_get_max_cap(struct enetc_ndev_priv *priv)
{
	struct enetc_hw *hw = &priv->si->hw;
	u32 reg;

	reg = enetc_port_rd(&priv->si->hw, ENETC_PSIDCAPR);
	reg = enetc_port_rd(hw, ENETC_PSIDCAPR);
	priv->psfp_cap.max_streamid = reg & ENETC_PSIDCAPR_MSK;
	/* Port stream filter capability */
	reg = enetc_port_rd(&priv->si->hw, ENETC_PSFCAPR);
	reg = enetc_port_rd(hw, ENETC_PSFCAPR);
	priv->psfp_cap.max_psfp_filter = reg & ENETC_PSFCAPR_MSK;
	/* Port stream gate capability */
	reg = enetc_port_rd(&priv->si->hw, ENETC_PSGCAPR);
	reg = enetc_port_rd(hw, ENETC_PSGCAPR);
	priv->psfp_cap.max_psfp_gate = (reg & ENETC_PSGCAPR_SGIT_MSK);
	priv->psfp_cap.max_psfp_gatelist = (reg & ENETC_PSGCAPR_GCL_MSK) >> 16;
	/* Port flow meter capability */
	reg = enetc_port_rd(&priv->si->hw, ENETC_PFMCAPR);
	reg = enetc_port_rd(hw, ENETC_PFMCAPR);
	priv->psfp_cap.max_psfp_meter = reg & ENETC_PFMCAPR_MSK;
}

@@ -520,6 +525,7 @@ static inline int enetc_psfp_disable(struct enetc_ndev_priv *priv)
}

#else
#define enetc_qos_query_caps(ndev, type_data) -EOPNOTSUPP
#define enetc_setup_tc_taprio(ndev, type_data) -EOPNOTSUPP
#define enetc_sched_speed_set(priv, speed) (void)0
#define enetc_setup_tc_cbs(ndev, type_data) -EOPNOTSUPP
Loading