123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- From: Russell King <[email protected]>
- Date: Thu, 5 Jan 2017 16:32:14 +0000
- Subject: [PATCH] net: phy: improve phylib correctness for non-autoneg
- settings
- phylib has some undesirable behaviour when forcing a link mode through
- ethtool. phylib uses this code:
- idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex),
- features);
- to find an index in the settings table. phy_find_setting() starts at
- index 0, and scans upwards looking for an exact speed and duplex match.
- When it doesn't find it, it returns MAX_NUM_SETTINGS - 1, which is
- 10baseT-Half duplex.
- phy_find_valid() then scans from the point (and effectively only checks
- one entry) before bailing out, returning MAX_NUM_SETTINGS - 1.
- phy_sanitize_settings() then sets ->speed to SPEED_10 and ->duplex to
- DUPLEX_HALF whether or not 10baseT-Half is supported or not. This goes
- against all the comments against these functions, and 10baseT-Half may
- not even be supported by the hardware.
- Rework these functions, introducing a new method of scanning the table.
- There are two modes of lookup that phylib wants: exact, and inexact.
- - in exact mode, we return either an exact match or failure
- - in inexact mode, we return an exact match if it exists, a match at
- the highest speed that is not greater than the requested speed
- (ignoring duplex), or failing that, the lowest supported speed, or
- failure.
- The biggest difference is that we always check whether the entry is
- supported before further consideration, so all unsupported entries are
- not considered as candidates.
- This results in arguably saner behaviour, better matches the comments,
- and is probably what users would expect.
- This becomes important as ethernet speeds increase, PHYs exist which do
- not support the 10Mbit speeds, and half-duplex is likely to become
- obsolete - it's already not even an option on 10Gbit and faster links.
- Signed-off-by: Russell King <[email protected]>
- ---
- --- a/drivers/net/phy/phy.c
- +++ b/drivers/net/phy/phy.c
- @@ -160,7 +160,9 @@ struct phy_setting {
- u32 setting;
- };
-
- -/* A mapping of all SUPPORTED settings to speed/duplex */
- +/* 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. */
- static const struct phy_setting settings[] = {
- {
- .speed = SPEED_10000,
- @@ -219,45 +221,70 @@ static const struct phy_setting settings
- },
- };
-
- -#define MAX_NUM_SETTINGS ARRAY_SIZE(settings)
- -
- /**
- - * phy_find_setting - find a PHY settings array entry that matches speed & duplex
- + * phy_lookup_setting - lookup a PHY setting
- * @speed: speed to match
- * @duplex: duplex to match
- + * @feature: 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.
- *
- - * Description: Searches the settings array for the setting which
- - * matches the desired speed and duplex, and returns the index
- - * of that setting. Returns the index of the last setting if
- - * none of the others match.
- + * 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.
- */
- -static inline unsigned int phy_find_setting(int speed, int duplex)
- +static const struct phy_setting *
- +phy_lookup_setting(int speed, int duplex, u32 features, bool exact)
- {
- - unsigned int idx = 0;
- + const struct phy_setting *p, *match = NULL, *last = NULL;
- + int i;
-
- - while (idx < ARRAY_SIZE(settings) &&
- - (settings[idx].speed != speed || settings[idx].duplex != duplex))
- - idx++;
- + for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {
- + if (p->setting & features) {
- + 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;
- + }
- + }
- + }
-
- - return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
- + if (!match && !exact)
- + match = last;
- +
- + return match;
- }
-
- /**
- - * phy_find_valid - find a PHY setting that matches the requested features mask
- - * @idx: The first index in settings[] to search
- - * @features: A mask of the valid settings
- + * phy_find_valid - find a PHY setting that matches the requested parameters
- + * @speed: desired speed
- + * @duplex: desired duplex
- + * @supported: mask of supported link modes
- *
- - * Description: Returns the index of the first valid setting less
- - * than or equal to the one pointed to by idx, as determined by
- - * the mask in features. Returns the index of the last setting
- - * if nothing else matches.
- + * 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 inline unsigned int phy_find_valid(unsigned int idx, u32 features)
- +static const struct phy_setting *
- +phy_find_valid(int speed, int duplex, u32 supported)
- {
- - while (idx < MAX_NUM_SETTINGS && !(settings[idx].setting & features))
- - idx++;
- -
- - return idx < MAX_NUM_SETTINGS ? idx : MAX_NUM_SETTINGS - 1;
- + return phy_lookup_setting(speed, duplex, supported, false);
- }
-
- /**
- @@ -271,12 +298,7 @@ static inline unsigned int phy_find_vali
- */
- static inline bool phy_check_valid(int speed, int duplex, u32 features)
- {
- - unsigned int idx;
- -
- - idx = phy_find_valid(phy_find_setting(speed, duplex), features);
- -
- - return settings[idx].speed == speed && settings[idx].duplex == duplex &&
- - (settings[idx].setting & features);
- + return !!phy_lookup_setting(speed, duplex, features, true);
- }
-
- /**
- @@ -289,18 +311,22 @@ static inline bool phy_check_valid(int s
- */
- static void phy_sanitize_settings(struct phy_device *phydev)
- {
- + const struct phy_setting *setting;
- u32 features = phydev->supported;
- - unsigned int idx;
-
- /* Sanitize settings based on PHY capabilities */
- if ((features & SUPPORTED_Autoneg) == 0)
- phydev->autoneg = AUTONEG_DISABLE;
-
- - idx = phy_find_valid(phy_find_setting(phydev->speed, phydev->duplex),
- - features);
- -
- - phydev->speed = settings[idx].speed;
- - phydev->duplex = settings[idx].duplex;
- + setting = phy_find_valid(phydev->speed, phydev->duplex, features);
- + if (setting) {
- + phydev->speed = setting->speed;
- + phydev->duplex = setting->duplex;
- + } else {
- + /* We failed to find anything (no supported speeds?) */
- + phydev->speed = SPEED_UNKNOWN;
- + phydev->duplex = DUPLEX_UNKNOWN;
- + }
- }
-
- /**
|