Skip to content

Commit

Permalink
Merge branch 'ocelot-felix-driver-support-for-preemptible-traffic-cla…
Browse files Browse the repository at this point in the history
…sses'

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: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Jakub Kicinski committed Apr 18, 2023
2 parents 3b53ada + 403ffc2 commit 3684a23
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 25 deletions.
43 changes: 32 additions & 11 deletions drivers/net/dsa/ocelot/felix_vsc9959.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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;
}
Expand Down
60 changes: 59 additions & 1 deletion drivers/net/ethernet/mscc/ocelot.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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];
Expand Down
3 changes: 3 additions & 0 deletions drivers/net/ethernet/mscc/ocelot.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
107 changes: 96 additions & 11 deletions drivers/net/ethernet/mscc/ocelot_mm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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,
Expand Down Expand Up @@ -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 |
Expand All @@ -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;
}
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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
Expand Down
Loading

0 comments on commit 3684a23

Please sign in to comment.