Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: ea578703b03d ("igc: Add support for LEDs on i225/i226")
Reported-by: Roman Lozko <lozko.roma@gmail.com>
Closes: https://lore.kernel.org/r/CAEhC_B=ksywxCG_+aQqXUrGEgKq+4mqnSV8EBHOKbC3-Obj9+Q@mail.gmail.com/
Reported-by: "Marek Marczykowski-Górecki" <marmarek@invisiblethingslab.com>
Closes: https://lore.kernel.org/r/ZhRD3cOtz5i-61PB@mail-itl/
Signed-off-by: Kurt Kanzenbach <kurt@linutronix.de>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Cc: Heiner Kallweit <hkallweit1@gmail.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Reviewed-by: Kurt Kanzenbach <kurt@linutronix.de>
Tested-by: Kurt Kanzenbach <kurt@linutronix.de> # Intel i225
Tested-by: Naama Meir <naamax.meir@linux.intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
Link: https://lore.kernel.org/r/20240422204503.225448-1-anthony.l.nguyen@intel.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Lukas Wunner and committed by
Jakub Kicinski
c04d1b9e edd2d250

+35 -8
+2
drivers/net/ethernet/intel/igc/igc.h
··· 298 298 299 299 /* LEDs */ 300 300 struct mutex led_mutex; 301 + struct igc_led_classdev *leds; 301 302 }; 302 303 303 304 void igc_up(struct igc_adapter *adapter); ··· 724 723 void igc_ptp_tx_tstamp_event(struct igc_adapter *adapter); 725 724 726 725 int igc_led_setup(struct igc_adapter *adapter); 726 + void igc_led_free(struct igc_adapter *adapter); 727 727 728 728 #define igc_rx_pg_size(_ring) (PAGE_SIZE << igc_rx_pg_order(_ring)) 729 729
+30 -8
drivers/net/ethernet/intel/igc/igc_leds.c
··· 236 236 pci_dev_id(adapter->pdev), index); 237 237 } 238 238 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) 241 241 { 242 242 struct igc_adapter *adapter = netdev_priv(netdev); 243 243 struct led_classdev *led_cdev = &ldev->led; ··· 257 257 led_cdev->hw_control_get = igc_led_hw_control_get; 258 258 led_cdev->hw_control_get_device = igc_led_hw_control_get_device; 259 259 260 - devm_led_classdev_register(&netdev->dev, led_cdev); 260 + return led_classdev_register(&netdev->dev, led_cdev); 261 261 } 262 262 263 263 int igc_led_setup(struct igc_adapter *adapter) 264 264 { 265 265 struct net_device *netdev = adapter->netdev; 266 - struct device *dev = &netdev->dev; 267 266 struct igc_led_classdev *leds; 268 - int i; 267 + int i, err; 269 268 270 269 mutex_init(&adapter->led_mutex); 271 270 272 - leds = devm_kcalloc(dev, IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL); 271 + leds = kcalloc(IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL); 273 272 if (!leds) 274 273 return -ENOMEM; 275 274 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; 278 282 279 283 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); 280 302 }
+3
drivers/net/ethernet/intel/igc/igc_main.c
··· 7021 7021 cancel_work_sync(&adapter->watchdog_task); 7022 7022 hrtimer_cancel(&adapter->hrtimer); 7023 7023 7024 + if (IS_ENABLED(CONFIG_IGC_LEDS)) 7025 + igc_led_free(adapter); 7026 + 7024 7027 /* Release control of h/w to f/w. If f/w is AMT enabled, this 7025 7028 * would have already happened in close and is redundant. 7026 7029 */