xref: /openbmc/skeleton/ledctl/led_controller.c (revision 9d572eb3)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <dirent.h>
6 #include <systemd/sd-bus.h>
7 
8 static int led_stable_state_function(const char *, const char *);
9 static int led_default_blink(const char *, const char *);
10 static int read_led(const char *, const char *, void *, const size_t);
11 static int led_custom_blink(const char *, sd_bus_message *);
12 
13 /*
14  * These are control files that are present for each led under
15  *'/sys/class/leds/<led_name>/' which are used to trigger action
16  * on the respective leds by writing predefined data.
17  */
18 const char *power_ctrl = "brightness";
19 const char *blink_ctrl = "trigger";
20 const char *duty_on = "delay_on";
21 const char *duty_off = "delay_off";
22 
23 /*
24  * --------------------------------------------------
25  * Given the dbus path, returns the name of the LED
26  * --------------------------------------------------
27  */
28 char *
get_led_name(const char * dbus_path)29 get_led_name(const char *dbus_path)
30 {
31 	char *led_name = NULL;
32 
33 	/* Get the led name from /org/openbmc/control/led/<name> */
34 	led_name = strrchr(dbus_path, '/');
35 	if(led_name)
36 	{
37 		led_name++;
38 	}
39 
40 	return led_name;
41 }
42 
43 /*
44  * -------------------------------------------------------------------------
45  * Writes the 'on / off / blink' trigger to leds.
46  * -------------------------------------------------------------------------
47  */
48 int
write_to_led(const char * name,const char * ctrl_file,const char * value)49 write_to_led(const char *name, const char *ctrl_file, const char *value)
50 {
51 	/* Generic error reporter. */
52 	int rc = -1;
53 
54 	/* To get /sys/class/leds/<name>/<control file> */
55 	char led_path[128] = {0};
56 
57 	int len = 0;
58 	len = snprintf(led_path, sizeof(led_path),
59 			"/sys/class/leds/%s/%s",name, ctrl_file);
60 	if(len >= sizeof(led_path))
61 	{
62 		fprintf(stderr, "Error. LED path is too long. :[%d]\n",len);
63 		return rc;
64 	}
65 
66 	FILE *fp = fopen(led_path,"w");
67 	if(fp == NULL)
68 	{
69 		fprintf(stderr,"Error:[%s] opening:[%s]\n",strerror(errno),led_path);
70 		return rc;
71 	}
72 
73 	rc = fwrite(value, strlen(value), 1, fp);
74 	if(rc != 1)
75 	{
76 		fprintf(stderr, "Error:[%s] writing to :[%s]\n",strerror(errno),led_path);
77 	}
78 
79 	fclose(fp);
80 
81 	/* When we get here, rc would be what it was from writing to the file */
82 	return (rc == 1) ? 0 : -1;
83 }
84 
85 /*
86  * ----------------------------------------------------------------
87  * Router function for any LED operations that come via dbus
88  *----------------------------------------------------------------
89  */
90 static int
led_function_router(sd_bus_message * msg,void * user_data,sd_bus_error * ret_error)91 led_function_router(sd_bus_message *msg, void *user_data,
92 		sd_bus_error *ret_error)
93 {
94 	/* Generic error reporter. */
95 	int rc = -1;
96 
97 	/* Extract the led name from the full dbus path */
98 	const char *led_path = sd_bus_message_get_path(msg);
99 	if(led_path == NULL)
100 	{
101 		fprintf(stderr, "Error. LED path is empty");
102 		return sd_bus_reply_method_return(msg, "i", rc);
103 	}
104 
105 	char *led_name = get_led_name(led_path);
106 	if(led_name == NULL)
107 	{
108 		fprintf(stderr, "Invalid LED name for path :[%s]\n",led_path);
109 		return sd_bus_reply_method_return(msg, "i", rc);
110 	}
111 
112 	/* Now that we have the LED name, get the Operation. */
113 	const char *led_function = sd_bus_message_get_member(msg);
114 	if(led_function == NULL)
115 	{
116 		fprintf(stderr, "Null LED function specified for : [%s]\n",led_name);
117 		return sd_bus_reply_method_return(msg, "i", rc);
118 	}
119 
120 	/* Route the user action to appropriate handlers. */
121 	if( (strcmp(led_function, "setOn") == 0) ||
122 			(strcmp(led_function, "setOff") == 0))
123 	{
124 		rc = led_stable_state_function(led_name, led_function);
125 		return sd_bus_reply_method_return(msg, "i", rc);
126 	}
127 	else if( (strcmp(led_function, "setBlinkFast") == 0) ||
128 			(strcmp(led_function, "setBlinkSlow") == 0))
129 	{
130 		rc = led_default_blink(led_name, led_function);
131 		return sd_bus_reply_method_return(msg, "i", rc);
132 	}
133 	else if(strcmp(led_function, "BlinkCustom") == 0)
134 	{
135 		rc = led_custom_blink(led_name, msg);
136 		return sd_bus_reply_method_return(msg, "i", rc);
137 	}
138 	else if(strcmp(led_function, "GetLedState") == 0)
139 	{
140 		char value_str[10] = {0};
141 		const char *led_state = NULL;
142 
143 		rc = read_led(led_name, power_ctrl, value_str, sizeof(value_str)-1);
144 		if(rc >= 0)
145 		{
146 			/* LED is active HI */
147 			led_state = strtoul(value_str, NULL, 0) ? "On" : "Off";
148 		}
149 		return sd_bus_reply_method_return(msg, "is", rc, led_state);
150 	}
151 	else
152 	{
153 		fprintf(stderr,"Invalid LED function:[%s]\n",led_function);
154 	}
155 
156 	return sd_bus_reply_method_return(msg, "i", rc);
157 }
158 
159 /*
160  * --------------------------------------------------------------
161  * Turn On or Turn Off the LED
162  * --------------------------------------------------------------
163  */
164 static int
led_stable_state_function(const char * led_name,const char * led_function)165 led_stable_state_function(const char *led_name, const char *led_function)
166 {
167 	/* Generic error reporter. */
168 	int rc = -1;
169 
170 	const char *value = NULL;
171 	if(strcmp(led_function, "setOff") == 0)
172 	{
173 		/* LED active low */
174 		value = "0";
175 	}
176 	else if(strcmp(led_function, "setOn") == 0)
177 	{
178 		value = "255";
179 	}
180 	else
181 	{
182 		fprintf(stderr,"Invalid LED stable state operation:[%s] \n",led_function);
183 		return rc;
184 	}
185 
186 	/*
187 	 * Before doing anything, need to turn off the blinking
188 	 * if there is one in progress by writing 'none' to trigger
189 	 */
190 	rc = write_to_led(led_name, blink_ctrl, "none");
191 	if(rc < 0)
192 	{
193 		fprintf(stderr,"Error disabling blink. Function:[%s]\n", led_function);
194 		return rc;
195 	}
196 
197 	/*
198 	 * Open the brightness file and write corresponding values.
199 	 */
200 	rc = write_to_led(led_name, power_ctrl, value);
201 	if(rc < 0)
202 	{
203 		fprintf(stderr,"Error driving LED. Function:[%s]\n", led_function);
204 	}
205 
206 	return rc;
207 }
208 
209 //-----------------------------------------------------------------------------------
210 // Given the on and off duration, applies the action on the specified LED.
211 //-----------------------------------------------------------------------------------
212 int
blink_led(const char * led_name,const char * on_duration,const char * off_duration)213 blink_led(const char *led_name, const char *on_duration, const char *off_duration)
214 {
215 	/* Generic error reporter */
216 	int rc = -1;
217 
218 	/* Protocol demands that 'timer' be echoed to 'trigger' */
219 	rc = write_to_led(led_name, blink_ctrl, "timer");
220 	if(rc < 0)
221 	{
222 		fprintf(stderr,"Error writing timer to Led:[%s]\n", led_name);
223 		return rc;
224 	}
225 
226 	/*
227 	 * After writing 'timer to 'trigger', 2 new files get generated namely
228 	 *'delay_on' and 'delay_off' which are telling the time duration for a
229 	 * particular LED on and off.
230 	 */
231 	rc = write_to_led(led_name, duty_on, on_duration);
232 	if(rc < 0)
233 	{
234 		fprintf(stderr,"Error writing [%s] to delay_on:[%s]\n",on_duration,led_name);
235 		return rc;
236 	}
237 
238 	rc = write_to_led(led_name, duty_off, off_duration);
239 	if(rc < 0)
240 	{
241 		fprintf(stderr,"Error writing [%s] to delay_off:[%s]\n",off_duration,led_name);
242 	}
243 
244 	return rc;
245 }
246 
247 /*
248  * ----------------------------------------------------
249  * Default blink action on the LED.
250  * ----------------------------------------------------
251  */
252 static int
led_default_blink(const char * led_name,const char * blink_type)253 led_default_blink(const char *led_name, const char *blink_type)
254 {
255 	/* Generic error reporter */
256 	int rc = -1;
257 
258 	/* How long the LED needs to be in on and off state while blinking */
259 	const char *on_duration = NULL;
260 	const char *off_duration = NULL;
261 	if(strcmp(blink_type, "setBlinkSlow") == 0)
262 	{
263 		//*Delay 900 millisec before 'on' and delay 900 millisec before off */
264 		on_duration = "900";
265 		off_duration = "900";
266 	}
267 	else if(strcmp(blink_type, "setBlinkFast") == 0)
268 	{
269 		/* Delay 200 millisec before 'on' and delay 200 millisec before off */
270 		on_duration = "200";
271 		off_duration = "200";
272 	}
273 	else
274 	{
275 		fprintf(stderr,"Invalid blink operation:[%s]\n",blink_type);
276 		return rc;
277 	}
278 
279 	rc = blink_led(led_name, on_duration, off_duration);
280 
281 	return rc;
282 }
283 
284 /*
285  * -------------------------------------------------
286  * Blinks at user defined 'on' and 'off' intervals.
287  * -------------------------------------------------
288  */
289 static int
led_custom_blink(const char * led_name,sd_bus_message * msg)290 led_custom_blink(const char *led_name, sd_bus_message *msg)
291 {
292 	/* Generic error reporter. */
293 	int rc = -1;
294 	int led_len = 0;
295 
296 	/* User supplied 'on' and 'off' duration converted into string */
297 	char on_duration[32] = {0};
298 	char off_duration[32] = {0};
299 
300 	/* User supplied 'on' and 'off' duration */
301 	uint32_t user_input_on = 0;
302 	uint32_t user_input_off = 0;
303 
304 	/* Extract values into 'ss' ( string, string) */
305 	rc = sd_bus_message_read(msg, "uu", &user_input_on, &user_input_off);
306 	if(rc < 0)
307 	{
308 		fprintf(stderr, "Failed to read 'on' and 'off' duration.[%s]\n", strerror(-rc));
309 	}
310 	else
311 	{
312 		/*
313 		 * Converting user supplied integer arguments into string as required by
314 		 * sys interface. The top level REST will make sure that an error is
315 		 * thrown right away on invalid inputs. However, REST is allowing the
316 		 * unsigned decimal and floating numbers but when its received here, its
317 		 * received as decimal so no input validation needed.
318 		 */
319 		led_len = snprintf(on_duration, sizeof(on_duration),
320 				"%d",user_input_on);
321 		if(led_len >= sizeof(on_duration))
322 		{
323 			fprintf(stderr, "Error. Blink ON duration is too long. :[%d]\n",led_len);
324 			return rc;
325 		}
326 
327 		led_len = snprintf(off_duration, sizeof(off_duration),
328 				"%d",user_input_off);
329 		if(led_len >= sizeof(off_duration))
330 		{
331 			fprintf(stderr, "Error. Blink OFF duration is too long. :[%d]\n",led_len);
332 			return rc;
333 		}
334 
335 		/* We are good here.*/
336 		rc = blink_led(led_name, on_duration, off_duration);
337 	}
338 	return rc;
339 }
340 
341 /*
342  * ---------------------------------------------------------------
343  * Gets the current value of passed in LED file
344  * Mainly used for reading 'brightness'
345  * NOTE : It is the responsibility of the caller to allocate
346  * sufficient space for buffer. This will read up to user supplied
347  * size -or- entire contents of file whichever is smaller
348  * ----------------------------------------------------------------
349  */
350 static int
read_led(const char * name,const char * ctrl_file,void * value,const size_t len)351 read_led(const char *name, const char *ctrl_file,
352 		void *value, const size_t len)
353 {
354 	/* Generic error reporter. */
355 	int rc = -1;
356 	int count = 0;
357 
358 	if(value == NULL || len <= 0)
359 	{
360 		fprintf(stderr, "Invalid buffer passed to LED read\n");
361 		return rc;
362 	}
363 
364 	/* To get /sys/class/leds/<name>/<control file> */
365 	char led_path[128] = {0};
366 
367 	int led_len = 0;
368 	led_len = snprintf(led_path, sizeof(led_path),
369 			"/sys/class/leds/%s/%s",name, ctrl_file);
370 	if(led_len >= sizeof(led_path))
371 	{
372 		fprintf(stderr, "Error. LED path is too long. :[%d]\n",led_len);
373 		return rc;
374 	}
375 
376 	FILE *fp = fopen(led_path,"rb");
377 	if(fp == NULL)
378 	{
379 		fprintf(stderr,"Error:[%s] opening:[%s]\n",strerror(errno),led_path);
380 		return rc;
381 	}
382 
383 	char *sysfs_value = (char *)value;
384 	while(!feof(fp) && (count < len))
385 	{
386 		sysfs_value[count++] = fgetc(fp);
387 	}
388 
389 	fclose(fp);
390 	return 0;
391 }
392 
393 /*
394  * -----------------------------------------------
395  * Dbus Services offered by this LED controller
396  * -----------------------------------------------
397  */
398 static const sd_bus_vtable led_control_vtable[] =
399 {
400 	SD_BUS_VTABLE_START(0),
401 	SD_BUS_METHOD("setOn", "", "i", &led_function_router, SD_BUS_VTABLE_UNPRIVILEGED),
402 	SD_BUS_METHOD("setOff", "", "i", &led_function_router, SD_BUS_VTABLE_UNPRIVILEGED),
403 	SD_BUS_METHOD("setBlinkFast", "", "i", &led_function_router, SD_BUS_VTABLE_UNPRIVILEGED),
404 	SD_BUS_METHOD("setBlinkSlow", "", "i", &led_function_router, SD_BUS_VTABLE_UNPRIVILEGED),
405 	SD_BUS_METHOD("GetLedState", "", "is", &led_function_router, SD_BUS_VTABLE_UNPRIVILEGED),
406 	SD_BUS_METHOD("BlinkCustom", "uu", "i", &led_function_router, SD_BUS_VTABLE_UNPRIVILEGED),
407 	SD_BUS_VTABLE_END,
408 };
409 
410 /*
411  * ---------------------------------------------
412  * Interested in all files except standard ones
413  * ---------------------------------------------
414  */
415 int
led_select(const struct dirent * entry)416 led_select(const struct dirent *entry)
417 {
418 	if( (strcmp(entry->d_name, ".") == 0) ||
419 			(strcmp(entry->d_name, "..") == 0))
420 	{
421 		return 0;
422 	}
423 	return 1;
424 }
425 
426 /*
427  * ------------------------------------------------
428  * Called as part of setting up skeleton services.
429  * -----------------------------------------------
430  */
431 int
start_led_services()432 start_led_services()
433 {
434 	static const char *led_dbus_root = "/org/openbmc/control/led";
435 
436 	/* Generic error reporter. */
437 	int rc = -1;
438 	int num_leds = 0;
439 	int count_leds = 0;
440 
441 	/* Bus and slot where we are offering the LED dbus service. */
442 	sd_bus *bus_type = NULL;
443 	sd_bus_slot *led_slot = NULL;
444 
445 	/* For walking '/sys/class/leds/' looking for names of LED.*/
446 	struct dirent **led_list;
447 
448 	/* Get a hook onto system bus. */
449 	rc = sd_bus_open_system(&bus_type);
450 	if(rc < 0)
451 	{
452 		fprintf(stderr,"Error opening system bus.\n");
453 		return rc;
454 	}
455 
456 	count_leds = num_leds = scandir("/sys/class/leds/",
457 			&led_list, led_select, alphasort);
458 	if(num_leds <= 0)
459 	{
460 		fprintf(stderr,"No LEDs present in the system\n");
461 
462 		sd_bus_slot_unref(led_slot);
463 		sd_bus_unref(bus_type);
464 		return rc;
465 	}
466 
467 	/* Install a freedesktop object manager */
468 	rc = sd_bus_add_object_manager(bus_type, NULL, led_dbus_root);
469 	if(rc < 0) {
470 		fprintf(stderr, "Failed to add object to dbus: %s\n",
471 				strerror(-rc));
472 
473 		sd_bus_slot_unref(led_slot);
474 		sd_bus_unref(bus_type);
475 		return rc;
476 	}
477 
478 	/* Fully qualified Dbus object for a particular LED */
479 	char led_object[128] = {0};
480 	int len = 0;
481 
482 	/* For each led present, announce the service on dbus. */
483 	while(num_leds--)
484 	{
485 		memset(led_object, 0x0, sizeof(led_object));
486 
487 		len = snprintf(led_object, sizeof(led_object), "%s%s%s",
488 				led_dbus_root, "/", led_list[num_leds]->d_name);
489 
490 		if(len >= sizeof(led_object))
491 		{
492 			fprintf(stderr, "Error. LED object is too long:[%d]\n",len);
493 			rc = -1;
494 			break;
495 		}
496 
497 		/* Install the object */
498 		rc = sd_bus_add_object_vtable(bus_type,
499 				&led_slot,
500 				led_object, /* object path */
501 				"org.openbmc.Led", /* interface name */
502 				led_control_vtable,
503 				NULL);
504 
505 		if(rc < 0)
506 		{
507 			fprintf(stderr, "Failed to add object to dbus: %s\n", strerror(-rc));
508 			break;
509 		}
510 
511 		rc = sd_bus_emit_object_added(bus_type, led_object);
512 
513 		if(rc < 0)
514 		{
515 			fprintf(stderr, "Failed to emit InterfacesAdded "
516 					"signal: %s\n", strerror(-rc));
517 			break;
518 		}
519 	}
520 
521 	/* Done with all registration. */
522 	while(count_leds > 0)
523 	{
524 		free(led_list[--count_leds]);
525 	}
526 	free(led_list);
527 
528 	/* If we had success in adding the providers, request for a bus name. */
529 	if(rc >= 0)
530 	{
531 		/* Take one in OpenBmc */
532 		rc = sd_bus_request_name(bus_type, "org.openbmc.control.led", 0);
533 		if(rc < 0)
534 		{
535 			fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-rc));
536 		}
537 		else
538 		{
539 			for(;;)
540 			{
541 				/* Process requests */
542 				rc = sd_bus_process(bus_type, NULL);
543 				if(rc < 0)
544 				{
545 					fprintf(stderr, "Failed to process bus: %s\n", strerror(-rc));
546 					break;
547 				}
548 				if(rc > 0)
549 				{
550 					continue;
551 				}
552 
553 				rc = sd_bus_wait(bus_type, (uint64_t) - 1);
554 				if(rc < 0)
555 				{
556 					fprintf(stderr, "Failed to wait on bus: %s\n", strerror(-rc));
557 					break;
558 				}
559 			}
560 		}
561 	}
562 	sd_bus_slot_unref(led_slot);
563 	sd_bus_unref(bus_type);
564 
565 	return rc;
566 }
567 
568 int
main(void)569 main(void)
570 {
571 	int rc = 0;
572 
573 	/* This call is not supposed to return. If it does, then an error */
574 	rc = start_led_services();
575 	if(rc < 0)
576 	{
577 		fprintf(stderr, "Error starting LED Services. Exiting");
578 	}
579 
580 	return rc;
581 }
582