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>
# include <linux/init.h>
# include <linux/jiffies.h>
# include <linux/kernel.h>
# include <linux/leds.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/netdevice.h>
# include <linux/spinlock.h>
# include <linux/timer.h>
# include "../leds.h"
/*
* 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
*
*/
struct led_netdev_data {
spinlock_t lock ;
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 ;
# define NETDEV_LED_LINK 0
# define NETDEV_LED_TX 1
# define NETDEV_LED_RX 2
# define NETDEV_LED_MODE_LINKUP 3
} ;
enum netdev_led_attr {
NETDEV_ATTR_LINK ,
NETDEV_ATTR_TX ,
NETDEV_ATTR_RX
} ;
static void set_baseline_state ( struct led_netdev_data * trigger_data )
{
int current_brightness ;
struct led_classdev * led_cdev = trigger_data - > led_cdev ;
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 ;
if ( ! test_bit ( NETDEV_LED_MODE_LINKUP , & trigger_data - > mode ) )
led_set_brightness ( led_cdev , LED_OFF ) ;
else {
if ( test_bit ( NETDEV_LED_LINK , & trigger_data - > mode ) )
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
*/
if ( test_bit ( NETDEV_LED_TX , & trigger_data - > mode ) | |
test_bit ( NETDEV_LED_RX , & trigger_data - > mode ) )
schedule_delayed_work ( & trigger_data - > work , 0 ) ;
}
}
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 ;
spin_lock_bh ( & trigger_data - > lock ) ;
len = sprintf ( buf , " %s \n " , trigger_data - > device_name ) ;
spin_unlock_bh ( & trigger_data - > lock ) ;
return len ;
}
static ssize_t device_name_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
if ( size > = IFNAMSIZ )
return - EINVAL ;
cancel_delayed_work_sync ( & trigger_data - > work ) ;
spin_lock_bh ( & trigger_data - > lock ) ;
if ( trigger_data - > net_dev ) {
dev_put ( trigger_data - > net_dev ) ;
trigger_data - > net_dev = NULL ;
}
2019-03-14 15:06:14 +01:00
memcpy ( trigger_data - > device_name , buf , size ) ;
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 ) ;
clear_bit ( NETDEV_LED_MODE_LINKUP , & trigger_data - > mode ) ;
if ( trigger_data - > net_dev ! = NULL )
if ( netif_carrier_ok ( trigger_data - > net_dev ) )
set_bit ( NETDEV_LED_MODE_LINKUP , & trigger_data - > mode ) ;
trigger_data - > last_activity = 0 ;
set_baseline_state ( trigger_data ) ;
spin_unlock_bh ( & trigger_data - > lock ) ;
return size ;
}
static DEVICE_ATTR_RW ( device_name ) ;
static ssize_t netdev_led_attr_show ( struct device * dev , char * buf ,
enum netdev_led_attr attr )
{
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 ) {
case NETDEV_ATTR_LINK :
bit = NETDEV_LED_LINK ;
break ;
case NETDEV_ATTR_TX :
bit = NETDEV_LED_TX ;
break ;
case NETDEV_ATTR_RX :
bit = NETDEV_LED_RX ;
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 ,
size_t size , enum netdev_led_attr attr )
{
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 state ;
int ret ;
int bit ;
ret = kstrtoul ( buf , 0 , & state ) ;
if ( ret )
return ret ;
switch ( attr ) {
case NETDEV_ATTR_LINK :
bit = NETDEV_LED_LINK ;
break ;
case NETDEV_ATTR_TX :
bit = NETDEV_LED_TX ;
break ;
case NETDEV_ATTR_RX :
bit = NETDEV_LED_RX ;
break ;
default :
return - EINVAL ;
}
cancel_delayed_work_sync ( & trigger_data - > work ) ;
if ( state )
set_bit ( bit , & trigger_data - > mode ) ;
else
clear_bit ( bit , & trigger_data - > mode ) ;
set_baseline_state ( trigger_data ) ;
return size ;
}
static ssize_t link_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return netdev_led_attr_show ( dev , buf , NETDEV_ATTR_LINK ) ;
}
static ssize_t link_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t size )
{
return netdev_led_attr_store ( dev , buf , size , NETDEV_ATTR_LINK ) ;
}
static DEVICE_ATTR_RW ( link ) ;
static ssize_t tx_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return netdev_led_attr_show ( dev , buf , NETDEV_ATTR_TX ) ;
}
static ssize_t tx_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t size )
{
return netdev_led_attr_store ( dev , buf , size , NETDEV_ATTR_TX ) ;
}
static DEVICE_ATTR_RW ( tx ) ;
static ssize_t rx_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
return netdev_led_attr_show ( dev , buf , NETDEV_ATTR_RX ) ;
}
static ssize_t rx_store ( struct device * dev ,
struct device_attribute * attr , const char * buf , size_t size )
{
return netdev_led_attr_store ( dev , buf , size , NETDEV_ATTR_RX ) ;
}
static DEVICE_ATTR_RW ( rx ) ;
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 ;
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 ) ;
2018-07-02 22:05:26 +02:00
static struct attribute * netdev_trig_attrs [ ] = {
& dev_attr_device_name . attr ,
& dev_attr_link . attr ,
& dev_attr_rx . attr ,
& dev_attr_tx . attr ,
& dev_attr_interval . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( netdev_trig ) ;
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 ) ;
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 ) ;
spin_lock_bh ( & trigger_data - > lock ) ;
clear_bit ( NETDEV_LED_MODE_LINKUP , & trigger_data - > mode ) ;
switch ( evt ) {
2019-10-25 09:01:42 +02:00
case NETDEV_CHANGENAME :
2017-12-10 21:17:55 +00:00
case NETDEV_REGISTER :
if ( trigger_data - > net_dev )
dev_put ( trigger_data - > net_dev ) ;
dev_hold ( dev ) ;
trigger_data - > net_dev = dev ;
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 :
if ( netif_carrier_ok ( dev ) )
set_bit ( NETDEV_LED_MODE_LINKUP , & trigger_data - > mode ) ;
break ;
}
set_baseline_state ( trigger_data ) ;
spin_unlock_bh ( & trigger_data - > lock ) ;
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 */
if ( ! test_bit ( NETDEV_LED_TX , & trigger_data - > mode ) & &
! test_bit ( NETDEV_LED_RX , & trigger_data - > mode ) )
return ;
dev_stats = dev_get_stats ( trigger_data - > net_dev , & temp ) ;
new_activity =
( test_bit ( NETDEV_LED_TX , & trigger_data - > mode ) ?
dev_stats - > tx_packets : 0 ) +
( test_bit ( NETDEV_LED_RX , & trigger_data - > mode ) ?
dev_stats - > rx_packets : 0 ) ;
if ( trigger_data - > last_activity ! = new_activity ) {
led_stop_software_blink ( trigger_data - > led_cdev ) ;
invert = test_bit ( NETDEV_LED_LINK , & trigger_data - > mode ) ;
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 ;
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
spin_lock_init ( & trigger_data - > lock ) ;
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 ;
atomic_set ( & trigger_data - > interval , msecs_to_jiffies ( 50 ) ) ;
trigger_data - > last_activity = 0 ;
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
2018-07-02 22:05:26 +02:00
if ( trigger_data - > net_dev )
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
} ;
static int __init netdev_trig_init ( void )
{
return led_trigger_register ( & netdev_led_trigger ) ;
}
static void __exit netdev_trig_exit ( void )
{
led_trigger_unregister ( & netdev_led_trigger ) ;
}
module_init ( netdev_trig_init ) ;
module_exit ( netdev_trig_exit ) ;
MODULE_AUTHOR ( " Ben Whitten <ben.whitten@gmail.com> " ) ;
MODULE_AUTHOR ( " Oliver Jowett <oliver@opencloud.com> " ) ;
MODULE_DESCRIPTION ( " Netdev LED trigger " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;