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: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
3684a23b5a
@ -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);
|
||||
|
||||
mutex_unlock(&mm->lock);
|
||||
if (ack)
|
||||
ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ocelot_port_mm_irq);
|
||||
|
||||
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_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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user