diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 94b2e85e00a3..8cb0d60fcbba 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1723,12 +1723,41 @@ static int phy_sfp_probe(struct phy_device *phydev) phydev->sfp_bus = NULL; } - if (!ret && phydev->sfp_bus) + if (!ret && phydev->sfp_bus) { ret = phy_setup_sfp_port(phydev); + if (ret) { + sfp_bus_del_upstream(phydev->sfp_bus); + phydev->sfp_bus = NULL; + } + } return ret; } +/** + * phy_sfp_release - release resources set up by phy_sfp_probe() + * @phydev: the PHY device + * + * Release the SFP resources set up by a successful phy_sfp_probe(). Unregister + * the upstream before destroying its phy_port, so SFP upstream callbacks cannot + * race with port destruction. + */ +static void phy_sfp_release(struct phy_device *phydev) +{ + struct phy_port *port, *tmp; + + sfp_bus_del_upstream(phydev->sfp_bus); + phydev->sfp_bus = NULL; + + list_for_each_entry_safe(port, tmp, &phydev->ports, head) { + if (!port->is_sfp) + continue; + + phy_del_port(phydev, port); + phy_port_destroy(port); + } +} + static bool phy_drv_supports_irq(const struct phy_driver *phydrv) { return phydrv->config_intr && phydrv->handle_interrupt; @@ -3454,6 +3483,7 @@ static int phy_default_setup_single_port(struct phy_device *phydev) { struct phy_port *port = phy_port_alloc(); unsigned long mode; + int ret; if (!port) return -ENOMEM; @@ -3480,9 +3510,11 @@ static int phy_default_setup_single_port(struct phy_device *phydev) port->pairs = max_t(int, port->pairs, ethtool_linkmode_n_pairs(mode)); - phy_add_port(phydev, port); + ret = phy_add_port(phydev, port); + if (ret) + phy_port_destroy(port); - return 0; + return ret; } static int of_phy_ports(struct phy_device *phydev) @@ -3547,13 +3579,13 @@ static int phy_setup_ports(struct phy_device *phydev) if (!phydev->is_genphy_driven) { ret = phy_sfp_probe(phydev); if (ret) - goto out; + goto err_ports; } if (phydev->n_ports < phydev->max_n_ports) { ret = phy_default_setup_single_port(phydev); if (ret) - goto out; + goto err_sfp; } linkmode_zero(ports_supported); @@ -3580,7 +3612,9 @@ static int phy_setup_ports(struct phy_device *phydev) return 0; -out: +err_sfp: + phy_sfp_release(phydev); +err_ports: phy_cleanup_ports(phydev); return ret; } @@ -3706,7 +3740,7 @@ static int phy_probe(struct device *dev) if (phydev->drv->probe) { err = phydev->drv->probe(phydev); if (err) - goto out; + goto out_reset; } phy_disable_interrupts(phydev); @@ -3727,7 +3761,7 @@ static int phy_probe(struct device *dev) err = genphy_read_abilities(phydev); if (err) - goto out; + goto out_remove; if (!linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported)) @@ -3744,7 +3778,7 @@ static int phy_probe(struct device *dev) err = phy_setup_ports(phydev); if (err) - goto out; + goto out_remove; phy_advertise_supported(phydev); @@ -3753,7 +3787,7 @@ static int phy_probe(struct device *dev) */ err = genphy_c45_read_eee_adv(phydev, phydev->advertising_eee); if (err) - goto out; + goto out_sfp_release; /* Get the EEE modes we want to prohibit. */ of_set_phy_eee_broken(phydev); @@ -3793,9 +3827,6 @@ static int phy_probe(struct device *dev) phydev->supported); } - /* Set the state to READY by default */ - phydev->state = PHY_READY; - /* Register the PHY LED triggers */ if (!phydev->is_on_sfp_module) phy_led_triggers_register(phydev); @@ -3806,20 +3837,27 @@ static int phy_probe(struct device *dev) if (IS_ENABLED(CONFIG_PHYLIB_LEDS) && !phy_driver_is_genphy(phydev)) { err = of_phy_leds(phydev); if (err) - goto out; + goto out_unreg_led_triggers; } + /* Set the state to READY by default */ + phydev->state = PHY_READY; + return 0; -out: - sfp_bus_del_upstream(phydev->sfp_bus); - phydev->sfp_bus = NULL; - - phy_cleanup_ports(phydev); - +out_unreg_led_triggers: if (!phydev->is_on_sfp_module) phy_led_triggers_unregister(phydev); +out_sfp_release: + phy_sfp_release(phydev); + phy_cleanup_ports(phydev); + +out_remove: + if (phydev->drv->remove) + phydev->drv->remove(phydev); + +out_reset: /* Re-assert the reset signal on error */ phy_device_reset(phydev, 1); @@ -3840,9 +3878,7 @@ static int phy_remove(struct device *dev) phydev->state = PHY_DOWN; - sfp_bus_del_upstream(phydev->sfp_bus); - phydev->sfp_bus = NULL; - + phy_sfp_release(phydev); phy_cleanup_ports(phydev); if (phydev->drv && phydev->drv->remove) diff --git a/drivers/net/phy/phy_led_triggers.c b/drivers/net/phy/phy_led_triggers.c index 4eb7716bb9d6..ff6e518395be 100644 --- a/drivers/net/phy/phy_led_triggers.c +++ b/drivers/net/phy/phy_led_triggers.c @@ -126,6 +126,7 @@ int phy_led_triggers_register(struct phy_device *phy) while (i--) phy_led_trigger_unregister(&phy->phy_led_triggers[i]); kfree(phy->phy_led_triggers); + phy->phy_led_triggers = NULL; out_unreg_link: phy_led_trigger_unregister(phy->led_link_trigger); out_free_link: @@ -141,10 +142,12 @@ void phy_led_triggers_unregister(struct phy_device *phy) { int i; - for (i = 0; i < phy->phy_num_led_triggers; i++) - phy_led_trigger_unregister(&phy->phy_led_triggers[i]); - kfree(phy->phy_led_triggers); - phy->phy_led_triggers = NULL; + if (phy->phy_led_triggers) { + for (i = 0; i < phy->phy_num_led_triggers; i++) + phy_led_trigger_unregister(&phy->phy_led_triggers[i]); + kfree(phy->phy_led_triggers); + phy->phy_led_triggers = NULL; + } if (phy->led_link_trigger) { phy_led_trigger_unregister(phy->led_link_trigger);