1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
|
// 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.
*
* Non-exact matches will try to return an exact speed and duplex match, but may
* return matching capabilities with same speed but a different duplex.
*
* 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, *match = NULL, *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 (!match && lcap->speed <= speed)
match = lcap;
if (lcap->speed < speed)
break;
}
}
}
if (!match && !exact)
match = last;
return match;
}
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);
|