[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <4ce2ed3e-a806-4a2f-8aba-e93a7c05a38d@csgroup.eu>
Date: Wed, 17 Sep 2025 08:59:30 +0200
From: Christophe Leroy <christophe.leroy@...roup.eu>
To: Maxime Chevallier <maxime.chevallier@...tlin.com>, davem@...emloft.net
Cc: netdev@...r.kernel.org, linux-kernel@...r.kernel.org,
linux-arm-msm@...r.kernel.org, thomas.petazzoni@...tlin.com,
Andrew Lunn <andrew@...n.ch>, Jakub Kicinski <kuba@...nel.org>,
Eric Dumazet <edumazet@...gle.com>, Paolo Abeni <pabeni@...hat.com>,
Russell King <linux@...linux.org.uk>, linux-arm-kernel@...ts.infradead.org,
Herve Codina <herve.codina@...tlin.com>,
Florian Fainelli <f.fainelli@...il.com>,
Heiner Kallweit <hkallweit1@...il.com>,
Vladimir Oltean <vladimir.oltean@....com>,
Köry Maincent <kory.maincent@...tlin.com>,
Marek Behún <kabel@...nel.org>,
Oleksij Rempel <o.rempel@...gutronix.de>,
Nicolò Veronese <nicveronese@...il.com>,
Simon Horman <horms@...nel.org>, mwojtas@...omium.org,
Antoine Tenart <atenart@...nel.org>, devicetree@...r.kernel.org,
Conor Dooley <conor+dt@...nel.org>, Krzysztof Kozlowski
<krzk+dt@...nel.org>, Rob Herring <robh@...nel.org>,
Romain Gantois <romain.gantois@...tlin.com>,
Daniel Golle <daniel@...rotopia.org>,
Dimitri Fedrau <dimitri.fedrau@...bherr.com>
Subject: Re: [PATCH net-next v12 04/18] net: phy: Introduce PHY ports
representation
Le 09/09/2025 à 17:26, Maxime Chevallier a écrit :
> Ethernet provides a wide variety of layer 1 protocols and standards for
> data transmission. The front-facing ports of an interface have their own
> complexity and configurability.
>
> Introduce a representation of these front-facing ports. The current code
> is minimalistic and only support ports controlled by PHY devices, but
> the plan is to extend that to SFP as well as raw Ethernet MACs that
> don't use PHY devices.
>
> This minimal port representation allows describing the media and number
> of lanes of a port. From that information, we can derive the linkmodes
> usable on the port, which can be used to limit the capabilities of an
> interface.
>
> For now, the port lanes and medium is derived from devicetree, defined
> by the PHY driver, or populated with default values (as we assume that
> all PHYs expose at least one port).
>
> The typical example is 100M ethernet. 100BaseT can work using only 2
> lanes on a Cat 5 cables. However, in the situation where a 10/100/1000
> capable PHY is wired to its RJ45 port through 2 lanes only, we have no
> way of detecting that. The "max-speed" DT property can be used, but a
> more accurate representation can be used :
>
> mdi {
> connector-0 {
> media = "BaseT";
> lanes = <2>;
> };
> };
>
> From that information, we can derive the max speed reachable on the
> port.
>
> Another benefit of having that is to avoid vendor-specific DT properties
> (micrel,fiber-mode or ti,fiber-mode).
>
> This basic representation is meant to be expanded, by the introduction
> of port ops, userspace listing of ports, and support for multi-port
> devices.
>
> Signed-off-by: Maxime Chevallier <maxime.chevallier@...tlin.com>
Reviewed-by: Christophe Leroy <christophe.leroy@...roup.eu>
> ---
> MAINTAINERS | 1 +
> drivers/net/phy/Makefile | 2 +-
> drivers/net/phy/phy-caps.h | 5 +
> drivers/net/phy/phy-core.c | 6 ++
> drivers/net/phy/phy_caps.c | 56 +++++++++++
> drivers/net/phy/phy_device.c | 185 +++++++++++++++++++++++++++++++++++
> drivers/net/phy/phy_port.c | 135 +++++++++++++++++++++++++
> include/linux/ethtool.h | 26 +++++
> include/linux/phy.h | 55 +++++++++++
> include/linux/phy_port.h | 96 ++++++++++++++++++
> 10 files changed, 566 insertions(+), 1 deletion(-)
> create mode 100644 drivers/net/phy/phy_port.c
> create mode 100644 include/linux/phy_port.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 384bb1dc8424..31e5293df595 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9127,6 +9127,7 @@ F: include/linux/of_net.h
> F: include/linux/phy.h
> F: include/linux/phy_fixed.h
> F: include/linux/phy_link_topology.h
> +F: include/linux/phy_port.h
> F: include/linux/phylib_stubs.h
> F: include/linux/platform_data/mdio-bcm-unimac.h
> F: include/linux/platform_data/mdio-gpio.h
> diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
> index 402a33d559de..74922f350b0c 100644
> --- a/drivers/net/phy/Makefile
> +++ b/drivers/net/phy/Makefile
> @@ -3,7 +3,7 @@
>
> libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \
> linkmode.o phy_link_topology.o \
> - phy_caps.o mdio_bus_provider.o
> + phy_caps.o mdio_bus_provider.o phy_port.o
> mdio-bus-y += mdio_bus.o mdio_device.o
>
> ifdef CONFIG_PHYLIB
> diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h
> index 157759966650..01df1bdc1516 100644
> --- a/drivers/net/phy/phy-caps.h
> +++ b/drivers/net/phy/phy-caps.h
> @@ -60,4 +60,9 @@ const struct link_capabilities *
> phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported,
> bool exact);
>
> +void phy_caps_medium_get_supported(unsigned long *supported,
> + enum ethtool_link_medium medium,
> + int lanes);
> +u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes);
> +
> #endif /* __PHY_CAPS_H */
> diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c
> index 605ca20ae192..6f825f63c1bf 100644
> --- a/drivers/net/phy/phy-core.c
> +++ b/drivers/net/phy/phy-core.c
> @@ -4,6 +4,7 @@
> */
> #include <linux/export.h>
> #include <linux/phy.h>
> +#include <linux/phy_port.h>
> #include <linux/of.h>
>
> #include "phylib.h"
> @@ -163,7 +164,12 @@ EXPORT_SYMBOL_GPL(phy_interface_num_ports);
>
> static void __set_phy_supported(struct phy_device *phydev, u32 max_speed)
> {
> + struct phy_port *port;
> +
> phy_caps_linkmode_max_speed(max_speed, phydev->supported);
> +
> + phy_for_each_port(phydev, port)
> + phy_caps_linkmode_max_speed(max_speed, port->supported);
> }
>
> /**
> diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c
> index 990c36ccd9e7..e4efd5c477b4 100644
> --- a/drivers/net/phy/phy_caps.c
> +++ b/drivers/net/phy/phy_caps.c
> @@ -384,3 +384,59 @@ unsigned long phy_caps_from_interface(phy_interface_t interface)
> return link_caps;
> }
> EXPORT_SYMBOL_GPL(phy_caps_from_interface);
> +
> +/**
> + * phy_caps_medium_get_supported() - Returns linkmodes supported on a given medium
> + * @supported: After this call, contains all possible linkmodes on a given medium,
> + * and with the given number of lanes, or less.
> + * @medium: The medium to get the support from
> + * @lanes: The number of lanes used on the given medium
> + *
> + * If no match exists, the supported field is left untouched.
> + */
> +void phy_caps_medium_get_supported(unsigned long *supported,
> + enum ethtool_link_medium medium,
> + int lanes)
> +{
> + int i;
> +
> + for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
> + /* Special bits such as Autoneg, Pause, Asym_pause, etc. are
> + * set and will be masked away by the port parent.
> + */
> + if (link_mode_params[i].mediums == BIT(ETHTOOL_LINK_MEDIUM_NONE)) {
> + linkmode_set_bit(i, supported);
> + continue;
> + }
> +
> + /* For most cases, min_lanes == lanes, except for 10/100BaseT that work
> + * on 2 lanes but are compatible with 4 lanes mediums
> + */
> + if (link_mode_params[i].mediums & BIT(medium) &&
> + link_mode_params[i].min_lanes <= lanes)
> + linkmode_set_bit(i, supported);
> + }
> +}
> +EXPORT_SYMBOL_GPL(phy_caps_medium_get_supported);
> +
> +/**
> + * phy_caps_mediums_from_linkmodes() - Get all mediums from a linkmodes list
> + * @linkmodes: A bitset of linkmodes to get the mediums from
> + *
> + * Returns: A bitset of ETHTOOL_MEDIUM_XXX values corresponding to all medium
> + * types in the linkmodes list
> + */
> +u32 phy_caps_mediums_from_linkmodes(unsigned long *linkmodes)
> +{
> + const struct link_mode_info *linkmode;
> + u32 mediums = 0;
> + int i;
> +
> + for_each_set_bit(i, linkmodes, __ETHTOOL_LINK_MODE_MASK_NBITS) {
> + linkmode = &link_mode_params[i];
> + mediums |= linkmode->mediums;
> + }
> +
> + return mediums;
> +}
> +EXPORT_SYMBOL_GPL(phy_caps_mediums_from_linkmodes);
> diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
> index 7556aa3dd7ee..8f6bef8ee263 100644
> --- a/drivers/net/phy/phy_device.c
> +++ b/drivers/net/phy/phy_device.c
> @@ -30,6 +30,7 @@
> #include <linux/phylib_stubs.h>
> #include <linux/phy_led_triggers.h>
> #include <linux/phy_link_topology.h>
> +#include <linux/phy_port.h>
> #include <linux/pse-pd/pse.h>
> #include <linux/property.h>
> #include <linux/ptp_clock_kernel.h>
> @@ -836,6 +837,13 @@ struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
>
> dev->state = PHY_DOWN;
> INIT_LIST_HEAD(&dev->leds);
> + INIT_LIST_HEAD(&dev->ports);
> +
> + /* The driver's probe function must change that to the real number
> + * of ports possible on the PHY. We assume by default we are dealing
> + * with a single-port PHY
> + */
> + dev->max_n_ports = 1;
>
> mutex_init(&dev->lock);
> INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
> @@ -1579,6 +1587,51 @@ void phy_sfp_detach(void *upstream, struct sfp_bus *bus)
> }
> EXPORT_SYMBOL(phy_sfp_detach);
>
> +static int phy_add_port(struct phy_device *phydev, struct phy_port *port)
> +{
> + int ret = 0;
> +
> + if (phydev->n_ports == phydev->max_n_ports)
> + return -EBUSY;
> +
> + /* We set all ports as active by default, PHY drivers may deactivate
> + * them (when unused)
> + */
> + port->active = true;
> +
> + if (port->is_mii) {
> + if (phydev->drv && phydev->drv->attach_mii_port)
> + ret = phydev->drv->attach_mii_port(phydev, port);
> + } else {
> + if (phydev->drv && phydev->drv->attach_mdi_port)
> + ret = phydev->drv->attach_mdi_port(phydev, port);
> + }
> +
> + if (ret)
> + return ret;
> +
> + /* The PHY driver might have added, removed or set medium/lanes info,
> + * so update the port supported accordingly.
> + */
> + phy_port_update_supported(port);
> +
> + list_add(&port->head, &phydev->ports);
> +
> + phydev->n_ports++;
> +
> + return 0;
> +}
> +
> +static void phy_del_port(struct phy_device *phydev, struct phy_port *port)
> +{
> + if (!phydev->n_ports)
> + return;
> +
> + list_del(&port->head);
> +
> + phydev->n_ports--;
> +}
> +
> /**
> * phy_sfp_probe - probe for a SFP cage attached to this PHY device
> * @phydev: Pointer to phy_device
> @@ -3312,6 +3365,131 @@ static int of_phy_leds(struct phy_device *phydev)
> return 0;
> }
>
> +static void phy_cleanup_ports(struct phy_device *phydev)
> +{
> + struct phy_port *tmp, *port;
> +
> + list_for_each_entry_safe(port, tmp, &phydev->ports, head) {
> + phy_del_port(phydev, port);
> + phy_port_destroy(port);
> + }
> +}
> +
> +static int phy_default_setup_single_port(struct phy_device *phydev)
> +{
> + struct phy_port *port = phy_port_alloc();
> +
> + if (!port)
> + return -ENOMEM;
> +
> + port->parent_type = PHY_PORT_PHY;
> + port->phy = phydev;
> +
> + /* Let the PHY driver know that this port was never described anywhere.
> + * This is the usual case, where we assume single-port PHY devices with
> + * no SFP. In that case, the port supports exactly the same thing as
> + * the PHY itself.
> + *
> + * However, this can also be because we have a combo-port PHY, with
> + * only one port described in DT, through SFP for example.
> + *
> + * In that case, the PHY driver will be in charge of saying what we can
> + * do on that non-represented port.
> + */
> + port->not_described = true;
> + linkmode_copy(port->supported, phydev->supported);
> + port->mediums = phy_caps_mediums_from_linkmodes(port->supported);
> +
> + phy_add_port(phydev, port);
> +
> + return 0;
> +}
> +
> +static int of_phy_ports(struct phy_device *phydev)
> +{
> + struct device_node *node = phydev->mdio.dev.of_node;
> + struct device_node *mdi;
> + struct phy_port *port;
> + int err;
> +
> + if (!IS_ENABLED(CONFIG_OF_MDIO))
> + return 0;
> +
> + if (!node)
> + return 0;
> +
> + mdi = of_get_child_by_name(node, "mdi");
> + if (!mdi)
> + return 0;
> +
> + for_each_available_child_of_node_scoped(mdi, port_node) {
> + port = phy_of_parse_port(port_node);
> + if (IS_ERR(port)) {
> + err = PTR_ERR(port);
> + goto out_err;
> + }
> +
> + port->parent_type = PHY_PORT_PHY;
> + port->phy = phydev;
> + err = phy_add_port(phydev, port);
> + if (err)
> + goto out_err;
> + }
> + of_node_put(mdi);
> +
> + return 0;
> +
> +out_err:
> + phy_cleanup_ports(phydev);
> + of_node_put(mdi);
> + return err;
> +}
> +
> +static int phy_setup_ports(struct phy_device *phydev)
> +{
> + __ETHTOOL_DECLARE_LINK_MODE_MASK(ports_supported);
> + struct phy_port *port;
> + int ret;
> +
> + ret = of_phy_ports(phydev);
> + if (ret)
> + return ret;
> +
> + if (phydev->n_ports < phydev->max_n_ports) {
> + ret = phy_default_setup_single_port(phydev);
> + if (ret)
> + goto out;
> + }
> +
> + linkmode_zero(ports_supported);
> +
> + /* Aggregate the supported modes, which are made-up of :
> + * - What the PHY itself supports
> + * - What the sum of all ports support
> + */
> + list_for_each_entry(port, &phydev->ports, head)
> + if (port->active)
> + linkmode_or(ports_supported, ports_supported,
> + port->supported);
> +
> + if (!linkmode_empty(ports_supported))
> + linkmode_and(phydev->supported, phydev->supported,
> + ports_supported);
> +
> + /* For now, the phy->port field is set as the first active port's type */
> + list_for_each_entry(port, &phydev->ports, head)
> + if (port->active) {
> + phydev->port = phy_port_get_type(port);
> + break;
> + }
> +
> + return 0;
> +
> +out:
> + phy_cleanup_ports(phydev);
> + return ret;
> +}
> +
> /**
> * fwnode_mdio_find_device - Given a fwnode, find the mdio_device
> * @fwnode: pointer to the mdio_device's fwnode
> @@ -3449,6 +3627,11 @@ static int phy_probe(struct device *dev)
> phydev->is_gigabit_capable = 1;
>
> of_set_phy_supported(phydev);
> +
> + err = phy_setup_ports(phydev);
> + if (err)
> + goto out;
> +
> phy_advertise_supported(phydev);
>
> /* Get PHY default EEE advertising modes and handle them as potentially
> @@ -3524,6 +3707,8 @@ static int phy_remove(struct device *dev)
>
> phydev->state = PHY_DOWN;
>
> + phy_cleanup_ports(phydev);
> +
> sfp_bus_del_upstream(phydev->sfp_bus);
> phydev->sfp_bus = NULL;
>
> diff --git a/drivers/net/phy/phy_port.c b/drivers/net/phy/phy_port.c
> new file mode 100644
> index 000000000000..cf897ed74c4c
> --- /dev/null
> +++ b/drivers/net/phy/phy_port.c
> @@ -0,0 +1,135 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/* Framework to drive Ethernet ports
> + *
> + * Copyright (c) 2024 Maxime Chevallier <maxime.chevallier@...tlin.com>
> + */
> +
> +#include <linux/linkmode.h>
> +#include <linux/of.h>
> +#include <linux/phy_port.h>
> +
> +#include "phy-caps.h"
> +
> +/**
> + * phy_port_alloc() - Allocate a new phy_port
> + *
> + * Returns: a newly allocated struct phy_port, or NULL.
> + */
> +struct phy_port *phy_port_alloc(void)
> +{
> + struct phy_port *port;
> +
> + port = kzalloc(sizeof(*port), GFP_KERNEL);
> + if (!port)
> + return NULL;
> +
> + linkmode_zero(port->supported);
> + INIT_LIST_HEAD(&port->head);
> +
> + return port;
> +}
> +EXPORT_SYMBOL_GPL(phy_port_alloc);
> +
> +/**
> + * phy_port_destroy() - Free a struct phy_port
> + * @port: The port to destroy
> + */
> +void phy_port_destroy(struct phy_port *port)
> +{
> + kfree(port);
> +}
> +EXPORT_SYMBOL_GPL(phy_port_destroy);
> +
> +/**
> + * phy_of_parse_port() - Create a phy_port from a firmware representation
> + * @dn: device_node representation of the port, following the
> + * ethernet-connector.yaml binding
> + *
> + * Returns: a newly allocated and initialized phy_port pointer, or an ERR_PTR.
> + */
> +struct phy_port *phy_of_parse_port(struct device_node *dn)
> +{
> + struct fwnode_handle *fwnode = of_fwnode_handle(dn);
> + enum ethtool_link_medium medium;
> + struct phy_port *port;
> + const char *med_str;
> + u32 lanes, mediums = 0;
> + int ret;
> +
> + ret = fwnode_property_read_u32(fwnode, "lanes", &lanes);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + ret = fwnode_property_read_string(fwnode, "media", &med_str);
> + if (ret)
> + return ERR_PTR(ret);
> +
> + medium = ethtool_str_to_medium(med_str);
> + if (medium == ETHTOOL_LINK_MEDIUM_NONE)
> + return ERR_PTR(-EINVAL);
> +
> + mediums |= BIT(medium);
> +
> + if (!mediums)
> + return ERR_PTR(-EINVAL);
> +
> + port = phy_port_alloc();
> + if (!port)
> + return ERR_PTR(-ENOMEM);
> +
> + port->lanes = lanes;
> + port->mediums = mediums;
> +
> + return port;
> +}
> +EXPORT_SYMBOL_GPL(phy_of_parse_port);
> +
> +/**
> + * phy_port_update_supported() - Setup the port->supported field
> + * @port: the port to update
> + *
> + * Once the port's medium list and number of lanes has been configured based
> + * on firmware, straps and vendor-specific properties, this function may be
> + * called to update the port's supported linkmodes list.
> + *
> + * Any mode that was manually set in the port's supported list remains set.
> + */
> +void phy_port_update_supported(struct phy_port *port)
> +{
> + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 };
> + int i, lanes = 1;
> +
> + /* If there's no lanes specified, we grab the default number of
> + * lanes as the max of the default lanes for each medium
> + */
> + if (!port->lanes)
> + for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST)
> + lanes = max_t(int, lanes, phy_medium_default_lanes(i));
> +
> + port->lanes = lanes;
> +
> + for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) {
> + linkmode_zero(supported);
> + phy_caps_medium_get_supported(supported, i, port->lanes);
> + linkmode_or(port->supported, port->supported, supported);
> + }
> +}
> +EXPORT_SYMBOL_GPL(phy_port_update_supported);
> +
> +/**
> + * phy_port_get_type() - get the PORT_* attribute for that port.
> + * @port: The port we want the information from
> + *
> + * Returns: A PORT_XXX value.
> + */
> +int phy_port_get_type(struct phy_port *port)
> +{
> + if (port->mediums & BIT(ETHTOOL_LINK_MEDIUM_BASET))
> + return PORT_TP;
> +
> + if (phy_port_is_fiber(port))
> + return PORT_FIBRE;
> +
> + return PORT_OTHER;
> +}
> +EXPORT_SYMBOL_GPL(phy_port_get_type);
> diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
> index a0696cb840f7..baa5ec1d76ed 100644
> --- a/include/linux/ethtool.h
> +++ b/include/linux/ethtool.h
> @@ -227,6 +227,10 @@ extern const struct link_mode_info link_mode_params[];
>
> extern const char ethtool_link_medium_names[][ETH_GSTRING_LEN];
>
> +#define ETHTOOL_MEDIUM_FIBER_BITS (BIT(ETHTOOL_LINK_MEDIUM_BASES) | \
> + BIT(ETHTOOL_LINK_MEDIUM_BASEL) | \
> + BIT(ETHTOOL_LINK_MEDIUM_BASEF))
> +
> static inline const char *phy_mediums(enum ethtool_link_medium medium)
> {
> if (medium >= __ETHTOOL_LINK_MEDIUM_LAST)
> @@ -235,6 +239,28 @@ static inline const char *phy_mediums(enum ethtool_link_medium medium)
> return ethtool_link_medium_names[medium];
> }
>
> +static inline enum ethtool_link_medium ethtool_str_to_medium(const char *str)
> +{
> + int i;
> +
> + for (i = 0; i < __ETHTOOL_LINK_MEDIUM_LAST; i++)
> + if (!strcmp(phy_mediums(i), str))
> + return i;
> +
> + return ETHTOOL_LINK_MEDIUM_NONE;
> +}
> +
> +static inline int phy_medium_default_lanes(enum ethtool_link_medium medium)
> +{
> + /* Let's consider that the default BaseT ethernet is BaseT4, i.e.
> + * Gigabit Ethernet.
> + */
> + if (medium == ETHTOOL_LINK_MEDIUM_BASET)
> + return 4;
> +
> + return 1;
> +}
> +
> /* declare a link mode bitmap */
> #define __ETHTOOL_DECLARE_LINK_MODE_MASK(name) \
> DECLARE_BITMAP(name, __ETHTOOL_LINK_MODE_MASK_NBITS)
> diff --git a/include/linux/phy.h b/include/linux/phy.h
> index 04553419adc3..a6cd0465b059 100644
> --- a/include/linux/phy.h
> +++ b/include/linux/phy.h
> @@ -322,6 +322,7 @@ static inline long rgmii_clock(int speed)
> struct device;
> struct kernel_hwtstamp_config;
> struct phylink;
> +struct phy_port;
> struct sfp_bus;
> struct sfp_upstream_ops;
> struct sk_buff;
> @@ -616,6 +617,9 @@ struct macsec_ops;
> * @master_slave_state: Current master/slave configuration
> * @mii_ts: Pointer to time stamper callbacks
> * @psec: Pointer to Power Sourcing Equipment control struct
> + * @ports: List of PHY ports structures
> + * @n_ports: Number of ports currently attached to the PHY
> + * @max_n_ports: Max number of ports this PHY can expose
> * @lock: Mutex for serialization access to PHY
> * @state_queue: Work queue for state machine
> * @link_down_events: Number of times link was lost
> @@ -753,6 +757,10 @@ struct phy_device {
> struct mii_timestamper *mii_ts;
> struct pse_control *psec;
>
> + struct list_head ports;
> + int n_ports;
> + int max_n_ports;
> +
> u8 mdix;
> u8 mdix_ctrl;
>
> @@ -775,6 +783,9 @@ struct phy_device {
>
> #define to_phy_device(__dev) container_of_const(to_mdio_device(__dev), struct phy_device, mdio)
>
> +#define phy_for_each_port(phydev, port) \
> + list_for_each_entry(port, &(phydev)->ports, head)
> +
> /**
> * struct phy_tdr_config - Configuration of a TDR raw test
> *
> @@ -1269,6 +1280,49 @@ struct phy_driver {
> * Returns the time in jiffies until the next update event.
> */
> unsigned int (*get_next_update_time)(struct phy_device *dev);
> +
> + /**
> + * @attach_mii_port: Attach the given MII port to the PHY device
> + * @dev: PHY device to notify
> + * @port: The port being added
> + *
> + * Called when an MII port that needs to be driven by the PHY is found.
> + *
> + * The port that is being passed may or may not be initialized. If it is
> + * already initialized, it is by the generic port representation from
> + * devicetree, which superseeds any strapping or vendor-specific
> + * properties.
> + *
> + * If the port isn't initialized, the port->mediums and port->lanes
> + * fields must be set, possibly according to strapping information.
> + *
> + * The PHY driver must set the port->interfaces field to indicate the
> + * possible MII modes that this PHY can output on the port.
> + *
> + * Returns 0, or an error code.
> + */
> + int (*attach_mii_port)(struct phy_device *dev, struct phy_port *port);
> +
> + /**
> + * @attach_mdi_port: Attach the given MII port to the PHY device
> + * @dev: PHY device to notify
> + * @port: The port being added
> + *
> + * Called when a port that needs to be driven by the PHY is found. The
> + * number of time this will be called depends on phydev->max_n_ports,
> + * which the driver can change in .probe().
> + *
> + * The port that is being passed may or may not be initialized. If it is
> + * already initialized, it is by the generic port representation from
> + * devicetree, which superseeds any strapping or vendor-specific
> + * properties.
> + *
> + * If the port isn't initialized, the port->mediums and port->lanes
> + * fields must be set, possibly according to strapping information.
> + *
> + * Returns 0, or an error code.
> + */
> + int (*attach_mdi_port)(struct phy_device *dev, struct phy_port *port);
> };
> #define to_phy_driver(d) container_of_const(to_mdio_common_driver(d), \
> struct phy_driver, mdiodrv)
> @@ -2023,6 +2077,7 @@ void phy_trigger_machine(struct phy_device *phydev);
> void phy_mac_interrupt(struct phy_device *phydev);
> void phy_start_machine(struct phy_device *phydev);
> void phy_stop_machine(struct phy_device *phydev);
> +
> void phy_ethtool_ksettings_get(struct phy_device *phydev,
> struct ethtool_link_ksettings *cmd);
> int phy_ethtool_ksettings_set(struct phy_device *phydev,
> diff --git a/include/linux/phy_port.h b/include/linux/phy_port.h
> new file mode 100644
> index 000000000000..f47ac5f5ef9e
> --- /dev/null
> +++ b/include/linux/phy_port.h
> @@ -0,0 +1,96 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +#ifndef __PHY_PORT_H
> +#define __PHY_PORT_H
> +
> +#include <linux/ethtool.h>
> +#include <linux/types.h>
> +#include <linux/phy.h>
> +
> +struct phy_port;
> +
> +/**
> + * enum phy_port_parent - The device this port is attached to
> + *
> + * @PHY_PORT_PHY: Indicates that the port is driven by a PHY device
> + */
> +enum phy_port_parent {
> + PHY_PORT_PHY,
> +};
> +
> +struct phy_port_ops {
> + /* Sometimes, the link state can be retrieved from physical,
> + * out-of-band channels such as the LOS signal on SFP. These
> + * callbacks allows notifying the port about state changes
> + */
> + void (*link_up)(struct phy_port *port);
> + void (*link_down)(struct phy_port *port);
> +
> + /* If the port acts as a Media Independent Interface (Serdes port),
> + * configures the port with the relevant state and mode. When enable is
> + * not set, interface should be ignored
> + */
> + int (*configure_mii)(struct phy_port *port, bool enable, phy_interface_t interface);
> +};
> +
> +/**
> + * struct phy_port - A representation of a network device physical interface
> + *
> + * @head: Used by the port's parent to list ports
> + * @parent_type: The type of device this port is directly connected to
> + * @phy: If the parent is PHY_PORT_PHYDEV, the PHY controlling that port
> + * @ops: Callback ops implemented by the port controller
> + * @lanes: The number of lanes (diff pairs) this port has, 0 if not applicable
> + * @mediums: Bitmask of the physical mediums this port provides access to
> + * @supported: The link modes this port can expose, if this port is MDI (not MII)
> + * @interfaces: The MII interfaces this port supports, if this port is MII
> + * @not_described: Indicates to the parent driver if this port isn't described,
> + * so it's up to the parent to filter its capabilities.
> + * @active: Indicates if the port is currently part of the active link.
> + * @is_mii: Indicates if this port is MII (Media Independent Interface),
> + * or MDI (Media Dependent Interface).
> + */
> +struct phy_port {
> + struct list_head head;
> + enum phy_port_parent parent_type;
> + union {
> + struct phy_device *phy;
> + };
> +
> + const struct phy_port_ops *ops;
> +
> + int lanes;
> + unsigned long mediums;
> + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
> + DECLARE_PHY_INTERFACE_MASK(interfaces);
> +
> + unsigned int not_described:1;
> + unsigned int active:1;
> + unsigned int is_mii:1;
> +};
> +
> +struct phy_port *phy_port_alloc(void);
> +void phy_port_destroy(struct phy_port *port);
> +
> +static inline struct phy_device *port_phydev(struct phy_port *port)
> +{
> + return port->phy;
> +}
> +
> +struct phy_port *phy_of_parse_port(struct device_node *dn);
> +
> +static inline bool phy_port_is_copper(struct phy_port *port)
> +{
> + return port->mediums == BIT(ETHTOOL_LINK_MEDIUM_BASET);
> +}
> +
> +static inline bool phy_port_is_fiber(struct phy_port *port)
> +{
> + return !!(port->mediums & ETHTOOL_MEDIUM_FIBER_BITS);
> +}
> +
> +void phy_port_update_supported(struct phy_port *port);
> +
> +int phy_port_get_type(struct phy_port *port);
> +
> +#endif
Powered by blists - more mailing lists