Skip to content

Commit c04d1b9

Browse files
l1kkuba-moo
authored andcommitted
igc: Fix LED-related deadlock on driver unbind
Roman reports a deadlock on unplug of a Thunderbolt docking station containing an Intel I225 Ethernet adapter. The root cause is that led_classdev's for LEDs on the adapter are registered such that they're device-managed by the netdev. That results in recursive acquisition of the rtnl_lock() mutex on unplug: When the driver calls unregister_netdev(), it acquires rtnl_lock(), then frees the device-managed resources. Upon unregistering the LEDs, netdev_trig_deactivate() invokes unregister_netdevice_notifier(), which tries to acquire rtnl_lock() again. Avoid by using non-device-managed LED registration. Stack trace for posterity: schedule+0x6e/0xf0 schedule_preempt_disabled+0x15/0x20 __mutex_lock+0x2a0/0x750 unregister_netdevice_notifier+0x40/0x150 netdev_trig_deactivate+0x1f/0x60 [ledtrig_netdev] led_trigger_set+0x102/0x330 led_classdev_unregister+0x4b/0x110 release_nodes+0x3d/0xb0 devres_release_all+0x8b/0xc0 device_del+0x34f/0x3c0 unregister_netdevice_many_notify+0x80b/0xaf0 unregister_netdev+0x7c/0xd0 igc_remove+0xd8/0x1e0 [igc] pci_device_remove+0x3f/0xb0 Fixes: ea57870 ("igc: Add support for LEDs on i225/i226") Reported-by: Roman Lozko <[email protected]> Closes: https://lore.kernel.org/r/CAEhC_B=ksywxCG_+aQqXUrGEgKq+4mqnSV8EBHOKbC3-Obj9+Q@mail.gmail.com/ Reported-by: "Marek Marczykowski-Górecki" <[email protected]> Closes: https://lore.kernel.org/r/ZhRD3cOtz5i-61PB@mail-itl/ Signed-off-by: Kurt Kanzenbach <[email protected]> Signed-off-by: Lukas Wunner <[email protected]> Cc: Heiner Kallweit <[email protected]> Reviewed-by: Simon Horman <[email protected]> Reviewed-by: Kurt Kanzenbach <[email protected]> Tested-by: Kurt Kanzenbach <[email protected]> # Intel i225 Tested-by: Naama Meir <[email protected]> Signed-off-by: Tony Nguyen <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent edd2d25 commit c04d1b9

File tree

3 files changed

+35
-8
lines changed

3 files changed

+35
-8
lines changed

drivers/net/ethernet/intel/igc/igc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ struct igc_adapter {
298298

299299
/* LEDs */
300300
struct mutex led_mutex;
301+
struct igc_led_classdev *leds;
301302
};
302303

303304
void igc_up(struct igc_adapter *adapter);
@@ -723,6 +724,7 @@ void igc_ptp_read(struct igc_adapter *adapter, struct timespec64 *ts);
723724
void igc_ptp_tx_tstamp_event(struct igc_adapter *adapter);
724725

725726
int igc_led_setup(struct igc_adapter *adapter);
727+
void igc_led_free(struct igc_adapter *adapter);
726728

727729
#define igc_rx_pg_size(_ring) (PAGE_SIZE << igc_rx_pg_order(_ring))
728730

drivers/net/ethernet/intel/igc/igc_leds.c

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ static void igc_led_get_name(struct igc_adapter *adapter, int index, char *buf,
236236
pci_dev_id(adapter->pdev), index);
237237
}
238238

239-
static void igc_setup_ldev(struct igc_led_classdev *ldev,
240-
struct net_device *netdev, int index)
239+
static int igc_setup_ldev(struct igc_led_classdev *ldev,
240+
struct net_device *netdev, int index)
241241
{
242242
struct igc_adapter *adapter = netdev_priv(netdev);
243243
struct led_classdev *led_cdev = &ldev->led;
@@ -257,24 +257,46 @@ static void igc_setup_ldev(struct igc_led_classdev *ldev,
257257
led_cdev->hw_control_get = igc_led_hw_control_get;
258258
led_cdev->hw_control_get_device = igc_led_hw_control_get_device;
259259

260-
devm_led_classdev_register(&netdev->dev, led_cdev);
260+
return led_classdev_register(&netdev->dev, led_cdev);
261261
}
262262

263263
int igc_led_setup(struct igc_adapter *adapter)
264264
{
265265
struct net_device *netdev = adapter->netdev;
266-
struct device *dev = &netdev->dev;
267266
struct igc_led_classdev *leds;
268-
int i;
267+
int i, err;
269268

270269
mutex_init(&adapter->led_mutex);
271270

272-
leds = devm_kcalloc(dev, IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL);
271+
leds = kcalloc(IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL);
273272
if (!leds)
274273
return -ENOMEM;
275274

276-
for (i = 0; i < IGC_NUM_LEDS; i++)
277-
igc_setup_ldev(leds + i, netdev, i);
275+
for (i = 0; i < IGC_NUM_LEDS; i++) {
276+
err = igc_setup_ldev(leds + i, netdev, i);
277+
if (err)
278+
goto err;
279+
}
280+
281+
adapter->leds = leds;
278282

279283
return 0;
284+
285+
err:
286+
for (i--; i >= 0; i--)
287+
led_classdev_unregister(&((leds + i)->led));
288+
289+
kfree(leds);
290+
return err;
291+
}
292+
293+
void igc_led_free(struct igc_adapter *adapter)
294+
{
295+
struct igc_led_classdev *leds = adapter->leds;
296+
int i;
297+
298+
for (i = 0; i < IGC_NUM_LEDS; i++)
299+
led_classdev_unregister(&((leds + i)->led));
300+
301+
kfree(leds);
280302
}

drivers/net/ethernet/intel/igc/igc_main.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7021,6 +7021,9 @@ static void igc_remove(struct pci_dev *pdev)
70217021
cancel_work_sync(&adapter->watchdog_task);
70227022
hrtimer_cancel(&adapter->hrtimer);
70237023

7024+
if (IS_ENABLED(CONFIG_IGC_LEDS))
7025+
igc_led_free(adapter);
7026+
70247027
/* Release control of h/w to f/w. If f/w is AMT enabled, this
70257028
* would have already happened in close and is redundant.
70267029
*/

0 commit comments

Comments
 (0)