Re: [net-next PATCH v4 4/4] net: pcs: xpcs: Add handling for 4 channel rsfec device
Jakub Kicinski <[email protected]> Mon, 3 Aug 2026 18:48:44 -0700
| Newsgroups | org.kernel.vger.netdev,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible. --- --- Patch [4]: [net-next PATCH v4 4/4] net: pcs: xpcs: Add handling for 4 channel rsfec device --- commit 7b16dc0f9f781491095c890579bd7608f7d1a49f Author: Mike Marciniszyn (Meta) <[email protected]> net: pcs: xpcs: Add handling for 4 channel rsfec device This adds programming of the DW PCS vendor registers for the alignment marker encodings, the PCS mode and the alignment marker insertion interval, repeated over each configured channel. Marker tables for 100G and 50G are added and selected per interface mode. No Link: tags are present in the commit header. A few questions on the new registers, constants and comments below. > diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c > index 3800c129dae2e..8f990291d5190 100644 > --- a/drivers/net/pcs/pcs-xpcs.c > +++ b/drivers/net/pcs/pcs-xpcs.c > @@ -1394,11 +1394,67 @@ static int xpcs_read_ids(struct dw_xpcs *xpcs) > return 0; > } > > +/* m0 - m2 from Table 82-2/82-3 > + * m4 - m6 are skipped since they are inversions of m0 - m2. > + * Inverted parity fields (IEEE 82.2.8) bip3 and bip7 are omitted. > + */ > +struct lane_markers { > + u8 m0, m1, m2; > +}; > + > +/* Alignment marker encodings, see table 82-2 in IEEE 802.3-2022 */ > +static const struct lane_markers xpcs_100gbaser_markers[] = { > + {0xc1, 0x68, 0x21}, > + {0x9d, 0x71, 0x8e}, > + {0x59, 0x4b, 0xe8}, > + {0x4d, 0x95, 0x7b}, > +}; > + > +/* Alignment marker encodings, see table 82-3 in IEEE 802.3-2022 > + * The content of the 50G markers is identical to 40G values (IEEE 133.2.2). > + */ > +static const struct lane_markers xpcs_50gbaser_markers[] = { > + {0x90, 0x76, 0x47}, > + {0xf0, 0xc4, 0xe6}, > + {0xc5, 0x65, 0x9b}, > + {0xa2, 0x79, 0x3d}, > +}; > + > struct pma_pcs_values { > + const struct lane_markers *vl0_markers; > + const struct lane_markers *vl123_markers; > int channels; > u16 rsfec_ctrl; > + u16 pcs_mode; > + u16 vl_intvl; > }; > > +#define XPCS_VL_TO_REG(vl, lh) \ > + (((vl) * 2) + DW_VR_MII_PCS_VL0_##lh) > + > +static int > +xpcs_write_pcs_ch(struct dw_xpcs *xpcs, int ch, int reg, u16 val) > +{ > + return xpcs_mdev_write_ch(xpcs, ch, MDIO_MMD_PCS, reg, val); > +} > + > +static int xpcs_config_ch_vl_markers(struct dw_xpcs *xpcs, int ch, int vl, > + const struct lane_markers *m) > +{ > + int ret; > + > + /* m0, m1, m2 written to _L and _H registers > + * > + * _L = (m1 << 8) | m0 > + * _H = m2 > + */ > + ret = xpcs_write_pcs_ch(xpcs, ch, XPCS_VL_TO_REG(vl, L), > + ((u16)m->m1 << 8) | m->m0); > + if (ret < 0) > + return ret; > + return xpcs_write_pcs_ch(xpcs, ch, XPCS_VL_TO_REG(vl, H), m->m2); > +} > + > static int > xpcs_config_rsfec_pma(struct dw_xpcs *xpcs, const struct pma_pcs_values *v) > { > @@ -1407,9 +1463,28 @@ xpcs_config_rsfec_pma(struct dw_xpcs *xpcs, const struct pma_pcs_values *v) > * work for enabling a non-fbnic device on this PCS. > */ > int pma_mmd = MDIO_MMD_SEP_PMA1; > - int ret = 0, i; > + int ret = 0, i, vl; > > for (i = 0; ret >= 0 && i < v->channels; i++) { > + /* code word markings */ > + for (vl = 0; ret >= 0 && vl < 4; vl++) { > + const struct lane_markers *m; > + > + m = vl ? v->vl123_markers + vl - 1 : v->vl0_markers; > + ret = xpcs_config_ch_vl_markers(xpcs, i, vl, m); > + } > + if (ret < 0) > + break; > + /* vendor registers */ > + ret = xpcs_write_pcs_ch(xpcs, i, > + DW_VR_MII_PCS_VL_INTVL, v->vl_intvl); > + if (ret < 0) > + break; > + ret = xpcs_write_pcs_ch(xpcs, i, > + DW_VR_MII_PCS_PCS_MODE, v->pcs_mode); > + if (ret < 0) > + break; > + /* rsfec register */ > ret = xpcs_mdev_write_ch(xpcs, i, pma_mmd, > MDIO_PMA_RSFEC_CTRL, v->rsfec_ctrl); > } [Severity: Low] [Finding: main-1-3] [Sources: opus-5] This isn't a bug, but should xpcs_config_rsfec_pma() keep its name now that it also programs PCS layer registers? It is registered as the .pma_config callback of struct dw_xpcs_compat and invoked from xpcs_config(): if (compat->pma_config) { ret = compat->pma_config(xpcs); After this patch the body writes MDIO_MMD_PCS vendor registers (the alignment markers 0x8008-0x800f via xpcs_config_ch_vl_markers(), the VL interval 0x8002 and the PCS mode 0x8010) in addition to the single MDIO_MMD_SEP_PMA1 MDIO_PMA_RSFEC_CTRL write. The other .pma_config implementations stay inside PMA/VEND2 register space: nxp_sja1105_sgmii_pma_config() writes DW_VR_MII_DIG_CTRL2 in VEND2, nxp_sja1110_pma_config() writes the MDIO_MMD_VEND2 PMA PLL registers, and txgbe_pma_config_*() write PMA registers. Would renaming it (xpcs_config_pcs_pma or xpcs_config_mode) or splitting the PCS layer programming into its own step make the layering clearer? > @@ -1422,6 +1497,13 @@ static int xpcs_25gbaser_pma_config(struct dw_xpcs *xpcs) > const struct pma_pcs_values v = { > .rsfec_ctrl = 0, > .channels = 1, > + /* 25g markers from 100g and 50g tables per 802.3-2022 > + * 108.5.2.4 > + */ > + .vl0_markers = &xpcs_100gbaser_markers[0], > + .vl123_markers = &xpcs_50gbaser_markers[1], > + .vl_intvl = 20479, > + .pcs_mode = DW_VR_MII_PCS_MODE_CLAUSE107, > }; > > return xpcs_config_rsfec_pma(xpcs, &v); [Severity: Low] [Finding: main-1-2] [Sources: opus-5] Where do the .vl_intvl values come from? The commit message says: The encoding tables are derived from the IEEE 8023-2022 spec sections 82.2.7 and tables 82-2 and 82-3 for the alignment markers and their insertion. 82.2.7 specifies a 16384 block insertion period, which matches the 16383 used by xpcs_100gbasep_pma_config(), but the 20479 used here and by xpcs_50gbaser_pma_config()/xpcs_50gbaser2_pma_config() corresponds to the Clause 133/108 50G and 25G period, which the message does not cite. Could the reference for 20479 and the reason for the -1 bias be spelled out? The two values also need different widths (16383 is 0x3fff, 14 bits; 20479 is 0x4fff, 15 bits) and are written raw with no #define and no GENMASK()/FIELD_PREP(), unlike the other multi-bit fields in this header such as DW_VR_MII_EEE_MULT_FACT_100NS. Is the interval field width documented anywhere? If it were [13:0], 20479 would truncate to 4095 and bit 14 would land on a neighbouring control bit, and an MDIO write cannot report that. Also, the lane count is hardcoded in xpcs_config_rsfec_pma(): for (vl = 0; ret >= 0 && vl < 4; vl++) { Would it be better as a field of pma_pcs_values, or tied to ARRAY_SIZE() of the marker tables and the documented 0x8008-0x800f window? As written, the single channel 25G and 50G-R1 configurations still program the VL1..VL3 marker sets, so the table does not express which fields are meaningful per mode. Last, the subject says "4 channel rsfec device" while DW_XPCS_MAX_CHANNELS is 2 ("2 channels per group DW PCS devices") and the largest .channels used is 2, which also reads differently from the message body ("over the one or two instances as required"). [ ... ] > @@ -1442,6 +1528,10 @@ static int xpcs_50gbaser2_pma_config(struct dw_xpcs *xpcs) > const struct pma_pcs_values v = { > .rsfec_ctrl = DW_VR_RSFEC_CTRL_TC_PAD_ALTER, > .channels = 2, > + .vl0_markers = &xpcs_50gbaser_markers[0], > + .vl123_markers = &xpcs_50gbaser_markers[1], > + .pcs_mode = 0, > + .vl_intvl = 20479, > }; > > return xpcs_config_rsfec_pma(xpcs, &v); > @@ -1452,6 +1542,10 @@ static int xpcs_100gbasep_pma_config(struct dw_xpcs *xpcs) > const struct pma_pcs_values v = { > .rsfec_ctrl = MDIO_PMA_RSFEC_CTRL_4LANE_PMD, > .channels = 2, > + .vl0_markers = &xpcs_100gbaser_markers[0], > + .vl123_markers = &xpcs_100gbaser_markers[1], > + .pcs_mode = DW_VR_MII_PCS_MODE_DISABLE_MLD, > + .vl_intvl = 16383, > }; > > return xpcs_config_rsfec_pma(xpcs, &v); [Severity: Medium] [Finding: main-2-5] [Sources: opus-5] Why do the two .channels = 2 configurations differ in MLD handling and in the marker interval? The commit message explains the repetition with: The DW PCS IP specification calls out the need to configure both lanes identically when using 2 lane modes such as 50-R2 and 100-R2, so the programming is repeated for each channel. but xpcs_100gbasep_pma_config() sets DW_VR_MII_PCS_MODE_DISABLE_MLD and .vl_intvl = 16383, while xpcs_50gbaser2_pma_config() leaves .pcs_mode = 0 and uses 20479. On top of that, the RS-FEC control value here is MDIO_PMA_RSFEC_CTRL_4LANE_PMD, a 4 lane PMD bit. PHY_INTERFACE_MODE_100GBASEP is described in include/linux/phy.h as "100GBase-P - with Clause 134 FEC", and fbnic maps a 2 lane AUI to it: drivers/net/ethernet/meta/fbnic/fbnic_phylink.c: case FBNIC_AUI_100GAUI2: return PHY_INTERFACE_MODE_100GBASEP; 16383 is the Clause 82 (40G/100G-R4) insertion period and 20479 is the Clause 133 50G period used by the sibling 2 channel configuration. Is 100GBASEP here intended as a Clause 133 100GBASE-R2 (which would suggest 20479 and MLD semantics like 50G-R2), or as a Clause 82/91 4 lane 100G? And is programming only four virtual lane marker sets correct for it? Every fbnic 100G link brings this path up through xpcs_config() -> compat->pma_config(), so if the values do not match the hardware the alignment marker/FEC lock would fail and the link would not come up. > diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h > index ab47d6dd050c7..adb7b1c13c2df 100644 > --- a/drivers/net/pcs/pcs-xpcs.h > +++ b/drivers/net/pcs/pcs-xpcs.h > @@ -100,6 +100,31 @@ > */ > #define DW_VR_RSFEC_CTRL_TC_PAD_ALTER BIT(10) > > +/* Vendor specific 4 channel PCS registers */ > + > +/* DW_VR_MII_PCS_VL_INTVL and DW_VR_MII_AN_INTR_STS conflict > + * but code paths are different > + */ > +#define DW_VR_MII_PCS_VL_INTVL 0x8002 [Severity: Low] [Finding: main-3-7] [Sources: opus-5] Is there really a conflict to document here? The new register is only ever reached through xpcs_write_pcs_ch(): return xpcs_mdev_write_ch(xpcs, ch, MDIO_MMD_PCS, reg, val); that is MMD 3, while every access to DW_VR_MII_AN_INTR_STS uses MDIO_MMD_VEND2, that is MMD 31: ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0); C45 register space is per MMD, so MMD3:0x8002 and MMD31:0x8002 are simply unrelated registers. The same applies to the commit message: There is also a conflict between DW_VR_MII_PCS_VL_INTVL and DW_VR_MII_AN_INTR_STS but an_mode differs, so again there is no issue. Would "different MMD" be the accurate reason instead of "code paths are different" and "an_mode differs"? As written, a later reader could conclude the MMD selection does not matter, or add an an_mode based guard that has no purpose. Two smaller wording points in the same patch. In the lane_markers comment: Inverted parity fields (IEEE 82.2.8) bip3 and bip7 are omitted. bip3 is the parity field and bip7 is its inversion, so calling both "inverted parity fields" reads incorrectly. Also the spec references such as "IEEE 82.2.8" and "IEEE 133.2.2" omit 802.3, could they be written as IEEE 802.3 clause 82.2.8 and clause 133.2.2? > +/* 0x8008 - 0x800f */ > +#define DW_VR_MII_PCS_VL0_L 0x8008 > +#define DW_VR_MII_PCS_VL0_H 0x8009 > +#define DW_VR_MII_PCS_PCS_MODE 0x8010 > + > +/* DW_VR_MII_PCS_PCS_MODE bits */ > +#define DW_VR_MII_PCS_MODE_HI_BER25 BIT(2) > +#define DW_VR_MII_PCS_MODE_DISABLE_MLD BIT(1) > +#define DW_VR_MII_PCS_MODE_CLAUSE49 BIT(0) [Severity: Low] [Finding: main-1-1] [Sources: opus-5] Is the DW_VR_MII_ prefix the right one for registers that live in the PCS MMD? Every pre-existing DW_VR_MII_* define in this header is a VR MII MMD register: DW_VR_MII_AN_CTRL and DW_VR_MII_AN_INTR_STS are accessed with MDIO_MMD_VEND2, and DW_VR_MII_DIG_CTRL2 likewise in pcs-xpcs-nxp.c. The PCS MMD vendor registers use the DW_VR_XS_PCS_ prefix with the DW_VENDOR bit added at access time by xpcs_read_vendor()/xpcs_write_vendor(), whereas these new defines hard-code the DW_VENDOR bit (0x8002/0x8008/0x8010), which is exactly how DW_VR_MII_PCS_PCS_MODE ends up equal to DW_VENDOR | DW_VR_XS_PCS_DIG_STS. The new bit names also share a prefix with existing defines for an unrelated register. DW_VR_MII_AN_CTRL (0x8001, MMD 31) already has: #define DW_VR_MII_PCS_MODE_MASK GENMASK(2, 1) #define DW_VR_MII_PCS_MODE_C37_1000BASEX 0x0 #define DW_VR_MII_PCS_MODE_C37_SGMII 0x2 which xpcs_config_aneg_c37_sgmii() uses as field values: mask = DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK; val = FIELD_PREP(DW_VR_MII_PCS_MODE_MASK, DW_VR_MII_PCS_MODE_C37_SGMII); A later xpcs_modify() on the new 0x8010 register that picks up the plausible looking DW_VR_MII_PCS_MODE_MASK would silently drop DW_VR_MII_PCS_MODE_CLAUSE49 (BIT(0)). Could the new register and bit names be given a distinct prefix, and the DW_VENDOR bit be applied through the existing vendor accessors?