diff options
Diffstat (limited to 'target/linux/mvebu/patches-4.9/406-net-phy-improve-phylib-correctness-for-non-autoneg-s.patch')
-rw-r--r-- | target/linux/mvebu/patches-4.9/406-net-phy-improve-phylib-correctness-for-non-autoneg-s.patch | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/target/linux/mvebu/patches-4.9/406-net-phy-improve-phylib-correctness-for-non-autoneg-s.patch b/target/linux/mvebu/patches-4.9/406-net-phy-improve-phylib-correctness-for-non-autoneg-s.patch new file mode 100644 index 0000000000..16516c6846 --- /dev/null +++ b/target/linux/mvebu/patches-4.9/406-net-phy-improve-phylib-correctness-for-non-autoneg-s.patch @@ -0,0 +1,199 @@ +From: Russell King <rmk+kernel@armlinux.org.uk> +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 <rmk+kernel@armlinux.org.uk> +--- + +--- 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; ++ } + } + + /** |