diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h index d107eade6d706..874d5126f1a4c 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h @@ -2749,6 +2749,157 @@ struct mxl862xx_xpcs_force_speed { __le16 result; } __packed; +/* + * Firmware >= 1.0.84 reshaped the XPCS PCS commands: PCS_CONFIG, + * PCS_GET_STATE and AN_RESTART take packed little-endian mode words + * instead of byte fields, PCS_ENABLE and AN_DISABLE were removed + * (bringup is implicit in PCS_CONFIG, power-down remains PCS_DISABLE) + * and command 0x1a07 became PCS_LINK_UP. The layouts below match the + * payload sizes the 1.0.85 dispatch table declares (10/12/4/6) and the + * field decoding of the disassembled 1.0.85 0x1a07 handler. + */ + +/** + * union mxl862xx_xpcs_an_word - XPCS AN code word, tagged by interface mode + * @cl37: 16-bit base page exchanged over the CL37 hardware AN path. + * Carries the 802.3 CL37 base page for 1000BASE-X/2500BASE-X and + * the Cisco SGMII config word for SGMII/QSGMII. + * @usx: USXGMII 16-bit AN code word, MDIO_USXGMII_* layout + * @cl73: CL73 48-bit base page (10GBASE-KR), per 802.3 Annex 28C + * @cl73.adv1: CL73 SR_AN_ADV1 / SR_AN_LP_ABL1 + * @cl73.adv2: CL73 SR_AN_ADV2 / SR_AN_LP_ABL2 + * @cl73.adv3: CL73 SR_AN_ADV3 / SR_AN_LP_ABL3 + * + * The host picks the member based on the interface field of the + * surrounding struct. + */ +union mxl862xx_xpcs_an_word { + __le16 cl37; + __le16 usx; + struct { + __le16 adv1; + __le16 adv2; + __le16 adv3; + } cl73; +} __packed; + +/* Fields of mxl862xx_xpcs_pcs_cfg_v2.mode */ +#define MXL862XX_XPCS_CFG_PORT_ID GENMASK(1, 0) +#define MXL862XX_XPCS_CFG_INTERFACE GENMASK(7, 2) +#define MXL862XX_XPCS_CFG_NEG_MODE GENMASK(9, 8) +#define MXL862XX_XPCS_CFG_PERMIT_PAUSE BIT(10) +#define MXL862XX_XPCS_CFG_USX_LANE_MODE GENMASK(12, 11) +#define MXL862XX_XPCS_CFG_ROLE BIT(13) +#define MXL862XX_XPCS_CFG_USX_SUBPORT GENMASK(15, 14) + +/* MXL862XX_XPCS_CFG_ROLE values */ +#define MXL862XX_XPCS_ROLE_MAC 0 +#define MXL862XX_XPCS_ROLE_PHY 1 + +/* usx_lane_mode values, all mode words */ +#define MXL862XX_XPCS_USX_SINGLE 0 +#define MXL862XX_XPCS_USX_QUAD 1 + +/** + * struct mxl862xx_xpcs_pcs_cfg_v2 - PCS configuration, firmware >= 1.0.84 + * @mode: packed input parameters, see MXL862XX_XPCS_CFG_*. port_id is + * the XPCS port index; interface is the PCS interface mode + * (enum mxl862xx_xpcs_if_mode); neg_mode is the negotiation mode + * (enum mxl862xx_xpcs_neg_mode); permit_pause allows pause to the + * MAC; usx_lane_mode is the USXGMII lane mode; role is the + * protocol role (MXL862XX_XPCS_ROLE_*); usx_subport selects the + * QSGMII/QUSXGMII sub-port + * @advertising: AN code word the local end transmits; member selected by + * the interface field. Ignored when the local end does not + * transmit an AN word or negotiation is not inband. + * @result: firmware result. >0 means the host must follow with an AN + * restart, 0 means no follow-up, <0 is a Zephyr errno + */ +struct mxl862xx_xpcs_pcs_cfg_v2 { + __le16 mode; + union mxl862xx_xpcs_an_word advertising; + __le16 result; +} __packed; + +static_assert(sizeof(struct mxl862xx_xpcs_pcs_cfg_v2) == 10); + +/* Fields of mxl862xx_xpcs_pcs_state_v2.mode */ +#define MXL862XX_XPCS_ST_PORT_ID GENMASK(1, 0) +#define MXL862XX_XPCS_ST_INTERFACE GENMASK(7, 2) +#define MXL862XX_XPCS_ST_USX_LANE_MODE GENMASK(9, 8) +#define MXL862XX_XPCS_ST_USX_SUBPORT GENMASK(11, 10) +#define MXL862XX_XPCS_ST_LINK BIT(12) +#define MXL862XX_XPCS_ST_AN_COMPLETE BIT(13) +#define MXL862XX_XPCS_ST_DUPLEX BIT(14) +#define MXL862XX_XPCS_ST_PCS_FAULT BIT(15) +#define MXL862XX_XPCS_ST_PAUSE GENMASK(17, 16) +#define MXL862XX_XPCS_ST_LP_EEE_CAP BIT(18) +#define MXL862XX_XPCS_ST_LP_EEE_CS_CAP BIT(19) + +/** + * struct mxl862xx_xpcs_pcs_state_v2 - PCS link state, firmware >= 1.0.84 + * @mode: packed input parameters and firmware status, see + * MXL862XX_XPCS_ST_*. The host writes port_id, interface, + * usx_lane_mode and usx_subport; the firmware fills in link, + * an_complete, duplex, pcs_fault, pause (bit 0 symmetric, bit 1 + * asymmetric), lp_eee_cap and lp_eee_cs_cap + * @speed: resolved speed in Mbit/s (output) + * @lpa: link partner ability word (output); member selected by the + * interface field + */ +struct mxl862xx_xpcs_pcs_state_v2 { + __le32 mode; + __le16 speed; + union mxl862xx_xpcs_an_word lpa; +} __packed; + +static_assert(sizeof(struct mxl862xx_xpcs_pcs_state_v2) == 12); + +/* Fields of mxl862xx_xpcs_an_restart_v2.mode */ +#define MXL862XX_XPCS_ANR_PORT_ID GENMASK(1, 0) +#define MXL862XX_XPCS_ANR_INTERFACE GENMASK(7, 2) +#define MXL862XX_XPCS_ANR_USX_LANE_MODE GENMASK(9, 8) +#define MXL862XX_XPCS_ANR_USX_SUBPORT GENMASK(11, 10) + +/** + * struct mxl862xx_xpcs_an_restart_v2 - AN restart, firmware >= 1.0.84 + * @mode: packed input parameters, see MXL862XX_XPCS_ANR_* + * @result: firmware result. 0 on success, <0 is a Zephyr errno + */ +struct mxl862xx_xpcs_an_restart_v2 { + __le16 mode; + __le16 result; +} __packed; + +static_assert(sizeof(struct mxl862xx_xpcs_an_restart_v2) == 4); + +/* Fields of mxl862xx_xpcs_pcs_link_up.mode */ +#define MXL862XX_XPCS_LU_PORT_ID GENMASK(1, 0) +#define MXL862XX_XPCS_LU_INTERFACE GENMASK(7, 2) +#define MXL862XX_XPCS_LU_DUPLEX BIT(8) +#define MXL862XX_XPCS_LU_USX_LANE_MODE GENMASK(10, 9) +#define MXL862XX_XPCS_LU_USX_SUBPORT GENMASK(12, 11) + +/** + * struct mxl862xx_xpcs_pcs_link_up - PCS link-up, firmware >= 1.0.84 + * @mode: packed input parameters, see MXL862XX_XPCS_LU_*. duplex is the + * duplex mode (enum mxl862xx_xpcs_duplex) + * @speed: resolved speed in Mbit/s + * @result: firmware result. 0 on success, <0 is a Zephyr errno + * + * Replaces FORCE_SPEED on command 0x1a07. Called once per link-up + * event after the host has resolved the line-side speed and duplex. + * The firmware acts on SGMII, USXGMII and QSGMII and succeeds without + * doing anything for the fixed-rate interface modes. + */ +struct mxl862xx_xpcs_pcs_link_up { + __le16 mode; + __le16 speed; + __le16 result; +} __packed; + +static_assert(sizeof(struct mxl862xx_xpcs_pcs_link_up) == 6); + /** * struct mxl862xx_xpcs_loopback_cfg - loopback control * @port_id: XPCS port index diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h index 93da8ab336c9b..6eb254a19f74e 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h @@ -34,6 +34,7 @@ #define MXL862XX_COMMON_REGISTERMOD (MXL862XX_COMMON_MAGIC + 0x11) #define MXL862XX_TFLOW_PCERULEWRITE (MXL862XX_TFLOW_MAGIC + 0x2) +#define MXL862XX_TFLOW_PCERULELOGICWRITE (MXL862XX_TFLOW_MAGIC + 0xa) #define MXL862XX_TFLOW_PCERULEALLOC (MXL862XX_TFLOW_MAGIC + 0x4) #define MXL862XX_TFLOW_PCERULEFREE (MXL862XX_TFLOW_MAGIC + 0x5) @@ -95,6 +96,8 @@ #define MXL862XX_XPCS_AN_RESTART (MXL862XX_XPCS_MAGIC + 0x5) #define MXL862XX_XPCS_AN_DISABLE (MXL862XX_XPCS_MAGIC + 0x6) #define MXL862XX_XPCS_FORCE_SPEED (MXL862XX_XPCS_MAGIC + 0x7) +/* Same command id as FORCE_SPEED; renamed and reshaped on firmware >= 1.0.84 */ +#define MXL862XX_XPCS_PCS_LINK_UP (MXL862XX_XPCS_MAGIC + 0x7) #define MXL862XX_XPCS_LOOPBACK (MXL862XX_XPCS_MAGIC + 0x8) #define MXL862XX_XPCS_RESET (MXL862XX_XPCS_MAGIC + 0x9) #define MXL862XX_XPCS_PRBS_CFG (MXL862XX_XPCS_MAGIC + 0xa) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c index f0a2e83c8eeee..8fdfb689333c8 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c @@ -7,6 +7,7 @@ * Copyright (C) 2025 Daniel Golle */ +#include #include #include #include @@ -534,13 +535,294 @@ static const struct phylink_pcs_ops mxl862xx_pcs_ops = { .pcs_inband_caps = mxl862xx_pcs_inband_caps, }; +/* Ops for the reshaped XPCS API (MXL862XX_CAP_XPCS_V2, firmware >= + * 1.0.84): PCS_CONFIG/PCS_GET_STATE/AN_RESTART take packed mode words, + * PCS_ENABLE and AN_DISABLE no longer exist (the bringup is idempotent + * and implicit in PCS_CONFIG, so there is no pre_config either) and + * 0x1a07 is PCS_LINK_UP. PCS_DISABLE is unchanged, so the v1 + * pcs_disable is reused. This tree drives each XPCS as a single lane + * with sub-port 0; the quad USXGMII fields stay zero. + */ + +static int mxl862xx_xpcs_errno(int result) +{ + switch (result) { + case -5: /* Zephyr -EIO */ + return -EIO; + case -134: /* Zephyr -ENOTSUP */ + return -EOPNOTSUPP; + default: /* Zephyr -EINVAL and anything unexpected */ + return -EINVAL; + } +} + +static int mxl862xx_pcs_v2_config(struct phylink_pcs *pcs, + unsigned int neg_mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs); + struct mxl862xx_priv *priv = mpcs->priv; + struct mxl862xx_xpcs_pcs_cfg_v2 cfg = {}; + int port = mpcs->port; + int if_mode, ret, adv; + + /* Sub-interfaces are set up implicitly by the main interface */ + if (port != 9 && port != 13) + return 0; + + if_mode = mxl862xx_xpcs_if_mode(interface); + if (if_mode < 0) { + dev_err(priv->ds->dev, "unsupported interface: %s\n", + phy_modes(interface)); + return if_mode; + } + + mpcs->if_mode = if_mode; + + cfg.mode = cpu_to_le16(FIELD_PREP(MXL862XX_XPCS_CFG_PORT_ID, + mxl862xx_xpcs_port_id(port)) | + FIELD_PREP(MXL862XX_XPCS_CFG_INTERFACE, + if_mode) | + FIELD_PREP(MXL862XX_XPCS_CFG_NEG_MODE, + mxl862xx_xpcs_neg_mode(neg_mode)) | + FIELD_PREP(MXL862XX_XPCS_CFG_USX_LANE_MODE, + MXL862XX_XPCS_USX_SINGLE) | + FIELD_PREP(MXL862XX_XPCS_CFG_ROLE, + MXL862XX_XPCS_ROLE_MAC) | + FIELD_PREP(MXL862XX_XPCS_CFG_PERMIT_PAUSE, + permit_pause_to_mac)); + + if (neg_mode & PHYLINK_PCS_NEG_INBAND) { + adv = phylink_mii_c22_pcs_encode_advertisement(interface, + advertising); + if (adv >= 0) + cfg.advertising.cl37 = cpu_to_le16(adv); + } + + ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_CONFIG, cfg); + if (ret) + return ret; + + ret = (s16)le16_to_cpu(cfg.result); + if (ret < 0) + return mxl862xx_xpcs_errno(ret); + + /* result > 0 means AN restart is needed */ + return ret > 0 ? 1 : 0; +} + +/* These interface modes carry no AN code word, so there is no + * negotiated pause to decode and phylink would otherwise resolve pause + * off. Report the flow control the switch port is configured with, + * which is what the legacy register path reports for every mode. + */ +static void mxl862xx_pcs_read_flow_ctrl(struct mxl862xx_priv *priv, int port, + struct phylink_link_state *state) +{ + struct mxl862xx_port_cfg port_cfg = { + .port_id = port, + }; + + if (MXL862XX_API_READ(priv, MXL862XX_COMMON_PORTCFGGET, port_cfg)) + return; + + state->pause &= ~(MLO_PAUSE_RX | MLO_PAUSE_TX); + switch (port_cfg.flow_ctrl) { + case MXL862XX_FLOW_RXTX: + state->pause |= MLO_PAUSE_TXRX_MASK; + break; + case MXL862XX_FLOW_TX: + state->pause |= MLO_PAUSE_TX; + break; + case MXL862XX_FLOW_RX: + state->pause |= MLO_PAUSE_RX; + break; + case MXL862XX_FLOW_OFF: + default: + break; + } +} + +static void mxl862xx_pcs_v2_get_state(struct phylink_pcs *pcs, + unsigned int neg_mode, + struct phylink_link_state *state) +{ + struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs); + struct mxl862xx_priv *priv = mpcs->priv; + struct mxl862xx_xpcs_pcs_state_v2 st = {}; + int port = mpcs->port; + int if_mode, ret; + u32 mode; + u16 bmsr; + + /* phylink presets state->link = 1 before calling pcs_get_state(); + * make sure a failed firmware read reports link down instead of a + * spurious link up with SPEED_UNKNOWN. + */ + state->link = false; + + if_mode = mxl862xx_xpcs_if_mode(state->interface); + if (if_mode < 0) + return; + + st.mode = cpu_to_le32(FIELD_PREP(MXL862XX_XPCS_ST_PORT_ID, + mxl862xx_xpcs_port_id(port)) | + FIELD_PREP(MXL862XX_XPCS_ST_INTERFACE, + if_mode) | + FIELD_PREP(MXL862XX_XPCS_ST_USX_LANE_MODE, + MXL862XX_XPCS_USX_SINGLE)); + + ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_GET_STATE, st); + if (ret) + return; + + mode = le32_to_cpu(st.mode); + state->link = FIELD_GET(MXL862XX_XPCS_ST_LINK, mode) && + !FIELD_GET(MXL862XX_XPCS_ST_PCS_FAULT, mode); + state->an_complete = FIELD_GET(MXL862XX_XPCS_ST_AN_COMPLETE, mode); + + switch (state->interface) { + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + bmsr = (state->link ? BMSR_LSTATUS : 0) | + (state->an_complete ? BMSR_ANEGCOMPLETE : 0); + phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, + le16_to_cpu(st.lpa.cl37)); + break; + + case PHY_INTERFACE_MODE_USXGMII: + if (state->link) + phylink_decode_usxgmii_word(state, + le16_to_cpu(st.lpa.usx)); + break; + + case PHY_INTERFACE_MODE_10GBASER: + case PHY_INTERFACE_MODE_10GKR: + if (state->link) { + state->speed = SPEED_10000; + state->duplex = DUPLEX_FULL; + mxl862xx_pcs_read_flow_ctrl(priv, port, state); + } + break; + + default: + state->link = false; + break; + } +} + +static void mxl862xx_pcs_v2_an_restart(struct phylink_pcs *pcs) +{ + struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs); + struct mxl862xx_priv *priv = mpcs->priv; + struct mxl862xx_xpcs_an_restart_v2 an = {}; + int port = mpcs->port; + + if (port != 9 && port != 13) + return; + + an.mode = cpu_to_le16(FIELD_PREP(MXL862XX_XPCS_ANR_PORT_ID, + mxl862xx_xpcs_port_id(port)) | + FIELD_PREP(MXL862XX_XPCS_ANR_INTERFACE, + mpcs->if_mode) | + FIELD_PREP(MXL862XX_XPCS_ANR_USX_LANE_MODE, + MXL862XX_XPCS_USX_SINGLE)); + + MXL862XX_API_WRITE(priv, MXL862XX_XPCS_AN_RESTART, an); +} + +static void mxl862xx_pcs_v2_link_up(struct phylink_pcs *pcs, + unsigned int neg_mode, + phy_interface_t interface, int speed, + int duplex) +{ + struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs); + struct mxl862xx_priv *priv = mpcs->priv; + struct mxl862xx_xpcs_pcs_link_up lu = {}; + int port = mpcs->port; + int if_mode, dup; + + if (port != 9 && port != 13) + return; + + /* With inband AN the XPCS resolves speed and duplex from the + * partner's AN word itself; skip the firmware round-trip. + */ + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) + return; + + if_mode = mxl862xx_xpcs_if_mode(interface); + if (if_mode < 0) + return; + + dup = (duplex == DUPLEX_FULL) ? MXL862XX_XPCS_DUPLEX_FULL : + MXL862XX_XPCS_DUPLEX_HALF; + + lu.mode = cpu_to_le16(FIELD_PREP(MXL862XX_XPCS_LU_PORT_ID, + mxl862xx_xpcs_port_id(port)) | + FIELD_PREP(MXL862XX_XPCS_LU_INTERFACE, + if_mode) | + FIELD_PREP(MXL862XX_XPCS_LU_USX_LANE_MODE, + MXL862XX_XPCS_USX_SINGLE) | + FIELD_PREP(MXL862XX_XPCS_LU_DUPLEX, dup)); + lu.speed = cpu_to_le16(speed); + + MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_LINK_UP, lu); +} + +static unsigned int mxl862xx_pcs_v2_inband_caps(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + switch (interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; + case PHY_INTERFACE_MODE_USXGMII: + case PHY_INTERFACE_MODE_10GKR: + return LINK_INBAND_ENABLE; + case PHY_INTERFACE_MODE_10GBASER: + return LINK_INBAND_DISABLE; + default: + return 0; + } +} + +static const struct phylink_pcs_ops mxl862xx_pcs_v2_ops = { + .pcs_disable = mxl862xx_pcs_disable, + .pcs_config = mxl862xx_pcs_v2_config, + .pcs_get_state = mxl862xx_pcs_v2_get_state, + .pcs_an_restart = mxl862xx_pcs_v2_an_restart, + .pcs_link_up = mxl862xx_pcs_v2_link_up, + .pcs_inband_caps = mxl862xx_pcs_v2_inband_caps, +}; + void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs, int port) { pcs->priv = priv; pcs->port = port; - if (MXL862XX_FW_VER_MIN(priv, 1, 0, 80)) + /* Keep the CPU port on the legacy path. Its link is fixed, so + * phylink resolves it from the fixed-link configuration and never + * calls pcs_get_state() for it, and the firmware refuses the + * negotiation commands for an instance configured into a + * fixed-rate mode, so the XPCS API adds nothing there. It also + * costs: PCS_CONFIG is not covered by that refusal and replaces + * the configuration the legacy path installs, after which the + * port keeps reporting link at 10G and forwards nothing, which + * takes the switch off the network it is managed over. + */ + if (dsa_is_cpu_port(priv->ds, port)) + pcs->pcs.ops = &mxl862xx_legacy_pcs_ops; + else if (mxl862xx_fw_has(priv, MXL862XX_CAP_XPCS_V2)) + pcs->pcs.ops = &mxl862xx_pcs_v2_ops; + else if (mxl862xx_fw_has(priv, MXL862XX_CAP_XPCS_API)) pcs->pcs.ops = &mxl862xx_pcs_ops; else pcs->pcs.ops = &mxl862xx_legacy_pcs_ops; @@ -636,7 +918,7 @@ static bool mxl862xx_port_has_serdes_stats(struct dsa_switch *ds, int port) struct mxl862xx_priv *priv = ds->priv; return port >= 9 && port <= 16 && - MXL862XX_FW_VER_MIN(priv, 1, 0, 80); + mxl862xx_fw_has(priv, MXL862XX_CAP_SERDES_STATS); } int mxl862xx_serdes_stats_count(struct dsa_switch *ds, int port) diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c index c2cc259c82cc1..5bde382a82902 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.c +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -314,6 +314,48 @@ static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int addr, return mxl862xx_phy_write_mmd(bus->priv, addr, devadd, regnum, val); } +/** + * mxl862xx_init_fw_caps - Derive the firmware capability mask + * @priv: driver private data + * + * Translates the cached firmware version into the capability bits the + * rest of the driver tests with mxl862xx_fw_has(). Called once from + * mxl862xx_wait_ready() after the version has been read. + * + * Keeping the version comparisons in one place means a firmware that + * gains or moves a feature only needs this function updated, rather + * than every call site that depends on the feature. + */ +static void mxl862xx_init_fw_caps(struct mxl862xx_priv *priv) +{ + u32 caps = 0; + + if (MXL862XX_FW_VER_MIN(priv, 1, 0, 83)) + caps |= MXL862XX_CAP_PCE_LOGIC_IDX; + + if (MXL862XX_FW_VER_MIN(priv, 1, 0, 80)) { + caps |= MXL862XX_CAP_XPCS_API | MXL862XX_CAP_SERDES_STATS; + + /* Firmware 1.0.84 reshaped the XPCS PCS commands: the + * PCS_CONFIG and PCS_GET_STATE payloads changed layout + * and size, PCS_ENABLE and AN_DISABLE were removed (the + * bringup became implicit in PCS_CONFIG) and FORCE_SPEED + * became PCS_LINK_UP. The Zephyr -ENOTSUP (-134) that + * XPCS_PCS_ENABLE draws on the BananaPi 1.0.85 build is + * the v2 firmware refusing a removed v1 command, not the + * API being absent: 0x1a03 and 0x1a06 are missing from + * the 1.0.85 dispatch table while the v2 payload sizes + * match it exactly. Select the matching ops generation. + */ + if (MXL862XX_FW_VER_MIN(priv, 1, 0, 84)) + caps |= MXL862XX_CAP_XPCS_V2; + } else { + caps |= MXL862XX_CAP_FW_GLOBAL_RULES; + } + + priv->fw_caps = caps; +} + static int mxl862xx_wait_ready(struct dsa_switch *ds) { struct mxl862xx_sys_fw_image_version ver = {}; @@ -348,6 +390,8 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds) priv->fw_version.minor = ver.iv_minor; priv->fw_version.revision = le16_to_cpu(ver.iv_revision); + mxl862xx_init_fw_caps(priv); + return 0; not_ready_yet: @@ -681,18 +725,57 @@ static void mxl862xx_fill_cpu_trap_action(struct dsa_switch *ds, int port, rule->action.fid = priv->cpu_trap_fid; } +/* Install one of the per-CTP protocol trap rules. + * + * Two firmware interfaces exist for this, taking the same 466-byte + * struct mxl862xx_pce_rule. With %MXL862XX_CAP_PCE_LOGIC_IDX the + * rule index is a logical index within the region selected by + * @region and @logicalportid, and the firmware grows the underlying + * block on demand. Without it, the index is a direct offset into a + * block the firmware pre-allocated for the CTP at init, and a write + * past the end of that block is refused. + * + * The older interface is what makes firmware 1.0.85 refuse some of + * these writes with -1022: the pre-allocated block is smaller than it + * was on 1.0.70, so the higher trap offsets fall outside it, and the + * refusal pattern follows the block size rather than the port type + * (port 0 accepts offsets 1-4, port 1 accepts only 1-3). + * + * The rules installed here only add link-local trapping and IGMP/MLD + * snooping and the switch forwards normally without them, so a + * rejection is still logged rather than propagated: failing the write + * would abort mxl862xx_refresh_cpu_targets() and leave the board with + * no user ports at all. + */ +static int mxl862xx_pce_trap_write(struct mxl862xx_priv *priv, + struct mxl862xx_pce_rule *rule) +{ + u16 cmd = mxl862xx_fw_has(priv, MXL862XX_CAP_PCE_LOGIC_IDX) ? + MXL862XX_TFLOW_PCERULELOGICWRITE : + MXL862XX_TFLOW_PCERULEWRITE; + int ret; + + ret = mxl862xx_api_wrap(priv, cmd, rule, sizeof(*rule), false, true); + if (ret) + dev_warn(&priv->mdiodev->dev, + "PCE trap rule rejected (port %u index %u): %pe\n", + rule->logicalportid, + le16_to_cpu(rule->pattern.index), ERR_PTR(ret)); + + return 0; +} + /* Install a PCE rule that traps IEEE 802.1D link-local frames * (01:80:c2:00:00:0x) to the CPU port for a single user port, * preventing the hardware bridge from flooding them to other ports. * The firmware does not install this rule by default because its own * STP module is not used when DSA manages STP. * - * The rule is written into the port's per-CTP flow table at offset 1. - * The firmware already allocates a 44-entry block for every CTP during - * init (8 entries exposed initially, expandable), so no dynamic - * allocation via PCERULEALLOC is needed. Using region=CTP causes the - * firmware to translate the CTP-relative offset into an absolute - * hardware index. + * The rule is written into the port's per-CTP flow table at index 1. + * Setting region=CTP makes the firmware resolve the index against that + * port's block rather than the global table, so no dynamic allocation + * via PCERULEALLOC is needed; see mxl862xx_pce_trap_write() for how the + * index is interpreted on either firmware interface. */ static int mxl862xx_setup_link_local_trap(struct dsa_switch *ds, int port) { @@ -710,7 +793,7 @@ static int mxl862xx_setup_link_local_trap(struct dsa_switch *ds, int port) mxl862xx_fill_cpu_trap_action(ds, port, &rule); - return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule); + return mxl862xx_pce_trap_write(priv, &rule); } /* Install PCE rules that trap IGMP and MLD frames to the CPU port for @@ -746,7 +829,7 @@ static int mxl862xx_setup_snooping_traps(struct dsa_switch *ds, int port) rule.pattern.protocol = IPPROTO_IGMP; rule.pattern.protocol_enable = 1; - ret = MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule); + ret = mxl862xx_pce_trap_write(priv, &rule); if (ret) return ret; @@ -765,7 +848,7 @@ static int mxl862xx_setup_snooping_traps(struct dsa_switch *ds, int port) rule.pattern.app_data_msb_enable = 1; rule.pattern.app_mask_range_msb_select = 1; /* range mode */ - ret = MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule); + ret = mxl862xx_pce_trap_write(priv, &rule); if (ret) return ret; @@ -782,7 +865,7 @@ static int mxl862xx_setup_snooping_traps(struct dsa_switch *ds, int port) rule.pattern.app_data_msb_enable = 1; /* app_mask_range_msb_select = 0: nibble mask mode (default) */ - return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule); + return mxl862xx_pce_trap_write(priv, &rule); } static bool mxl862xx_is_lag_master(const struct mxl862xx_priv *priv, int port) @@ -1217,7 +1300,7 @@ static int mxl862xx_setup(struct dsa_switch *ds) if (ret) return ret; - if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80)) { + if (mxl862xx_fw_has(priv, MXL862XX_CAP_FW_GLOBAL_RULES)) { ret = mxl862xx_disable_fw_global_rules(ds); if (ret) return ret; diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h index 52e538dd4bfa3..1f154b25bb2d7 100644 --- a/drivers/net/dsa/mxl862xx/mxl862xx.h +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h @@ -336,6 +336,41 @@ union mxl862xx_fw_version { #define MXL862XX_FW_VER_MIN(priv, maj, min, rev) \ ((priv)->fw_version.raw >= MXL862XX_FW_VER(maj, min, rev)) +/* Firmware capability bits, see struct mxl862xx_priv::fw_caps. + * + * Each bit describes one behaviour of the switch firmware that the + * driver has to adapt to. Capabilities are derived from the firmware + * version once in mxl862xx_wait_ready() and tested with + * mxl862xx_fw_has(); call sites must not compare versions directly, so + * that a firmware that moves a feature only needs the derivation + * updated. + * + * %MXL862XX_CAP_XPCS_API: the XPCS command family is implemented and + * the SerDes can be driven through it instead + * of the legacy register path + * %MXL862XX_CAP_SERDES_STATS: the equalisation, PRBS and signal-detect + * commands behind the SerDes ethtool statistics + * and self-test are implemented + * %MXL862XX_CAP_FW_GLOBAL_RULES: the firmware installs its own global PCE + * rules at init, which the driver has to + * disable because it manages the flow table + * itself + * %MXL862XX_CAP_PCE_LOGIC_IDX: PCE rules can be addressed by logical index + * within a region, and the firmware grows the + * underlying block on demand, instead of the + * index being a direct offset into a + * fixed-size pre-allocated block + * %MXL862XX_CAP_XPCS_V2: the XPCS PCS commands take the reshaped + * (firmware >= 1.0.84) payloads: packed mode + * words, PCS_ENABLE and AN_DISABLE removed, + * and PCS_LINK_UP in place of FORCE_SPEED + */ +#define MXL862XX_CAP_XPCS_API BIT(0) +#define MXL862XX_CAP_SERDES_STATS BIT(1) +#define MXL862XX_CAP_FW_GLOBAL_RULES BIT(2) +#define MXL862XX_CAP_PCE_LOGIC_IDX BIT(3) +#define MXL862XX_CAP_XPCS_V2 BIT(4) + /* Bit indices for struct mxl862xx_priv::flags */ #define MXL862XX_FLAG_CRC_ERR 0 #define MXL862XX_FLAG_WORK_STOPPED 1 @@ -372,7 +407,10 @@ struct combo_port_mux { * used to unconditionally drop traffic (used to block * flooding) * @fw_version: cached firmware version, populated at probe and - * compared with MXL862XX_FW_VER_MIN() + * used to derive @fw_caps + * @fw_caps: firmware capabilities derived from @fw_version at + * probe, a mask of %MXL862XX_CAP_ bits; test with + * mxl862xx_fw_has() * @serdes_ports: SerDes interfaces incl. sub-interfaces in case of * 10G_QXGMII * @ports: per-port state, indexed by switch port number @@ -413,6 +451,7 @@ struct mxl862xx_priv { enum dsa_tag_protocol tag_proto; u16 drop_meter; union mxl862xx_fw_version fw_version; + u32 fw_caps; struct mxl862xx_pcs serdes_ports[8]; struct mxl862xx_port ports[MXL862XX_MAX_PORTS]; u16 bridges[MXL862XX_MAX_BRIDGES + 1]; @@ -430,4 +469,16 @@ struct mxl862xx_priv { struct combo_port_mux *ds_mux[MXL862XX_MAX_PORTS]; }; +/** + * mxl862xx_fw_has - Test whether the firmware provides a capability + * @priv: driver private data + * @cap: single %MXL862XX_CAP_ bit to test + * + * Return: true if the running firmware provides @cap. + */ +static inline bool mxl862xx_fw_has(const struct mxl862xx_priv *priv, u32 cap) +{ + return !!(priv->fw_caps & cap); +} + #endif /* __MXL862XX_H */