1
1

realtek: pcs: derive SerDes link count from DT at probe time

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 <jelonek.jonas@gmail.com>
This commit is contained in:
Jonas Jelonek 2026-05-22 20:14:20 +00:00
parent a7bd3b147e
commit 15593de376
No known key found for this signature in database
5 changed files with 83 additions and 3 deletions

View File

@ -252,21 +252,27 @@
serdes0: serdes@0 { serdes0: serdes@0 {
reg = <0>; reg = <0>;
#pcs-cells = <0>;
}; };
serdes1: serdes@1 { serdes1: serdes@1 {
reg = <1>; reg = <1>;
#pcs-cells = <0>;
}; };
serdes2: serdes@2 { serdes2: serdes@2 {
reg = <2>; reg = <2>;
#pcs-cells = <0>;
}; };
serdes3: serdes@3 { serdes3: serdes@3 {
reg = <3>; reg = <3>;
#pcs-cells = <0>;
}; };
serdes4: serdes@4 { serdes4: serdes@4 {
reg = <4>; reg = <4>;
#pcs-cells = <0>;
}; };
serdes5: serdes@5 { serdes5: serdes@5 {
reg = <5>; reg = <5>;
#pcs-cells = <0>;
}; };
}; };

View File

@ -265,45 +265,59 @@
serdes0: serdes@0 { serdes0: serdes@0 {
reg = <0>; reg = <0>;
#pcs-cells = <0>;
}; };
serdes1: serdes@1 { serdes1: serdes@1 {
reg = <1>; reg = <1>;
#pcs-cells = <0>;
}; };
serdes2: serdes@2 { serdes2: serdes@2 {
reg = <2>; reg = <2>;
#pcs-cells = <0>;
}; };
serdes3: serdes@3 { serdes3: serdes@3 {
reg = <3>; reg = <3>;
#pcs-cells = <0>;
}; };
serdes4: serdes@4 { serdes4: serdes@4 {
reg = <4>; reg = <4>;
#pcs-cells = <0>;
}; };
serdes5: serdes@5 { serdes5: serdes@5 {
reg = <5>; reg = <5>;
#pcs-cells = <0>;
}; };
serdes6: serdes@6 { serdes6: serdes@6 {
reg = <6>; reg = <6>;
#pcs-cells = <0>;
}; };
serdes7: serdes@7 { serdes7: serdes@7 {
reg = <7>; reg = <7>;
#pcs-cells = <0>;
}; };
serdes8: serdes@8 { serdes8: serdes@8 {
reg = <8>; reg = <8>;
#pcs-cells = <0>;
}; };
serdes9: serdes@9 { serdes9: serdes@9 {
reg = <9>; reg = <9>;
#pcs-cells = <0>;
}; };
serdes10: serdes@10 { serdes10: serdes@10 {
reg = <10>; reg = <10>;
#pcs-cells = <0>;
}; };
serdes11: serdes@11 { serdes11: serdes@11 {
reg = <11>; reg = <11>;
#pcs-cells = <0>;
}; };
serdes12: serdes@12 { serdes12: serdes@12 {
reg = <12>; reg = <12>;
#pcs-cells = <0>;
}; };
serdes13: serdes@13 { serdes13: serdes@13 {
reg = <13>; reg = <13>;
#pcs-cells = <0>;
}; };
}; };

View File

@ -252,39 +252,51 @@
serdes0: serdes@0 { serdes0: serdes@0 {
reg = <0>; reg = <0>;
#pcs-cells = <0>;
}; };
serdes1: serdes@1 { serdes1: serdes@1 {
reg = <1>; reg = <1>;
#pcs-cells = <0>;
}; };
serdes2: serdes@2 { serdes2: serdes@2 {
reg = <2>; reg = <2>;
#pcs-cells = <0>;
}; };
serdes3: serdes@3 { serdes3: serdes@3 {
reg = <3>; reg = <3>;
#pcs-cells = <0>;
}; };
serdes4: serdes@4 { serdes4: serdes@4 {
reg = <4>; reg = <4>;
#pcs-cells = <0>;
}; };
serdes5: serdes@5 { serdes5: serdes@5 {
reg = <5>; reg = <5>;
#pcs-cells = <0>;
}; };
serdes6: serdes@6 { serdes6: serdes@6 {
reg = <6>; reg = <6>;
#pcs-cells = <0>;
}; };
serdes7: serdes@7 { serdes7: serdes@7 {
reg = <7>; reg = <7>;
#pcs-cells = <0>;
}; };
serdes8: serdes@8 { serdes8: serdes@8 {
reg = <8>; reg = <8>;
#pcs-cells = <0>;
}; };
serdes9: serdes@9 { serdes9: serdes@9 {
reg = <9>; reg = <9>;
#pcs-cells = <0>;
}; };
serdes10: serdes@10 { serdes10: serdes@10 {
reg = <10>; reg = <10>;
#pcs-cells = <0>;
}; };
serdes11: serdes@11 { serdes11: serdes@11 {
reg = <11>; reg = <11>;
#pcs-cells = <0>;
}; };
}; };

View File

@ -284,45 +284,59 @@
serdes0: serdes@0 { serdes0: serdes@0 {
reg = <0>; reg = <0>;
#pcs-cells = <0>;
}; };
serdes1: serdes@1 { serdes1: serdes@1 {
reg = <1>; reg = <1>;
#pcs-cells = <0>;
}; };
serdes2: serdes@2 { serdes2: serdes@2 {
reg = <2>; reg = <2>;
#pcs-cells = <0>;
}; };
serdes3: serdes@3 { serdes3: serdes@3 {
reg = <3>; reg = <3>;
#pcs-cells = <0>;
}; };
serdes4: serdes@4 { serdes4: serdes@4 {
reg = <4>; reg = <4>;
#pcs-cells = <0>;
}; };
serdes5: serdes@5 { serdes5: serdes@5 {
reg = <5>; reg = <5>;
#pcs-cells = <0>;
}; };
serdes6: serdes@6 { serdes6: serdes@6 {
reg = <6>; reg = <6>;
#pcs-cells = <0>;
}; };
serdes7: serdes@7 { serdes7: serdes@7 {
reg = <7>; reg = <7>;
#pcs-cells = <0>;
}; };
serdes8: serdes@8 { serdes8: serdes@8 {
reg = <8>; reg = <8>;
#pcs-cells = <0>;
}; };
serdes9: serdes@9 { serdes9: serdes@9 {
reg = <9>; reg = <9>;
#pcs-cells = <0>;
}; };
serdes10: serdes@10 { serdes10: serdes@10 {
reg = <10>; reg = <10>;
#pcs-cells = <0>;
}; };
serdes11: serdes@11 { serdes11: serdes@11 {
reg = <11>; reg = <11>;
#pcs-cells = <0>;
}; };
serdes12: serdes@12 { serdes12: serdes@12 {
reg = <12>; reg = <12>;
#pcs-cells = <0>;
}; };
serdes13: serdes@13 { serdes13: serdes@13 {
reg = <13>; reg = <13>;
#pcs-cells = <0>;
}; };
}; };

View File

@ -4107,8 +4107,6 @@ struct phylink_pcs *rtpcs_create(struct device *dev, struct device_node *np, int
sds = &ctrl->serdes[sds_id]; sds = &ctrl->serdes[sds_id];
if (rtpcs_sds_read(sds, 0, 0) < 0) if (rtpcs_sds_read(sds, 0, 0) < 0)
return ERR_PTR(-EINVAL); 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); link = devm_kzalloc(ctrl->dev, sizeof(*link), GFP_KERNEL);
if (!link) { 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); device_link_add(dev, ctrl->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
sds->num_of_links++;
link->ctrl = ctrl; link->ctrl = ctrl;
link->port = port; link->port = port;
link->sds = sds; link->sds = sds;
@ -4166,6 +4163,41 @@ static void rtpcs_sds_put_of_node(void *data)
of_node_put(sds->of_node); 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) static int rtpcs_probe(struct platform_device *pdev)
{ {
struct device_node *np = pdev->dev.of_node; struct device_node *np = pdev->dev.of_node;
@ -4220,6 +4252,8 @@ static int rtpcs_probe(struct platform_device *pdev)
return ret; return ret;
} }
rtpcs_count_links(ctrl);
if (ctrl->cfg->init) { if (ctrl->cfg->init) {
ret = ctrl->cfg->init(ctrl); ret = ctrl->cfg->init(ctrl);
if (ret) if (ret)