271 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Activity LED trigger4 *5 * Copyright (C) 2017 Willy Tarreau <w@1wt.eu>6 * Partially based on Atsushi Nemoto's ledtrig-heartbeat.c.7 */8 9#include <linux/init.h>10#include <linux/kernel.h>11#include <linux/kernel_stat.h>12#include <linux/leds.h>13#include <linux/module.h>14#include <linux/panic_notifier.h>15#include <linux/reboot.h>16#include <linux/sched.h>17#include <linux/slab.h>18#include <linux/timer.h>19#include "../leds.h"20 21static int panic_detected;22 23struct activity_data {24 struct timer_list timer;25 struct led_classdev *led_cdev;26 u64 last_used;27 u64 last_boot;28 int time_left;29 int state;30 int invert;31};32 33static void led_activity_function(struct timer_list *t)34{35 struct activity_data *activity_data = from_timer(activity_data, t,36 timer);37 struct led_classdev *led_cdev = activity_data->led_cdev;38 unsigned int target;39 unsigned int usage;40 int delay;41 u64 curr_used;42 u64 curr_boot;43 s32 diff_used;44 s32 diff_boot;45 int cpus;46 int i;47 48 if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))49 led_cdev->blink_brightness = led_cdev->new_blink_brightness;50 51 if (unlikely(panic_detected)) {52 /* full brightness in case of panic */53 led_set_brightness_nosleep(led_cdev, led_cdev->blink_brightness);54 return;55 }56 57 cpus = 0;58 curr_used = 0;59 60 for_each_possible_cpu(i) {61 struct kernel_cpustat kcpustat;62 63 kcpustat_cpu_fetch(&kcpustat, i);64 65 curr_used += kcpustat.cpustat[CPUTIME_USER]66 + kcpustat.cpustat[CPUTIME_NICE]67 + kcpustat.cpustat[CPUTIME_SYSTEM]68 + kcpustat.cpustat[CPUTIME_SOFTIRQ]69 + kcpustat.cpustat[CPUTIME_IRQ];70 cpus++;71 }72 73 /* We come here every 100ms in the worst case, so that's 100M ns of74 * cumulated time. By dividing by 2^16, we get the time resolution75 * down to 16us, ensuring we won't overflow 32-bit computations below76 * even up to 3k CPUs, while keeping divides cheap on smaller systems.77 */78 curr_boot = ktime_get_boottime_ns() * cpus;79 diff_boot = (curr_boot - activity_data->last_boot) >> 16;80 diff_used = (curr_used - activity_data->last_used) >> 16;81 activity_data->last_boot = curr_boot;82 activity_data->last_used = curr_used;83 84 if (diff_boot <= 0 || diff_used < 0)85 usage = 0;86 else if (diff_used >= diff_boot)87 usage = 100;88 else89 usage = 100 * diff_used / diff_boot;90 91 /*92 * Now we know the total boot_time multiplied by the number of CPUs, and93 * the total idle+wait time for all CPUs. We'll compare how they evolved94 * since last call. The % of overall CPU usage is :95 *96 * 1 - delta_idle / delta_boot97 *98 * What we want is that when the CPU usage is zero, the LED must blink99 * slowly with very faint flashes that are detectable but not disturbing100 * (typically 10ms every second, or 10ms ON, 990ms OFF). Then we want101 * blinking frequency to increase up to the point where the load is102 * enough to saturate one core in multi-core systems or 50% in single103 * core systems. At this point it should reach 10 Hz with a 10/90 duty104 * cycle (10ms ON, 90ms OFF). After this point, the blinking frequency105 * remains stable (10 Hz) and only the duty cycle increases to report106 * the activity, up to the point where we have 90ms ON, 10ms OFF when107 * all cores are saturated. It's important that the LED never stays in108 * a steady state so that it's easy to distinguish an idle or saturated109 * machine from a hung one.110 *111 * This gives us :112 * - a target CPU usage of min(50%, 100%/#CPU) for a 10% duty cycle113 * (10ms ON, 90ms OFF)114 * - below target :115 * ON_ms = 10116 * OFF_ms = 90 + (1 - usage/target) * 900117 * - above target :118 * ON_ms = 10 + (usage-target)/(100%-target) * 80119 * OFF_ms = 90 - (usage-target)/(100%-target) * 80120 *121 * In order to keep a good responsiveness, we cap the sleep time to122 * 100 ms and keep track of the sleep time left. This allows us to123 * quickly change it if needed.124 */125 126 activity_data->time_left -= 100;127 if (activity_data->time_left <= 0) {128 activity_data->time_left = 0;129 activity_data->state = !activity_data->state;130 led_set_brightness_nosleep(led_cdev,131 (activity_data->state ^ activity_data->invert) ?132 led_cdev->blink_brightness : LED_OFF);133 }134 135 target = (cpus > 1) ? (100 / cpus) : 50;136 137 if (usage < target)138 delay = activity_data->state ?139 10 : /* ON */140 990 - 900 * usage / target; /* OFF */141 else142 delay = activity_data->state ?143 10 + 80 * (usage - target) / (100 - target) : /* ON */144 90 - 80 * (usage - target) / (100 - target); /* OFF */145 146 147 if (!activity_data->time_left || delay <= activity_data->time_left)148 activity_data->time_left = delay;149 150 delay = min_t(int, activity_data->time_left, 100);151 mod_timer(&activity_data->timer, jiffies + msecs_to_jiffies(delay));152}153 154static ssize_t led_invert_show(struct device *dev,155 struct device_attribute *attr, char *buf)156{157 struct activity_data *activity_data = led_trigger_get_drvdata(dev);158 159 return sprintf(buf, "%u\n", activity_data->invert);160}161 162static ssize_t led_invert_store(struct device *dev,163 struct device_attribute *attr,164 const char *buf, size_t size)165{166 struct activity_data *activity_data = led_trigger_get_drvdata(dev);167 unsigned long state;168 int ret;169 170 ret = kstrtoul(buf, 0, &state);171 if (ret)172 return ret;173 174 activity_data->invert = !!state;175 176 return size;177}178 179static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);180 181static struct attribute *activity_led_attrs[] = {182 &dev_attr_invert.attr,183 NULL184};185ATTRIBUTE_GROUPS(activity_led);186 187static int activity_activate(struct led_classdev *led_cdev)188{189 struct activity_data *activity_data;190 191 activity_data = kzalloc(sizeof(*activity_data), GFP_KERNEL);192 if (!activity_data)193 return -ENOMEM;194 195 led_set_trigger_data(led_cdev, activity_data);196 197 activity_data->led_cdev = led_cdev;198 timer_setup(&activity_data->timer, led_activity_function, 0);199 if (!led_cdev->blink_brightness)200 led_cdev->blink_brightness = led_cdev->max_brightness;201 led_activity_function(&activity_data->timer);202 set_bit(LED_BLINK_SW, &led_cdev->work_flags);203 204 return 0;205}206 207static void activity_deactivate(struct led_classdev *led_cdev)208{209 struct activity_data *activity_data = led_get_trigger_data(led_cdev);210 211 timer_shutdown_sync(&activity_data->timer);212 kfree(activity_data);213 clear_bit(LED_BLINK_SW, &led_cdev->work_flags);214}215 216static struct led_trigger activity_led_trigger = {217 .name = "activity",218 .activate = activity_activate,219 .deactivate = activity_deactivate,220 .groups = activity_led_groups,221};222 223static int activity_reboot_notifier(struct notifier_block *nb,224 unsigned long code, void *unused)225{226 led_trigger_unregister(&activity_led_trigger);227 return NOTIFY_DONE;228}229 230static int activity_panic_notifier(struct notifier_block *nb,231 unsigned long code, void *unused)232{233 panic_detected = 1;234 return NOTIFY_DONE;235}236 237static struct notifier_block activity_reboot_nb = {238 .notifier_call = activity_reboot_notifier,239};240 241static struct notifier_block activity_panic_nb = {242 .notifier_call = activity_panic_notifier,243};244 245static int __init activity_init(void)246{247 int rc = led_trigger_register(&activity_led_trigger);248 249 if (!rc) {250 atomic_notifier_chain_register(&panic_notifier_list,251 &activity_panic_nb);252 register_reboot_notifier(&activity_reboot_nb);253 }254 return rc;255}256 257static void __exit activity_exit(void)258{259 unregister_reboot_notifier(&activity_reboot_nb);260 atomic_notifier_chain_unregister(&panic_notifier_list,261 &activity_panic_nb);262 led_trigger_unregister(&activity_led_trigger);263}264 265module_init(activity_init);266module_exit(activity_exit);267 268MODULE_AUTHOR("Willy Tarreau <w@1wt.eu>");269MODULE_DESCRIPTION("Activity LED trigger");270MODULE_LICENSE("GPL v2");271