Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * LED Heartbeat Trigger
0004  *
0005  * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
0006  *
0007  * Based on Richard Purdie's ledtrig-timer.c and some arch's
0008  * CONFIG_HEARTBEAT code.
0009  */
0010 
0011 #include <linux/module.h>
0012 #include <linux/kernel.h>
0013 #include <linux/init.h>
0014 #include <linux/panic_notifier.h>
0015 #include <linux/slab.h>
0016 #include <linux/timer.h>
0017 #include <linux/sched.h>
0018 #include <linux/sched/loadavg.h>
0019 #include <linux/leds.h>
0020 #include <linux/reboot.h>
0021 #include "../leds.h"
0022 
0023 static int panic_heartbeats;
0024 
0025 struct heartbeat_trig_data {
0026     struct led_classdev *led_cdev;
0027     unsigned int phase;
0028     unsigned int period;
0029     struct timer_list timer;
0030     unsigned int invert;
0031 };
0032 
0033 static void led_heartbeat_function(struct timer_list *t)
0034 {
0035     struct heartbeat_trig_data *heartbeat_data =
0036         from_timer(heartbeat_data, t, timer);
0037     struct led_classdev *led_cdev;
0038     unsigned long brightness = LED_OFF;
0039     unsigned long delay = 0;
0040 
0041     led_cdev = heartbeat_data->led_cdev;
0042 
0043     if (unlikely(panic_heartbeats)) {
0044         led_set_brightness_nosleep(led_cdev, LED_OFF);
0045         return;
0046     }
0047 
0048     if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
0049         led_cdev->blink_brightness = led_cdev->new_blink_brightness;
0050 
0051     /* acts like an actual heart beat -- ie thump-thump-pause... */
0052     switch (heartbeat_data->phase) {
0053     case 0:
0054         /*
0055          * The hyperbolic function below modifies the
0056          * heartbeat period length in dependency of the
0057          * current (1min) load. It goes through the points
0058          * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
0059          */
0060         heartbeat_data->period = 300 +
0061             (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
0062         heartbeat_data->period =
0063             msecs_to_jiffies(heartbeat_data->period);
0064         delay = msecs_to_jiffies(70);
0065         heartbeat_data->phase++;
0066         if (!heartbeat_data->invert)
0067             brightness = led_cdev->blink_brightness;
0068         break;
0069     case 1:
0070         delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
0071         heartbeat_data->phase++;
0072         if (heartbeat_data->invert)
0073             brightness = led_cdev->blink_brightness;
0074         break;
0075     case 2:
0076         delay = msecs_to_jiffies(70);
0077         heartbeat_data->phase++;
0078         if (!heartbeat_data->invert)
0079             brightness = led_cdev->blink_brightness;
0080         break;
0081     default:
0082         delay = heartbeat_data->period - heartbeat_data->period / 4 -
0083             msecs_to_jiffies(70);
0084         heartbeat_data->phase = 0;
0085         if (heartbeat_data->invert)
0086             brightness = led_cdev->blink_brightness;
0087         break;
0088     }
0089 
0090     led_set_brightness_nosleep(led_cdev, brightness);
0091     mod_timer(&heartbeat_data->timer, jiffies + delay);
0092 }
0093 
0094 static ssize_t led_invert_show(struct device *dev,
0095         struct device_attribute *attr, char *buf)
0096 {
0097     struct heartbeat_trig_data *heartbeat_data =
0098         led_trigger_get_drvdata(dev);
0099 
0100     return sprintf(buf, "%u\n", heartbeat_data->invert);
0101 }
0102 
0103 static ssize_t led_invert_store(struct device *dev,
0104         struct device_attribute *attr, const char *buf, size_t size)
0105 {
0106     struct heartbeat_trig_data *heartbeat_data =
0107         led_trigger_get_drvdata(dev);
0108     unsigned long state;
0109     int ret;
0110 
0111     ret = kstrtoul(buf, 0, &state);
0112     if (ret)
0113         return ret;
0114 
0115     heartbeat_data->invert = !!state;
0116 
0117     return size;
0118 }
0119 
0120 static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
0121 
0122 static struct attribute *heartbeat_trig_attrs[] = {
0123     &dev_attr_invert.attr,
0124     NULL
0125 };
0126 ATTRIBUTE_GROUPS(heartbeat_trig);
0127 
0128 static int heartbeat_trig_activate(struct led_classdev *led_cdev)
0129 {
0130     struct heartbeat_trig_data *heartbeat_data;
0131 
0132     heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
0133     if (!heartbeat_data)
0134         return -ENOMEM;
0135 
0136     led_set_trigger_data(led_cdev, heartbeat_data);
0137     heartbeat_data->led_cdev = led_cdev;
0138 
0139     timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0);
0140     heartbeat_data->phase = 0;
0141     if (!led_cdev->blink_brightness)
0142         led_cdev->blink_brightness = led_cdev->max_brightness;
0143     led_heartbeat_function(&heartbeat_data->timer);
0144     set_bit(LED_BLINK_SW, &led_cdev->work_flags);
0145 
0146     return 0;
0147 }
0148 
0149 static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
0150 {
0151     struct heartbeat_trig_data *heartbeat_data =
0152         led_get_trigger_data(led_cdev);
0153 
0154     del_timer_sync(&heartbeat_data->timer);
0155     kfree(heartbeat_data);
0156     clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
0157 }
0158 
0159 static struct led_trigger heartbeat_led_trigger = {
0160     .name     = "heartbeat",
0161     .activate = heartbeat_trig_activate,
0162     .deactivate = heartbeat_trig_deactivate,
0163     .groups = heartbeat_trig_groups,
0164 };
0165 
0166 static int heartbeat_reboot_notifier(struct notifier_block *nb,
0167                      unsigned long code, void *unused)
0168 {
0169     led_trigger_unregister(&heartbeat_led_trigger);
0170     return NOTIFY_DONE;
0171 }
0172 
0173 static int heartbeat_panic_notifier(struct notifier_block *nb,
0174                      unsigned long code, void *unused)
0175 {
0176     panic_heartbeats = 1;
0177     return NOTIFY_DONE;
0178 }
0179 
0180 static struct notifier_block heartbeat_reboot_nb = {
0181     .notifier_call = heartbeat_reboot_notifier,
0182 };
0183 
0184 static struct notifier_block heartbeat_panic_nb = {
0185     .notifier_call = heartbeat_panic_notifier,
0186 };
0187 
0188 static int __init heartbeat_trig_init(void)
0189 {
0190     int rc = led_trigger_register(&heartbeat_led_trigger);
0191 
0192     if (!rc) {
0193         atomic_notifier_chain_register(&panic_notifier_list,
0194                            &heartbeat_panic_nb);
0195         register_reboot_notifier(&heartbeat_reboot_nb);
0196     }
0197     return rc;
0198 }
0199 
0200 static void __exit heartbeat_trig_exit(void)
0201 {
0202     unregister_reboot_notifier(&heartbeat_reboot_nb);
0203     atomic_notifier_chain_unregister(&panic_notifier_list,
0204                      &heartbeat_panic_nb);
0205     led_trigger_unregister(&heartbeat_led_trigger);
0206 }
0207 
0208 module_init(heartbeat_trig_init);
0209 module_exit(heartbeat_trig_exit);
0210 
0211 MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
0212 MODULE_DESCRIPTION("Heartbeat LED trigger");
0213 MODULE_LICENSE("GPL v2");