447 lines · 13.4 KB · C
RawDownload
// 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_80000, DUPLEX_FULL, {0} }, /* LINK_CAPA_80000FD */	{ 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 */	{ SPEED_1600000, DUPLEX_FULL, {0} }, /* LINK_CAPA_1600000FD */}; 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_80000: return LINK_CAPA_80000FD;	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;	case SPEED_1600000: return LINK_CAPA_1600000FD;	} 	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 __init 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]; 		/* 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) {			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_MIILITE:		link_caps |= BIT(LINK_CAPA_10FD) | 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_50GBASER:	case PHY_INTERFACE_MODE_LAUI:		link_caps |= BIT(LINK_CAPA_50000FD);		break; 	case PHY_INTERFACE_MODE_100GBASEP:		link_caps |= BIT(LINK_CAPA_100000FD);		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); /** * 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);