2017-12-10 21:17:55 +00:00
// SPDX-License-Identifier: GPL-2.0
// Copyright 2017 Ben Whitten <ben.whitten@gmail.com>
// Copyright 2007 Oliver Jowett <oliver@opencloud.com>
//
// LED Kernel Netdev Trigger
//
// Toggles the LED to reflect the link and traffic state of a named net device
//
// Derived from ledtrig-timer.c which is:
// Copyright 2005-2006 Openedhand Ltd.
// Author: Richard Purdie <rpurdie@openedhand.com>
# include <linux/atomic.h>
# include <linux/ctype.h>
# include <linux/device.h>
2023-06-19 22:46:58 +02:00
# include <linux/ethtool.h>
2017-12-10 21:17:55 +00:00
# include <linux/init.h>
# include <linux/jiffies.h>
# include <linux/kernel.h>
# include <linux/leds.h>
2024-01-11 17:04:54 +01:00
# include <linux/linkmode.h>
2017-12-10 21:17:55 +00:00
# include <linux/list.h>
# include <linux/module.h>
# include <linux/netdevice.h>
2023-04-19 23:07:43 +02:00
# include <linux/mutex.h>
2024-01-11 17:04:54 +01:00
# include <linux/phy.h>
2023-06-19 22:46:58 +02:00
# include <linux/rtnetlink.h>
2017-12-10 21:17:55 +00:00
# include <linux/timer.h>
# include "../leds.h"
2023-05-29 18:32:38 +02:00
# define NETDEV_LED_DEFAULT_INTERVAL 50
2017-12-10 21:17:55 +00:00
/*
* Configurable sysfs attributes :
*
* device_name - network device name to monitor
* interval - duration of LED blink , in milliseconds
* link - LED ' s normal state reflects whether the link is up
* ( has carrier ) or not
* tx - LED blinks on transmitted data
* rx - LED blinks on receive data
*
2023-12-17 19:46:42 +01:00
* Note : If the user selects a mode that is not supported by hw , default
* behavior is to fall back to software control of the LED . However not every
* hw supports software control . LED callbacks brightness_set ( ) and
* brightness_set_blocking ( ) are NULL in this case . hw_control_is_supported ( )
* should use available means supported by hw to inform the user that selected
* mode isn ' t supported by hw . This could be switching off the LED or any
* hw blink mode . If software control fallback isn ' t possible , we return
* - EOPNOTSUPP to the user , but still store the selected mode . This is needed
* in case an intermediate unsupported mode is necessary to switch from one
* supported mode to another .
2017-12-10 21:17:55 +00:00
*/
struct led_netdev_data {
2023-04-19 23:07:43 +02:00
struct mutex lock ;
2017-12-10 21:17:55 +00:00
struct delayed_work work ;
struct notifier_block notifier ;
struct led_classdev * led_cdev ;
struct net_device * net_dev ;
char device_name [ IFNAMSIZ ] ;
atomic_t interval ;
unsigned int last_activity ;
unsigned long mode ;
2023-06-19 22:46:58 +02:00
int link_speed ;
2024-01-11 17:04:54 +01:00
__ETHTOOL_DECLARE_LINK_MODE_MASK ( supported_link_modes ) ;
2023-06-19 22:46:59 +02:00
u8 duplex ;
2023-06-19 22:46:58 +02:00
2023-04-19 23:07:40 +02:00
bool carrier_link_up ;
2023-05-29 18:32:35 +02:00
bool hw_control ;
2017-12-10 21:17:55 +00:00
} ;
2024-01-11 17:04:54 +01:00
static const struct attribute_group netdev_trig_link_speed_attrs_group ;
2017-12-10 21:17:55 +00:00
static void set_baseline_state ( struct led_netdev_data * trigger_data )
{
int current_brightness ;
struct led_classdev * led_cdev = trigger_data - > led_cdev ;
2023-05-29 18:32:38 +02:00
/* Already validated, hw control is possible with the requested mode */
if ( trigger_data - > hw_control ) {
led_cdev - > hw_control_set ( led_cdev , trigger_data - > mode ) ;
return ;
}
2017-12-10 21:17:55 +00:00
current_brightness = led_cdev - > brightness ;
if ( current_brightness )
led_cdev - > blink_brightness = current_brightness ;
if ( ! led_cdev - > blink_brightness )
led_cdev - > blink_brightness = led_cdev - > max_brightness ;
2023-04-19 23:07:40 +02:00
if ( ! trigger_data - > carrier_link_up ) {
2017-12-10 21:17:55 +00:00
led_set_brightness ( led_cdev , LED_OFF ) ;
2023-04-19 23:07:40 +02:00
} else {
2023-06-19 22:46:58 +02:00
bool blink_on = false ;
2023-04-19 23:07:41 +02:00
if ( test_bit ( TRIGGER_NETDEV_LINK , & trigger_data - > mode ) )
2023-06-19 22:46:58 +02:00
blink_on = true ;
if ( test_bit ( TRIGGER_NETDEV_LINK_10 , & trigger_data - > mode ) & &
trigger_data - > link_speed = = SPEED_10 )
blink_on = true ;
if ( test_bit ( TRIGGER_NETDEV_LINK_100 , & trigger_data - > mode ) & &
trigger_data - > link_speed = = SPEED_100 )
blink_on = true ;
if ( test_bit ( TRIGGER_NETDEV_LINK_1000 , & trigger_data - > mode ) & &
trigger_data - > link_speed = = SPEED_1000 )
blink_on = true ;
2023-11-28 04:00:10 +00:00
if ( test_bit ( TRIGGER_NETDEV_LINK_2500 , & trigger_data - > mode ) & &
trigger_data - > link_speed = = SPEED_2500 )
blink_on = true ;
if ( test_bit ( TRIGGER_NETDEV_LINK_5000 , & trigger_data - > mode ) & &
trigger_data - > link_speed = = SPEED_5000 )
blink_on = true ;
if ( test_bit ( TRIGGER_NETDEV_LINK_10000 , & trigger_data - > mode ) & &
trigger_data - > link_speed = = SPEED_10000 )
blink_on = true ;
2023-06-19 22:46:59 +02:00
if ( test_bit ( TRIGGER_NETDEV_HALF_DUPLEX , & trigger_data - > mode ) & &
trigger_data - > duplex = = DUPLEX_HALF )
blink_on = true ;
if ( test_bit ( TRIGGER_NETDEV_FULL_DUPLEX , & trigger_data - > mode ) & &
trigger_data - > duplex = = DUPLEX_FULL )
blink_on = true ;
2023-06-19 22:46:58 +02:00
if ( blink_on )
2017-12-10 21:17:55 +00:00
led_set_brightness ( led_cdev ,
led_cdev - > blink_brightness ) ;
else
led_set_brightness ( led_cdev , LED_OFF ) ;
/* If we are looking for RX/TX start periodically
* checking stats
*/
2023-04-19 23:07:41 +02:00
if ( test_bit ( TRIGGER_NETDEV_TX , & trigger_data - > mode ) | |
test_bit ( TRIGGER_NETDEV_RX , & trigger_data - > mode ) )
2017-12-10 21:17:55 +00:00
schedule_delayed_work ( & trigger_data - > work , 0 ) ;
}
}
2023-05-29 18:32:36 +02:00
static bool supports_hw_control ( struct led_classdev * led_cdev )
{
if ( ! led_cdev - > hw_control_get | | ! led_cdev - > hw_control_set | |
! led_cdev - > hw_control_is_supported )
return false ;
return ! strcmp ( led_cdev - > hw_control_trigger , led_cdev - > trigger - > name ) ;
}
2023-05-29 18:32:39 +02:00
/*
* Validate the configured netdev is the same as the one associated with
* the LED driver in hw control .
*/
static bool validate_net_dev ( struct led_classdev * led_cdev ,
struct net_device * net_dev )
{
struct device * dev = led_cdev - > hw_control_get_device ( led_cdev ) ;
struct net_device * ndev ;
if ( ! dev )
return false ;
ndev = to_net_dev ( dev ) ;
return ndev = = net_dev ;
}
2023-05-29 18:32:35 +02:00
static bool can_hw_control ( struct led_netdev_data * trigger_data )
{
2023-05-29 18:32:38 +02:00
unsigned long default_interval = msecs_to_jiffies ( NETDEV_LED_DEFAULT_INTERVAL ) ;
unsigned int interval = atomic_read ( & trigger_data - > interval ) ;
2023-05-29 18:32:36 +02:00
struct led_classdev * led_cdev = trigger_data - > led_cdev ;
2023-05-29 18:32:38 +02:00
int ret ;
2023-05-29 18:32:36 +02:00
if ( ! supports_hw_control ( led_cdev ) )
return false ;
2023-05-29 18:32:38 +02:00
/*
* Interval must be set to the default
* value . Any different value is rejected if in hw
* control .
*/
if ( interval ! = default_interval )
return false ;
/*
* net_dev must be set with hw control , otherwise no
* blinking can be happening and there is nothing to
2023-05-29 18:32:39 +02:00
* offloaded . Additionally , for hw control to be
* valid , the configured netdev must be the same as
* netdev associated to the LED .
2023-05-29 18:32:38 +02:00
*/
2023-05-29 18:32:39 +02:00
if ( ! validate_net_dev ( led_cdev , trigger_data - > net_dev ) )
2023-05-29 18:32:38 +02:00
return false ;
/* Check if the requested mode is supported */
ret = led_cdev - > hw_control_is_supported ( led_cdev , trigger_data - > mode ) ;
/* Fall back to software blinking if not supported */
if ( ret = = - EOPNOTSUPP )
return false ;
if ( ret ) {
dev_warn ( led_cdev - > dev ,
" Current mode check failed with error %d \n " , ret ) ;
return false ;
}
return true ;
2023-05-29 18:32:35 +02:00
}
2023-06-19 22:46:58 +02:00
static void get_device_state ( struct led_netdev_data * trigger_data )
{
struct ethtool_link_ksettings cmd ;
trigger_data - > carrier_link_up = netif_carrier_ok ( trigger_data - > net_dev ) ;
2024-01-11 17:04:54 +01:00
if ( __ethtool_get_link_ksettings ( trigger_data - > net_dev , & cmd ) )
2023-06-19 22:46:58 +02:00
return ;
2024-01-11 17:04:54 +01:00
if ( trigger_data - > carrier_link_up ) {
2023-06-19 22:46:58 +02:00
trigger_data - > link_speed = cmd . base . speed ;
2023-06-19 22:46:59 +02:00
trigger_data - > duplex = cmd . base . duplex ;
}
2024-01-11 17:04:54 +01:00
/*
* Have a local copy of the link speed supported to avoid rtnl lock every time
* modes are refreshed on any change event
*/
linkmode_copy ( trigger_data - > supported_link_modes , cmd . link_modes . supported ) ;
2023-06-19 22:46:58 +02:00
}
2017-12-10 21:17:55 +00:00
static ssize_t device_name_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data = led_trigger_get_drvdata ( dev ) ;
2017-12-10 21:17:55 +00:00
ssize_t len ;
2023-04-19 23:07:43 +02:00
mutex_lock ( & trigger_data - > lock ) ;
2017-12-10 21:17:55 +00:00
len = sprintf ( buf , " %s \n " , trigger_data - > device_name ) ;
2023-04-19 23:07:43 +02:00
mutex_unlock ( & trigger_data - > lock ) ;
2017-12-10 21:17:55 +00:00
return len ;
}
2023-05-29 18:32:34 +02:00
static int set_device_name ( struct led_netdev_data * trigger_data ,
const char * name , size_t size )
2017-12-10 21:17:55 +00:00
{
2023-10-07 15:10:42 +02:00
if ( size > = IFNAMSIZ )
return - EINVAL ;
2017-12-10 21:17:55 +00:00
cancel_delayed_work_sync ( & trigger_data - > work ) ;
leds: trigger: netdev: fix RTNL handling to prevent potential deadlock
When working on LED support for r8169 I got the following lockdep
warning. Easiest way to prevent this scenario seems to be to take
the RTNL lock before the trigger_data lock in set_device_name().
======================================================
WARNING: possible circular locking dependency detected
6.7.0-rc2-next-20231124+ #2 Not tainted
------------------------------------------------------
bash/383 is trying to acquire lock:
ffff888103aa1c68 (&trigger_data->lock){+.+.}-{3:3}, at: netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
but task is already holding lock:
ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (rtnl_mutex){+.+.}-{3:3}:
__mutex_lock+0x9b/0xb50
mutex_lock_nested+0x16/0x20
rtnl_lock+0x12/0x20
set_device_name+0xa9/0x120 [ledtrig_netdev]
netdev_trig_activate+0x1a1/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
-> #0 (&trigger_data->lock){+.+.}-{3:3}:
__lock_acquire+0x1459/0x25a0
lock_acquire+0xc8/0x2d0
__mutex_lock+0x9b/0xb50
mutex_lock_nested+0x16/0x20
netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
call_netdevice_register_net_notifiers+0x5a/0x100
register_netdevice_notifier+0x85/0x120
netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(rtnl_mutex);
lock(&trigger_data->lock);
lock(rtnl_mutex);
lock(&trigger_data->lock);
*** DEADLOCK ***
8 locks held by bash/383:
#0: ffff888103ff33f0 (sb_writers#3){.+.+}-{0:0}, at: ksys_write+0x6c/0xf0
#1: ffff888103aa1e88 (&of->mutex){+.+.}-{3:3}, at: kernfs_fop_write_iter+0x114/0x210
#2: ffff8881036f1890 (kn->active#82){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x11d/0x210
#3: ffff888108e2c358 (&led_cdev->led_access){+.+.}-{3:3}, at: led_trigger_write+0x30/0x140
#4: ffffffff8cdd9e10 (triggers_list_lock){++++}-{3:3}, at: led_trigger_write+0x75/0x140
#5: ffff888108e2c270 (&led_cdev->trigger_lock){++++}-{3:3}, at: led_trigger_write+0xe3/0x140
#6: ffffffff8cdde3d0 (pernet_ops_rwsem){++++}-{3:3}, at: register_netdevice_notifier+0x1c/0x120
#7: ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
stack backtrace:
CPU: 0 PID: 383 Comm: bash Not tainted 6.7.0-rc2-next-20231124+ #2
Hardware name: Default string Default string/Default string, BIOS ADLN.M6.SODIMM.ZB.CY.015 08/08/2023
Call Trace:
<TASK>
dump_stack_lvl+0x5c/0xd0
dump_stack+0x10/0x20
print_circular_bug+0x2dd/0x410
check_noncircular+0x131/0x150
__lock_acquire+0x1459/0x25a0
lock_acquire+0xc8/0x2d0
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
__mutex_lock+0x9b/0xb50
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
? __this_cpu_preempt_check+0x13/0x20
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
? __cancel_work_timer+0x11c/0x1b0
? __mutex_lock+0x123/0xb50
mutex_lock_nested+0x16/0x20
? mutex_lock_nested+0x16/0x20
netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
call_netdevice_register_net_notifiers+0x5a/0x100
register_netdevice_notifier+0x85/0x120
netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
? preempt_count_add+0x49/0xc0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
RIP: 0033:0x7f269055d034
Code: c7 00 16 00 00 00 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 80 3d 35 c3 0d 00 00 74 13 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 54 c3 0f 1f 00 48 83 ec 28 48 89 54 24 18 48
RSP: 002b:00007ffddb7ef748 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 0000000000000007 RCX: 00007f269055d034
RDX: 0000000000000007 RSI: 000055bf5f4af3c0 RDI: 0000000000000001
RBP: 000055bf5f4af3c0 R08: 0000000000000073 R09: 0000000000000001
R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000007
R13: 00007f26906325c0 R14: 00007f269062ff20 R15: 0000000000000000
</TASK>
Fixes: d5e01266e7f5 ("leds: trigger: netdev: add additional specific link speed mode")
Cc: stable@vger.kernel.org
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Acked-by: Lee Jones <lee@kernel.org>
Link: https://lore.kernel.org/r/fb5c8294-2a10-4bf5-8f10-3d2b77d2757e@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2023-12-01 11:23:22 +01:00
/*
* Take RTNL lock before trigger_data lock to prevent potential
* deadlock with netdev notifier registration .
*/
rtnl_lock ( ) ;
2023-04-19 23:07:43 +02:00
mutex_lock ( & trigger_data - > lock ) ;
2017-12-10 21:17:55 +00:00
if ( trigger_data - > net_dev ) {
dev_put ( trigger_data - > net_dev ) ;
trigger_data - > net_dev = NULL ;
}
2023-05-29 18:32:34 +02:00
memcpy ( trigger_data - > device_name , name , size ) ;
2019-03-14 15:06:14 +01:00
trigger_data - > device_name [ size ] = 0 ;
2017-12-10 21:17:55 +00:00
if ( size > 0 & & trigger_data - > device_name [ size - 1 ] = = ' \n ' )
trigger_data - > device_name [ size - 1 ] = 0 ;
if ( trigger_data - > device_name [ 0 ] ! = 0 )
trigger_data - > net_dev =
dev_get_by_name ( & init_net , trigger_data - > device_name ) ;
2023-04-19 23:07:40 +02:00
trigger_data - > carrier_link_up = false ;
2023-06-19 22:46:58 +02:00
trigger_data - > link_speed = SPEED_UNKNOWN ;
2023-06-19 22:46:59 +02:00
trigger_data - > duplex = DUPLEX_UNKNOWN ;
leds: trigger: netdev: fix RTNL handling to prevent potential deadlock
When working on LED support for r8169 I got the following lockdep
warning. Easiest way to prevent this scenario seems to be to take
the RTNL lock before the trigger_data lock in set_device_name().
======================================================
WARNING: possible circular locking dependency detected
6.7.0-rc2-next-20231124+ #2 Not tainted
------------------------------------------------------
bash/383 is trying to acquire lock:
ffff888103aa1c68 (&trigger_data->lock){+.+.}-{3:3}, at: netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
but task is already holding lock:
ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (rtnl_mutex){+.+.}-{3:3}:
__mutex_lock+0x9b/0xb50
mutex_lock_nested+0x16/0x20
rtnl_lock+0x12/0x20
set_device_name+0xa9/0x120 [ledtrig_netdev]
netdev_trig_activate+0x1a1/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
-> #0 (&trigger_data->lock){+.+.}-{3:3}:
__lock_acquire+0x1459/0x25a0
lock_acquire+0xc8/0x2d0
__mutex_lock+0x9b/0xb50
mutex_lock_nested+0x16/0x20
netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
call_netdevice_register_net_notifiers+0x5a/0x100
register_netdevice_notifier+0x85/0x120
netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(rtnl_mutex);
lock(&trigger_data->lock);
lock(rtnl_mutex);
lock(&trigger_data->lock);
*** DEADLOCK ***
8 locks held by bash/383:
#0: ffff888103ff33f0 (sb_writers#3){.+.+}-{0:0}, at: ksys_write+0x6c/0xf0
#1: ffff888103aa1e88 (&of->mutex){+.+.}-{3:3}, at: kernfs_fop_write_iter+0x114/0x210
#2: ffff8881036f1890 (kn->active#82){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x11d/0x210
#3: ffff888108e2c358 (&led_cdev->led_access){+.+.}-{3:3}, at: led_trigger_write+0x30/0x140
#4: ffffffff8cdd9e10 (triggers_list_lock){++++}-{3:3}, at: led_trigger_write+0x75/0x140
#5: ffff888108e2c270 (&led_cdev->trigger_lock){++++}-{3:3}, at: led_trigger_write+0xe3/0x140
#6: ffffffff8cdde3d0 (pernet_ops_rwsem){++++}-{3:3}, at: register_netdevice_notifier+0x1c/0x120
#7: ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
stack backtrace:
CPU: 0 PID: 383 Comm: bash Not tainted 6.7.0-rc2-next-20231124+ #2
Hardware name: Default string Default string/Default string, BIOS ADLN.M6.SODIMM.ZB.CY.015 08/08/2023
Call Trace:
<TASK>
dump_stack_lvl+0x5c/0xd0
dump_stack+0x10/0x20
print_circular_bug+0x2dd/0x410
check_noncircular+0x131/0x150
__lock_acquire+0x1459/0x25a0
lock_acquire+0xc8/0x2d0
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
__mutex_lock+0x9b/0xb50
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
? __this_cpu_preempt_check+0x13/0x20
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
? __cancel_work_timer+0x11c/0x1b0
? __mutex_lock+0x123/0xb50
mutex_lock_nested+0x16/0x20
? mutex_lock_nested+0x16/0x20
netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
call_netdevice_register_net_notifiers+0x5a/0x100
register_netdevice_notifier+0x85/0x120
netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
? preempt_count_add+0x49/0xc0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
RIP: 0033:0x7f269055d034
Code: c7 00 16 00 00 00 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 80 3d 35 c3 0d 00 00 74 13 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 54 c3 0f 1f 00 48 83 ec 28 48 89 54 24 18 48
RSP: 002b:00007ffddb7ef748 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 0000000000000007 RCX: 00007f269055d034
RDX: 0000000000000007 RSI: 000055bf5f4af3c0 RDI: 0000000000000001
RBP: 000055bf5f4af3c0 R08: 0000000000000073 R09: 0000000000000001
R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000007
R13: 00007f26906325c0 R14: 00007f269062ff20 R15: 0000000000000000
</TASK>
Fixes: d5e01266e7f5 ("leds: trigger: netdev: add additional specific link speed mode")
Cc: stable@vger.kernel.org
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Acked-by: Lee Jones <lee@kernel.org>
Link: https://lore.kernel.org/r/fb5c8294-2a10-4bf5-8f10-3d2b77d2757e@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2023-12-01 11:23:22 +01:00
if ( trigger_data - > net_dev )
2023-06-19 22:46:58 +02:00
get_device_state ( trigger_data ) ;
2017-12-10 21:17:55 +00:00
trigger_data - > last_activity = 0 ;
2023-12-21 18:31:48 +01:00
/* Skip if we're called from netdev_trig_activate() and hw_control is true */
if ( ! trigger_data - > hw_control | | led_get_trigger_data ( trigger_data - > led_cdev ) )
set_baseline_state ( trigger_data ) ;
2023-04-19 23:07:43 +02:00
mutex_unlock ( & trigger_data - > lock ) ;
leds: trigger: netdev: fix RTNL handling to prevent potential deadlock
When working on LED support for r8169 I got the following lockdep
warning. Easiest way to prevent this scenario seems to be to take
the RTNL lock before the trigger_data lock in set_device_name().
======================================================
WARNING: possible circular locking dependency detected
6.7.0-rc2-next-20231124+ #2 Not tainted
------------------------------------------------------
bash/383 is trying to acquire lock:
ffff888103aa1c68 (&trigger_data->lock){+.+.}-{3:3}, at: netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
but task is already holding lock:
ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (rtnl_mutex){+.+.}-{3:3}:
__mutex_lock+0x9b/0xb50
mutex_lock_nested+0x16/0x20
rtnl_lock+0x12/0x20
set_device_name+0xa9/0x120 [ledtrig_netdev]
netdev_trig_activate+0x1a1/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
-> #0 (&trigger_data->lock){+.+.}-{3:3}:
__lock_acquire+0x1459/0x25a0
lock_acquire+0xc8/0x2d0
__mutex_lock+0x9b/0xb50
mutex_lock_nested+0x16/0x20
netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
call_netdevice_register_net_notifiers+0x5a/0x100
register_netdevice_notifier+0x85/0x120
netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(rtnl_mutex);
lock(&trigger_data->lock);
lock(rtnl_mutex);
lock(&trigger_data->lock);
*** DEADLOCK ***
8 locks held by bash/383:
#0: ffff888103ff33f0 (sb_writers#3){.+.+}-{0:0}, at: ksys_write+0x6c/0xf0
#1: ffff888103aa1e88 (&of->mutex){+.+.}-{3:3}, at: kernfs_fop_write_iter+0x114/0x210
#2: ffff8881036f1890 (kn->active#82){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x11d/0x210
#3: ffff888108e2c358 (&led_cdev->led_access){+.+.}-{3:3}, at: led_trigger_write+0x30/0x140
#4: ffffffff8cdd9e10 (triggers_list_lock){++++}-{3:3}, at: led_trigger_write+0x75/0x140
#5: ffff888108e2c270 (&led_cdev->trigger_lock){++++}-{3:3}, at: led_trigger_write+0xe3/0x140
#6: ffffffff8cdde3d0 (pernet_ops_rwsem){++++}-{3:3}, at: register_netdevice_notifier+0x1c/0x120
#7: ffffffff8cddf808 (rtnl_mutex){+.+.}-{3:3}, at: rtnl_lock+0x12/0x20
stack backtrace:
CPU: 0 PID: 383 Comm: bash Not tainted 6.7.0-rc2-next-20231124+ #2
Hardware name: Default string Default string/Default string, BIOS ADLN.M6.SODIMM.ZB.CY.015 08/08/2023
Call Trace:
<TASK>
dump_stack_lvl+0x5c/0xd0
dump_stack+0x10/0x20
print_circular_bug+0x2dd/0x410
check_noncircular+0x131/0x150
__lock_acquire+0x1459/0x25a0
lock_acquire+0xc8/0x2d0
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
__mutex_lock+0x9b/0xb50
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
? __this_cpu_preempt_check+0x13/0x20
? netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
? __cancel_work_timer+0x11c/0x1b0
? __mutex_lock+0x123/0xb50
mutex_lock_nested+0x16/0x20
? mutex_lock_nested+0x16/0x20
netdev_trig_notify+0xec/0x190 [ledtrig_netdev]
call_netdevice_register_net_notifiers+0x5a/0x100
register_netdevice_notifier+0x85/0x120
netdev_trig_activate+0x1d4/0x230 [ledtrig_netdev]
led_trigger_set+0x172/0x2c0
? preempt_count_add+0x49/0xc0
led_trigger_write+0xf1/0x140
sysfs_kf_bin_write+0x5d/0x80
kernfs_fop_write_iter+0x15d/0x210
vfs_write+0x1f0/0x510
ksys_write+0x6c/0xf0
__x64_sys_write+0x14/0x20
do_syscall_64+0x3f/0xf0
entry_SYSCALL_64_after_hwframe+0x6c/0x74
RIP: 0033:0x7f269055d034
Code: c7 00 16 00 00 00 b8 ff ff ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 f3 0f 1e fa 80 3d 35 c3 0d 00 00 74 13 b8 01 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 54 c3 0f 1f 00 48 83 ec 28 48 89 54 24 18 48
RSP: 002b:00007ffddb7ef748 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 0000000000000007 RCX: 00007f269055d034
RDX: 0000000000000007 RSI: 000055bf5f4af3c0 RDI: 0000000000000001
RBP: 000055bf5f4af3c0 R08: 0000000000000073 R09: 0000000000000001
R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000007
R13: 00007f26906325c0 R14: 00007f269062ff20 R15: 0000000000000000
</TASK>
Fixes: d5e01266e7f5 ("leds: trigger: netdev: add additional specific link speed mode")
Cc: stable@vger.kernel.org
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Acked-by: Lee Jones <lee@kernel.org>
Link: https://lore.kernel.org/r/fb5c8294-2a10-4bf5-8f10-3d2b77d2757e@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2023-12-01 11:23:22 +01:00
rtnl_unlock ( ) ;
2017-12-10 21:17:55 +00:00
2023-05-29 18:32:34 +02:00
return 0 ;
}
static ssize_t device_name_store ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t size )
{
struct led_netdev_data * trigger_data = led_trigger_get_drvdata ( dev ) ;
int ret ;
ret = set_device_name ( trigger_data , buf , size ) ;
if ( ret < 0 )
return ret ;
2024-01-11 17:04:54 +01:00
/* Refresh link_speed visibility */
sysfs_update_group ( & dev - > kobj , & netdev_trig_link_speed_attrs_group ) ;
2017-12-10 21:17:55 +00:00
return size ;
}
static DEVICE_ATTR_RW ( device_name ) ;
static ssize_t netdev_led_attr_show ( struct device * dev , char * buf ,
2023-04-19 23:07:41 +02:00
enum led_trigger_netdev_modes attr )
2017-12-10 21:17:55 +00:00
{
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data = led_trigger_get_drvdata ( dev ) ;
2017-12-10 21:17:55 +00:00
int bit ;
switch ( attr ) {
2023-04-19 23:07:41 +02:00
case TRIGGER_NETDEV_LINK :
2023-06-19 22:46:58 +02:00
case TRIGGER_NETDEV_LINK_10 :
case TRIGGER_NETDEV_LINK_100 :
case TRIGGER_NETDEV_LINK_1000 :
2023-11-28 04:00:10 +00:00
case TRIGGER_NETDEV_LINK_2500 :
case TRIGGER_NETDEV_LINK_5000 :
case TRIGGER_NETDEV_LINK_10000 :
2023-06-19 22:46:59 +02:00
case TRIGGER_NETDEV_HALF_DUPLEX :
case TRIGGER_NETDEV_FULL_DUPLEX :
2023-04-19 23:07:41 +02:00
case TRIGGER_NETDEV_TX :
case TRIGGER_NETDEV_RX :
bit = attr ;
2017-12-10 21:17:55 +00:00
break ;
default :
return - EINVAL ;
}
return sprintf ( buf , " %u \n " , test_bit ( bit , & trigger_data - > mode ) ) ;
}
static ssize_t netdev_led_attr_store ( struct device * dev , const char * buf ,
2023-04-19 23:07:41 +02:00
size_t size , enum led_trigger_netdev_modes attr )
2017-12-10 21:17:55 +00:00
{
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data = led_trigger_get_drvdata ( dev ) ;
2023-12-17 19:46:42 +01:00
struct led_classdev * led_cdev = trigger_data - > led_cdev ;
2023-06-19 22:46:58 +02:00
unsigned long state , mode = trigger_data - > mode ;
2017-12-10 21:17:55 +00:00
int ret ;
int bit ;
ret = kstrtoul ( buf , 0 , & state ) ;
if ( ret )
return ret ;
switch ( attr ) {
2023-04-19 23:07:41 +02:00
case TRIGGER_NETDEV_LINK :
2023-06-19 22:46:58 +02:00
case TRIGGER_NETDEV_LINK_10 :
case TRIGGER_NETDEV_LINK_100 :
case TRIGGER_NETDEV_LINK_1000 :
2023-11-28 04:00:10 +00:00
case TRIGGER_NETDEV_LINK_2500 :
case TRIGGER_NETDEV_LINK_5000 :
case TRIGGER_NETDEV_LINK_10000 :
2023-06-19 22:46:59 +02:00
case TRIGGER_NETDEV_HALF_DUPLEX :
case TRIGGER_NETDEV_FULL_DUPLEX :
2023-04-19 23:07:41 +02:00
case TRIGGER_NETDEV_TX :
case TRIGGER_NETDEV_RX :
bit = attr ;
2017-12-10 21:17:55 +00:00
break ;
default :
return - EINVAL ;
}
if ( state )
2023-06-19 22:46:58 +02:00
set_bit ( bit , & mode ) ;
2017-12-10 21:17:55 +00:00
else
2023-06-19 22:46:58 +02:00
clear_bit ( bit , & mode ) ;
if ( test_bit ( TRIGGER_NETDEV_LINK , & mode ) & &
( test_bit ( TRIGGER_NETDEV_LINK_10 , & mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_100 , & mode ) | |
2023-11-28 04:00:10 +00:00
test_bit ( TRIGGER_NETDEV_LINK_1000 , & mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_2500 , & mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_5000 , & mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_10000 , & mode ) ) )
2023-06-19 22:46:58 +02:00
return - EINVAL ;
cancel_delayed_work_sync ( & trigger_data - > work ) ;
2017-12-10 21:17:55 +00:00
2023-06-19 22:46:58 +02:00
trigger_data - > mode = mode ;
2023-05-29 18:32:35 +02:00
trigger_data - > hw_control = can_hw_control ( trigger_data ) ;
2023-12-17 19:46:42 +01:00
if ( ! led_cdev - > brightness_set & & ! led_cdev - > brightness_set_blocking & &
! trigger_data - > hw_control )
return - EOPNOTSUPP ;
2017-12-10 21:17:55 +00:00
set_baseline_state ( trigger_data ) ;
return size ;
}
2023-04-19 23:07:42 +02:00
# define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \
static ssize_t trigger_name # # _show ( struct device * dev , \
struct device_attribute * attr , char * buf ) \
{ \
return netdev_led_attr_show ( dev , buf , trigger ) ; \
} \
static ssize_t trigger_name # # _store ( struct device * dev , \
struct device_attribute * attr , const char * buf , size_t size ) \
{ \
return netdev_led_attr_store ( dev , buf , size , trigger ) ; \
} \
static DEVICE_ATTR_RW ( trigger_name )
DEFINE_NETDEV_TRIGGER ( link , TRIGGER_NETDEV_LINK ) ;
2023-06-19 22:46:58 +02:00
DEFINE_NETDEV_TRIGGER ( link_10 , TRIGGER_NETDEV_LINK_10 ) ;
DEFINE_NETDEV_TRIGGER ( link_100 , TRIGGER_NETDEV_LINK_100 ) ;
DEFINE_NETDEV_TRIGGER ( link_1000 , TRIGGER_NETDEV_LINK_1000 ) ;
2023-11-28 04:00:10 +00:00
DEFINE_NETDEV_TRIGGER ( link_2500 , TRIGGER_NETDEV_LINK_2500 ) ;
DEFINE_NETDEV_TRIGGER ( link_5000 , TRIGGER_NETDEV_LINK_5000 ) ;
DEFINE_NETDEV_TRIGGER ( link_10000 , TRIGGER_NETDEV_LINK_10000 ) ;
2023-06-19 22:46:59 +02:00
DEFINE_NETDEV_TRIGGER ( half_duplex , TRIGGER_NETDEV_HALF_DUPLEX ) ;
DEFINE_NETDEV_TRIGGER ( full_duplex , TRIGGER_NETDEV_FULL_DUPLEX ) ;
2023-04-19 23:07:42 +02:00
DEFINE_NETDEV_TRIGGER ( tx , TRIGGER_NETDEV_TX ) ;
DEFINE_NETDEV_TRIGGER ( rx , TRIGGER_NETDEV_RX ) ;
2017-12-10 21:17:55 +00:00
static ssize_t interval_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data = led_trigger_get_drvdata ( dev ) ;
2017-12-10 21:17:55 +00:00
return sprintf ( buf , " %u \n " ,
jiffies_to_msecs ( atomic_read ( & trigger_data - > interval ) ) ) ;
}
static ssize_t interval_store ( struct device * dev ,
struct device_attribute * attr , const char * buf ,
size_t size )
{
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data = led_trigger_get_drvdata ( dev ) ;
2017-12-10 21:17:55 +00:00
unsigned long value ;
int ret ;
2023-05-29 18:32:37 +02:00
if ( trigger_data - > hw_control )
return - EINVAL ;
2017-12-10 21:17:55 +00:00
ret = kstrtoul ( buf , 0 , & value ) ;
if ( ret )
return ret ;
/* impose some basic bounds on the timer interval */
if ( value > = 5 & & value < = 10000 ) {
cancel_delayed_work_sync ( & trigger_data - > work ) ;
atomic_set ( & trigger_data - > interval , msecs_to_jiffies ( value ) ) ;
set_baseline_state ( trigger_data ) ; /* resets timer */
}
return size ;
}
static DEVICE_ATTR_RW ( interval ) ;
2023-08-21 14:14:53 +02:00
static ssize_t offloaded_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2023-06-19 22:47:00 +02:00
{
struct led_netdev_data * trigger_data = led_trigger_get_drvdata ( dev ) ;
return sprintf ( buf , " %d \n " , trigger_data - > hw_control ) ;
}
2023-08-21 14:14:53 +02:00
static DEVICE_ATTR_RO ( offloaded ) ;
2023-06-19 22:47:00 +02:00
2024-01-11 17:04:54 +01:00
# define CHECK_LINK_MODE_ATTR(link_speed) \
do { \
if ( attr = = & dev_attr_link_ # # link_speed . attr & & \
link_ksettings . base . speed = = SPEED_ # # link_speed ) \
return attr - > mode ; \
} while ( 0 )
static umode_t netdev_trig_link_speed_visible ( struct kobject * kobj ,
struct attribute * attr , int n )
{
struct device * dev = kobj_to_dev ( kobj ) ;
struct led_netdev_data * trigger_data ;
unsigned long * supported_link_modes ;
u32 mode ;
trigger_data = led_trigger_get_drvdata ( dev ) ;
supported_link_modes = trigger_data - > supported_link_modes ;
/*
* Search in the supported link mode mask a matching supported mode .
* Stop at the first matching entry as we care only to check if a particular
* speed is supported and not the kind .
*/
for_each_set_bit ( mode , supported_link_modes , __ETHTOOL_LINK_MODE_MASK_NBITS ) {
struct ethtool_link_ksettings link_ksettings ;
ethtool_params_from_link_mode ( & link_ksettings , mode ) ;
CHECK_LINK_MODE_ATTR ( 10 ) ;
CHECK_LINK_MODE_ATTR ( 100 ) ;
CHECK_LINK_MODE_ATTR ( 1000 ) ;
CHECK_LINK_MODE_ATTR ( 2500 ) ;
CHECK_LINK_MODE_ATTR ( 5000 ) ;
CHECK_LINK_MODE_ATTR ( 10000 ) ;
}
return 0 ;
}
static struct attribute * netdev_trig_link_speed_attrs [ ] = {
2023-06-19 22:46:58 +02:00
& dev_attr_link_10 . attr ,
& dev_attr_link_100 . attr ,
& dev_attr_link_1000 . attr ,
2023-11-28 04:00:10 +00:00
& dev_attr_link_2500 . attr ,
& dev_attr_link_5000 . attr ,
& dev_attr_link_10000 . attr ,
2024-01-11 17:04:54 +01:00
NULL
} ;
static const struct attribute_group netdev_trig_link_speed_attrs_group = {
. attrs = netdev_trig_link_speed_attrs ,
. is_visible = netdev_trig_link_speed_visible ,
} ;
static struct attribute * netdev_trig_attrs [ ] = {
& dev_attr_device_name . attr ,
& dev_attr_link . attr ,
2023-06-19 22:46:59 +02:00
& dev_attr_full_duplex . attr ,
& dev_attr_half_duplex . attr ,
2018-07-02 22:05:26 +02:00
& dev_attr_rx . attr ,
& dev_attr_tx . attr ,
& dev_attr_interval . attr ,
2023-08-21 14:14:53 +02:00
& dev_attr_offloaded . attr ,
2018-07-02 22:05:26 +02:00
NULL
} ;
2024-01-11 17:04:54 +01:00
static const struct attribute_group netdev_trig_attrs_group = {
. attrs = netdev_trig_attrs ,
} ;
static const struct attribute_group * netdev_trig_groups [ ] = {
& netdev_trig_attrs_group ,
& netdev_trig_link_speed_attrs_group ,
NULL ,
} ;
2018-07-02 22:05:26 +02:00
2017-12-10 21:17:55 +00:00
static int netdev_trig_notify ( struct notifier_block * nb ,
unsigned long evt , void * dv )
{
struct net_device * dev =
netdev_notifier_info_to_dev ( ( struct netdev_notifier_info * ) dv ) ;
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data =
container_of ( nb , struct led_netdev_data , notifier ) ;
2024-01-11 17:04:54 +01:00
struct led_classdev * led_cdev = trigger_data - > led_cdev ;
2017-12-10 21:17:55 +00:00
if ( evt ! = NETDEV_UP & & evt ! = NETDEV_DOWN & & evt ! = NETDEV_CHANGE
2019-10-25 09:01:42 +02:00
& & evt ! = NETDEV_REGISTER & & evt ! = NETDEV_UNREGISTER
& & evt ! = NETDEV_CHANGENAME )
2017-12-10 21:17:55 +00:00
return NOTIFY_DONE ;
2019-02-28 22:57:33 +01:00
if ( ! ( dev = = trigger_data - > net_dev | |
2019-10-25 09:01:42 +02:00
( evt = = NETDEV_CHANGENAME & & ! strcmp ( dev - > name , trigger_data - > device_name ) ) | |
2019-02-28 22:57:33 +01:00
( evt = = NETDEV_REGISTER & & ! strcmp ( dev - > name , trigger_data - > device_name ) ) ) )
2017-12-10 21:17:55 +00:00
return NOTIFY_DONE ;
cancel_delayed_work_sync ( & trigger_data - > work ) ;
2023-04-19 23:07:43 +02:00
mutex_lock ( & trigger_data - > lock ) ;
2017-12-10 21:17:55 +00:00
2023-04-19 23:07:40 +02:00
trigger_data - > carrier_link_up = false ;
2023-06-19 22:46:58 +02:00
trigger_data - > link_speed = SPEED_UNKNOWN ;
2023-06-19 22:46:59 +02:00
trigger_data - > duplex = DUPLEX_UNKNOWN ;
2017-12-10 21:17:55 +00:00
switch ( evt ) {
2019-10-25 09:01:42 +02:00
case NETDEV_CHANGENAME :
2017-12-10 21:17:55 +00:00
case NETDEV_REGISTER :
2023-05-11 15:08:20 +08:00
dev_put ( trigger_data - > net_dev ) ;
2017-12-10 21:17:55 +00:00
dev_hold ( dev ) ;
trigger_data - > net_dev = dev ;
2024-02-04 00:54:01 +01:00
if ( evt = = NETDEV_CHANGENAME )
get_device_state ( trigger_data ) ;
2017-12-10 21:17:55 +00:00
break ;
case NETDEV_UNREGISTER :
2019-02-28 22:57:33 +01:00
dev_put ( trigger_data - > net_dev ) ;
trigger_data - > net_dev = NULL ;
2017-12-10 21:17:55 +00:00
break ;
case NETDEV_UP :
case NETDEV_CHANGE :
2023-06-19 22:46:58 +02:00
get_device_state ( trigger_data ) ;
2024-01-11 17:04:54 +01:00
/* Refresh link_speed visibility */
if ( evt = = NETDEV_CHANGE )
sysfs_update_group ( & led_cdev - > dev - > kobj ,
& netdev_trig_link_speed_attrs_group ) ;
2017-12-10 21:17:55 +00:00
break ;
}
set_baseline_state ( trigger_data ) ;
2023-04-19 23:07:43 +02:00
mutex_unlock ( & trigger_data - > lock ) ;
2017-12-10 21:17:55 +00:00
return NOTIFY_DONE ;
}
/* here's the real work! */
static void netdev_trig_work ( struct work_struct * work )
{
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data =
container_of ( work , struct led_netdev_data , work . work ) ;
2017-12-10 21:17:55 +00:00
struct rtnl_link_stats64 * dev_stats ;
unsigned int new_activity ;
struct rtnl_link_stats64 temp ;
unsigned long interval ;
int invert ;
/* If we dont have a device, insure we are off */
if ( ! trigger_data - > net_dev ) {
led_set_brightness ( trigger_data - > led_cdev , LED_OFF ) ;
return ;
}
/* If we are not looking for RX/TX then return */
2023-04-19 23:07:41 +02:00
if ( ! test_bit ( TRIGGER_NETDEV_TX , & trigger_data - > mode ) & &
! test_bit ( TRIGGER_NETDEV_RX , & trigger_data - > mode ) )
2017-12-10 21:17:55 +00:00
return ;
dev_stats = dev_get_stats ( trigger_data - > net_dev , & temp ) ;
new_activity =
2023-04-19 23:07:41 +02:00
( test_bit ( TRIGGER_NETDEV_TX , & trigger_data - > mode ) ?
2017-12-10 21:17:55 +00:00
dev_stats - > tx_packets : 0 ) +
2023-04-19 23:07:41 +02:00
( test_bit ( TRIGGER_NETDEV_RX , & trigger_data - > mode ) ?
2017-12-10 21:17:55 +00:00
dev_stats - > rx_packets : 0 ) ;
if ( trigger_data - > last_activity ! = new_activity ) {
led_stop_software_blink ( trigger_data - > led_cdev ) ;
2023-06-19 22:46:58 +02:00
invert = test_bit ( TRIGGER_NETDEV_LINK , & trigger_data - > mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_10 , & trigger_data - > mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_100 , & trigger_data - > mode ) | |
2023-06-19 22:46:59 +02:00
test_bit ( TRIGGER_NETDEV_LINK_1000 , & trigger_data - > mode ) | |
2023-11-28 04:00:10 +00:00
test_bit ( TRIGGER_NETDEV_LINK_2500 , & trigger_data - > mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_5000 , & trigger_data - > mode ) | |
test_bit ( TRIGGER_NETDEV_LINK_10000 , & trigger_data - > mode ) | |
2023-06-19 22:46:59 +02:00
test_bit ( TRIGGER_NETDEV_HALF_DUPLEX , & trigger_data - > mode ) | |
test_bit ( TRIGGER_NETDEV_FULL_DUPLEX , & trigger_data - > mode ) ;
2017-12-10 21:17:55 +00:00
interval = jiffies_to_msecs (
atomic_read ( & trigger_data - > interval ) ) ;
/* base state is ON (link present) */
led_blink_set_oneshot ( trigger_data - > led_cdev ,
& interval ,
& interval ,
invert ) ;
trigger_data - > last_activity = new_activity ;
}
schedule_delayed_work ( & trigger_data - > work ,
( atomic_read ( & trigger_data - > interval ) * 2 ) ) ;
}
2018-07-02 22:05:21 +02:00
static int netdev_trig_activate ( struct led_classdev * led_cdev )
2017-12-10 21:17:55 +00:00
{
struct led_netdev_data * trigger_data ;
2023-06-14 10:03:59 +03:00
unsigned long mode = 0 ;
2023-05-29 18:32:40 +02:00
struct device * dev ;
2017-12-10 21:17:55 +00:00
int rc ;
trigger_data = kzalloc ( sizeof ( struct led_netdev_data ) , GFP_KERNEL ) ;
if ( ! trigger_data )
2018-07-02 22:05:26 +02:00
return - ENOMEM ;
2017-12-10 21:17:55 +00:00
2023-04-19 23:07:43 +02:00
mutex_init ( & trigger_data - > lock ) ;
2017-12-10 21:17:55 +00:00
trigger_data - > notifier . notifier_call = netdev_trig_notify ;
trigger_data - > notifier . priority = 10 ;
INIT_DELAYED_WORK ( & trigger_data - > work , netdev_trig_work ) ;
trigger_data - > led_cdev = led_cdev ;
trigger_data - > net_dev = NULL ;
trigger_data - > device_name [ 0 ] = 0 ;
trigger_data - > mode = 0 ;
2023-05-29 18:32:38 +02:00
atomic_set ( & trigger_data - > interval , msecs_to_jiffies ( NETDEV_LED_DEFAULT_INTERVAL ) ) ;
2017-12-10 21:17:55 +00:00
trigger_data - > last_activity = 0 ;
2023-05-29 18:32:40 +02:00
/* Check if hw control is active by default on the LED.
* Init already enabled mode in hw control .
*/
2023-08-08 23:04:33 +02:00
if ( supports_hw_control ( led_cdev ) ) {
2023-05-29 18:32:40 +02:00
dev = led_cdev - > hw_control_get_device ( led_cdev ) ;
if ( dev ) {
const char * name = dev_name ( dev ) ;
trigger_data - > hw_control = true ;
2023-12-21 18:31:48 +01:00
set_device_name ( trigger_data , name , strlen ( name ) ) ;
2023-08-08 23:04:33 +02:00
rc = led_cdev - > hw_control_get ( led_cdev , & mode ) ;
if ( ! rc )
trigger_data - > mode = mode ;
2023-05-29 18:32:40 +02:00
}
}
2018-07-02 22:05:26 +02:00
led_set_trigger_data ( led_cdev , trigger_data ) ;
2017-12-10 21:17:55 +00:00
rc = register_netdevice_notifier ( & trigger_data - > notifier ) ;
if ( rc )
2018-07-02 22:05:26 +02:00
kfree ( trigger_data ) ;
2018-07-02 22:05:21 +02:00
2018-07-02 22:05:26 +02:00
return rc ;
2017-12-10 21:17:55 +00:00
}
static void netdev_trig_deactivate ( struct led_classdev * led_cdev )
{
2018-07-02 22:05:26 +02:00
struct led_netdev_data * trigger_data = led_get_trigger_data ( led_cdev ) ;
2017-12-10 21:17:55 +00:00
2018-07-02 22:05:26 +02:00
unregister_netdevice_notifier ( & trigger_data - > notifier ) ;
2017-12-10 21:17:55 +00:00
2018-07-02 22:05:26 +02:00
cancel_delayed_work_sync ( & trigger_data - > work ) ;
2017-12-10 21:17:55 +00:00
2023-05-11 15:08:20 +08:00
dev_put ( trigger_data - > net_dev ) ;
2017-12-10 21:17:55 +00:00
2018-07-02 22:05:26 +02:00
kfree ( trigger_data ) ;
2017-12-10 21:17:55 +00:00
}
static struct led_trigger netdev_led_trigger = {
. name = " netdev " ,
. activate = netdev_trig_activate ,
. deactivate = netdev_trig_deactivate ,
2018-07-02 22:05:26 +02:00
. groups = netdev_trig_groups ,
2017-12-10 21:17:55 +00:00
} ;
2023-08-15 15:59:43 +08:00
module_led_trigger ( netdev_led_trigger ) ;
2017-12-10 21:17:55 +00:00
MODULE_AUTHOR ( " Ben Whitten <ben.whitten@gmail.com> " ) ;
MODULE_AUTHOR ( " Oliver Jowett <oliver@opencloud.com> " ) ;
MODULE_DESCRIPTION ( " Netdev LED trigger " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
2023-12-21 23:20:26 +01:00
MODULE_ALIAS ( " ledtrig:netdev " ) ;