1 // SPDX-License-Identifier: GPL-2.0-only 2 #include <linux/kernel.h> 3 #include <linux/module.h> 4 #include <linux/init.h> 5 #include <linux/proc_fs.h> 6 #include <linux/seq_file.h> 7 #include <linux/slab.h> 8 #include <linux/string.h> 9 #include <linux/jiffies.h> 10 #include <linux/timer.h> 11 #include <linux/uaccess.h> 12 #include <linux/sched/loadavg.h> 13 14 #include <asm/auxio.h> 15 16 #define LED_MAX_LENGTH 8 /* maximum chars written to proc file */ 17 18 static inline void led_toggle(void) 19 { 20 unsigned char val = get_auxio(); 21 unsigned char on, off; 22 23 if (val & AUXIO_LED) { 24 on = 0; 25 off = AUXIO_LED; 26 } else { 27 on = AUXIO_LED; 28 off = 0; 29 } 30 31 set_auxio(on, off); 32 } 33 34 static struct timer_list led_blink_timer; 35 static unsigned long led_blink_timer_timeout; 36 37 static void led_blink(struct timer_list *unused) 38 { 39 unsigned long timeout = led_blink_timer_timeout; 40 41 led_toggle(); 42 43 /* reschedule */ 44 if (!timeout) { /* blink according to load */ 45 led_blink_timer.expires = jiffies + 46 ((1 + (avenrun[0] >> FSHIFT)) * HZ); 47 } else { /* blink at user specified interval */ 48 led_blink_timer.expires = jiffies + (timeout * HZ); 49 } 50 add_timer(&led_blink_timer); 51 } 52 53 static int led_proc_show(struct seq_file *m, void *v) 54 { 55 if (get_auxio() & AUXIO_LED) 56 seq_puts(m, "on\n"); 57 else 58 seq_puts(m, "off\n"); 59 return 0; 60 } 61 62 static int led_proc_open(struct inode *inode, struct file *file) 63 { 64 return single_open(file, led_proc_show, NULL); 65 } 66 67 static ssize_t led_proc_write(struct file *file, const char __user *buffer, 68 size_t count, loff_t *ppos) 69 { 70 char *buf = NULL; 71 72 if (count > LED_MAX_LENGTH) 73 count = LED_MAX_LENGTH; 74 75 buf = memdup_user_nul(buffer, count); 76 if (IS_ERR(buf)) 77 return PTR_ERR(buf); 78 79 /* work around \n when echo'ing into proc */ 80 if (buf[count - 1] == '\n') 81 buf[count - 1] = '\0'; 82 83 /* before we change anything we want to stop any running timers, 84 * otherwise calls such as on will have no persistent effect 85 */ 86 del_timer_sync(&led_blink_timer); 87 88 if (!strcmp(buf, "on")) { 89 auxio_set_led(AUXIO_LED_ON); 90 } else if (!strcmp(buf, "toggle")) { 91 led_toggle(); 92 } else if ((*buf > '0') && (*buf <= '9')) { 93 led_blink_timer_timeout = simple_strtoul(buf, NULL, 10); 94 led_blink(&led_blink_timer); 95 } else if (!strcmp(buf, "load")) { 96 led_blink_timer_timeout = 0; 97 led_blink(&led_blink_timer); 98 } else { 99 auxio_set_led(AUXIO_LED_OFF); 100 } 101 102 kfree(buf); 103 104 return count; 105 } 106 107 static const struct file_operations led_proc_fops = { 108 .owner = THIS_MODULE, 109 .open = led_proc_open, 110 .read = seq_read, 111 .llseek = seq_lseek, 112 .release = single_release, 113 .write = led_proc_write, 114 }; 115 116 static struct proc_dir_entry *led; 117 118 #define LED_VERSION "0.1" 119 120 static int __init led_init(void) 121 { 122 timer_setup(&led_blink_timer, led_blink, 0); 123 124 led = proc_create("led", 0, NULL, &led_proc_fops); 125 if (!led) 126 return -ENOMEM; 127 128 printk(KERN_INFO 129 "led: version %s, Lars Kotthoff <metalhead@metalhead.ws>\n", 130 LED_VERSION); 131 132 return 0; 133 } 134 135 static void __exit led_exit(void) 136 { 137 remove_proc_entry("led", NULL); 138 del_timer_sync(&led_blink_timer); 139 } 140 141 module_init(led_init); 142 module_exit(led_exit); 143 144 MODULE_AUTHOR("Lars Kotthoff <metalhead@metalhead.ws>"); 145 MODULE_DESCRIPTION("Provides control of the front LED on SPARC systems."); 146 MODULE_LICENSE("GPL"); 147 MODULE_VERSION(LED_VERSION); 148