Re: [PATCH net-next v9 06/12] net: Document PCS subsystem
From: Christian Marangi
Date: Sun Jul 26 2026 - 10:34:02 EST
On Tue, Jul 21, 2026 at 02:11:46PM +0200, Maxime Chevallier wrote:
>
>
> On 7/17/26 08:54, Christian Marangi wrote:
> > Add extensive documentation of the new PCS subsystem and the fwnode
> > implementation with producer/consumer API.
> >
> > Signed-off-by: Christian Marangi <ansuelsmth@xxxxxxxxx>
> > ---
> > Documentation/networking/index.rst | 1 +
> > Documentation/networking/pcs.rst | 229 +++++++++++++++++++++++++++++
> > 2 files changed, 230 insertions(+)
> > create mode 100644 Documentation/networking/pcs.rst
> >
> > diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst
> > index 44a422ad3b05..3fce8f6ac089 100644
> > --- a/Documentation/networking/index.rst
> > +++ b/Documentation/networking/index.rst
> > @@ -28,6 +28,7 @@ Contents:
> > net_failover
> > page_pool
> > phy
> > + pcs
> > sfp-phylink
> > alias
> > bridge
> > diff --git a/Documentation/networking/pcs.rst b/Documentation/networking/pcs.rst
> > new file mode 100644
> > index 000000000000..98592cdee3ef
> > --- /dev/null
> > +++ b/Documentation/networking/pcs.rst
> > @@ -0,0 +1,229 @@
> > +.. SPDX-License-Identifier: GPL-2.0
> > +
> > +=============
> > +PCS Subsystem
> > +=============
> > +
> > +The PCS (Physical Coding Sublayer) subsystem handles the registration and lookup
> > +of PCS devices. These devices contain the upper sublayers of the Ethernet
> > +physical layer, generally handling framing, scrambling, and encoding tasks. PCS
> > +devices may also include PMA (Physical Medium Attachment) components. PCS
> > +devices transfer data between the Link-layer MAC device, and the rest of the
> > +physical layer, typically via a serdes. The output of the serdes may be
> > +connected more-or-less directly to the medium when using fiber-optic or
> > +backplane connections (1000BASE-SX, 1000BASE-KX, etc). It may also communicate
> > +with a separate PHY (such as over SGMII) which handles the connection to the
> > +medium (such as 1000BASE-T).
> > +
> > +Remark on usage of .mac_select_pcs and fw_node PCS
> > +--------------------------------------------------
> > +
> > +There are generally two ways to look up a PCS device.
> > +
> > +1. MAC OP struct .mac_select_pcs (considered legacy)
> > +2. firmware node (fwnode) PCS entirely handled by phylink
> > +
> > +Implementation 1 leaves the entire handling of the PCS to the MAC
> > +driver with the selection of the PCS driven by .mac_select_pcs.
> > +Custom implementations are required if the PCS is external to the MAC
> > +and needs to be handled by a separate driver.
> > +
> > +This implementation is considered legacy and it's suggested to
> > +switch to the new fwnode PCS.
>
> The .mac_select_pcs can be deprecated, with the .fill_available_pcs()
> mecanism available, we can keep PCS implems inside the MAC driver when
> it makes sense, no ?
Yes that is the idea but it might take a while to deprecate it.
>
> If PCSs aren't described in firmware (i.e. DT), we have to use .fill_available_pcs()
> without using the fwnode API if I get your code right.
Yes everything should be described that way.
>
> The 'legacy' part is the only remark I have, the rest is all good :)
>
Any hint on the wording? I remember you already said something about this.
I will check previous review.
> > +
> > +Looking up PCS Devices (fwnode implementation)
> > +-----------------------------------------------
> > +
> > +The lookup of a PCS device follows the common producer/consumer implementation
> > +used by similar subsystems with a ``#pcs-cells`` on the producer and a
> > +``pcs-handle`` property on the consumer::
> > +
> > + pcs: pcs {
> > + // ...
> > + #pcs-cells = <0>;
> > + };
> > +
> > + ethernet-controller {
> > + // ...
> > + pcs-handle = <&pcs>;
> > + };
> > +
> > +On :c:func:`phylink_create`, phylink will use the ``num_possible_pcs``
> > +value and ``fill_available_pcs`` helper function in
> > +:c:struct:`phylink_config` to compose the list of available PCS that can be
> > +used for the phylink instance.
> > +
> > +Phylink will then internally handle the selection of the correct PCS for
> > +the requested interface mode based on the interface modes configured in
> > +``pcs_interfaces`` in :c:struct:`phylink_config` struct and
> > +``supported_interfaces`` in :c:struct:`phylink_pcs` struct.
> > +
> > +A PCS is considered eligible when the requested interface mode is present
> > +in both ``pcs_interfaces`` in :c:struct:`phylink_config` struct and
> > +``supported_interfaces`` in :c:struct:`phylink_pcs` struct.
> > +
> > +``supported_interfaces`` describes all interface modes supported by the MAC,
> > +whereas ``pcs_interfaces`` identifies the subset that require PCS selection.
> > +
> > +For the special implementation where the PCS is internal or part of the MAC
> > +and a dedicated driver is not needed, it's possible to leave the implementation
> > +of the PCS to the MAC driver and just implement the ``num_possible_pcs``
> > +value and ``fill_available_pcs`` helper function in
> > +:c:struct:`phylink_config` referencing the local :c:struct:`phylink_pcs`
> > +struct allocated from the MAC driver.
> > +
> > +Using PCS Devices
> > +-----------------
> > +
> > +It's mandatory to either implement the ``mac_select_pcs`` callback
> > +of :c:struct:`phylink_mac_ops` or ``num_possible_pcs`` and ``fill_available_pcs``
> > +of :c:struct:`phylink_config` to use a PCS for a MAC.
> > +
> > +The fwnode implementation exposes simple helpers to parse the PCS from
> > +the fwnode :c:func:`fwnode_phylink_pcs_count` and
> > +:c:func:`fwnode_phylink_pcs_parse`. The :c:func:`fwnode_phylink_pcs_count` helper
> > +takes the fwnode where the ``pcs-handle`` should be parsed and return the
> > +number of PCS entries described in the fwnode.
> > +The :c:func:`fwnode_phylink_pcs_parse` helper takes three arguments,
> > +the fwnode where the ``pcs-handle`` should be parsed, an allocated array
> > +of :c:struct:`phylink_pcs` pointer where to put the parsed PCS from the fwnode
> > +and the maximum number of PCS to parse.
> > +Contrary to :c:func:`fwnode_phylink_pcs_count`, :c:func:`fwnode_phylink_pcs_parse`
> > +helper fills the allocated array with ONLY the available PCS and return the
> > +number of available PCS found. PCS that returns -ENODEV will be skipped and
> > +won't be inserted in the allocated array.
> > +
> > +A phylink instance may use multiple PCS devices. The maximum number is reported
> > +through ``num_possible_pcs``.
> > +
> > +It's mandatory to specify for what interface a PCS is needed. This can be done
> > +by filling the ``pcs_interfaces`` in :c:struct:`phylink_config` struct.
> > +If the requested interface mode is not present in this bitmask, phylink does
> > +not search for a PCS for that specific mode. (example MAC doesn't need a PCS
> > +for SGMII but require one for USXGMII)
> > +
> > +With the use of the :c:func:`fwnode_phylink_pcs_parse` a common implementation
> > +is the following::
> > +
> > + static int mac_fill_available_pcs(struct phylink_config *config,
> > + struct phylink_pcs **available_pcs,
> > + unsigned int num_possible_pcs)
> > + {
> > + struct device *dev = config->dev;
> > +
> > + return fwnode_phylink_pcs_parse(dev_fwnode(dev), available_pcs,
> > + num_possible_pcs);
> > + }
> > +
> > + static int mac_setup_phylink(struct net_device *netdev)
> > + {
> > + struct phylink_config *config;
> > +
> > + // ...
> > +
> > + config->dev = &netdev->dev;
> > +
> > + // ...
> > +
> > + // Parse possible PCS and fill num_possible_pcs.
> > + config->num_possible_pcs = fwnode_phylink_pcs_count(dev_fwnode(&netdev->dev));
> > + config->fill_available_pcs = mac_fill_available_pcs;
> > +
> > + __set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces);
> > + __set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
> > + __set_bit(PHY_INTERFACE_MODE_1000BASEX, config->supported_interfaces);
> > + __set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
> > +
> > + // PCS required only for USXGMII
> > + __set_bit(PHY_INTERFACE_MODE_USXGMII, config->pcs_interfaces);
> > +
> > + phylink = phylink_create(config, //...
> > +
> > +It's worth to mention that it's phylink code that takes care of allocating
> > +the array of :c:struct:`phylink_pcs` pointer for ``fill_available_pcs``
> > +callback based on the value set in ``num_possible_pcs`` for
> > +:c:struct:`phylink_config` struct.
> > +
> > +The ``fill_available_pcs`` callback must not write more than
> > +``num_possible_pcs`` entries. The third argument may be used to validate
> > +that there is enough space to fill all the available PCS in the passed array
> > +of :c:struct:`phylink_pcs` pointer.
> > +
> > +The ``fill_available_pcs`` callback is called only on :c:func:`phylink_create`
> > +and is used only to compose the initial available PCS list. Ownership of PCS
> > +is held by phylink and :c:func:`phylink_release_pcs` should be used to release
> > +them.
> > +
> > +Writing PCS Drivers
> > +-------------------
> > +
> > +To write a PCS driver, first implement :c:struct:`phylink_pcs_ops`. Then,
> > +register your PCS in your probe function using :c:func:`fwnode_pcs_add_provider`.
> > +The :c:func:`fwnode_pcs_add_provider` takes three arguments, the fwnode where
> > +the PCS provider should be registered to, a get function to return the requested
> > +PCS based on ``#pcs-cells`` and a pointer to reference private data for the get
> > +function.
> > +
> > +The PCS will then be registered to a global list of PCS provider that the
> > +PCS fwnode implementation will use to parse it.
> > +
> > +For the simple case where the PCS driver expose a single PCS,
> > +:c:func:`fwnode_pcs_simple_get` can be used as the get function.
> > +
> > +You must call :c:func:`fwnode_pcs_del_provider` from your remove function and
> > +release the PCS from any phylink instance under RTNL lock with
> > +:c:func:`phylink_release_pcs`::
> > +
> > + fwnode_pcs_del_provider(dev_fwnode(&pdev->dev));
> > +
> > + rtnl_lock();
> > +
> > + for (i = 0; i < data->num_port; i++) {
> > + struct pcs_port *port = &priv->ports[i];
> > +
> > + phylink_release_pcs(&port->pcs);
> > + }
> > +
> > + rtnl_unlock();
> > +
> > +Late PCS registration handling
> > +------------------------------
> > +
> > +It's possible that a PCS becomes available after the MAC finished probing.
> > +Contrary to the usual producer/consumer implementation, when a PCS is not
> > +registered and can't be found, the fwnode parser helper returns ``-ENODEV``
> > +instead of ``-EPROBE_DEFER``.
> > +
> > +This is to prevent race condition with particular devices that register
> > +MAC and PCS with USB or PCIe and require the MAC to be registered before
> > +the PCS.
> > +
> > +The phylink logic correctly handle this special case and keep the phylink
> > +instance in a fail condition.
> > +
> > +The PCS fwnode implementation provides a notifier to which each phylink
> > +instance with a non-empty ``pcs_interfaces`` in :c:type:`phylink_config`
> > +registers. When a new PCS provider is registered, the notifier is called
> > +triggering the :c:func:`pcs_provider_notify` function.
> > +
> > +Function :c:func:`pcs_provider_notify` will check if the just added PCS
> > +should be used by the phylink instance. If it should be used then,
> > +it's added to the internal list of available PCS and a phylink major
> > +config is forced.
> > +
> > +If a phylink instance was in a failure state, with the just added PCS
> > +now part of the available PCS internal phylink list, provided all other
> > +conditions are satisfied, the configuration is retried and the failure
> > +condition is cleared.
> > +
> > +API Reference
> > +-------------
> > +
> > +.. kernel-doc:: include/linux/phylink.h
> > + :identifiers: phylink_pcs
> > +
> > +.. kernel-doc:: include/linux/pcs/pcs.h
> > + :internal:
> > +
> > +.. kernel-doc:: include/linux/pcs/pcs-provider.h
> > + :internal:
>
--
Ansuel