Commit 50698b29 authored by Paolo Abeni's avatar Paolo Abeni
Browse files

Merge branch 'net-phy-rework-linkmodes-handling-in-a-dedicated-file'

Maxime Chevallier says:

====================
net: phy: Rework linkmodes handling in a dedicated file

This is V5 of the phy_caps series. In a nutshell, this series reworks the way
we maintain the list of speed/duplex capablities for each linkmode so that we
no longer have multiple definition of these associations.

That will help making sure that when people add new linkmodes in
include/uapi/linux/ethtool.h, they don't have to update phylib and phylink as
well, making the process more straightforward and less error-prone.

It also generalises the phy_caps interface to be able to lookup linkmodes
from phy_interface_t, which is needed for the multi-port work I've been working
on for a while.

This V5 addresse Russell's and Paolo's reviews, namely :

 - Error out when encountering an unknown SPEED_XXX setting

   It prints an error and fails to initialize phylib. I've tested by
   introducing a dummy 1.6T speed, I guess it's only a matter of time
   before that actually happens :)

 - Deal more gracefully with the fixed-link settings, keeping some level of
   compatibility with what we had before by making sure we report a
   single BaseT mode like before.

V1 : https://lore.kernel.org/netdev/20250222142727.894124-1-maxime.chevallier@bootlin.com/
V2 : https://lore.kernel.org/netdev/20250226100929.1646454-1-maxime.chevallier@bootlin.com/
V3 : https://lore.kernel.org/netdev/20250228145540.2209551-1-maxime.chevallier@bootlin.com/
V4 : https://lore.kernel.org/netdev/20250303090321.805785-1-maxime.chevallier@bootlin.com/
====================

Link: https://patch.msgid.link/20250307173611.129125-1-maxime.chevallier@bootlin.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parents 702e3fa1 3bd87f3b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@

libphy-y			:= phy.o phy-c45.o phy-core.o phy_device.o \
				   linkmode.o phy_link_topology.o \
				   phy_package.o
				   phy_package.o phy_caps.o
mdio-bus-y			+= mdio_bus.o mdio_device.o

ifdef CONFIG_MDIO_DEVICE
+63 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * link caps internal header, for link modes <-> capabilities <-> interfaces
 * conversions.
 */

#ifndef __PHY_CAPS_H
#define __PHY_CAPS_H

#include <linux/ethtool.h>
#include <linux/phy.h>

enum {
	LINK_CAPA_10HD = 0,
	LINK_CAPA_10FD,
	LINK_CAPA_100HD,
	LINK_CAPA_100FD,
	LINK_CAPA_1000HD,
	LINK_CAPA_1000FD,
	LINK_CAPA_2500FD,
	LINK_CAPA_5000FD,
	LINK_CAPA_10000FD,
	LINK_CAPA_20000FD,
	LINK_CAPA_25000FD,
	LINK_CAPA_40000FD,
	LINK_CAPA_50000FD,
	LINK_CAPA_56000FD,
	LINK_CAPA_100000FD,
	LINK_CAPA_200000FD,
	LINK_CAPA_400000FD,
	LINK_CAPA_800000FD,

	__LINK_CAPA_MAX,
};

#define LINK_CAPA_ALL	GENMASK((__LINK_CAPA_MAX - 1), 0)

struct link_capabilities {
	int speed;
	unsigned int duplex;
	__ETHTOOL_DECLARE_LINK_MODE_MASK(linkmodes);
};

int phy_caps_init(void);

size_t phy_caps_speeds(unsigned int *speeds, size_t size,
		       unsigned long *linkmodes);
void phy_caps_linkmode_max_speed(u32 max_speed, unsigned long *linkmodes);
bool phy_caps_valid(int speed, int duplex, const unsigned long *linkmodes);
void phy_caps_linkmodes(unsigned long caps, unsigned long *linkmodes);
unsigned long phy_caps_from_interface(phy_interface_t interface);

const struct link_capabilities *
phy_caps_lookup_by_linkmode(const unsigned long *linkmodes);

const struct link_capabilities *
phy_caps_lookup_by_linkmode_rev(const unsigned long *linkmodes, bool fdx_only);

const struct link_capabilities *
phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported,
		bool exact);

#endif /* __PHY_CAPS_H */
+18 −235
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@

#include "phylib.h"
#include "phylib-internal.h"
#include "phy-caps.h"

/**
 * phy_speed_to_str - Return a string representing the PHY link speed
@@ -156,221 +157,9 @@ int phy_interface_num_ports(phy_interface_t interface)
}
EXPORT_SYMBOL_GPL(phy_interface_num_ports);

/* A mapping of all SUPPORTED settings to speed/duplex.  This table
 * must be grouped by speed and sorted in descending match priority
 * - iow, descending speed.
 */

#define PHY_SETTING(s, d, b) { .speed = SPEED_ ## s, .duplex = DUPLEX_ ## d, \
			       .bit = ETHTOOL_LINK_MODE_ ## b ## _BIT}

static const struct phy_setting settings[] = {
	/* 800G */
	PHY_SETTING( 800000, FULL, 800000baseCR8_Full		),
	PHY_SETTING( 800000, FULL, 800000baseKR8_Full		),
	PHY_SETTING( 800000, FULL, 800000baseDR8_Full		),
	PHY_SETTING( 800000, FULL, 800000baseDR8_2_Full		),
	PHY_SETTING( 800000, FULL, 800000baseSR8_Full		),
	PHY_SETTING( 800000, FULL, 800000baseVR8_Full		),
	PHY_SETTING( 800000, FULL, 800000baseCR4_Full		),
	PHY_SETTING( 800000, FULL, 800000baseKR4_Full		),
	PHY_SETTING( 800000, FULL, 800000baseDR4_Full		),
	PHY_SETTING( 800000, FULL, 800000baseDR4_2_Full		),
	PHY_SETTING( 800000, FULL, 800000baseSR4_Full		),
	PHY_SETTING( 800000, FULL, 800000baseVR4_Full		),
	/* 400G */
	PHY_SETTING( 400000, FULL, 400000baseCR8_Full		),
	PHY_SETTING( 400000, FULL, 400000baseKR8_Full		),
	PHY_SETTING( 400000, FULL, 400000baseLR8_ER8_FR8_Full	),
	PHY_SETTING( 400000, FULL, 400000baseDR8_Full		),
	PHY_SETTING( 400000, FULL, 400000baseSR8_Full		),
	PHY_SETTING( 400000, FULL, 400000baseCR4_Full		),
	PHY_SETTING( 400000, FULL, 400000baseKR4_Full		),
	PHY_SETTING( 400000, FULL, 400000baseLR4_ER4_FR4_Full	),
	PHY_SETTING( 400000, FULL, 400000baseDR4_Full		),
	PHY_SETTING( 400000, FULL, 400000baseSR4_Full		),
	PHY_SETTING( 400000, FULL, 400000baseCR2_Full		),
	PHY_SETTING( 400000, FULL, 400000baseKR2_Full		),
	PHY_SETTING( 400000, FULL, 400000baseDR2_Full		),
	PHY_SETTING( 400000, FULL, 400000baseDR2_2_Full		),
	PHY_SETTING( 400000, FULL, 400000baseSR2_Full		),
	PHY_SETTING( 400000, FULL, 400000baseVR2_Full		),
	/* 200G */
	PHY_SETTING( 200000, FULL, 200000baseCR4_Full		),
	PHY_SETTING( 200000, FULL, 200000baseKR4_Full		),
	PHY_SETTING( 200000, FULL, 200000baseLR4_ER4_FR4_Full	),
	PHY_SETTING( 200000, FULL, 200000baseDR4_Full		),
	PHY_SETTING( 200000, FULL, 200000baseSR4_Full		),
	PHY_SETTING( 200000, FULL, 200000baseCR2_Full		),
	PHY_SETTING( 200000, FULL, 200000baseKR2_Full		),
	PHY_SETTING( 200000, FULL, 200000baseLR2_ER2_FR2_Full	),
	PHY_SETTING( 200000, FULL, 200000baseDR2_Full		),
	PHY_SETTING( 200000, FULL, 200000baseSR2_Full		),
	PHY_SETTING( 200000, FULL, 200000baseCR_Full		),
	PHY_SETTING( 200000, FULL, 200000baseKR_Full		),
	PHY_SETTING( 200000, FULL, 200000baseDR_Full		),
	PHY_SETTING( 200000, FULL, 200000baseDR_2_Full		),
	PHY_SETTING( 200000, FULL, 200000baseSR_Full		),
	PHY_SETTING( 200000, FULL, 200000baseVR_Full		),
	/* 100G */
	PHY_SETTING( 100000, FULL, 100000baseCR4_Full		),
	PHY_SETTING( 100000, FULL, 100000baseKR4_Full		),
	PHY_SETTING( 100000, FULL, 100000baseLR4_ER4_Full	),
	PHY_SETTING( 100000, FULL, 100000baseSR4_Full		),
	PHY_SETTING( 100000, FULL, 100000baseCR2_Full		),
	PHY_SETTING( 100000, FULL, 100000baseKR2_Full		),
	PHY_SETTING( 100000, FULL, 100000baseLR2_ER2_FR2_Full	),
	PHY_SETTING( 100000, FULL, 100000baseDR2_Full		),
	PHY_SETTING( 100000, FULL, 100000baseSR2_Full		),
	PHY_SETTING( 100000, FULL, 100000baseCR_Full		),
	PHY_SETTING( 100000, FULL, 100000baseKR_Full		),
	PHY_SETTING( 100000, FULL, 100000baseLR_ER_FR_Full	),
	PHY_SETTING( 100000, FULL, 100000baseDR_Full		),
	PHY_SETTING( 100000, FULL, 100000baseSR_Full		),
	/* 56G */
	PHY_SETTING(  56000, FULL,  56000baseCR4_Full	  	),
	PHY_SETTING(  56000, FULL,  56000baseKR4_Full	  	),
	PHY_SETTING(  56000, FULL,  56000baseLR4_Full	  	),
	PHY_SETTING(  56000, FULL,  56000baseSR4_Full	  	),
	/* 50G */
	PHY_SETTING(  50000, FULL,  50000baseCR2_Full		),
	PHY_SETTING(  50000, FULL,  50000baseKR2_Full		),
	PHY_SETTING(  50000, FULL,  50000baseSR2_Full		),
	PHY_SETTING(  50000, FULL,  50000baseCR_Full		),
	PHY_SETTING(  50000, FULL,  50000baseKR_Full		),
	PHY_SETTING(  50000, FULL,  50000baseLR_ER_FR_Full	),
	PHY_SETTING(  50000, FULL,  50000baseDR_Full		),
	PHY_SETTING(  50000, FULL,  50000baseSR_Full		),
	/* 40G */
	PHY_SETTING(  40000, FULL,  40000baseCR4_Full		),
	PHY_SETTING(  40000, FULL,  40000baseKR4_Full		),
	PHY_SETTING(  40000, FULL,  40000baseLR4_Full		),
	PHY_SETTING(  40000, FULL,  40000baseSR4_Full		),
	/* 25G */
	PHY_SETTING(  25000, FULL,  25000baseCR_Full		),
	PHY_SETTING(  25000, FULL,  25000baseKR_Full		),
	PHY_SETTING(  25000, FULL,  25000baseSR_Full		),
	/* 20G */
	PHY_SETTING(  20000, FULL,  20000baseKR2_Full		),
	PHY_SETTING(  20000, FULL,  20000baseMLD2_Full		),
	/* 10G */
	PHY_SETTING(  10000, FULL,  10000baseCR_Full		),
	PHY_SETTING(  10000, FULL,  10000baseER_Full		),
	PHY_SETTING(  10000, FULL,  10000baseKR_Full		),
	PHY_SETTING(  10000, FULL,  10000baseKX4_Full		),
	PHY_SETTING(  10000, FULL,  10000baseLR_Full		),
	PHY_SETTING(  10000, FULL,  10000baseLRM_Full		),
	PHY_SETTING(  10000, FULL,  10000baseR_FEC		),
	PHY_SETTING(  10000, FULL,  10000baseSR_Full		),
	PHY_SETTING(  10000, FULL,  10000baseT_Full		),
	/* 5G */
	PHY_SETTING(   5000, FULL,   5000baseT_Full		),
	/* 2.5G */
	PHY_SETTING(   2500, FULL,   2500baseT_Full		),
	PHY_SETTING(   2500, FULL,   2500baseX_Full		),
	/* 1G */
	PHY_SETTING(   1000, FULL,   1000baseT_Full		),
	PHY_SETTING(   1000, HALF,   1000baseT_Half		),
	PHY_SETTING(   1000, FULL,   1000baseT1_Full		),
	PHY_SETTING(   1000, FULL,   1000baseX_Full		),
	PHY_SETTING(   1000, FULL,   1000baseKX_Full		),
	/* 100M */
	PHY_SETTING(    100, FULL,    100baseT_Full		),
	PHY_SETTING(    100, FULL,    100baseT1_Full		),
	PHY_SETTING(    100, HALF,    100baseT_Half		),
	PHY_SETTING(    100, HALF,    100baseFX_Half		),
	PHY_SETTING(    100, FULL,    100baseFX_Full		),
	/* 10M */
	PHY_SETTING(     10, FULL,     10baseT_Full		),
	PHY_SETTING(     10, HALF,     10baseT_Half		),
	PHY_SETTING(     10, FULL,     10baseT1L_Full		),
	PHY_SETTING(     10, FULL,     10baseT1S_Full		),
	PHY_SETTING(     10, HALF,     10baseT1S_Half		),
	PHY_SETTING(     10, HALF,     10baseT1S_P2MP_Half	),
	PHY_SETTING(     10, FULL,     10baseT1BRR_Full		),
};
#undef PHY_SETTING

/**
 * phy_lookup_setting - lookup a PHY setting
 * @speed: speed to match
 * @duplex: duplex to match
 * @mask: allowed link modes
 * @exact: an exact match is required
 *
 * Search the settings array for a setting that matches the speed and
 * duplex, and which is supported.
 *
 * If @exact is unset, either an exact match or %NULL for no match will
 * be returned.
 *
 * If @exact is set, an exact match, the fastest supported setting at
 * or below the specified speed, the slowest supported setting, or if
 * they all fail, %NULL will be returned.
 */
const struct phy_setting *
phy_lookup_setting(int speed, int duplex, const unsigned long *mask, bool exact)
{
	const struct phy_setting *p, *match = NULL, *last = NULL;
	int i;

	for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
		if (p->bit < __ETHTOOL_LINK_MODE_MASK_NBITS &&
		    test_bit(p->bit, mask)) {
			last = p;
			if (p->speed == speed && p->duplex == duplex) {
				/* Exact match for speed and duplex */
				match = p;
				break;
			} else if (!exact) {
				if (!match && p->speed <= speed)
					/* Candidate */
					match = p;

				if (p->speed < speed)
					break;
			}
		}
	}

	if (!match && !exact)
		match = last;

	return match;
}
EXPORT_SYMBOL_GPL(phy_lookup_setting);

size_t phy_speeds(unsigned int *speeds, size_t size,
		  unsigned long *mask)
{
	size_t count;
	int i;

	for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++)
		if (settings[i].bit < __ETHTOOL_LINK_MODE_MASK_NBITS &&
		    test_bit(settings[i].bit, mask) &&
		    (count == 0 || speeds[count - 1] != settings[i].speed))
			speeds[count++] = settings[i].speed;

	return count;
}

static void __set_linkmode_max_speed(u32 max_speed, unsigned long *addr)
{
	const struct phy_setting *p;
	int i;

	for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
		if (p->speed > max_speed)
			linkmode_clear_bit(p->bit, addr);
		else
			break;
	}
}

static void __set_phy_supported(struct phy_device *phydev, u32 max_speed)
{
	__set_linkmode_max_speed(max_speed, phydev->supported);
	phy_caps_linkmode_max_speed(max_speed, phydev->supported);
}

/**
@@ -496,15 +285,14 @@ EXPORT_SYMBOL_GPL(phy_resolve_aneg_pause);
void phy_resolve_aneg_linkmode(struct phy_device *phydev)
{
	__ETHTOOL_DECLARE_LINK_MODE_MASK(common);
	int i;
	const struct link_capabilities *c;

	linkmode_and(common, phydev->lp_advertising, phydev->advertising);

	for (i = 0; i < ARRAY_SIZE(settings); i++)
		if (test_bit(settings[i].bit, common)) {
			phydev->speed = settings[i].speed;
			phydev->duplex = settings[i].duplex;
			break;
	c = phy_caps_lookup_by_linkmode(common);
	if (c) {
		phydev->speed = c->speed;
		phydev->duplex = c->duplex;
	}

	phy_resolve_aneg_pause(phydev);
@@ -523,7 +311,8 @@ EXPORT_SYMBOL_GPL(phy_resolve_aneg_linkmode);
void phy_check_downshift(struct phy_device *phydev)
{
	__ETHTOOL_DECLARE_LINK_MODE_MASK(common);
	int i, speed = SPEED_UNKNOWN;
	const struct link_capabilities *c;
	int speed = SPEED_UNKNOWN;

	phydev->downshifted_rate = 0;

@@ -533,11 +322,9 @@ void phy_check_downshift(struct phy_device *phydev)

	linkmode_and(common, phydev->lp_advertising, phydev->advertising);

	for (i = 0; i < ARRAY_SIZE(settings); i++)
		if (test_bit(settings[i].bit, common)) {
			speed = settings[i].speed;
			break;
		}
	c = phy_caps_lookup_by_linkmode(common);
	if (c)
		speed = c->speed;

	if (speed == SPEED_UNKNOWN || phydev->speed >= speed)
		return;
@@ -551,17 +338,13 @@ void phy_check_downshift(struct phy_device *phydev)
static int phy_resolve_min_speed(struct phy_device *phydev, bool fdx_only)
{
	__ETHTOOL_DECLARE_LINK_MODE_MASK(common);
	int i = ARRAY_SIZE(settings);
	const struct link_capabilities *c;

	linkmode_and(common, phydev->lp_advertising, phydev->advertising);

	while (--i >= 0) {
		if (test_bit(settings[i].bit, common)) {
			if (fdx_only && settings[i].duplex != DUPLEX_FULL)
				continue;
			return settings[i].speed;
		}
	}
	c = phy_caps_lookup_by_linkmode_rev(common, fdx_only);
	if (c)
		return c->speed;

	return SPEED_UNKNOWN;
}
@@ -573,7 +356,7 @@ int phy_speed_down_core(struct phy_device *phydev)
	if (min_common_speed == SPEED_UNKNOWN)
		return -EINVAL;

	__set_linkmode_max_speed(min_common_speed, phydev->advertising);
	phy_caps_linkmode_max_speed(min_common_speed, phydev->advertising);

	return 0;
}
+10 −27
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@
#include <net/sock.h>

#include "phylib-internal.h"
#include "phy-caps.h"

#define PHY_STATE_TIME	HZ

@@ -212,25 +213,6 @@ int phy_aneg_done(struct phy_device *phydev)
}
EXPORT_SYMBOL(phy_aneg_done);

/**
 * phy_find_valid - find a PHY setting that matches the requested parameters
 * @speed: desired speed
 * @duplex: desired duplex
 * @supported: mask of supported link modes
 *
 * Locate a supported phy setting that is, in priority order:
 * - an exact match for the specified speed and duplex mode
 * - a match for the specified speed, or slower speed
 * - the slowest supported speed
 * Returns the matched phy_setting entry, or %NULL if no supported phy
 * settings were found.
 */
static const struct phy_setting *
phy_find_valid(int speed, int duplex, unsigned long *supported)
{
	return phy_lookup_setting(speed, duplex, supported, false);
}

/**
 * phy_supported_speeds - return all speeds currently supported by a phy device
 * @phy: The phy device to return supported speeds of.
@@ -245,7 +227,7 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
				  unsigned int *speeds,
				  unsigned int size)
{
	return phy_speeds(speeds, size, phy->supported);
	return phy_caps_speeds(speeds, size, phy->supported);
}

/**
@@ -259,7 +241,7 @@ unsigned int phy_supported_speeds(struct phy_device *phy,
 */
bool phy_check_valid(int speed, int duplex, unsigned long *features)
{
	return !!phy_lookup_setting(speed, duplex, features, true);
	return phy_caps_valid(speed, duplex, features);
}
EXPORT_SYMBOL(phy_check_valid);

@@ -273,13 +255,14 @@ EXPORT_SYMBOL(phy_check_valid);
 */
static void phy_sanitize_settings(struct phy_device *phydev)
{
	const struct phy_setting *setting;
	const struct link_capabilities *c;

	c = phy_caps_lookup(phydev->speed, phydev->duplex, phydev->supported,
			    false);

	setting = phy_find_valid(phydev->speed, phydev->duplex,
				 phydev->supported);
	if (setting) {
		phydev->speed = setting->speed;
		phydev->duplex = setting->duplex;
	if (c) {
		phydev->speed = c->speed;
		phydev->duplex = c->duplex;
	} else {
		/* We failed to find anything (no supported speeds?) */
		phydev->speed = SPEED_UNKNOWN;
+359 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later

#include <linux/ethtool.h>
#include <linux/linkmode.h>
#include <linux/phy.h>

#include "phy-caps.h"

static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = {
	{ SPEED_10, DUPLEX_HALF, {0} }, /* LINK_CAPA_10HD */
	{ SPEED_10, DUPLEX_FULL, {0} }, /* LINK_CAPA_10FD */
	{ SPEED_100, DUPLEX_HALF, {0} }, /* LINK_CAPA_100HD */
	{ SPEED_100, DUPLEX_FULL, {0} }, /* LINK_CAPA_100FD */
	{ SPEED_1000, DUPLEX_HALF, {0} }, /* LINK_CAPA_1000HD */
	{ SPEED_1000, DUPLEX_FULL, {0} }, /* LINK_CAPA_1000FD */
	{ SPEED_2500, DUPLEX_FULL, {0} }, /* LINK_CAPA_2500FD */
	{ SPEED_5000, DUPLEX_FULL, {0} }, /* LINK_CAPA_5000FD */
	{ SPEED_10000, DUPLEX_FULL, {0} }, /* LINK_CAPA_10000FD */
	{ SPEED_20000, DUPLEX_FULL, {0} }, /* LINK_CAPA_20000FD */
	{ SPEED_25000, DUPLEX_FULL, {0} }, /* LINK_CAPA_25000FD */
	{ SPEED_40000, DUPLEX_FULL, {0} }, /* LINK_CAPA_40000FD */
	{ SPEED_50000, DUPLEX_FULL, {0} }, /* LINK_CAPA_50000FD */
	{ SPEED_56000, DUPLEX_FULL, {0} }, /* LINK_CAPA_56000FD */
	{ SPEED_100000, DUPLEX_FULL, {0} }, /* LINK_CAPA_100000FD */
	{ SPEED_200000, DUPLEX_FULL, {0} }, /* LINK_CAPA_200000FD */
	{ SPEED_400000, DUPLEX_FULL, {0} }, /* LINK_CAPA_400000FD */
	{ SPEED_800000, DUPLEX_FULL, {0} }, /* LINK_CAPA_800000FD */
};

static int speed_duplex_to_capa(int speed, unsigned int duplex)
{
	if (duplex == DUPLEX_UNKNOWN ||
	    (speed > SPEED_1000 && duplex != DUPLEX_FULL))
		return -EINVAL;

	switch (speed) {
	case SPEED_10: return duplex == DUPLEX_FULL ?
			      LINK_CAPA_10FD : LINK_CAPA_10HD;
	case SPEED_100: return duplex == DUPLEX_FULL ?
			       LINK_CAPA_100FD : LINK_CAPA_100HD;
	case SPEED_1000: return duplex == DUPLEX_FULL ?
				LINK_CAPA_1000FD : LINK_CAPA_1000HD;
	case SPEED_2500: return LINK_CAPA_2500FD;
	case SPEED_5000: return LINK_CAPA_5000FD;
	case SPEED_10000: return LINK_CAPA_10000FD;
	case SPEED_20000: return LINK_CAPA_20000FD;
	case SPEED_25000: return LINK_CAPA_25000FD;
	case SPEED_40000: return LINK_CAPA_40000FD;
	case SPEED_50000: return LINK_CAPA_50000FD;
	case SPEED_56000: return LINK_CAPA_56000FD;
	case SPEED_100000: return LINK_CAPA_100000FD;
	case SPEED_200000: return LINK_CAPA_200000FD;
	case SPEED_400000: return LINK_CAPA_400000FD;
	case SPEED_800000: return LINK_CAPA_800000FD;
	}

	return -EINVAL;
}

#define for_each_link_caps_asc_speed(cap) \
	for (cap = link_caps; cap < &link_caps[__LINK_CAPA_MAX]; cap++)

#define for_each_link_caps_desc_speed(cap) \
	for (cap = &link_caps[__LINK_CAPA_MAX - 1]; cap >= link_caps; cap--)

/**
 * phy_caps_init() - Initializes the link_caps array from the link_mode_params.
 *
 * Returns: 0 if phy caps init was successful, -EINVAL if we found an
 *	    unexpected linkmode setting that requires LINK_CAPS update.
 *
 */
int phy_caps_init(void)
{
	const struct link_mode_info *linkmode;
	int i, capa;

	/* Fill the caps array from net/ethtool/common.c */
	for (i = 0; i < __ETHTOOL_LINK_MODE_MASK_NBITS; i++) {
		linkmode = &link_mode_params[i];
		capa = speed_duplex_to_capa(linkmode->speed, linkmode->duplex);

		if (capa < 0) {
			if (linkmode->speed != SPEED_UNKNOWN) {
				pr_err("Unknown speed %d, please update LINK_CAPS\n",
				       linkmode->speed);
				return -EINVAL;
			}
			continue;
		}

		__set_bit(i, link_caps[capa].linkmodes);
	}

	return 0;
}

/**
 * phy_caps_speeds() - Fill an array of supported SPEED_* values for given modes
 * @speeds: Output array to store the speeds list into
 * @size: Size of the output array
 * @linkmodes: Linkmodes to get the speeds from
 *
 * Fills the speeds array with all possible speeds that can be achieved with
 * the specified linkmodes.
 *
 * Returns: The number of speeds filled into the array. If the input array isn't
 *	    big enough to store all speeds, fill it as much as possible.
 */
size_t phy_caps_speeds(unsigned int *speeds, size_t size,
		       unsigned long *linkmodes)
{
	struct link_capabilities *lcap;
	size_t count = 0;

	for_each_link_caps_asc_speed(lcap) {
		if (linkmode_intersects(lcap->linkmodes, linkmodes) &&
		    (count == 0 || speeds[count - 1] != lcap->speed)) {
			speeds[count++] = lcap->speed;
			if (count >= size)
				break;
		}
	}

	return count;
}

/**
 * phy_caps_lookup_by_linkmode() - Lookup the fastest matching link_capabilities
 * @linkmodes: Linkmodes to match against
 *
 * Returns: The highest-speed link_capabilities that intersects the given
 *	    linkmodes. In case several DUPLEX_ options exist at that speed,
 *	    DUPLEX_FULL is matched first. NULL is returned if no match.
 */
const struct link_capabilities *
phy_caps_lookup_by_linkmode(const unsigned long *linkmodes)
{
	struct link_capabilities *lcap;

	for_each_link_caps_desc_speed(lcap)
		if (linkmode_intersects(lcap->linkmodes, linkmodes))
			return lcap;

	return NULL;
}

/**
 * phy_caps_lookup_by_linkmode_rev() - Lookup the slowest matching link_capabilities
 * @linkmodes: Linkmodes to match against
 * @fdx_only: Full duplex match only when set
 *
 * Returns: The lowest-speed link_capabilities that intersects the given
 *	    linkmodes. When set, fdx_only will ignore half-duplex matches.
 *	    NULL is returned if no match.
 */
const struct link_capabilities *
phy_caps_lookup_by_linkmode_rev(const unsigned long *linkmodes, bool fdx_only)
{
	struct link_capabilities *lcap;

	for_each_link_caps_asc_speed(lcap) {
		if (fdx_only && lcap->duplex != DUPLEX_FULL)
			continue;

		if (linkmode_intersects(lcap->linkmodes, linkmodes))
			return lcap;
	}

	return NULL;
}

/**
 * phy_caps_lookup() - Lookup capabilities by speed/duplex that matches a mask
 * @speed: Speed to match
 * @duplex: Duplex to match
 * @supported: Mask of linkmodes to match
 * @exact: Perform an exact match or not.
 *
 * Lookup a link_capabilities entry that intersect the supported linkmodes mask,
 * and that matches the passed speed and duplex.
 *
 * When @exact is set, an exact match is performed on speed and duplex, meaning
 * that if the linkmodes for the given speed and duplex intersect the supported
 * mask, this capability is returned, otherwise we don't have a match and return
 * NULL.
 *
 * When @exact is not set, we return either an exact match, or matching capabilities
 * at lower speed, or the lowest matching speed, or NULL.
 *
 * Returns: a matched link_capabilities according to the above process, NULL
 *	    otherwise.
 */
const struct link_capabilities *
phy_caps_lookup(int speed, unsigned int duplex, const unsigned long *supported,
		bool exact)
{
	const struct link_capabilities *lcap, *last = NULL;

	for_each_link_caps_desc_speed(lcap) {
		if (linkmode_intersects(lcap->linkmodes, supported)) {
			last = lcap;
			/* exact match on speed and duplex*/
			if (lcap->speed == speed && lcap->duplex == duplex) {
				return lcap;
			} else if (!exact) {
				if (lcap->speed <= speed)
					return lcap;
			}
		}
	}

	if (!exact)
		return last;

	return NULL;
}
EXPORT_SYMBOL_GPL(phy_caps_lookup);

/**
 * phy_caps_linkmode_max_speed() - Clamp a linkmodes set to a max speed
 * @max_speed: Speed limit for the linkmode set
 * @linkmodes: Linkmodes to limit
 */
void phy_caps_linkmode_max_speed(u32 max_speed, unsigned long *linkmodes)
{
	struct link_capabilities *lcap;

	for_each_link_caps_desc_speed(lcap)
		if (lcap->speed > max_speed)
			linkmode_andnot(linkmodes, linkmodes, lcap->linkmodes);
		else
			break;
}

/**
 * phy_caps_valid() - Validate a linkmodes set agains given speed and duplex
 * @speed: input speed to validate
 * @duplex: input duplex to validate. Passing DUPLEX_UNKNOWN is always not valid
 * @linkmodes: The linkmodes to validate
 *
 * Returns: True if at least one of the linkmodes in @linkmodes can function at
 *          the given speed and duplex, false otherwise.
 */
bool phy_caps_valid(int speed, int duplex, const unsigned long *linkmodes)
{
	int capa = speed_duplex_to_capa(speed, duplex);

	if (capa < 0)
		return false;

	return linkmode_intersects(link_caps[capa].linkmodes, linkmodes);
}

/**
 * phy_caps_linkmodes() - Convert a bitfield of capabilities into linkmodes
 * @caps: The list of caps, each bit corresponding to a LINK_CAPA value
 * @linkmodes: The set of linkmodes to fill. Must be previously initialized.
 */
void phy_caps_linkmodes(unsigned long caps, unsigned long *linkmodes)
{
	unsigned long capa;

	for_each_set_bit(capa, &caps, __LINK_CAPA_MAX)
		linkmode_or(linkmodes, linkmodes, link_caps[capa].linkmodes);
}
EXPORT_SYMBOL_GPL(phy_caps_linkmodes);

/**
 * phy_caps_from_interface() - Get the link capa from a given PHY interface
 * @interface: The PHY interface we want to get the possible Speed/Duplex from
 *
 * Returns: A bitmask of LINK_CAPA_xxx values that can be achieved with the
 *          provided interface.
 */
unsigned long phy_caps_from_interface(phy_interface_t interface)
{
	unsigned long link_caps = 0;

	switch (interface) {
	case PHY_INTERFACE_MODE_USXGMII:
		link_caps |= BIT(LINK_CAPA_10000FD) | BIT(LINK_CAPA_5000FD);
		fallthrough;

	case PHY_INTERFACE_MODE_10G_QXGMII:
		link_caps |= BIT(LINK_CAPA_2500FD);
		fallthrough;

	case PHY_INTERFACE_MODE_RGMII_TXID:
	case PHY_INTERFACE_MODE_RGMII_RXID:
	case PHY_INTERFACE_MODE_RGMII_ID:
	case PHY_INTERFACE_MODE_RGMII:
	case PHY_INTERFACE_MODE_PSGMII:
	case PHY_INTERFACE_MODE_QSGMII:
	case PHY_INTERFACE_MODE_QUSGMII:
	case PHY_INTERFACE_MODE_SGMII:
	case PHY_INTERFACE_MODE_GMII:
		link_caps |= BIT(LINK_CAPA_1000HD) | BIT(LINK_CAPA_1000FD);
		fallthrough;

	case PHY_INTERFACE_MODE_REVRMII:
	case PHY_INTERFACE_MODE_RMII:
	case PHY_INTERFACE_MODE_SMII:
	case PHY_INTERFACE_MODE_REVMII:
	case PHY_INTERFACE_MODE_MII:
		link_caps |= BIT(LINK_CAPA_10HD) | BIT(LINK_CAPA_10FD);
		fallthrough;

	case PHY_INTERFACE_MODE_100BASEX:
		link_caps |= BIT(LINK_CAPA_100HD) | BIT(LINK_CAPA_100FD);
		break;

	case PHY_INTERFACE_MODE_TBI:
	case PHY_INTERFACE_MODE_MOCA:
	case PHY_INTERFACE_MODE_RTBI:
	case PHY_INTERFACE_MODE_1000BASEX:
		link_caps |= BIT(LINK_CAPA_1000HD);
		fallthrough;
	case PHY_INTERFACE_MODE_1000BASEKX:
	case PHY_INTERFACE_MODE_TRGMII:
		link_caps |= BIT(LINK_CAPA_1000FD);
		break;

	case PHY_INTERFACE_MODE_2500BASEX:
		link_caps |= BIT(LINK_CAPA_2500FD);
		break;

	case PHY_INTERFACE_MODE_5GBASER:
		link_caps |= BIT(LINK_CAPA_5000FD);
		break;

	case PHY_INTERFACE_MODE_XGMII:
	case PHY_INTERFACE_MODE_RXAUI:
	case PHY_INTERFACE_MODE_XAUI:
	case PHY_INTERFACE_MODE_10GBASER:
	case PHY_INTERFACE_MODE_10GKR:
		link_caps |= BIT(LINK_CAPA_10000FD);
		break;

	case PHY_INTERFACE_MODE_25GBASER:
		link_caps |= BIT(LINK_CAPA_25000FD);
		break;

	case PHY_INTERFACE_MODE_XLGMII:
		link_caps |= BIT(LINK_CAPA_40000FD);
		break;

	case PHY_INTERFACE_MODE_INTERNAL:
		link_caps |= LINK_CAPA_ALL;
		break;

	case PHY_INTERFACE_MODE_NA:
	case PHY_INTERFACE_MODE_MAX:
		break;
	}

	return link_caps;
}
EXPORT_SYMBOL_GPL(phy_caps_from_interface);
Loading