// SPDX-License-Identifier: (GPL-2.0 OR MIT) /* * Hardware library for MAC Merge Layer and Frame Preemption on TSN-capable * switches (VSC9959) * * Copyright 2022-2023 NXP */ #include #include #include #include #include "ocelot.h" static const char * mm_verify_state_to_string(enum ethtool_mm_verify_status state) { switch (state) { case ETHTOOL_MM_VERIFY_STATUS_INITIAL: return "INITIAL"; case ETHTOOL_MM_VERIFY_STATUS_VERIFYING: return "VERIFYING"; case ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED: return "SUCCEEDED"; case ETHTOOL_MM_VERIFY_STATUS_FAILED: return "FAILED"; case ETHTOOL_MM_VERIFY_STATUS_DISABLED: return "DISABLED"; default: return "UNKNOWN"; } } static enum ethtool_mm_verify_status ocelot_mm_verify_status(u32 val) { switch (DEV_MM_STAT_MM_STATUS_PRMPT_VERIFY_STATE_X(val)) { case 0: return ETHTOOL_MM_VERIFY_STATUS_INITIAL; case 1: return ETHTOOL_MM_VERIFY_STATUS_VERIFYING; case 2: return ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED; case 3: return ETHTOOL_MM_VERIFY_STATUS_FAILED; case 4: return ETHTOOL_MM_VERIFY_STATUS_DISABLED; default: return ETHTOOL_MM_VERIFY_STATUS_UNKNOWN; } } 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. Also, changing the preemptible * TCs affects the oversized frame dropping logic, so that needs to be * re-triggered. And since tas_guard_bands_update() also implicitly * calls cut_through_fwd(), we don't need to explicitly call it. */ mm->active_preemptible_tcs = val; ocelot->ops->tas_guard_bands_update(ocelot, port); 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]; lockdep_assert_held(&ocelot->fwd_domain_lock); if (mm->preemptible_tcs == preemptible_tcs) return; mm->preemptible_tcs = preemptible_tcs; ocelot_port_update_active_preemptible_tcs(ocelot, port); } 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, ack = 0; if (!mm->tx_enabled) return; val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); verify_status = ocelot_mm_verify_status(val); if (mm->verify_status != verify_status) { dev_dbg(ocelot->dev, "Port %d MAC Merge verification state %s\n", port, mm_verify_state_to_string(verify_status)); mm->verify_status = verify_status; } if (val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY) { mm->tx_active = !!(val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STATUS); 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; } if (ack) ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS); } 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, struct netlink_ext_ack *extack) { struct ocelot_port *ocelot_port = ocelot->ports[port]; u32 mm_enable = 0, verify_disable = 0, add_frag_size; struct ocelot_mm_state *mm; int err; if (!ocelot->mm_supported) return -EOPNOTSUPP; mm = &ocelot->mm[port]; err = ethtool_mm_frag_size_min_to_add(cfg->tx_min_frag_size, &add_frag_size, extack); if (err) return err; if (cfg->pmac_enabled) mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA; if (cfg->tx_enabled) mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA; if (!cfg->verify_enabled) verify_disable = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS; mutex_lock(&ocelot->fwd_domain_lock); ocelot_port_rmwl(ocelot_port, mm_enable, DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA | DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA, DEV_MM_ENABLE_CONFIG); ocelot_port_rmwl(ocelot_port, verify_disable | DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME(cfg->verify_time), DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS | DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_M, DEV_MM_VERIF_CONFIG); ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE(add_frag_size), QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_M, QSYS_PREEMPTION_CFG, port); /* 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; } EXPORT_SYMBOL_GPL(ocelot_port_set_mm); int ocelot_port_get_mm(struct ocelot *ocelot, int port, struct ethtool_mm_state *state) { struct ocelot_port *ocelot_port = ocelot->ports[port]; struct ocelot_mm_state *mm; u32 val, add_frag_size; if (!ocelot->mm_supported) return -EOPNOTSUPP; mm = &ocelot->mm[port]; 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); state->tx_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA); val = ocelot_port_readl(ocelot_port, DEV_MM_VERIF_CONFIG); state->verify_enabled = !(val & DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS); state->verify_time = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_X(val); state->max_verify_time = 128; val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port); add_frag_size = QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_X(val); 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(&ocelot->fwd_domain_lock); return 0; } EXPORT_SYMBOL_GPL(ocelot_port_get_mm); int ocelot_mm_init(struct ocelot *ocelot) { struct ocelot_port *ocelot_port; struct ocelot_mm_state *mm; int port; if (!ocelot->mm_supported) return 0; ocelot->mm = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports, sizeof(*ocelot->mm), GFP_KERNEL); if (!ocelot->mm) return -ENOMEM; for (port = 0; port < ocelot->num_phys_ports; port++) { u32 val; mm = &ocelot->mm[port]; ocelot_port = ocelot->ports[port]; /* Update initial status variable for the * verification state machine */ val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); mm->verify_status = ocelot_mm_verify_status(val); } return 0; }