2019-06-04 10:11:33 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-08-28 20:44:51 +02:00
/*
* Activity LED trigger
*
* Copyright ( C ) 2017 Willy Tarreau < w @ 1 wt . eu >
* Partially based on Atsushi Nemoto ' s ledtrig - heartbeat . c .
*/
2018-07-02 22:05:20 +02:00
2017-08-28 20:44:51 +02:00
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/kernel_stat.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/reboot.h>
# include <linux/sched.h>
# include <linux/slab.h>
# include <linux/timer.h>
# include "../leds.h"
static int panic_detected ;
struct activity_data {
struct timer_list timer ;
2017-10-25 03:30:01 -07:00
struct led_classdev * led_cdev ;
2017-08-28 20:44:51 +02:00
u64 last_used ;
u64 last_boot ;
int time_left ;
int state ;
int invert ;
} ;
2017-10-25 03:30:01 -07:00
static void led_activity_function ( struct timer_list * t )
2017-08-28 20:44:51 +02:00
{
2017-10-25 03:30:01 -07:00
struct activity_data * activity_data = from_timer ( activity_data , t ,
timer ) ;
struct led_classdev * led_cdev = activity_data - > led_cdev ;
2017-08-28 20:44:51 +02:00
unsigned int target ;
unsigned int usage ;
int delay ;
u64 curr_used ;
u64 curr_boot ;
s32 diff_used ;
s32 diff_boot ;
int cpus ;
int i ;
if ( test_and_clear_bit ( LED_BLINK_BRIGHTNESS_CHANGE , & led_cdev - > work_flags ) )
led_cdev - > blink_brightness = led_cdev - > new_blink_brightness ;
if ( unlikely ( panic_detected ) ) {
/* full brightness in case of panic */
led_set_brightness_nosleep ( led_cdev , led_cdev - > blink_brightness ) ;
return ;
}
cpus = 0 ;
curr_used = 0 ;
for_each_possible_cpu ( i ) {
2019-11-21 03:44:29 +01:00
struct kernel_cpustat kcpustat ;
kcpustat_cpu_fetch ( & kcpustat , i ) ;
curr_used + = kcpustat . cpustat [ CPUTIME_USER ]
+ kcpustat . cpustat [ CPUTIME_NICE ]
+ kcpustat . cpustat [ CPUTIME_SYSTEM ]
+ kcpustat . cpustat [ CPUTIME_SOFTIRQ ]
+ kcpustat . cpustat [ CPUTIME_IRQ ] ;
2017-08-28 20:44:51 +02:00
cpus + + ;
}
/* We come here every 100ms in the worst case, so that's 100M ns of
* cumulated time . By dividing by 2 ^ 16 , we get the time resolution
* down to 16u s , ensuring we won ' t overflow 32 - bit computations below
* even up to 3 k CPUs , while keeping divides cheap on smaller systems .
*/
2019-06-21 22:32:48 +02:00
curr_boot = ktime_get_boottime_ns ( ) * cpus ;
2017-08-28 20:44:51 +02:00
diff_boot = ( curr_boot - activity_data - > last_boot ) > > 16 ;
diff_used = ( curr_used - activity_data - > last_used ) > > 16 ;
activity_data - > last_boot = curr_boot ;
activity_data - > last_used = curr_used ;
if ( diff_boot < = 0 | | diff_used < 0 )
usage = 0 ;
else if ( diff_used > = diff_boot )
usage = 100 ;
else
usage = 100 * diff_used / diff_boot ;
/*
* Now we know the total boot_time multiplied by the number of CPUs , and
* the total idle + wait time for all CPUs . We ' ll compare how they evolved
* since last call . The % of overall CPU usage is :
*
* 1 - delta_idle / delta_boot
*
* What we want is that when the CPU usage is zero , the LED must blink
* slowly with very faint flashes that are detectable but not disturbing
* ( typically 10 ms every second , or 10 ms ON , 990 ms OFF ) . Then we want
* blinking frequency to increase up to the point where the load is
* enough to saturate one core in multi - core systems or 50 % in single
* core systems . At this point it should reach 10 Hz with a 10 / 90 duty
* cycle ( 10 ms ON , 90 ms OFF ) . After this point , the blinking frequency
* remains stable ( 10 Hz ) and only the duty cycle increases to report
* the activity , up to the point where we have 90 ms ON , 10 ms OFF when
* all cores are saturated . It ' s important that the LED never stays in
* a steady state so that it ' s easy to distinguish an idle or saturated
* machine from a hung one .
*
* This gives us :
* - a target CPU usage of min ( 50 % , 100 % / # CPU ) for a 10 % duty cycle
* ( 10 ms ON , 90 ms OFF )
* - below target :
* ON_ms = 10
* OFF_ms = 90 + ( 1 - usage / target ) * 900
* - above target :
* ON_ms = 10 + ( usage - target ) / ( 100 % - target ) * 80
* OFF_ms = 90 - ( usage - target ) / ( 100 % - target ) * 80
*
* In order to keep a good responsiveness , we cap the sleep time to
* 100 ms and keep track of the sleep time left . This allows us to
* quickly change it if needed .
*/
activity_data - > time_left - = 100 ;
if ( activity_data - > time_left < = 0 ) {
activity_data - > time_left = 0 ;
activity_data - > state = ! activity_data - > state ;
led_set_brightness_nosleep ( led_cdev ,
( activity_data - > state ^ activity_data - > invert ) ?
led_cdev - > blink_brightness : LED_OFF ) ;
}
target = ( cpus > 1 ) ? ( 100 / cpus ) : 50 ;
if ( usage < target )
delay = activity_data - > state ?
10 : /* ON */
990 - 900 * usage / target ; /* OFF */
else
delay = activity_data - > state ?
10 + 80 * ( usage - target ) / ( 100 - target ) : /* ON */
90 - 80 * ( usage - target ) / ( 100 - target ) ; /* OFF */
if ( ! activity_data - > time_left | | delay < = activity_data - > time_left )
activity_data - > time_left = delay ;
delay = min_t ( int , activity_data - > time_left , 100 ) ;
mod_timer ( & activity_data - > timer , jiffies + msecs_to_jiffies ( delay ) ) ;
}
static ssize_t led_invert_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
2018-07-02 22:05:32 +02:00
struct activity_data * activity_data = led_trigger_get_drvdata ( dev ) ;
2017-08-28 20:44:51 +02:00
return sprintf ( buf , " %u \n " , activity_data - > invert ) ;
}
static ssize_t led_invert_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t size )
{
2018-07-02 22:05:32 +02:00
struct activity_data * activity_data = led_trigger_get_drvdata ( dev ) ;
2017-08-28 20:44:51 +02:00
unsigned long state ;
int ret ;
ret = kstrtoul ( buf , 0 , & state ) ;
if ( ret )
return ret ;
activity_data - > invert = ! ! state ;
return size ;
}
static DEVICE_ATTR ( invert , 0644 , led_invert_show , led_invert_store ) ;
2018-07-02 22:05:32 +02:00
static struct attribute * activity_led_attrs [ ] = {
& dev_attr_invert . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( activity_led ) ;
2018-07-02 22:05:21 +02:00
static int activity_activate ( struct led_classdev * led_cdev )
2017-08-28 20:44:51 +02:00
{
struct activity_data * activity_data ;
activity_data = kzalloc ( sizeof ( * activity_data ) , GFP_KERNEL ) ;
if ( ! activity_data )
2018-07-02 22:05:32 +02:00
return - ENOMEM ;
2017-08-28 20:44:51 +02:00
2018-07-02 22:05:32 +02:00
led_set_trigger_data ( led_cdev , activity_data ) ;
2017-08-28 20:44:51 +02:00
2017-10-25 03:30:01 -07:00
activity_data - > led_cdev = led_cdev ;
timer_setup ( & activity_data - > timer , led_activity_function , 0 ) ;
2017-08-28 20:44:51 +02:00
if ( ! led_cdev - > blink_brightness )
led_cdev - > blink_brightness = led_cdev - > max_brightness ;
2017-10-25 03:30:01 -07:00
led_activity_function ( & activity_data - > timer ) ;
2017-08-28 20:44:51 +02:00
set_bit ( LED_BLINK_SW , & led_cdev - > work_flags ) ;
2018-07-02 22:05:21 +02:00
return 0 ;
2017-08-28 20:44:51 +02:00
}
static void activity_deactivate ( struct led_classdev * led_cdev )
{
2018-07-02 22:05:32 +02:00
struct activity_data * activity_data = led_get_trigger_data ( led_cdev ) ;
del_timer_sync ( & activity_data - > timer ) ;
kfree ( activity_data ) ;
clear_bit ( LED_BLINK_SW , & led_cdev - > work_flags ) ;
2017-08-28 20:44:51 +02:00
}
static struct led_trigger activity_led_trigger = {
. name = " activity " ,
. activate = activity_activate ,
. deactivate = activity_deactivate ,
2018-07-02 22:05:32 +02:00
. groups = activity_led_groups ,
2017-08-28 20:44:51 +02:00
} ;
static int activity_reboot_notifier ( struct notifier_block * nb ,
unsigned long code , void * unused )
{
led_trigger_unregister ( & activity_led_trigger ) ;
return NOTIFY_DONE ;
}
static int activity_panic_notifier ( struct notifier_block * nb ,
unsigned long code , void * unused )
{
panic_detected = 1 ;
return NOTIFY_DONE ;
}
static struct notifier_block activity_reboot_nb = {
. notifier_call = activity_reboot_notifier ,
} ;
static struct notifier_block activity_panic_nb = {
. notifier_call = activity_panic_notifier ,
} ;
static int __init activity_init ( void )
{
int rc = led_trigger_register ( & activity_led_trigger ) ;
if ( ! rc ) {
atomic_notifier_chain_register ( & panic_notifier_list ,
& activity_panic_nb ) ;
register_reboot_notifier ( & activity_reboot_nb ) ;
}
return rc ;
}
static void __exit activity_exit ( void )
{
unregister_reboot_notifier ( & activity_reboot_nb ) ;
atomic_notifier_chain_unregister ( & panic_notifier_list ,
& activity_panic_nb ) ;
led_trigger_unregister ( & activity_led_trigger ) ;
}
module_init ( activity_init ) ;
module_exit ( activity_exit ) ;
MODULE_AUTHOR ( " Willy Tarreau <w@1wt.eu> " ) ;
MODULE_DESCRIPTION ( " Activity LED trigger " ) ;
2018-07-02 22:05:20 +02:00
MODULE_LICENSE ( " GPL v2 " ) ;