]> granicus.if.org Git - intel_nuc_led/blob - nuc_led.c
Add README with CentOS build information
[intel_nuc_led] / nuc_led.c
1 /*
2  * Intel NUC LED Control Driver
3  *
4  * Copyright (C) 2017 Miles Peterson
5  *
6  * Portions based on asus-wmi.c:
7  * Copyright (C) 2010 Intel Corporation.
8  * Copyright (C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
9  *
10  * Portions based on acpi_call.c:
11  * Copyright (C) 2010: Michal Kottman
12  *
13  * Based on Intel Article ID 000023426
14  * http://www.intel.com/content/www/us/en/support/boards-and-kits/intel-nuc-kits/000023426.html
15  *
16  *  This program is free software; you can redistribute it and/or modify
17  *  it under the terms of the GNU General Public License as published by
18  *  the Free Software Foundation; either version 2 of the License, or
19  *  (at your option) any later version.
20  *
21  *  This program is distributed in the hope that it will be useful,
22  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  *  GNU General Public License for more details.
25  *
26  *  You should have received a copy of the GNU General Public License
27  *  along with this program; if not, write to the Free Software
28  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29  */
30
31 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
32
33 #include <linux/module.h>
34 #include <linux/kernel.h>
35 #include <linux/types.h>
36 #include <linux/proc_fs.h>
37 #include <linux/acpi.h>
38 #include <linux/vmalloc.h>
39 #include <linux/uaccess.h>
40
41 MODULE_AUTHOR("Miles Peterson");
42 MODULE_DESCRIPTION("Intel NUC LED Control WMI Driver");
43 MODULE_LICENSE("GPL");
44
45 /* Intel NUC WMI GUID */
46 #define NUCLED_WMI_MGMT_GUID            "8C5DA44C-CDC3-46b3-8619-4E26D34390B7"
47 MODULE_ALIAS("wmi:" NUCLED_WMI_MGMT_GUID);
48
49 /* LED Control Method ID */
50 #define NUCLED_WMI_METHODID_GETSTATE    0x01
51 #define NUCLED_WMI_METHODID_SETSTATE    0x02
52
53 /* LED Identifiers */
54 #define NUCLED_WMI_POWER_LED_ID         0x01
55 #define NUCLED_WMI_RING_LED_ID          0x02
56
57 /* Return codes */
58 #define NUCLED_WMI_RETURN_SUCCESS       0x00
59 #define NUCLED_WMI_RETURN_NOSUPPORT     0xE1
60 #define NUCLED_WMI_RETURN_UNDEFINED     0xE2
61 #define NUCLED_WMI_RETURN_NORESPONSE    0xE3
62 #define NUCLED_WMI_RETURN_BADPARAM      0xE4
63 #define NUCLED_WMI_RETURN_UNEXPECTED    0xEF
64
65 /* Blink and fade */
66 #define NUCLED_WMI_BLINK_1HZ            0x01
67 #define NUCLED_WMI_BLINK_0_25HZ         0x02
68 #define NUCLED_WMI_FADE_1HZ             0x03
69 #define NUCLED_WMI_ALWAYS_ON            0x04
70 #define NUCLED_WMI_BLINK_0_5HZ          0x05
71 #define NUCLED_WMI_FADE_0_25HZ          0x06
72 #define NUCLED_WMI_FADE_0_5HZ           0x07
73
74 /* Power button colors */
75 #define NUCLED_WMI_POWER_COLOR_DISABLE  0x00
76 #define NUCLED_WMI_POWER_COLOR_BLUE     0x01
77 #define NUCLED_WMI_POWER_COLOR_AMBER    0x02
78
79 /* Ring colors */
80 #define NUCLED_WMI_RING_COLOR_DISABLE   0x00
81 #define NUCLED_WMI_RING_COLOR_CYAN      0x01
82 #define NUCLED_WMI_RING_COLOR_PINK      0x02
83 #define NUCLED_WMI_RING_COLOR_YELLOW    0x03
84 #define NUCLED_WMI_RING_COLOR_BLUE      0x04
85 #define NUCLED_WMI_RING_COLOR_RED       0x05
86 #define NUCLED_WMI_RING_COLOR_GREEN     0x06
87 #define NUCLED_WMI_RING_COLOR_WHITE     0x07
88
89 extern struct proc_dir_entry *acpi_root_dir;
90
91 struct led_get_state_args {
92         u32 led;
93 } __packed;
94
95 struct led_get_state_return {
96         u32 return_code;
97         u32 brightness;
98         u32 blink_fade;
99         u32 color_state;
100 } __packed;
101
102 struct led_set_state_args {
103         u8 led;
104         u8 brightness;
105         u8 blink_fade;
106         u8 color_state;
107 }__packed;
108
109 struct led_set_state_return {
110         u32 brightness_return;
111         u32 blink_fade_return;
112         u32 color_return;
113 } __packed;
114
115 #define BUFFER_SIZE 512
116 static char result_buffer[BUFFER_SIZE];
117 static char *get_buffer_end(void) {
118     return result_buffer + strlen(result_buffer);
119 }
120
121 /* Convert blink/fade value to text */
122 static const char* const blink_fade_text[] = { "Off", "1Hz Blink", "0.25Hz Blink", "1Hz Fade", "Always On", "0.5Hz Blink", "0.25Hz Fade", "0.5Hz Fade" };
123
124 /* Convert color value to text */
125 static const char* const pwrcolor_text[] =   { "Off", "Blue", "Amber" };
126 static const char* const ringcolor_text[] =  { "Off", "Cyan", "Pink", "Yellow", "Blue", "Red", "Green", "White" };
127
128 /* Get LED state */
129 static int nuc_led_get_state(u32 led, struct led_get_state_return *state)
130 {
131         struct led_get_state_args args = {
132                 .led = led
133         };
134         struct acpi_buffer input;
135         struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
136         acpi_status status;
137         union acpi_object *obj;
138
139         input.length = (acpi_size) sizeof(args);
140         input.pointer = &args;
141
142         status = wmi_evaluate_method(NUCLED_WMI_MGMT_GUID, 1, NUCLED_WMI_METHODID_GETSTATE,
143                                      &input, &output);
144
145         if (ACPI_FAILURE(status))
146                 return -EIO;
147
148         // Always returns a buffer
149         obj = (union acpi_object *)output.pointer;
150         if (obj && state)
151         {
152                 state->return_code = obj->buffer.pointer[0];
153                 state->brightness  = obj->buffer.pointer[1];
154                 state->blink_fade  = obj->buffer.pointer[2];
155                 state->color_state = obj->buffer.pointer[3];
156         }
157
158         kfree(obj);
159
160         return 0;
161 }
162
163 /* Set LED state */
164 static int nuc_led_set_state(u32 led, u32 brightness, u32 blink_fade, u32 color_state,
165                 struct led_set_state_return *retval)
166 {
167         struct led_set_state_args args = {
168                 .led = led,
169                 .brightness = brightness,
170                 .blink_fade = blink_fade,
171                 .color_state = color_state
172         };
173
174         struct acpi_buffer input;
175         struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
176         acpi_status status;
177         union acpi_object *obj;
178
179         input.length = (acpi_size) sizeof(args);
180         input.pointer = &args;
181         
182         status = wmi_evaluate_method(NUCLED_WMI_MGMT_GUID, 1, NUCLED_WMI_METHODID_SETSTATE,
183                                      &input, &output);
184
185         if (ACPI_FAILURE(status))
186                 return -EIO;
187
188         // Always returns a buffer
189         obj = (union acpi_object *)output.pointer;
190         if (obj && retval)
191         {
192                 retval->brightness_return = obj->buffer.pointer[0];
193                 retval->blink_fade_return = obj->buffer.pointer[1];
194                 retval->color_return      = obj->buffer.pointer[2];
195         }
196
197         kfree(obj);
198
199         return 0;
200 }
201
202 static ssize_t acpi_proc_write(struct file *filp, const char __user *buff,
203                 size_t len, loff_t *data)
204 {
205         int i = 0;
206         int ret = 0;
207         char *input, *arg;
208         static int status  = 0;
209         struct led_set_state_return retval;
210         u32 led, brightness, blink_fade, color_state;
211
212         // Move buffer from user space to kernel space
213         input = vmalloc(len);
214         if (!input)
215                 return -ENOMEM;
216
217         if (copy_from_user(input, buff, len))
218                 return -EFAULT;
219
220         // Strip new line
221         input[len] = '\0';
222         if (input[len - 1] == '\n')
223                 input[len - 1] = '\0';
224
225         // Parse input string
226         while ((arg = strsep(&input, ",")) && *arg)
227         {
228                 if (i == 0)             // First arg: LED ("power" or "ring")
229                 {
230                         if (!strcmp(arg, "power"))
231                                 led = NUCLED_WMI_POWER_LED_ID;
232                         else if (!strcmp(arg, "ring"))
233                                 led = NUCLED_WMI_RING_LED_ID;
234                         else
235                                 ret = -EINVAL;
236                 }
237                 else if (i == 1)        // Second arg: brightness (0 - 100)
238                 {
239                         long val;
240
241                         if (kstrtol(arg, 0, &val))
242                         {
243                                 ret = -EINVAL;
244                         }
245                         else
246                         {
247                                 if (val < 0 || val > 100)
248                                         ret = -EINVAL;
249                                 else
250                                         brightness = val;
251                         }
252                 }
253                 else if (i == 2)        // Third arg: fade/brightness (text values)
254                 {
255                         if (!strcmp(arg, "none"))
256                                 blink_fade = NUCLED_WMI_ALWAYS_ON;
257                         else if (!strcmp(arg, "blink_fast"))
258                                 blink_fade = NUCLED_WMI_BLINK_1HZ;
259                         else if (!strcmp(arg, "blink_medium"))
260                                 blink_fade = NUCLED_WMI_BLINK_0_5HZ;
261                         else if (!strcmp(arg, "blink_slow"))
262                                 blink_fade = NUCLED_WMI_BLINK_0_25HZ;
263                         else if (!strcmp(arg, "fade_fast"))
264                                 blink_fade = NUCLED_WMI_FADE_1HZ;
265                         else if (!strcmp(arg, "fade_medium"))
266                                 blink_fade = NUCLED_WMI_FADE_0_5HZ;
267                         else if (!strcmp(arg, "fade_slow"))
268                                 blink_fade = NUCLED_WMI_FADE_0_25HZ;
269                         else
270                                 ret = -EINVAL;
271                 }
272                 else if (i == 3)        // Fourth arg: color (text values)
273                 {
274                         if (led == NUCLED_WMI_POWER_LED_ID)
275                         {
276                                 if (!strcmp(arg, "off"))
277                                         color_state = NUCLED_WMI_POWER_COLOR_DISABLE;
278                                 else if (!strcmp(arg, "blue"))
279                                         color_state = NUCLED_WMI_POWER_COLOR_BLUE;
280                                 else if (!strcmp(arg, "amber"))
281                                         color_state = NUCLED_WMI_POWER_COLOR_AMBER;
282                                 else
283                                         ret = -EINVAL;
284                         }
285                         else if (led == NUCLED_WMI_RING_LED_ID)
286                         {
287                                 if (!strcmp(arg, "off"))
288                                         color_state = NUCLED_WMI_RING_COLOR_DISABLE;
289                                 else if (!strcmp(arg, "cyan"))
290                                         color_state = NUCLED_WMI_RING_COLOR_CYAN;
291                                 else if (!strcmp(arg, "pink"))
292                                         color_state = NUCLED_WMI_RING_COLOR_PINK;
293                                 else if (!strcmp(arg, "yellow"))
294                                         color_state = NUCLED_WMI_RING_COLOR_YELLOW;
295                                 else if (!strcmp(arg, "blue"))
296                                         color_state = NUCLED_WMI_RING_COLOR_BLUE;
297                                 else if (!strcmp(arg, "red"))
298                                         color_state = NUCLED_WMI_RING_COLOR_RED;
299                                 else if (!strcmp(arg, "green"))
300                                         color_state = NUCLED_WMI_RING_COLOR_GREEN;
301                                 else if (!strcmp(arg, "white"))
302                                         color_state = NUCLED_WMI_RING_COLOR_WHITE;
303                                 else
304                                         ret = -EINVAL;
305                         }
306                 }
307                 else                    // Too many args!
308                         ret = -EOVERFLOW;
309
310                 // Track iterations
311                 i++;
312         }
313
314         vfree(input);
315
316         if (ret == -EOVERFLOW)
317         {
318                 pr_warn("Too many arguments while setting NUC LED state\n");
319         }
320         else if (i != 4)
321         {
322                 pr_warn("Too few arguments while setting NUC LED state\n");
323         }
324         else if (ret == -EINVAL)
325         {
326                 pr_warn("Invalid argument while setting NUC LED state\n");
327         }
328         else
329         {
330                 status = nuc_led_set_state(led, brightness, blink_fade, color_state, &retval);
331                 if (status)
332                 {
333                         pr_warn("Unable to set NUC LED state: WMI call failed\n");
334                 }
335                 else
336                 {
337                         if (retval.brightness_return == NUCLED_WMI_RETURN_UNDEFINED)
338                         {
339                                 if (led == NUCLED_WMI_POWER_LED_ID)
340                                         pr_warn("Unable set NUC power LED state: not set for SW control\n");
341                                 else if (led == NUCLED_WMI_RING_LED_ID)
342                                         pr_warn("Unable set NUC ring LED state: not set for SW control\n");
343                         }
344                         else if (retval.brightness_return == NUCLED_WMI_RETURN_BADPARAM || retval.blink_fade_return == NUCLED_WMI_RETURN_BADPARAM ||
345                                  retval.color_return == NUCLED_WMI_RETURN_BADPARAM)
346                         {
347                                 pr_warn("Unable to set NUC LED state: invalid parameter\n");
348                         }
349                         else if (retval.brightness_return != NUCLED_WMI_RETURN_SUCCESS)
350                         {
351                                 pr_warn("Unable to set NUC LED state: WMI call returned error\n");
352                         }
353                 }
354         }
355
356         return len;
357 }
358
359 static ssize_t acpi_proc_read(struct file *filp, char __user *buff,
360                 size_t count, loff_t *off)
361 {
362         ssize_t ret;
363         static int status_pwr  = 0;
364         static int status_ring = 0;
365         struct led_get_state_return power_led;
366         struct led_get_state_return ring_led;
367         int len = 0;
368
369         // Get statuses from WMI interface
370         status_pwr = nuc_led_get_state(NUCLED_WMI_POWER_LED_ID, &power_led);
371         if (status_pwr)
372                 pr_warn("Unable to get NUC power LED state\n");
373
374         status_ring = nuc_led_get_state(NUCLED_WMI_RING_LED_ID, &ring_led);
375         if (status_ring)
376                 pr_warn("Unable to get NUC ring LED state\n");
377
378         // Clear buffer
379         memset(result_buffer, 0, BUFFER_SIZE);
380
381         // Process state for power LED
382         if (status_pwr)
383         {
384                 sprintf(get_buffer_end(), "Power LED state could not be determined: WMI call failed\n\n");
385         }
386         else
387         {
388                 if (power_led.return_code == NUCLED_WMI_RETURN_SUCCESS)
389                         sprintf(get_buffer_end(), "Power LED Brightness: %d%%\nPower LED Blink/Fade: %s (0x%02x)\nPower LED Color: %s (0x%02x)\n\n",
390                                 power_led.brightness,
391                                 blink_fade_text[power_led.blink_fade], power_led.blink_fade,
392                                 pwrcolor_text[power_led.color_state], power_led.color_state);
393                 else if (power_led.return_code == NUCLED_WMI_RETURN_UNDEFINED)
394                         sprintf(get_buffer_end(), "Power LED not set for software control\n\n");
395                 else
396                         sprintf(get_buffer_end(), "Power LED state could not be determined: WMI call returned error\n\n");
397         }
398
399         // Process state for ring LED
400         if (status_ring)
401         {
402                 sprintf(get_buffer_end(), "Ring LED state could not be determined: WMI call failed\n\n");
403         }
404         else
405         {
406                 if (ring_led.return_code == NUCLED_WMI_RETURN_SUCCESS)
407                         sprintf(get_buffer_end(), "Ring LED Brightness: %d%%\nRing LED Blink/Fade: %s (0x%02x)\nRing LED Color: %s (0x%02x)\n\n",
408                                 ring_led.brightness,
409                                 blink_fade_text[ring_led.blink_fade], ring_led.blink_fade,
410                                 ringcolor_text[ring_led.color_state], ring_led.color_state);
411                 else if (power_led.return_code == NUCLED_WMI_RETURN_UNDEFINED)
412                         sprintf(get_buffer_end(), "Ring LED not set for software control\n\n");
413                 else
414                         sprintf(get_buffer_end(), "Ring LED state could not be determined: WMI call returned error\n\n");
415         }
416
417         // Return buffer via proc
418         len = strlen(result_buffer);
419         ret = simple_read_from_buffer(buff, count, off, result_buffer, len + 1);
420
421         return ret;
422 }
423
424 static struct file_operations proc_acpi_operations = {
425         .owner    = THIS_MODULE,
426         .read     = acpi_proc_read,
427         .write    = acpi_proc_write,
428 };
429
430 /* Init & unload */
431 static int __init init_nuc_led(void)
432 {
433         struct proc_dir_entry *acpi_entry;
434
435         // Make sure LED control WMI GUID exists
436         if (!wmi_has_guid(NUCLED_WMI_MGMT_GUID)) {
437                 pr_warn("Intel NUC LED WMI GUID not found\n");
438                 return -ENODEV;
439         }
440
441         // Create nuc_led ACPI proc entry
442         acpi_entry = proc_create("nuc_led", 0664, acpi_root_dir, &proc_acpi_operations);
443
444         if (acpi_entry == NULL) {
445                 pr_warn("Intel NUC LED control driver could not create proc entry\n");
446                 return -ENOMEM;
447         }
448
449         pr_info("Intel NUC LED control driver loaded\n");
450
451         return 0;
452 }
453
454 static void __exit unload_nuc_led(void)
455 {
456         remove_proc_entry("nuc_led", acpi_root_dir);
457         pr_info("Intel NUC LED control driver unloaded\n");
458 }
459
460 module_init(init_nuc_led);
461 module_exit(unload_nuc_led);