Merge branch 'net-phy-introduce-phy-ports-representation'

Maxime Chevallier says:

====================
net: phy: Introduce PHY ports representation

A few important notes:

 - This is only a first phase. It instantiates the port, and leverage
   that to make the MAC <-> PHY <-> SFP usecase simpler.

 - Next phase will deal with controlling the port state, as well as the
   netlink uAPI for that.

 - The end-goal is to enable support for complex port MUX. This
   preliminary work focuses on PHY-driven ports, but this will be
   extended to support muxing at the MII level (Multi-phy, or compo PHY
   + SFP as found on Turris Omnia for example).

 - The naming is definitely not set in stone. I named that "phy_port",
   but this may convey the false sense that this is phylib-specific.
   Even the word "port" is not that great, as it already has several
   different meanings in the net world (switch port, devlink port,
   etc.). I used the term "connector" in the binding.

A bit of history on that work :

The end goal that I personnaly want to achieve is :

            + PHY - RJ45
            |
 MAC - MUX -+ PHY - RJ45

After many discussions here on netdev@, but also at netdevconf[1] and
LPC[2], there appears to be several analoguous designs that exist out
there.

[1] : https://netdevconf.info/0x17/sessions/talk/improving-multi-phy-and-multi-port-interfaces.html
[2] : https://lpc.events/event/18/contributions/1964/ (video isn't the
right one)

Take the MAchiatobin, it has 2 interfaces that looks like this :

 MAC - PHY -+ RJ45
            |
	    + SFP - Whatever the module does

Now, looking at the Turris Omnia, we have :

 MAC - MUX -+ PHY - RJ45
            |
	    + SFP - Whatever the module does

We can find more example of this kind of designs, the common part is
that we expose multiple front-facing media ports. This is what this
current work aims at supporting. As of right now, it does'nt add any
support for muxing, but this will come later on.

This first phase focuses on phy-driven ports only, but there are already
quite some challenges already. For one, we can't really autodetect how
many ports are sitting behind a PHY. That's why this series introduces a
new binding. Describing ports in DT should however be a last-resort
thing when we need to clear some ambiguity about the PHY media-side.

The only use-cases that we have today for multi-port PHYs are combo PHYs
that drive both a Copper port and an SFP (the Macchiatobin case). This
in itself is challenging and this series only addresses part of this
support, by registering a phy_port for the PHY <-> SFP connection. The
SFP module should in the end be considered as a port as well, but that's
not yet the case.

However, because now PHYs can register phy_ports for every media-side
interface they have, they can register the capabilities of their ports,
which allows making the PHY-driver SFP case much more generic.
====================

Link: https://patch.msgid.link/20260108080041.553250-1-maxime.chevallier@bootlin.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-01-13 18:52:37 -08:00
commit 75fe2b7adc
22 changed files with 1399 additions and 391 deletions

View File

@ -0,0 +1,56 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/net/ethernet-connector.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Generic Ethernet Connector
maintainers:
- Maxime Chevallier <maxime.chevallier@bootlin.com>
description:
An Ethernet Connector represents the output of a network component such as
a PHY, an Ethernet controller with no PHY, or an SFP module.
properties:
pairs:
description:
Defines the number of BaseT pairs that are used on the connector.
$ref: /schemas/types.yaml#/definitions/uint32
enum: [1, 2, 4]
media:
description:
The mediums, as defined in 802.3, that can be used on the port.
enum:
- BaseT
- BaseK
- BaseS
- BaseC
- BaseL
- BaseD
- BaseE
- BaseF
- BaseV
- BaseMLD
required:
- media
allOf:
- if:
properties:
media:
const: BaseT
then:
required:
- pairs
else:
properties:
pairs: false
additionalProperties: true
...

View File

@ -281,6 +281,17 @@ properties:
additionalProperties: false
mdi:
type: object
patternProperties:
'^connector-[0-9]+$':
$ref: /schemas/net/ethernet-connector.yaml#
unevaluatedProperties: false
additionalProperties: false
required:
- reg
@ -317,5 +328,12 @@ examples:
default-state = "keep";
};
};
/* Fast Ethernet port, with only 2 pairs wired */
mdi {
connector-0 {
pairs = <2>;
media = "BaseT";
};
};
};
};

View File

@ -47,6 +47,9 @@ properties:
is disabled.
In fiber mode, auto-negotiation is disabled and the PHY can only work in
100base-fx (full and half duplex) modes.
This property is deprecated, for details please refer to
Documentation/devicetree/bindings/net/ethernet-connector.yaml
deprecated: true
rx-internal-delay-ps:
description: |
@ -141,7 +144,11 @@ examples:
tx-internal-delay-ps = <1>;
ti,gpio2-clk-out = "xi";
mac-termination-ohms = <43>;
mdi {
connector-0 {
media = "BaseF";
};
};
};
};
...

View File

@ -96,6 +96,7 @@ Contents:
packet_mmap
phonet
phy-link-topology
phy-port
pktgen
plip
ppp_generic

View File

@ -0,0 +1,111 @@
.. SPDX-License-Identifier: GPL-2.0
.. _phy_port:
=================
Ethernet ports
=================
This document is a basic description of the phy_port infrastructure,
introduced to represent physical interfaces of Ethernet devices.
Without phy_port, we already have quite a lot of information about what the
media-facing interface of a NIC can do and looks like, through the
:c:type:`struct ethtool_link_ksettings <ethtool_link_ksettings>` attributes,
which includes :
- What the NIC can do through the :c:member:`supported` field
- What the Link Partner advertises through :c:member:`lp_advertising`
- Which features we're advertising through :c:member:`advertising`
We also have info about the number of pairs and the PORT type. These settings
are built by aggregating together information reported by various devices that
are sitting on the link :
- The NIC itself, through the :c:member:`get_link_ksettings` callback
- Precise information from the MAC and PCS by using phylink in the MAC driver
- Information reported by the PHY device
- Information reported by an SFP module (which can itself include a PHY)
This model however starts showing its limitations when we consider devices that
have more than one media interface. In such a case, only information about the
actively used interface is reported, and it's not possible to know what the
other interfaces can do. In fact, we have very little information about whether
or not there are any other media interfaces.
The goal of the phy_port representation is to provide a way of representing a
physical interface of a NIC, regardless of what is driving the port (NIC through
a firmware, SFP module, Ethernet PHY).
Multi-port interfaces examples
==============================
Several cases of multi-interface NICs have been observed so far :
Internal MII Mux::
+------------------+
| SoC |
| +-----+ | +-----+
| +-----+ | |-------------| PHY |
| | MAC |--| Mux | | +-----+ +-----+
| +-----+ | |-----| SFP |
| +-----+ | +-----+
+------------------+
Internal Mux with internal PHY::
+------------------------+
| SoC |
| +-----+ +-----+
| +-----+ | |-| PHY |
| | MAC |--| Mux | +-----+ +-----+
| +-----+ | |-----------| SFP |
| +-----+ | +-----+
+------------------------+
External Mux::
+---------+
| SoC | +-----+ +-----+
| | | |--| PHY |
| +-----+ | | | +-----+
| | MAC |----| Mux | +-----+
| +-----+ | | |--| PHY |
| | +-----+ +-----+
| | |
| GPIO-------+
+---------+
Double-port PHY::
+---------+
| SoC | +-----+
| | | |--- RJ45
| +-----+ | | |
| | MAC |---| PHY | +-----+
| +-----+ | | |---| SFP |
+---------+ +-----+ +-----+
phy_port aims at providing a path to support all the above topologies, by
representing the media interfaces in a way that's agnostic to what's driving
the interface. the struct phy_port object has its own set of callback ops, and
will eventually be able to report its own ksettings::
_____ +------+
( )-----| Port |
+-----+ ( ) +------+
| MAC |--( ??? )
+-----+ ( ) +------+
(_____)-----| Port |
+------+
Next steps
==========
As of writing this documentation, only ports controlled by PHY devices are
supported. The next steps will be to add the Netlink API to expose these
to userspace and add support for raw ports (controlled by some firmware, and directly
managed by the NIC driver).
Another parallel task is the introduction of a MII muxing framework to allow the
control of non-PHY driver multi-port setups.

View File

@ -9395,9 +9395,11 @@ R: Russell King <linux@armlinux.org.uk>
L: netdev@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-class-net-phydev
F: Documentation/devicetree/bindings/net/ethernet-connector.yaml
F: Documentation/devicetree/bindings/net/ethernet-phy.yaml
F: Documentation/devicetree/bindings/net/mdio*
F: Documentation/devicetree/bindings/net/qca,ar803x.yaml
F: Documentation/networking/phy-port.rst
F: Documentation/networking/phy.rst
F: drivers/net/mdio/
F: drivers/net/mdio/acpi_mdio.c
@ -18217,6 +18219,14 @@ F: drivers/net/phy/phy_link_topology.c
F: include/linux/phy_link_topology.h
F: net/ethtool/phy.c
NETWORKING [ETHTOOL PHY PORT]
M: Maxime Chevallier <maxime.chevallier@bootlin.com>
F: Documentation/devicetree/bindings/net/ethernet-connector.yaml
F: Documentation/networking/phy-port.rst
F: drivers/net/phy/phy_port.c
F: include/linux/phy_port.h
K: struct\s+phy_port|phy_port_
NETWORKING [GENERAL]
M: "David S. Miller" <davem@davemloft.net>
M: Eric Dumazet <edumazet@google.com>

View File

@ -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

View File

@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/phy_port.h>
#include <linux/netdevice.h>
#include <linux/bitfield.h>
@ -811,17 +812,6 @@ static int dp83822_of_init(struct phy_device *phydev)
int i, ret;
u32 val;
/* Signal detection for the PHY is only enabled if the FX_EN and the
* SD_EN pins are strapped. Signal detection can only enabled if FX_EN
* is strapped otherwise signal detection is disabled for the PHY.
*/
if (dp83822->fx_enabled && dp83822->fx_sd_enable)
dp83822->fx_signal_det_low = device_property_present(dev,
"ti,link-loss-low");
if (!dp83822->fx_enabled)
dp83822->fx_enabled = device_property_present(dev,
"ti,fiber-mode");
if (!device_property_read_string(dev, "ti,gpio2-clk-out", &of_val)) {
if (strcmp(of_val, "mac-if") == 0) {
dp83822->gpio2_clk_out = DP83822_CLK_SRC_MAC_IF;
@ -950,6 +940,48 @@ static int dp83822_read_straps(struct phy_device *phydev)
return 0;
}
static int dp83822_attach_mdi_port(struct phy_device *phydev,
struct phy_port *port)
{
struct dp83822_private *dp83822 = phydev->priv;
int ret;
if (port->mediums) {
if (phy_port_is_fiber(port))
dp83822->fx_enabled = true;
} else {
ret = dp83822_read_straps(phydev);
if (ret)
return ret;
#if IS_ENABLED(CONFIG_OF_MDIO)
if (dp83822->fx_enabled && dp83822->fx_sd_enable)
dp83822->fx_signal_det_low =
device_property_present(&phydev->mdio.dev,
"ti,link-loss-low");
/* ti,fiber-mode is still used for backwards compatibility, but
* has been replaced with the mdi node definition, see
* ethernet-port.yaml
*/
if (!dp83822->fx_enabled)
dp83822->fx_enabled =
device_property_present(&phydev->mdio.dev,
"ti,fiber-mode");
#endif /* CONFIG_OF_MDIO */
if (dp83822->fx_enabled) {
port->mediums = BIT(ETHTOOL_LINK_MEDIUM_BASEF);
} else {
/* This PHY can only to 100BaseTX max, so on 2 pairs */
port->pairs = 2;
port->mediums = BIT(ETHTOOL_LINK_MEDIUM_BASET);
}
}
return 0;
}
static int dp8382x_probe(struct phy_device *phydev)
{
struct dp83822_private *dp83822;
@ -968,27 +1000,13 @@ static int dp8382x_probe(struct phy_device *phydev)
static int dp83822_probe(struct phy_device *phydev)
{
struct dp83822_private *dp83822;
int ret;
ret = dp8382x_probe(phydev);
if (ret)
return ret;
dp83822 = phydev->priv;
ret = dp83822_read_straps(phydev);
if (ret)
return ret;
ret = dp83822_of_init(phydev);
if (ret)
return ret;
if (dp83822->fx_enabled)
phydev->port = PORT_FIBRE;
return 0;
return dp83822_of_init(phydev);
}
static int dp83826_probe(struct phy_device *phydev)
@ -1172,6 +1190,7 @@ static int dp83822_led_hw_control_get(struct phy_device *phydev, u8 index,
.led_hw_is_supported = dp83822_led_hw_is_supported, \
.led_hw_control_set = dp83822_led_hw_control_set, \
.led_hw_control_get = dp83822_led_hw_control_get, \
.attach_mdi_port = dp83822_attach_mdi_port \
}
#define DP83825_PHY_DRIVER(_id, _name) \

View File

@ -13,7 +13,7 @@
#include <linux/mdio.h>
#include <linux/marvell_phy.h>
#include <linux/of.h>
#include <linux/sfp.h>
#include <linux/phy_port.h>
#include <linux/netdevice.h>
/* Port PCS Configuration */
@ -473,89 +473,70 @@ static int mv2222_config_init(struct phy_device *phydev)
return 0;
}
static int mv2222_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
static int mv2222_configure_serdes(struct phy_port *port, bool enable,
phy_interface_t interface)
{
struct phy_device *phydev = upstream;
const struct sfp_module_caps *caps;
phy_interface_t sfp_interface;
struct phy_device *phydev = port_phydev(port);
struct mv2222_data *priv;
struct device *dev;
int ret;
int ret = 0;
priv = phydev->priv;
dev = &phydev->mdio.dev;
priv->line_interface = interface;
caps = sfp_get_module_caps(phydev->sfp_bus);
if (enable) {
linkmode_and(priv->supported, phydev->supported, port->supported);
phydev->port = caps->port;
sfp_interface = sfp_select_interface(phydev->sfp_bus, caps->link_modes);
ret = mv2222_config_line(phydev);
if (ret < 0)
return ret;
dev_info(dev, "%s SFP module inserted\n", phy_modes(sfp_interface));
if (mutex_trylock(&phydev->lock)) {
ret = mv2222_config_aneg(phydev);
mutex_unlock(&phydev->lock);
}
if (sfp_interface != PHY_INTERFACE_MODE_10GBASER &&
sfp_interface != PHY_INTERFACE_MODE_1000BASEX &&
sfp_interface != PHY_INTERFACE_MODE_SGMII) {
dev_err(dev, "Incompatible SFP module inserted\n");
return -EINVAL;
}
priv->line_interface = sfp_interface;
linkmode_and(priv->supported, phydev->supported, caps->link_modes);
ret = mv2222_config_line(phydev);
if (ret < 0)
return ret;
if (mutex_trylock(&phydev->lock)) {
ret = mv2222_config_aneg(phydev);
mutex_unlock(&phydev->lock);
} else {
linkmode_zero(priv->supported);
}
return ret;
}
static void mv2222_sfp_remove(void *upstream)
static void mv2222_port_link_up(struct phy_port *port)
{
struct phy_device *phydev = upstream;
struct mv2222_data *priv;
priv = phydev->priv;
priv->line_interface = PHY_INTERFACE_MODE_NA;
linkmode_zero(priv->supported);
phydev->port = PORT_NONE;
}
static void mv2222_sfp_link_up(void *upstream)
{
struct phy_device *phydev = upstream;
struct phy_device *phydev = port_phydev(port);
struct mv2222_data *priv;
priv = phydev->priv;
priv->sfp_link = true;
}
static void mv2222_sfp_link_down(void *upstream)
static void mv2222_port_link_down(struct phy_port *port)
{
struct phy_device *phydev = upstream;
struct phy_device *phydev = port_phydev(port);
struct mv2222_data *priv;
priv = phydev->priv;
priv->sfp_link = false;
}
static const struct sfp_upstream_ops sfp_phy_ops = {
.module_insert = mv2222_sfp_insert,
.module_remove = mv2222_sfp_remove,
.link_up = mv2222_sfp_link_up,
.link_down = mv2222_sfp_link_down,
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
static const struct phy_port_ops mv2222_port_ops = {
.link_up = mv2222_port_link_up,
.link_down = mv2222_port_link_down,
.configure_mii = mv2222_configure_serdes,
};
static int mv2222_attach_mii_port(struct phy_device *phydev, struct phy_port *port)
{
port->ops = &mv2222_port_ops;
__set_bit(PHY_INTERFACE_MODE_10GBASER, port->interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces);
__set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces);
return 0;
}
static int mv2222_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
@ -591,7 +572,7 @@ static int mv2222_probe(struct phy_device *phydev)
priv->line_interface = PHY_INTERFACE_MODE_NA;
phydev->priv = priv;
return phy_sfp_probe(phydev, &sfp_phy_ops);
return 0;
}
static struct phy_driver mv2222_drivers[] = {
@ -608,6 +589,7 @@ static struct phy_driver mv2222_drivers[] = {
.suspend = mv2222_suspend,
.resume = mv2222_resume,
.read_status = mv2222_read_status,
.attach_mii_port = mv2222_attach_mii_port,
},
};
module_phy_driver(mv2222_drivers);

View File

@ -29,10 +29,10 @@
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/phy.h>
#include <linux/phy_port.h>
#include <linux/marvell_phy.h>
#include <linux/bitfield.h>
#include <linux/of.h>
#include <linux/sfp.h>
#include <linux/io.h>
#include <asm/irq.h>
@ -3598,11 +3598,10 @@ static int marvell_probe(struct phy_device *phydev)
return marvell_hwmon_probe(phydev);
}
static int m88e1510_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
static int m88e1510_port_configure_serdes(struct phy_port *port, bool enable,
phy_interface_t interface)
{
struct phy_device *phydev = upstream;
const struct sfp_module_caps *caps;
phy_interface_t interface;
struct phy_device *phydev = port_phydev(port);
struct device *dev;
int oldpage;
int ret = 0;
@ -3610,28 +3609,27 @@ static int m88e1510_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
dev = &phydev->mdio.dev;
caps = sfp_get_module_caps(phydev->sfp_bus);
interface = sfp_select_interface(phydev->sfp_bus, caps->link_modes);
if (enable) {
switch (interface) {
case PHY_INTERFACE_MODE_1000BASEX:
mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_1000X;
dev_info(dev, "%s SFP module inserted\n", phy_modes(interface));
break;
case PHY_INTERFACE_MODE_100BASEX:
mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_100FX;
switch (interface) {
case PHY_INTERFACE_MODE_1000BASEX:
mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_1000X;
break;
case PHY_INTERFACE_MODE_SGMII:
mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_SGMII;
break;
case PHY_INTERFACE_MODE_100BASEX:
mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_100FX;
break;
default:
dev_err(dev, "Incompatible SFP module inserted\n");
break;
case PHY_INTERFACE_MODE_SGMII:
mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII_SGMII;
break;
default:
dev_err(dev, "Incompatible SFP module inserted\n");
return -EINVAL;
return -EINVAL;
}
} else {
mode = MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII;
}
oldpage = phy_select_page(phydev, MII_MARVELL_MODE_PAGE);
@ -3650,47 +3648,20 @@ static int m88e1510_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
return phy_restore_page(phydev, oldpage, ret);
}
static void m88e1510_sfp_remove(void *upstream)
{
struct phy_device *phydev = upstream;
int oldpage;
int ret = 0;
oldpage = phy_select_page(phydev, MII_MARVELL_MODE_PAGE);
if (oldpage < 0)
goto error;
ret = __phy_modify(phydev, MII_88E1510_GEN_CTRL_REG_1,
MII_88E1510_GEN_CTRL_REG_1_MODE_MASK,
MII_88E1510_GEN_CTRL_REG_1_MODE_RGMII);
if (ret < 0)
goto error;
ret = __phy_set_bits(phydev, MII_88E1510_GEN_CTRL_REG_1,
MII_88E1510_GEN_CTRL_REG_1_RESET);
error:
phy_restore_page(phydev, oldpage, ret);
}
static const struct sfp_upstream_ops m88e1510_sfp_ops = {
.module_insert = m88e1510_sfp_insert,
.module_remove = m88e1510_sfp_remove,
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
static const struct phy_port_ops m88e1510_serdes_port_ops = {
.configure_mii = m88e1510_port_configure_serdes,
};
static int m88e1510_probe(struct phy_device *phydev)
static int m88e1510_attach_mii_port(struct phy_device *phy_device,
struct phy_port *port)
{
int err;
port->ops = &m88e1510_serdes_port_ops;
err = marvell_probe(phydev);
if (err)
return err;
__set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces);
__set_bit(PHY_INTERFACE_MODE_100BASEX, port->interfaces);
return phy_sfp_probe(phydev, &m88e1510_sfp_ops);
return 0;
}
static struct phy_driver marvell_drivers[] = {
@ -3950,7 +3921,7 @@ static struct phy_driver marvell_drivers[] = {
.driver_data = DEF_MARVELL_HWMON_OPS(m88e1510_hwmon_ops),
.features = PHY_GBIT_FIBRE_FEATURES,
.flags = PHY_POLL_CABLE_TEST,
.probe = m88e1510_probe,
.probe = marvell_probe,
.config_init = m88e1510_config_init,
.config_aneg = m88e1510_config_aneg,
.read_status = marvell_read_status,
@ -3976,6 +3947,7 @@ static struct phy_driver marvell_drivers[] = {
.led_hw_is_supported = m88e1318_led_hw_is_supported,
.led_hw_control_set = m88e1318_led_hw_control_set,
.led_hw_control_get = m88e1318_led_hw_control_get,
.attach_mii_port = m88e1510_attach_mii_port,
},
{
.phy_id = MARVELL_PHY_ID_88E1540,

View File

@ -28,7 +28,7 @@
#include <linux/hwmon.h>
#include <linux/marvell_phy.h>
#include <linux/phy.h>
#include <linux/sfp.h>
#include <linux/phy_port.h>
#include <linux/netdevice.h>
#define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe
@ -463,29 +463,28 @@ static int mv3310_set_edpd(struct phy_device *phydev, u16 edpd)
return err;
}
static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
static int mv3310_attach_mii_port(struct phy_device *phydev,
struct phy_port *port)
{
struct phy_device *phydev = upstream;
const struct sfp_module_caps *caps;
phy_interface_t iface;
caps = sfp_get_module_caps(phydev->sfp_bus);
iface = sfp_select_interface(phydev->sfp_bus, caps->link_modes);
if (iface != PHY_INTERFACE_MODE_10GBASER) {
dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
return -EINVAL;
}
__set_bit(PHY_INTERFACE_MODE_10GBASER, port->interfaces);
return 0;
}
static const struct sfp_upstream_ops mv3310_sfp_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
.module_insert = mv3310_sfp_insert,
};
static int mv3310_attach_mdi_port(struct phy_device *phydev,
struct phy_port *port)
{
/* This PHY can do combo-ports, i.e. 2 MDI outputs, usually one
* of them going to an SFP and the other one to a RJ45
* connector. If we don't have any representation for the port
* in DT, and we are dealing with a non-SFP port, then we
* mask the port's capabilities to report BaseT-only modes
*/
if (port->not_described)
return phy_port_restrict_mediums(port,
BIT(ETHTOOL_LINK_MEDIUM_BASET));
return 0;
}
static int mv3310_probe(struct phy_device *phydev)
{
@ -544,7 +543,9 @@ static int mv3310_probe(struct phy_device *phydev)
chip->init_supported_interfaces(priv->supported_interfaces);
return phy_sfp_probe(phydev, &mv3310_sfp_ops);
phydev->max_n_ports = 2;
return 0;
}
static void mv3310_remove(struct phy_device *phydev)
@ -1405,6 +1406,8 @@ static struct phy_driver mv3310_drivers[] = {
.set_loopback = genphy_c45_loopback,
.get_wol = mv3110_get_wol,
.set_wol = mv3110_set_wol,
.attach_mii_port = mv3310_attach_mii_port,
.attach_mdi_port = mv3310_attach_mdi_port,
},
{
.phy_id = MARVELL_PHY_ID_88X3310,
@ -1424,6 +1427,8 @@ static struct phy_driver mv3310_drivers[] = {
.set_tunable = mv3310_set_tunable,
.remove = mv3310_remove,
.set_loopback = genphy_c45_loopback,
.attach_mii_port = mv3310_attach_mii_port,
.attach_mdi_port = mv3310_attach_mdi_port,
},
{
.phy_id = MARVELL_PHY_ID_88E2110,
@ -1444,6 +1449,8 @@ static struct phy_driver mv3310_drivers[] = {
.set_loopback = genphy_c45_loopback,
.get_wol = mv3110_get_wol,
.set_wol = mv3110_set_wol,
.attach_mii_port = mv3310_attach_mii_port,
.attach_mdi_port = mv3310_attach_mdi_port,
},
{
.phy_id = MARVELL_PHY_ID_88E2110,
@ -1462,6 +1469,8 @@ static struct phy_driver mv3310_drivers[] = {
.set_tunable = mv3310_set_tunable,
.remove = mv3310_remove,
.set_loopback = genphy_c45_loopback,
.attach_mii_port = mv3310_attach_mii_port,
.attach_mdi_port = mv3310_attach_mdi_port,
},
};

View File

@ -61,4 +61,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 */

View File

@ -4,6 +4,7 @@
*/
#include <linux/export.h>
#include <linux/phy.h>
#include <linux/phy_port.h>
#include <linux/of.h>
#include "phylib.h"
@ -208,7 +209,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);
}
/**

View File

@ -80,6 +80,14 @@ int __init phy_caps_init(void)
/* Fill the caps array from net/ethtool/common.c */
for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
linkmode = &link_mode_params[i];
/* Sanity check the linkmodes array for number of pairs */
if (linkmode->pairs < linkmode->min_pairs) {
pr_err("Pairs count must not be under min_pairs for linkmode %d\n",
i);
return -EINVAL;
}
capa = speed_duplex_to_capa(linkmode->speed, linkmode->duplex);
if (capa < 0) {
@ -378,3 +386,60 @@ 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 pairs, or less.
* @medium: The medium to get the support from
* @pairs: The number of pairs used on the given medium. Only relevant for modes
* that support this notion, such as BaseT. Pass 0 if not applicable.
*
* 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 pairs)
{
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;
}
/* If this medium matches, and had a non-zero min-pairs */
if (link_mode_params[i].mediums & BIT(medium) &&
(!link_mode_params[i].min_pairs ||
(link_mode_params[i].min_pairs <= pairs &&
link_mode_params[i].pairs >= pairs)))
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);

View File

@ -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>
@ -845,6 +846,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);
@ -1524,7 +1532,7 @@ static DEVICE_ATTR_RO(phy_standalone);
*
* Return: 0 on success, otherwise a negative error code.
*/
int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
static int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
{
struct phy_device *phydev = upstream;
struct net_device *dev = phydev->attached_dev;
@ -1534,7 +1542,6 @@ int phy_sfp_connect_phy(void *upstream, struct phy_device *phy)
return 0;
}
EXPORT_SYMBOL(phy_sfp_connect_phy);
/**
* phy_sfp_disconnect_phy - Disconnect the SFP module's PHY from the upstream PHY
@ -1546,7 +1553,7 @@ EXPORT_SYMBOL(phy_sfp_connect_phy);
* will be destroyed, re-inserting the same module will add a new phy with a
* new index.
*/
void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy)
static void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy)
{
struct phy_device *phydev = upstream;
struct net_device *dev = phydev->attached_dev;
@ -1554,7 +1561,6 @@ void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy)
if (dev)
phy_link_topo_del_phy(dev, phy);
}
EXPORT_SYMBOL(phy_sfp_disconnect_phy);
/**
* phy_sfp_attach - attach the SFP bus to the PHY upstream network device
@ -1563,7 +1569,7 @@ EXPORT_SYMBOL(phy_sfp_disconnect_phy);
*
* This is used to fill in the sfp_upstream_ops .attach member.
*/
void phy_sfp_attach(void *upstream, struct sfp_bus *bus)
static void phy_sfp_attach(void *upstream, struct sfp_bus *bus)
{
struct phy_device *phydev = upstream;
@ -1571,7 +1577,6 @@ void phy_sfp_attach(void *upstream, struct sfp_bus *bus)
phydev->attached_dev->sfp_bus = bus;
phydev->sfp_bus_attached = true;
}
EXPORT_SYMBOL(phy_sfp_attach);
/**
* phy_sfp_detach - detach the SFP bus from the PHY upstream network device
@ -1580,7 +1585,7 @@ EXPORT_SYMBOL(phy_sfp_attach);
*
* This is used to fill in the sfp_upstream_ops .detach member.
*/
void phy_sfp_detach(void *upstream, struct sfp_bus *bus)
static void phy_sfp_detach(void *upstream, struct sfp_bus *bus)
{
struct phy_device *phydev = upstream;
@ -1588,15 +1593,164 @@ void phy_sfp_detach(void *upstream, struct sfp_bus *bus)
phydev->attached_dev->sfp_bus = NULL;
phydev->sfp_bus_attached = false;
}
EXPORT_SYMBOL(phy_sfp_detach);
static int phy_sfp_module_insert(void *upstream, const struct sfp_eeprom_id *id)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
struct phy_device *phydev = upstream;
const struct sfp_module_caps *caps;
struct phy_port *port;
phy_interface_t iface;
linkmode_zero(sfp_support);
port = phy_get_sfp_port(phydev);
if (!port)
return -EINVAL;
caps = sfp_get_module_caps(phydev->sfp_bus);
linkmode_and(sfp_support, port->supported, caps->link_modes);
if (linkmode_empty(sfp_support)) {
dev_err(&phydev->mdio.dev, "incompatible SFP module inserted, no common linkmode\n");
return -EINVAL;
}
iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
if (iface == PHY_INTERFACE_MODE_NA) {
dev_err(&phydev->mdio.dev, "PHY %s does not support the SFP module's requested MII interfaces\n",
phydev_name(phydev));
return -EINVAL;
}
if (phydev->n_ports == 1)
phydev->port = caps->port;
if (port->ops && port->ops->configure_mii)
return port->ops->configure_mii(port, true, iface);
return 0;
}
static void phy_sfp_module_remove(void *upstream)
{
struct phy_device *phydev = upstream;
struct phy_port *port = phy_get_sfp_port(phydev);
if (port && port->ops && port->ops->configure_mii)
port->ops->configure_mii(port, false, PHY_INTERFACE_MODE_NA);
if (phydev->n_ports == 1)
phydev->port = PORT_NONE;
}
static void phy_sfp_link_up(void *upstream)
{
struct phy_device *phydev = upstream;
struct phy_port *port = phy_get_sfp_port(phydev);
if (port && port->ops && port->ops->link_up)
port->ops->link_up(port);
}
static void phy_sfp_link_down(void *upstream)
{
struct phy_device *phydev = upstream;
struct phy_port *port = phy_get_sfp_port(phydev);
if (port && port->ops && port->ops->link_down)
port->ops->link_down(port);
}
static const struct sfp_upstream_ops sfp_phydev_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.module_insert = phy_sfp_module_insert,
.module_remove = phy_sfp_module_remove,
.link_up = phy_sfp_link_up,
.link_down = phy_sfp_link_down,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
};
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/pairs 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--;
}
static int phy_setup_sfp_port(struct phy_device *phydev)
{
struct phy_port *port = phy_port_alloc();
int ret;
if (!port)
return -ENOMEM;
port->parent_type = PHY_PORT_PHY;
port->phy = phydev;
/* The PHY is a media converter, the port connected to the SFP cage
* is a MII port.
*/
port->is_mii = true;
port->is_sfp = true;
/* The port->supported and port->interfaces list will be populated
* when attaching the port to the phydev.
*/
ret = phy_add_port(phydev, port);
if (ret)
phy_port_destroy(port);
return ret;
}
/**
* phy_sfp_probe - probe for a SFP cage attached to this PHY device
* @phydev: Pointer to phy_device
* @ops: SFP's upstream operations
*/
int phy_sfp_probe(struct phy_device *phydev,
const struct sfp_upstream_ops *ops)
static int phy_sfp_probe(struct phy_device *phydev)
{
struct sfp_bus *bus;
int ret = 0;
@ -1608,12 +1762,15 @@ int phy_sfp_probe(struct phy_device *phydev,
phydev->sfp_bus = bus;
ret = sfp_bus_add_upstream(bus, phydev, ops);
ret = sfp_bus_add_upstream(bus, phydev, &sfp_phydev_ops);
sfp_bus_put(bus);
}
if (!ret && phydev->sfp_bus)
ret = phy_setup_sfp_port(phydev);
return ret;
}
EXPORT_SYMBOL(phy_sfp_probe);
static bool phy_drv_supports_irq(const struct phy_driver *phydrv)
{
@ -3325,6 +3482,161 @@ 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();
unsigned long mode;
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);
for_each_set_bit(mode, port->supported, __ETHTOOL_LINK_MODE_MASK_NBITS)
port->pairs = max_t(int, port->pairs,
ethtool_linkmode_n_pairs(mode));
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) {
phy_port_destroy(port);
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;
ret = phy_sfp_probe(phydev);
if (ret)
goto out;
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;
}
/**
* phy_get_sfp_port() - Returns the first valid SFP port of a PHY
* @phydev: pointer to the PHY device to get the SFP port from
*
* Returns: The first active SFP (serdes) port of a PHY device, NULL if none
* exist.
*/
struct phy_port *phy_get_sfp_port(struct phy_device *phydev)
{
struct phy_port *port;
list_for_each_entry(port, &phydev->ports, head)
if (port->active && port->is_sfp)
return port;
return NULL;
}
EXPORT_SYMBOL_GPL(phy_get_sfp_port);
/**
* fwnode_mdio_find_device - Given a fwnode, find the mdio_device
* @fwnode: pointer to the mdio_device's fwnode
@ -3462,6 +3774,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
@ -3537,6 +3854,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;

212
drivers/net/phy/phy_port.c Normal file
View File

@ -0,0 +1,212 @@
// SPDX-License-Identifier: GPL-2.0+
/* Framework to drive Ethernet ports
*
* Copyright (c) 2024 Maxime Chevallier <maxime.chevallier@bootlin.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 pairs = 0, mediums = 0;
int 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);
if (medium == ETHTOOL_LINK_MEDIUM_BASET) {
ret = fwnode_property_read_u32(fwnode, "pairs", &pairs);
if (ret)
return ERR_PTR(ret);
switch (pairs) {
case 1: /* BaseT1 */
case 2: /* 100BaseTX */
case 4:
break;
default:
pr_err("%u is not a valid number of pairs\n", pairs);
return ERR_PTR(-EINVAL);
}
}
if (pairs && medium != ETHTOOL_LINK_MEDIUM_BASET) {
pr_err("pairs property is only compatible with BaseT medium\n");
return ERR_PTR(-EINVAL);
}
mediums |= BIT(medium);
if (!mediums)
return ERR_PTR(-EINVAL);
port = phy_port_alloc();
if (!port)
return ERR_PTR(-ENOMEM);
port->pairs = pairs;
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 pairs 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 };
unsigned long mode;
int i;
for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST) {
linkmode_zero(supported);
phy_caps_medium_get_supported(supported, i, port->pairs);
linkmode_or(port->supported, port->supported, supported);
}
/* If there's no pairs specified, we grab the default number of
* pairs as the max of the default pairs for each linkmode
*/
if (!port->pairs)
for_each_set_bit(mode, port->supported,
__ETHTOOL_LINK_MODE_MASK_NBITS)
port->pairs = max_t(int, port->pairs,
ethtool_linkmode_n_pairs(mode));
/* Serdes ports supported through SFP may not have any medium set,
* as they will output PHY_INTERFACE_MODE_XXX modes. In that case, derive
* the supported list based on these interfaces
*/
if (port->is_mii && !port->mediums) {
unsigned long interface, link_caps = 0;
/* Get each interface's caps */
for_each_set_bit(interface, port->interfaces,
PHY_INTERFACE_MODE_MAX)
link_caps |= phy_caps_from_interface(interface);
phy_caps_linkmodes(link_caps, port->supported);
}
}
EXPORT_SYMBOL_GPL(phy_port_update_supported);
/**
* phy_port_filter_supported() - Make sure that port->supported match port->mediums
* @port: The port to filter
*
* After updating a port's mediums to a more restricted subset, this helper will
* make sure that port->supported only contains linkmodes that are compatible
* with port->mediums.
*/
static void phy_port_filter_supported(struct phy_port *port)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0 };
int i;
for_each_set_bit(i, &port->mediums, __ETHTOOL_LINK_MEDIUM_LAST)
phy_caps_medium_get_supported(supported, i, port->pairs);
linkmode_and(port->supported, port->supported, supported);
}
/**
* phy_port_restrict_mediums - Mask away some of the port's supported mediums
* @port: The port to act upon
* @mediums: A mask of mediums to support on the port
*
* This helper allows removing some mediums from a port's list of supported
* mediums, which occurs once we have enough information about the port to
* know its nature.
*
* Returns: 0 if the change was donne correctly, a negative value otherwise.
*/
int phy_port_restrict_mediums(struct phy_port *port, unsigned long mediums)
{
/* We forbid ending-up with a port with empty mediums */
if (!(port->mediums & mediums))
return -EINVAL;
port->mediums &= mediums;
phy_port_filter_supported(port);
return 0;
}
EXPORT_SYMBOL_GPL(phy_port_restrict_mediums);
/**
* 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);

View File

@ -20,7 +20,7 @@
#include <linux/of.h>
#include <linux/phylink.h>
#include <linux/reset.h>
#include <linux/sfp.h>
#include <linux/phy_port.h>
#include <dt-bindings/net/qca-ar803x.h>
#include "qcom.h"
@ -769,57 +769,44 @@ static int at8031_register_regulators(struct phy_device *phydev)
return 0;
}
static int at8031_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
static int at803x_configure_mii(struct phy_port *port, bool enable,
phy_interface_t interface)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(phy_support);
__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
struct phy_device *phydev = upstream;
const struct sfp_module_caps *caps;
phy_interface_t iface;
struct phy_device *phydev = port_phydev(port);
linkmode_zero(phy_support);
phylink_set(phy_support, 1000baseX_Full);
phylink_set(phy_support, 1000baseT_Full);
phylink_set(phy_support, Autoneg);
phylink_set(phy_support, Pause);
phylink_set(phy_support, Asym_Pause);
caps = sfp_get_module_caps(phydev->sfp_bus);
/* Some modules support 10G modes as well as others we support.
* Mask out non-supported modes so the correct interface is picked.
*/
linkmode_and(sfp_support, phy_support, caps->link_modes);
if (linkmode_empty(sfp_support)) {
dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
return -EINVAL;
}
iface = sfp_select_interface(phydev->sfp_bus, sfp_support);
/* Only 1000Base-X is supported by AR8031/8033 as the downstream SerDes
* interface for use with SFP modules.
* However, some copper modules detected as having a preferred SGMII
* interface do default to and function in 1000Base-X mode, so just
* print a warning and allow such modules, as they may have some chance
* of working.
*/
if (iface == PHY_INTERFACE_MODE_SGMII)
dev_warn(&phydev->mdio.dev, "module may not function if 1000Base-X not supported\n");
else if (iface != PHY_INTERFACE_MODE_1000BASEX)
return -EINVAL;
if (interface == PHY_INTERFACE_MODE_SGMII)
dev_warn(&phydev->mdio.dev,
"module may not function if 1000Base-X not supported\n");
return 0;
}
static const struct sfp_upstream_ops at8031_sfp_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.module_insert = at8031_sfp_insert,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
static const struct phy_port_ops at803x_port_ops = {
.configure_mii = at803x_configure_mii,
};
static int at8031_attach_mii_port(struct phy_device *phydev,
struct phy_port *port)
{
linkmode_zero(port->supported);
phylink_set(port->supported, 1000baseX_Full);
phylink_set(port->supported, 1000baseT_Full);
phylink_set(port->supported, Autoneg);
phylink_set(port->supported, Pause);
phylink_set(port->supported, Asym_Pause);
/* This device doesn't really support SGMII. However, do our best
* to be compatible with copper modules (that usually require SGMII),
* in a degraded mode as we only allow 1000BaseT Full
*/
__set_bit(PHY_INTERFACE_MODE_SGMII, port->interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces);
port->ops = &at803x_port_ops;
return 0;
}
static int at8031_parse_dt(struct phy_device *phydev)
{
struct device_node *node = phydev->mdio.dev.of_node;
@ -840,8 +827,7 @@ static int at8031_parse_dt(struct phy_device *phydev)
return ret;
}
/* Only AR8031/8033 support 1000Base-X for SFP modules */
return phy_sfp_probe(phydev, &at8031_sfp_ops);
return 0;
}
static int at8031_probe(struct phy_device *phydev)
@ -1172,6 +1158,7 @@ static struct phy_driver at803x_driver[] = {
.set_tunable = at803x_set_tunable,
.cable_test_start = at8031_cable_test_start,
.cable_test_get_status = at8031_cable_test_get_status,
.attach_mii_port = at8031_attach_mii_port,
}, {
/* Qualcomm Atheros AR8032 */
PHY_ID_MATCH_EXACT(ATH8032_PHY_ID),

View File

@ -13,7 +13,7 @@
#include <linux/phy.h>
#include <linux/bitfield.h>
#include <linux/gpio/driver.h>
#include <linux/sfp.h>
#include <linux/phy_port.h>
#include "../phylib.h"
#include "qcom.h"
@ -643,67 +643,54 @@ static int qca807x_phy_package_config_init_once(struct phy_device *phydev)
return ret;
}
static int qca807x_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
static int qca807x_configure_serdes(struct phy_port *port, bool enable,
phy_interface_t interface)
{
struct phy_device *phydev = upstream;
const struct sfp_module_caps *caps;
phy_interface_t iface;
struct phy_device *phydev = port_phydev(port);
int ret;
caps = sfp_get_module_caps(phydev->sfp_bus);
iface = sfp_select_interface(phydev->sfp_bus, caps->link_modes);
if (!phydev)
return -ENODEV;
dev_info(&phydev->mdio.dev, "%s SFP module inserted\n", phy_modes(iface));
switch (iface) {
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_100BASEX:
if (enable) {
/* Set PHY mode to PSGMII combo (1/4 copper + combo ports) mode */
ret = phy_modify(phydev,
QCA807X_CHIP_CONFIGURATION,
QCA807X_CHIP_CONFIGURATION_MODE_CFG_MASK,
QCA807X_CHIP_CONFIGURATION_MODE_PSGMII_FIBER);
if (ret)
return ret;
/* Enable fiber mode autodection (1000Base-X or 100Base-FX) */
ret = phy_set_bits_mmd(phydev,
MDIO_MMD_AN,
QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION,
QCA807X_MMD7_FIBER_MODE_AUTO_DETECTION_EN);
/* Select fiber page */
ret = phy_clear_bits(phydev,
QCA807X_CHIP_CONFIGURATION,
QCA807X_BT_BX_REG_SEL);
phydev->port = PORT_FIBRE;
break;
default:
dev_err(&phydev->mdio.dev, "Incompatible SFP module inserted\n");
return -EINVAL;
if (ret)
return ret;
}
return ret;
phydev->port = enable ? PORT_FIBRE : PORT_TP;
return phy_modify(phydev, QCA807X_CHIP_CONFIGURATION,
QCA807X_BT_BX_REG_SEL,
enable ? 0 : QCA807X_BT_BX_REG_SEL);
}
static void qca807x_sfp_remove(void *upstream)
{
struct phy_device *phydev = upstream;
/* Select copper page */
phy_set_bits(phydev,
QCA807X_CHIP_CONFIGURATION,
QCA807X_BT_BX_REG_SEL);
phydev->port = PORT_TP;
}
static const struct sfp_upstream_ops qca807x_sfp_ops = {
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
.module_insert = qca807x_sfp_insert,
.module_remove = qca807x_sfp_remove,
.connect_phy = phy_sfp_connect_phy,
.disconnect_phy = phy_sfp_disconnect_phy,
static const struct phy_port_ops qca807x_serdes_port_ops = {
.configure_mii = qca807x_configure_serdes,
};
static int qca807x_attach_mii_port(struct phy_device *phydev,
struct phy_port *port)
{
__set_bit(PHY_INTERFACE_MODE_1000BASEX, port->interfaces);
__set_bit(PHY_INTERFACE_MODE_100BASEX, port->interfaces);
port->ops = &qca807x_serdes_port_ops;
return 0;
}
static int qca807x_probe(struct phy_device *phydev)
{
struct device_node *node = phydev->mdio.dev.of_node;
@ -744,9 +731,8 @@ static int qca807x_probe(struct phy_device *phydev)
/* Attach SFP bus on combo port*/
if (phy_read(phydev, QCA807X_CHIP_CONFIGURATION)) {
ret = phy_sfp_probe(phydev, &qca807x_sfp_ops);
if (ret)
return ret;
phydev->max_n_ports = 2;
linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->supported);
linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, phydev->advertising);
}
@ -824,6 +810,7 @@ static struct phy_driver qca807x_drivers[] = {
.get_phy_stats = qca807x_get_phy_stats,
.set_wol = at8031_set_wol,
.get_wol = at803x_get_wol,
.attach_mii_port = qca807x_attach_mii_port,
},
{
PHY_ID_MATCH_EXACT(PHY_ID_QCA8075),
@ -851,6 +838,7 @@ static struct phy_driver qca807x_drivers[] = {
.get_phy_stats = qca807x_get_phy_stats,
.set_wol = at8031_set_wol,
.get_wol = at803x_get_wol,
.attach_mii_port = qca807x_attach_mii_port,
},
};
module_phy_driver(qca807x_drivers);

View File

@ -216,13 +216,43 @@ static inline u8 *ethtool_rxfh_context_key(struct ethtool_rxfh_context *ctx)
void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id);
struct link_mode_info {
int speed;
u8 lanes;
u8 duplex;
int speed;
u8 lanes;
u8 min_pairs;
u8 pairs;
u8 duplex;
u16 mediums;
};
extern const struct link_mode_info link_mode_params[];
enum ethtool_link_medium {
ETHTOOL_LINK_MEDIUM_BASET = 0,
ETHTOOL_LINK_MEDIUM_BASEK,
ETHTOOL_LINK_MEDIUM_BASES,
ETHTOOL_LINK_MEDIUM_BASEC,
ETHTOOL_LINK_MEDIUM_BASEL,
ETHTOOL_LINK_MEDIUM_BASED,
ETHTOOL_LINK_MEDIUM_BASEE,
ETHTOOL_LINK_MEDIUM_BASEF,
ETHTOOL_LINK_MEDIUM_BASEV,
ETHTOOL_LINK_MEDIUM_BASEMLD,
ETHTOOL_LINK_MEDIUM_NONE,
__ETHTOOL_LINK_MEDIUM_LAST,
};
#define ETHTOOL_MEDIUM_FIBER_BITS (BIT(ETHTOOL_LINK_MEDIUM_BASES) | \
BIT(ETHTOOL_LINK_MEDIUM_BASEL) | \
BIT(ETHTOOL_LINK_MEDIUM_BASEF))
enum ethtool_link_medium ethtool_str_to_medium(const char *str);
static inline int ethtool_linkmode_n_pairs(unsigned int mode)
{
return link_mode_params[mode].pairs;
}
/* declare a link mode bitmap */
#define __ETHTOOL_DECLARE_LINK_MODE_MASK(name) \
DECLARE_BITMAP(name, __ETHTOOL_LINK_MODE_MASK_NBITS)

View File

@ -327,6 +327,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;
@ -645,6 +646,9 @@ struct phy_oatc14_sqi_capability {
* @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
@ -783,6 +787,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;
@ -807,6 +815,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
*
@ -1507,6 +1518,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)
@ -2097,12 +2151,6 @@ int phy_suspend(struct phy_device *phydev);
int phy_resume(struct phy_device *phydev);
int __phy_resume(struct phy_device *phydev);
int phy_loopback(struct phy_device *phydev, bool enable, int speed);
int phy_sfp_connect_phy(void *upstream, struct phy_device *phy);
void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy);
void phy_sfp_attach(void *upstream, struct sfp_bus *bus);
void phy_sfp_detach(void *upstream, struct sfp_bus *bus);
int phy_sfp_probe(struct phy_device *phydev,
const struct sfp_upstream_ops *ops);
struct phy_device *phy_attach(struct net_device *dev, const char *bus_id,
phy_interface_t interface);
struct phy_device *phy_find_next(struct mii_bus *bus, struct phy_device *pos);
@ -2310,6 +2358,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,
@ -2400,6 +2449,8 @@ int __phy_hwtstamp_set(struct phy_device *phydev,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack);
struct phy_port *phy_get_sfp_port(struct phy_device *phydev);
extern const struct bus_type mdio_bus_type;
extern const struct class mdio_bus_class;

99
include/linux/phy_port.h Normal file
View File

@ -0,0 +1,99 @@
/* 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
* @pairs: The number of 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).
* @is_sfp: Indicates if this port drives an SFP cage.
*/
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 pairs;
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;
unsigned int is_sfp: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_restrict_mediums(struct phy_port *port, unsigned long mediums);
int phy_port_get_type(struct phy_port *port);
#endif

View File

@ -285,12 +285,35 @@ static_assert(ARRAY_SIZE(link_mode_names) == __ETHTOOL_LINK_MODE_MASK_NBITS);
#define __LINK_MODE_LANES_DR8_2 8
#define __LINK_MODE_LANES_T1BRR 1
#define __DEFINE_LINK_MODE_PARAMS(_speed, _type, _duplex) \
#define __DEFINE_LINK_MODE_PARAMS_PAIRS(_speed, _type, _min_pairs, _pairs, _duplex, _medium) \
[ETHTOOL_LINK_MODE(_speed, _type, _duplex)] = { \
.speed = SPEED_ ## _speed, \
.lanes = __LINK_MODE_LANES_ ## _type, \
.duplex = __DUPLEX_ ## _duplex \
.min_pairs = _min_pairs, \
.pairs = _pairs, \
.duplex = __DUPLEX_ ## _duplex, \
.mediums = BIT(ETHTOOL_LINK_MEDIUM_BASE ## _medium) \
}
#define __DEFINE_LINK_MODE_PARAMS(_speed, _type, _duplex, _medium) \
[ETHTOOL_LINK_MODE(_speed, _type, _duplex)] = { \
.speed = SPEED_ ## _speed, \
.lanes = __LINK_MODE_LANES_ ## _type, \
.min_pairs = 0, \
.pairs = 0, \
.duplex = __DUPLEX_ ## _duplex, \
.mediums = BIT(ETHTOOL_LINK_MEDIUM_BASE ## _medium) \
}
#define __DEFINE_LINK_MODE_PARAMS_MEDIUMS(_speed, _type, _duplex, _mediums) \
[ETHTOOL_LINK_MODE(_speed, _type, _duplex)] = { \
.speed = SPEED_ ## _speed, \
.lanes = __LINK_MODE_LANES_ ## _type, \
.min_pairs = 0, \
.pairs = 0, \
.duplex = __DUPLEX_ ## _duplex, \
.mediums = (_mediums) \
}
#define __MED(_medium) (BIT(ETHTOOL_LINK_MEDIUM_BASE ## _medium))
#define __DUPLEX_Half DUPLEX_HALF
#define __DUPLEX_Full DUPLEX_FULL
#define __DEFINE_SPECIAL_MODE_PARAMS(_mode) \
@ -298,142 +321,168 @@ static_assert(ARRAY_SIZE(link_mode_names) == __ETHTOOL_LINK_MODE_MASK_NBITS);
.speed = SPEED_UNKNOWN, \
.lanes = 0, \
.duplex = DUPLEX_UNKNOWN, \
.mediums = BIT(ETHTOOL_LINK_MEDIUM_NONE), \
}
const struct link_mode_info link_mode_params[] = {
__DEFINE_LINK_MODE_PARAMS(10, T, Half),
__DEFINE_LINK_MODE_PARAMS(10, T, Full),
__DEFINE_LINK_MODE_PARAMS(100, T, Half),
__DEFINE_LINK_MODE_PARAMS(100, T, Full),
__DEFINE_LINK_MODE_PARAMS(1000, T, Half),
__DEFINE_LINK_MODE_PARAMS(1000, T, Full),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10, T, 2, 4, Half, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10, T, 2, 4, Full, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(100, T, 2, 4, Half, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(100, T, 2, 4, Full, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(1000, T, 4, 4, Half, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(1000, T, 4, 4, Full, T),
__DEFINE_SPECIAL_MODE_PARAMS(Autoneg),
__DEFINE_SPECIAL_MODE_PARAMS(TP),
__DEFINE_SPECIAL_MODE_PARAMS(AUI),
__DEFINE_SPECIAL_MODE_PARAMS(MII),
__DEFINE_SPECIAL_MODE_PARAMS(FIBRE),
__DEFINE_SPECIAL_MODE_PARAMS(BNC),
__DEFINE_LINK_MODE_PARAMS(10000, T, Full),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10000, T, 4, 4, Full, T),
__DEFINE_SPECIAL_MODE_PARAMS(Pause),
__DEFINE_SPECIAL_MODE_PARAMS(Asym_Pause),
__DEFINE_LINK_MODE_PARAMS(2500, X, Full),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(2500, X, Full,
__MED(C) | __MED(S) | __MED(L)),
__DEFINE_SPECIAL_MODE_PARAMS(Backplane),
__DEFINE_LINK_MODE_PARAMS(1000, KX, Full),
__DEFINE_LINK_MODE_PARAMS(10000, KX4, Full),
__DEFINE_LINK_MODE_PARAMS(10000, KR, Full),
__DEFINE_LINK_MODE_PARAMS(1000, KX, Full, K),
__DEFINE_LINK_MODE_PARAMS(10000, KX4, Full, K),
__DEFINE_LINK_MODE_PARAMS(10000, KR, Full, K),
[ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = {
.speed = SPEED_10000,
.lanes = 1,
.duplex = DUPLEX_FULL,
},
__DEFINE_LINK_MODE_PARAMS(20000, MLD2, Full),
__DEFINE_LINK_MODE_PARAMS(20000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(40000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(40000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(40000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(40000, LR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(56000, LR4, Full),
__DEFINE_LINK_MODE_PARAMS(25000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(25000, KR, Full),
__DEFINE_LINK_MODE_PARAMS(25000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, CR2, Full),
__DEFINE_LINK_MODE_PARAMS(50000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(100000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(100000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(100000, LR4_ER4, Full),
__DEFINE_LINK_MODE_PARAMS(50000, SR2, Full),
__DEFINE_LINK_MODE_PARAMS(1000, X, Full),
__DEFINE_LINK_MODE_PARAMS(10000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(10000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(10000, LR, Full),
__DEFINE_LINK_MODE_PARAMS(10000, LRM, Full),
__DEFINE_LINK_MODE_PARAMS(10000, ER, Full),
__DEFINE_LINK_MODE_PARAMS(2500, T, Full),
__DEFINE_LINK_MODE_PARAMS(5000, T, Full),
__DEFINE_LINK_MODE_PARAMS(20000, MLD2, Full, MLD),
__DEFINE_LINK_MODE_PARAMS(20000, KR2, Full, K),
__DEFINE_LINK_MODE_PARAMS(40000, KR4, Full, K),
__DEFINE_LINK_MODE_PARAMS(40000, CR4, Full, C),
__DEFINE_LINK_MODE_PARAMS(40000, SR4, Full, S),
__DEFINE_LINK_MODE_PARAMS(40000, LR4, Full, L),
__DEFINE_LINK_MODE_PARAMS(56000, KR4, Full, K),
__DEFINE_LINK_MODE_PARAMS(56000, CR4, Full, C),
__DEFINE_LINK_MODE_PARAMS(56000, SR4, Full, S),
__DEFINE_LINK_MODE_PARAMS(56000, LR4, Full, L),
__DEFINE_LINK_MODE_PARAMS(25000, CR, Full, C),
__DEFINE_LINK_MODE_PARAMS(25000, KR, Full, K),
__DEFINE_LINK_MODE_PARAMS(25000, SR, Full, S),
__DEFINE_LINK_MODE_PARAMS(50000, CR2, Full, C),
__DEFINE_LINK_MODE_PARAMS(50000, KR2, Full, K),
__DEFINE_LINK_MODE_PARAMS(100000, KR4, Full, K),
__DEFINE_LINK_MODE_PARAMS(100000, SR4, Full, S),
__DEFINE_LINK_MODE_PARAMS(100000, CR4, Full, C),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(100000, LR4_ER4, Full,
__MED(L) | __MED(E)),
__DEFINE_LINK_MODE_PARAMS(50000, SR2, Full, S),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(1000, X, Full,
__MED(C) | __MED(S) | __MED(L)),
__DEFINE_LINK_MODE_PARAMS(10000, CR, Full, C),
__DEFINE_LINK_MODE_PARAMS(10000, SR, Full, S),
__DEFINE_LINK_MODE_PARAMS(10000, LR, Full, L),
__DEFINE_LINK_MODE_PARAMS(10000, LRM, Full, L),
__DEFINE_LINK_MODE_PARAMS(10000, ER, Full, E),
__DEFINE_LINK_MODE_PARAMS_PAIRS(2500, T, 4, 4, Full, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(5000, T, 4, 4, Full, T),
__DEFINE_SPECIAL_MODE_PARAMS(FEC_NONE),
__DEFINE_SPECIAL_MODE_PARAMS(FEC_RS),
__DEFINE_SPECIAL_MODE_PARAMS(FEC_BASER),
__DEFINE_LINK_MODE_PARAMS(50000, KR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, LR_ER_FR, Full),
__DEFINE_LINK_MODE_PARAMS(50000, DR, Full),
__DEFINE_LINK_MODE_PARAMS(100000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, SR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, CR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, LR2_ER2_FR2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, DR2, Full),
__DEFINE_LINK_MODE_PARAMS(200000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, LR4_ER4_FR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, DR4, Full),
__DEFINE_LINK_MODE_PARAMS(200000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(100, T1, Full),
__DEFINE_LINK_MODE_PARAMS(1000, T1, Full),
__DEFINE_LINK_MODE_PARAMS(400000, KR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, SR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, LR8_ER8_FR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, DR8, Full),
__DEFINE_LINK_MODE_PARAMS(400000, CR8, Full),
__DEFINE_LINK_MODE_PARAMS(50000, KR, Full, K),
__DEFINE_LINK_MODE_PARAMS(50000, SR, Full, S),
__DEFINE_LINK_MODE_PARAMS(50000, CR, Full, C),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(50000, LR_ER_FR, Full,
__MED(L) | __MED(E) | __MED(F)),
__DEFINE_LINK_MODE_PARAMS(50000, DR, Full, D),
__DEFINE_LINK_MODE_PARAMS(100000, KR2, Full, K),
__DEFINE_LINK_MODE_PARAMS(100000, SR2, Full, S),
__DEFINE_LINK_MODE_PARAMS(100000, CR2, Full, C),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(100000, LR2_ER2_FR2, Full,
__MED(L) | __MED(E) | __MED(F)),
__DEFINE_LINK_MODE_PARAMS(100000, DR2, Full, D),
__DEFINE_LINK_MODE_PARAMS(200000, KR4, Full, K),
__DEFINE_LINK_MODE_PARAMS(200000, SR4, Full, S),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(200000, LR4_ER4_FR4, Full,
__MED(L) | __MED(E) | __MED(F)),
__DEFINE_LINK_MODE_PARAMS(200000, DR4, Full, D),
__DEFINE_LINK_MODE_PARAMS(200000, CR4, Full, C),
__DEFINE_LINK_MODE_PARAMS_PAIRS(100, T1, 1, 1, Full, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(1000, T1, 1, 1, Full, T),
__DEFINE_LINK_MODE_PARAMS(400000, KR8, Full, K),
__DEFINE_LINK_MODE_PARAMS(400000, SR8, Full, S),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(400000, LR8_ER8_FR8, Full,
__MED(L) | __MED(E) | __MED(F)),
__DEFINE_LINK_MODE_PARAMS(400000, DR8, Full, D),
__DEFINE_LINK_MODE_PARAMS(400000, CR8, Full, C),
__DEFINE_SPECIAL_MODE_PARAMS(FEC_LLRS),
__DEFINE_LINK_MODE_PARAMS(100000, KR, Full),
__DEFINE_LINK_MODE_PARAMS(100000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(100000, LR_ER_FR, Full),
__DEFINE_LINK_MODE_PARAMS(100000, DR, Full),
__DEFINE_LINK_MODE_PARAMS(100000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(200000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(200000, SR2, Full),
__DEFINE_LINK_MODE_PARAMS(200000, LR2_ER2_FR2, Full),
__DEFINE_LINK_MODE_PARAMS(200000, DR2, Full),
__DEFINE_LINK_MODE_PARAMS(200000, CR2, Full),
__DEFINE_LINK_MODE_PARAMS(400000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(400000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(400000, LR4_ER4_FR4, Full),
__DEFINE_LINK_MODE_PARAMS(400000, DR4, Full),
__DEFINE_LINK_MODE_PARAMS(400000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(100, FX, Half),
__DEFINE_LINK_MODE_PARAMS(100, FX, Full),
__DEFINE_LINK_MODE_PARAMS(10, T1L, Full),
__DEFINE_LINK_MODE_PARAMS(800000, CR8, Full),
__DEFINE_LINK_MODE_PARAMS(800000, KR8, Full),
__DEFINE_LINK_MODE_PARAMS(800000, DR8, Full),
__DEFINE_LINK_MODE_PARAMS(800000, DR8_2, Full),
__DEFINE_LINK_MODE_PARAMS(800000, SR8, Full),
__DEFINE_LINK_MODE_PARAMS(800000, VR8, Full),
__DEFINE_LINK_MODE_PARAMS(10, T1S, Full),
__DEFINE_LINK_MODE_PARAMS(10, T1S, Half),
__DEFINE_LINK_MODE_PARAMS(10, T1S_P2MP, Half),
__DEFINE_LINK_MODE_PARAMS(10, T1BRR, Full),
__DEFINE_LINK_MODE_PARAMS(200000, CR, Full),
__DEFINE_LINK_MODE_PARAMS(200000, KR, Full),
__DEFINE_LINK_MODE_PARAMS(200000, DR, Full),
__DEFINE_LINK_MODE_PARAMS(200000, DR_2, Full),
__DEFINE_LINK_MODE_PARAMS(200000, SR, Full),
__DEFINE_LINK_MODE_PARAMS(200000, VR, Full),
__DEFINE_LINK_MODE_PARAMS(400000, CR2, Full),
__DEFINE_LINK_MODE_PARAMS(400000, KR2, Full),
__DEFINE_LINK_MODE_PARAMS(400000, DR2, Full),
__DEFINE_LINK_MODE_PARAMS(400000, DR2_2, Full),
__DEFINE_LINK_MODE_PARAMS(400000, SR2, Full),
__DEFINE_LINK_MODE_PARAMS(400000, VR2, Full),
__DEFINE_LINK_MODE_PARAMS(800000, CR4, Full),
__DEFINE_LINK_MODE_PARAMS(800000, KR4, Full),
__DEFINE_LINK_MODE_PARAMS(800000, DR4, Full),
__DEFINE_LINK_MODE_PARAMS(800000, DR4_2, Full),
__DEFINE_LINK_MODE_PARAMS(800000, SR4, Full),
__DEFINE_LINK_MODE_PARAMS(800000, VR4, Full),
__DEFINE_LINK_MODE_PARAMS(1600000, CR8, Full),
__DEFINE_LINK_MODE_PARAMS(1600000, KR8, Full),
__DEFINE_LINK_MODE_PARAMS(1600000, DR8, Full),
__DEFINE_LINK_MODE_PARAMS(1600000, DR8_2, Full),
__DEFINE_LINK_MODE_PARAMS(100000, KR, Full, K),
__DEFINE_LINK_MODE_PARAMS(100000, SR, Full, S),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(100000, LR_ER_FR, Full,
__MED(L) | __MED(E) | __MED(F)),
__DEFINE_LINK_MODE_PARAMS(100000, DR, Full, D),
__DEFINE_LINK_MODE_PARAMS(100000, CR, Full, C),
__DEFINE_LINK_MODE_PARAMS(200000, KR2, Full, K),
__DEFINE_LINK_MODE_PARAMS(200000, SR2, Full, S),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(200000, LR2_ER2_FR2, Full,
__MED(L) | __MED(E) | __MED(F)),
__DEFINE_LINK_MODE_PARAMS(200000, DR2, Full, D),
__DEFINE_LINK_MODE_PARAMS(200000, CR2, Full, C),
__DEFINE_LINK_MODE_PARAMS(400000, KR4, Full, K),
__DEFINE_LINK_MODE_PARAMS(400000, SR4, Full, S),
__DEFINE_LINK_MODE_PARAMS_MEDIUMS(400000, LR4_ER4_FR4, Full,
__MED(L) | __MED(E) | __MED(F)),
__DEFINE_LINK_MODE_PARAMS(400000, DR4, Full, D),
__DEFINE_LINK_MODE_PARAMS(400000, CR4, Full, C),
__DEFINE_LINK_MODE_PARAMS(100, FX, Half, F),
__DEFINE_LINK_MODE_PARAMS(100, FX, Full, F),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10, T1L, 1, 1, Full, T),
__DEFINE_LINK_MODE_PARAMS(800000, CR8, Full, C),
__DEFINE_LINK_MODE_PARAMS(800000, KR8, Full, K),
__DEFINE_LINK_MODE_PARAMS(800000, DR8, Full, D),
__DEFINE_LINK_MODE_PARAMS(800000, DR8_2, Full, D),
__DEFINE_LINK_MODE_PARAMS(800000, SR8, Full, S),
__DEFINE_LINK_MODE_PARAMS(800000, VR8, Full, V),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10, T1S, 1, 1, Full, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10, T1S, 1, 1, Half, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10, T1S_P2MP, 1, 1, Half, T),
__DEFINE_LINK_MODE_PARAMS_PAIRS(10, T1BRR, 1, 1, Full, T),
__DEFINE_LINK_MODE_PARAMS(200000, CR, Full, C),
__DEFINE_LINK_MODE_PARAMS(200000, KR, Full, K),
__DEFINE_LINK_MODE_PARAMS(200000, DR, Full, D),
__DEFINE_LINK_MODE_PARAMS(200000, DR_2, Full, D),
__DEFINE_LINK_MODE_PARAMS(200000, SR, Full, S),
__DEFINE_LINK_MODE_PARAMS(200000, VR, Full, V),
__DEFINE_LINK_MODE_PARAMS(400000, CR2, Full, C),
__DEFINE_LINK_MODE_PARAMS(400000, KR2, Full, K),
__DEFINE_LINK_MODE_PARAMS(400000, DR2, Full, D),
__DEFINE_LINK_MODE_PARAMS(400000, DR2_2, Full, D),
__DEFINE_LINK_MODE_PARAMS(400000, SR2, Full, S),
__DEFINE_LINK_MODE_PARAMS(400000, VR2, Full, V),
__DEFINE_LINK_MODE_PARAMS(800000, CR4, Full, C),
__DEFINE_LINK_MODE_PARAMS(800000, KR4, Full, K),
__DEFINE_LINK_MODE_PARAMS(800000, DR4, Full, D),
__DEFINE_LINK_MODE_PARAMS(800000, DR4_2, Full, D),
__DEFINE_LINK_MODE_PARAMS(800000, SR4, Full, S),
__DEFINE_LINK_MODE_PARAMS(800000, VR4, Full, V),
__DEFINE_LINK_MODE_PARAMS(1600000, CR8, Full, C),
__DEFINE_LINK_MODE_PARAMS(1600000, KR8, Full, K),
__DEFINE_LINK_MODE_PARAMS(1600000, DR8, Full, D),
__DEFINE_LINK_MODE_PARAMS(1600000, DR8_2, Full, D),
};
static_assert(ARRAY_SIZE(link_mode_params) == __ETHTOOL_LINK_MODE_MASK_NBITS);
EXPORT_SYMBOL_GPL(link_mode_params);
static const char ethtool_link_medium_names[][ETH_GSTRING_LEN] = {
[ETHTOOL_LINK_MEDIUM_BASET] = "BaseT",
[ETHTOOL_LINK_MEDIUM_BASEK] = "BaseK",
[ETHTOOL_LINK_MEDIUM_BASES] = "BaseS",
[ETHTOOL_LINK_MEDIUM_BASEC] = "BaseC",
[ETHTOOL_LINK_MEDIUM_BASEL] = "BaseL",
[ETHTOOL_LINK_MEDIUM_BASED] = "BaseD",
[ETHTOOL_LINK_MEDIUM_BASEE] = "BaseE",
[ETHTOOL_LINK_MEDIUM_BASEF] = "BaseF",
[ETHTOOL_LINK_MEDIUM_BASEV] = "BaseV",
[ETHTOOL_LINK_MEDIUM_BASEMLD] = "BaseMLD",
[ETHTOOL_LINK_MEDIUM_NONE] = "None",
};
static_assert(ARRAY_SIZE(ethtool_link_medium_names) == __ETHTOOL_LINK_MEDIUM_LAST);
const char netif_msg_class_names[][ETH_GSTRING_LEN] = {
[NETIF_MSG_DRV_BIT] = "drv",
[NETIF_MSG_PROBE_BIT] = "probe",
@ -1167,3 +1216,15 @@ void ethtool_rxfh_context_lost(struct net_device *dev, u32 context_id)
ethtool_rss_notify(dev, ETHTOOL_MSG_RSS_DELETE_NTF, context_id);
}
EXPORT_SYMBOL(ethtool_rxfh_context_lost);
enum ethtool_link_medium ethtool_str_to_medium(const char *str)
{
int i;
for (i = 0; i < __ETHTOOL_LINK_MEDIUM_LAST; i++)
if (!strcmp(ethtool_link_medium_names[i], str))
return i;
return ETHTOOL_LINK_MEDIUM_NONE;
}
EXPORT_SYMBOL_GPL(ethtool_str_to_medium);