From 15593de3768242d3c459811159e1c9a7339c8ceb Mon Sep 17 00:00:00 2001 From: Jonas Jelonek Date: Fri, 22 May 2026 20:14:20 +0000 Subject: [PATCH] realtek: pcs: derive SerDes link count from DT at probe time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, sds->num_of_links was incremented from rtpcs_create() as each DSA port bound its phylink_pcs. The count therefore relied on a temporal contract (DSA must finish enumerating before pcs_config runs) and on rtpcs_create() being the single chokepoint for all consumers. Replace this with a probe-time scan of pcs-handle references in the live OF tree: for every available consumer node carrying a pcs-handle property pointing at one of our SerDes subnodes, bump that SerDes' num_of_links. After the scan, the count is final regardless of when or whether DSA later calls in. To allow of_parse_phandle_with_args() to walk the property correctly, add #pcs-cells = <0> to every serdes@N node in the 838x/839x/930x/931x .dtsi files. A future cell-bearing form remains possible without touching the scan. Over-references (DT pointing more consumers at one SerDes than the hardware can carry) are clamped at RTPCS_MAX_LINKS_PER_SDS and warned about, but do not fail probe — the correctly-wired ports on that SerDes still come up, and only the surplus reference is dropped. The bounds check and the bare ++ in rtpcs_create() become redundant under the scan-driven count and are removed. This decouples num_of_links from DSA call ordering and is a prereq for migrating to fwnode_pcs providers, where rtpcs_create() goes away as the centralised counter. Link: https://github.com/openwrt/openwrt/pull/23484 Signed-off-by: Jonas Jelonek --- target/linux/realtek/dts/rtl838x.dtsi | 6 +++ target/linux/realtek/dts/rtl839x.dtsi | 14 +++++++ target/linux/realtek/dts/rtl930x.dtsi | 12 ++++++ target/linux/realtek/dts/rtl931x.dtsi | 14 +++++++ .../files-6.18/drivers/net/pcs/pcs-rtl-otto.c | 40 +++++++++++++++++-- 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/target/linux/realtek/dts/rtl838x.dtsi b/target/linux/realtek/dts/rtl838x.dtsi index 911abe7ad0..900004a0e8 100644 --- a/target/linux/realtek/dts/rtl838x.dtsi +++ b/target/linux/realtek/dts/rtl838x.dtsi @@ -252,21 +252,27 @@ serdes0: serdes@0 { reg = <0>; + #pcs-cells = <0>; }; serdes1: serdes@1 { reg = <1>; + #pcs-cells = <0>; }; serdes2: serdes@2 { reg = <2>; + #pcs-cells = <0>; }; serdes3: serdes@3 { reg = <3>; + #pcs-cells = <0>; }; serdes4: serdes@4 { reg = <4>; + #pcs-cells = <0>; }; serdes5: serdes@5 { reg = <5>; + #pcs-cells = <0>; }; }; diff --git a/target/linux/realtek/dts/rtl839x.dtsi b/target/linux/realtek/dts/rtl839x.dtsi index 48b383687d..ed388948f7 100644 --- a/target/linux/realtek/dts/rtl839x.dtsi +++ b/target/linux/realtek/dts/rtl839x.dtsi @@ -265,45 +265,59 @@ serdes0: serdes@0 { reg = <0>; + #pcs-cells = <0>; }; serdes1: serdes@1 { reg = <1>; + #pcs-cells = <0>; }; serdes2: serdes@2 { reg = <2>; + #pcs-cells = <0>; }; serdes3: serdes@3 { reg = <3>; + #pcs-cells = <0>; }; serdes4: serdes@4 { reg = <4>; + #pcs-cells = <0>; }; serdes5: serdes@5 { reg = <5>; + #pcs-cells = <0>; }; serdes6: serdes@6 { reg = <6>; + #pcs-cells = <0>; }; serdes7: serdes@7 { reg = <7>; + #pcs-cells = <0>; }; serdes8: serdes@8 { reg = <8>; + #pcs-cells = <0>; }; serdes9: serdes@9 { reg = <9>; + #pcs-cells = <0>; }; serdes10: serdes@10 { reg = <10>; + #pcs-cells = <0>; }; serdes11: serdes@11 { reg = <11>; + #pcs-cells = <0>; }; serdes12: serdes@12 { reg = <12>; + #pcs-cells = <0>; }; serdes13: serdes@13 { reg = <13>; + #pcs-cells = <0>; }; }; diff --git a/target/linux/realtek/dts/rtl930x.dtsi b/target/linux/realtek/dts/rtl930x.dtsi index 3096c78d8d..8474d5013b 100644 --- a/target/linux/realtek/dts/rtl930x.dtsi +++ b/target/linux/realtek/dts/rtl930x.dtsi @@ -252,39 +252,51 @@ serdes0: serdes@0 { reg = <0>; + #pcs-cells = <0>; }; serdes1: serdes@1 { reg = <1>; + #pcs-cells = <0>; }; serdes2: serdes@2 { reg = <2>; + #pcs-cells = <0>; }; serdes3: serdes@3 { reg = <3>; + #pcs-cells = <0>; }; serdes4: serdes@4 { reg = <4>; + #pcs-cells = <0>; }; serdes5: serdes@5 { reg = <5>; + #pcs-cells = <0>; }; serdes6: serdes@6 { reg = <6>; + #pcs-cells = <0>; }; serdes7: serdes@7 { reg = <7>; + #pcs-cells = <0>; }; serdes8: serdes@8 { reg = <8>; + #pcs-cells = <0>; }; serdes9: serdes@9 { reg = <9>; + #pcs-cells = <0>; }; serdes10: serdes@10 { reg = <10>; + #pcs-cells = <0>; }; serdes11: serdes@11 { reg = <11>; + #pcs-cells = <0>; }; }; diff --git a/target/linux/realtek/dts/rtl931x.dtsi b/target/linux/realtek/dts/rtl931x.dtsi index 1e1b855dfb..c6581eb30a 100644 --- a/target/linux/realtek/dts/rtl931x.dtsi +++ b/target/linux/realtek/dts/rtl931x.dtsi @@ -284,45 +284,59 @@ serdes0: serdes@0 { reg = <0>; + #pcs-cells = <0>; }; serdes1: serdes@1 { reg = <1>; + #pcs-cells = <0>; }; serdes2: serdes@2 { reg = <2>; + #pcs-cells = <0>; }; serdes3: serdes@3 { reg = <3>; + #pcs-cells = <0>; }; serdes4: serdes@4 { reg = <4>; + #pcs-cells = <0>; }; serdes5: serdes@5 { reg = <5>; + #pcs-cells = <0>; }; serdes6: serdes@6 { reg = <6>; + #pcs-cells = <0>; }; serdes7: serdes@7 { reg = <7>; + #pcs-cells = <0>; }; serdes8: serdes@8 { reg = <8>; + #pcs-cells = <0>; }; serdes9: serdes@9 { reg = <9>; + #pcs-cells = <0>; }; serdes10: serdes@10 { reg = <10>; + #pcs-cells = <0>; }; serdes11: serdes@11 { reg = <11>; + #pcs-cells = <0>; }; serdes12: serdes@12 { reg = <12>; + #pcs-cells = <0>; }; serdes13: serdes@13 { reg = <13>; + #pcs-cells = <0>; }; }; diff --git a/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c b/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c index 1668c61fe4..c93a08c1fa 100644 --- a/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c +++ b/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c @@ -4107,8 +4107,6 @@ struct phylink_pcs *rtpcs_create(struct device *dev, struct device_node *np, int sds = &ctrl->serdes[sds_id]; if (rtpcs_sds_read(sds, 0, 0) < 0) return ERR_PTR(-EINVAL); - if (sds->num_of_links >= RTPCS_MAX_LINKS_PER_SDS) - return ERR_PTR(-ERANGE); link = devm_kzalloc(ctrl->dev, sizeof(*link), GFP_KERNEL); if (!link) { @@ -4118,7 +4116,6 @@ struct phylink_pcs *rtpcs_create(struct device *dev, struct device_node *np, int device_link_add(dev, ctrl->dev, DL_FLAG_AUTOREMOVE_CONSUMER); - sds->num_of_links++; link->ctrl = ctrl; link->port = port; link->sds = sds; @@ -4166,6 +4163,41 @@ static void rtpcs_sds_put_of_node(void *data) of_node_put(sds->of_node); } +static void rtpcs_count_links(struct rtpcs_ctrl *ctrl) +{ + struct device_node *consumer __free(device_node) = NULL; + struct of_phandle_args args; + + for_each_node_with_property(consumer, "pcs-handle") { + int idx = 0; + + if (!of_device_is_available(consumer)) + continue; + + while (!of_parse_phandle_with_args(consumer, "pcs-handle", + "#pcs-cells", idx++, &args)) { + struct device_node *arg_np __free(device_node) = args.np; + + for (int s = 0; s < ctrl->cfg->serdes_count; s++) { + struct rtpcs_serdes *sds = &ctrl->serdes[s]; + + if (arg_np != sds->of_node) + continue; + + if (sds->num_of_links >= RTPCS_MAX_LINKS_PER_SDS) { + dev_warn(ctrl->dev, + "%pOF: pcs-handle to sds%u exceeds max %u, clamping\n", + consumer, sds->id, RTPCS_MAX_LINKS_PER_SDS); + break; + } + + sds->num_of_links++; + break; + } + } + } +} + static int rtpcs_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -4220,6 +4252,8 @@ static int rtpcs_probe(struct platform_device *pdev) return ret; } + rtpcs_count_links(ctrl); + if (ctrl->cfg->init) { ret = ctrl->cfg->init(ctrl); if (ret)