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

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.comSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 3b53ada5 403ffc2c
......@@ -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;
}
......
......@@ -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];
......
......@@ -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;
......
......@@ -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
......
......@@ -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,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment