diff --git a/Documentation/devicetree/bindings/net/ethernet-connector.yaml b/Documentation/devicetree/bindings/net/ethernet-connector.yaml new file mode 100644 index 000000000000..9ad7a00d4d01 --- /dev/null +++ b/Documentation/devicetree/bindings/net/ethernet-connector.yaml @@ -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 + +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 + +... diff --git a/Documentation/devicetree/bindings/net/ethernet-phy.yaml b/Documentation/devicetree/bindings/net/ethernet-phy.yaml index bb4c49fc5fd8..58634fee9fc4 100644 --- a/Documentation/devicetree/bindings/net/ethernet-phy.yaml +++ b/Documentation/devicetree/bindings/net/ethernet-phy.yaml @@ -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"; + }; + }; }; }; diff --git a/Documentation/devicetree/bindings/net/ti,dp83822.yaml b/Documentation/devicetree/bindings/net/ti,dp83822.yaml index 28a0bddb9af9..23c70d863c39 100644 --- a/Documentation/devicetree/bindings/net/ti,dp83822.yaml +++ b/Documentation/devicetree/bindings/net/ti,dp83822.yaml @@ -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"; + }; + }; }; }; - ... diff --git a/Documentation/networking/index.rst b/Documentation/networking/index.rst index 75db2251649b..49fcfa577711 100644 --- a/Documentation/networking/index.rst +++ b/Documentation/networking/index.rst @@ -96,6 +96,7 @@ Contents: packet_mmap phonet phy-link-topology + phy-port pktgen plip ppp_generic diff --git a/Documentation/networking/phy-port.rst b/Documentation/networking/phy-port.rst new file mode 100644 index 000000000000..6e28d9094bce --- /dev/null +++ b/Documentation/networking/phy-port.rst @@ -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 ` 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. diff --git a/MAINTAINERS b/MAINTAINERS index 6a0635fe11f9..d38dcab1fa06 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9395,9 +9395,11 @@ R: Russell King 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 +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" M: Eric Dumazet diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 76e0db40f879..3a34917adea7 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/dp83822.c b/drivers/net/phy/dp83822.c index 33db21251f2e..c012dfab3171 100644 --- a/drivers/net/phy/dp83822.c +++ b/drivers/net/phy/dp83822.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -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) \ diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 894bcee61e65..ba1bbb6c63d6 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include /* 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); diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index c248c90510ae..7a578b5aa2ed 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -29,10 +29,10 @@ #include #include #include +#include #include #include #include -#include #include #include @@ -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, diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c index 8fd42131cdbf..b40df82152cd 100644 --- a/drivers/net/phy/marvell10g.c +++ b/drivers/net/phy/marvell10g.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #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, }, }; diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h index 4951a39f3828..5f3f757e0b2f 100644 --- a/drivers/net/phy/phy-caps.h +++ b/drivers/net/phy/phy-caps.h @@ -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 */ diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 277c034bc32f..3badf6e84554 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -4,6 +4,7 @@ */ #include #include +#include #include #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); } /** diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 3a05982b39bf..17a63c931335 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -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); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 81984d4ebb7c..85e2ba401140 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -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; diff --git a/drivers/net/phy/phy_port.c b/drivers/net/phy/phy_port.c new file mode 100644 index 000000000000..ec93c8ca051e --- /dev/null +++ b/drivers/net/phy/phy_port.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Framework to drive Ethernet ports + * + * Copyright (c) 2024 Maxime Chevallier + */ + +#include +#include +#include + +#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); diff --git a/drivers/net/phy/qcom/at803x.c b/drivers/net/phy/qcom/at803x.c index 338acd11a9b6..2995b08bac96 100644 --- a/drivers/net/phy/qcom/at803x.c +++ b/drivers/net/phy/qcom/at803x.c @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #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), diff --git a/drivers/net/phy/qcom/qca807x.c b/drivers/net/phy/qcom/qca807x.c index 1be8295a95cb..d8f1ce5a7128 100644 --- a/drivers/net/phy/qcom/qca807x.c +++ b/drivers/net/phy/qcom/qca807x.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #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); diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 5c9162193d26..798abec67a1b 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -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) diff --git a/include/linux/phy.h b/include/linux/phy.h index fbbe028cc4b7..3beb5dbba791 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -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; diff --git a/include/linux/phy_port.h b/include/linux/phy_port.h new file mode 100644 index 000000000000..0ef0f5ce4709 --- /dev/null +++ b/include/linux/phy_port.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __PHY_PORT_H +#define __PHY_PORT_H + +#include +#include +#include + +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 diff --git a/net/ethtool/common.c b/net/ethtool/common.c index 369c05cf8163..4036561b078b 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -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);