1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright (c) 2001 by Sun Microsystems, Inc.
24 * All rights reserved.
25 */
26
27 #pragma ident "%Z%%M% %I% %E% SMI"
28
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <errno.h>
35 #include <libintl.h>
36 #include <syslog.h>
37 #include "fcal_leds.h"
38
39 /*
40 * function templates for static functions
41 */
42 static token_t get_token(char **pptr, int lineNo, actfun_t *fun);
43 static int get_cstr(str *p_str, cstr *p_cstr_res);
44 static int get_assert(str *p_str, int *assert);
45 static int get_pnz(str *p_str, int *pnz);
46 static int get_mask(str *p_str, int n_disks, int *p_intarray);
47
48 /*
49 * Templates for functions which may be returned by get_token().
50 * These functions are all called with a pointer to the position just
51 * beyond the token being actioned.
52 */
53 static int act_version(str *p_str, led_dtls_t *dtls);
54 static int act_leds_board(str *p_str, led_dtls_t *dtls);
55 static int act_status_board(str *p_str, led_dtls_t *dtls);
56 static int act_disk_driver(str *p_str, led_dtls_t *dtls);
57 static int act_n_disks(str *p_str, led_dtls_t *dtls);
58 static int act_asrt_pres(str *p_str, led_dtls_t *dtls);
59 static int act_asrt_fault(str *p_str, led_dtls_t *dtls);
60 static int act_led_on(str *p_str, led_dtls_t *dtls);
61 static int act_disk_present(str *p_str, led_dtls_t *dtls);
62 static int act_disk_fault(str *p_str, led_dtls_t *dtls);
63 static int act_led_id(str *p_str, led_dtls_t *dtls);
64 static int act_slow_poll(str *p_str, led_dtls_t *dtls);
65 static int act_fast_poll(str *p_str, led_dtls_t *dtls);
66 static int act_relax_interval(str *p_str, led_dtls_t *dtls);
67 static int act_test_interval(str *p_str, led_dtls_t *dtls);
68 static int act_disk_parent(str *p_str, led_dtls_t *dtls);
69 static int act_unit_parent(str *p_str, led_dtls_t *dtls);
70 static int act_led_nodes(str *p_str, led_dtls_t *dtls);
71
72 /*
73 * The table below is used to lookup .conf file keywords to yield either
74 * a corresponding enum or a function to process the keyword.
75 */
76 static lookup_t table[] = {
77 { FCAL_VERSION, "VERSION", act_version },
78 { FCAL_REMOK_LED, "REMOK", NULL },
79 { FCAL_FAULT_LED, "FAULT", NULL },
80 { FCAL_READY_LED, "READY", NULL },
81 { FCAL_LEDS_BOARD, "FCAL-LEDS", act_leds_board },
82 { FCAL_STATUS_BOARD, "FCAL-STATUS", act_status_board },
83 { FCAL_DISK_DRIVER, "FCAL-DISK-DRIVER", act_disk_driver },
84 { FCAL_N_DISKS, "N-DISKS", act_n_disks },
85 { FCAL_ASSERT_PRESENT, "ASSERT-PRESENT", act_asrt_pres },
86 { FCAL_ASSERT_FAULT, "ASSERT-FAULT", act_asrt_fault },
87 { FCAL_LED_ON, "LED-ON", act_led_on },
88 { FCAL_DISK_PRESENT, "DISK-PRESENT", act_disk_present },
89 { FCAL_DISK_FAULT, "DISK-FAULT", act_disk_fault },
90 { FCAL_LED_ID, "LED", act_led_id },
91 { FCAL_SLOW_POLL, "SLOW-POLL", act_slow_poll },
92 { FCAL_FAST_POLL, "FAST-POLL", act_fast_poll },
93 { FCAL_RELAX_INTERVAL, "RELAX-INTERVAL", act_relax_interval },
94 { FCAL_TEST_INTERVAL, "LED-TEST-INTERVAL", act_test_interval },
95 { FCAL_DISK_PARENT, "FCAL-DISK-PARENT", act_disk_parent },
96 { FCAL_UNIT_PARENT, "DISK-UNIT-PARENT", act_unit_parent },
97 { FCAL_LED_NODES, "DISK-LED-NODES", act_led_nodes }
98 };
99
100 /*
101 * length of longest string in table (with space for null terminator)
102 */
103 #define MAX_FCAL_TOKEN_LEN 18
104
105 static const int tab_len = (sizeof (table))/sizeof (table[0]);
106
107 /*
108 * get_token
109 * Parses the current line of data and returns the next token.
110 * If there are no significant characters in the line, NO_TOKEN is returned.
111 * If a syntax error is encountered, TOKEN_ERROR is returned.
112 * Pointer to position in current line is updated to point to the terminator
113 * of the token, unless TOKEN_ERROR is returned.
114 */
115 static token_t
get_token(char ** pptr,int lineNo,actfun_t * fun)116 get_token(
117 char **pptr, /* pointer to pointer to position in current line */
118 /* *ptr is updated by the function */
119 int lineNo, /* current line number, used for syslog. If set to */
120 /* zero, syslogging is supressed */
121 actfun_t *fun) /* pointer to function variable to receive action */
122 /* pointer for the token found. NULL may be returned */
123 {
124 char *ptr;
125 char *token_start;
126 int toklen;
127 int i;
128 int ch;
129
130 *fun = NULL;
131 ptr = *pptr;
132
133 /* strip leading white space */
134 do {
135 ch = (unsigned)(*ptr++);
136
137 } while (isspace(ch));
138
139 if ((ch == '\0') || (ch == '#')) {
140 *pptr = ptr;
141 return (NO_TOKEN); /* empty line or comment */
142 }
143
144 if (!isalpha(ch)) {
145 if (lineNo != 0)
146 SYSLOG(LOG_ERR, EM_NONALF_TOK, lineNo);
147 return (TOKEN_ERROR);
148 }
149 token_start = ptr - 1;
150 toklen = strcspn(token_start, ",: \t");
151 *pptr = token_start + toklen;
152 /*
153 * got token, now look it up
154 */
155 for (i = 0; i < tab_len; i++) {
156 if ((strncasecmp(token_start, table[i].tok_str,
157 toklen) == 0) && (table[i].tok_str[toklen] == '\0')) {
158 *fun = table[i].action;
159 return (table[i].tok);
160 }
161 }
162 if (lineNo != 0)
163 SYSLOG(LOG_ERR, EM_UNKN_TOK, lineNo);
164 return (TOKEN_ERROR);
165 }
166
167 static int
act_version(str * p_str,led_dtls_t * dtls)168 act_version(str *p_str, led_dtls_t *dtls)
169 {
170 dtls->ver_maj = strtoul(*p_str, p_str, 0);
171 if (*(*p_str)++ != '.') {
172 SYSLOG(LOG_ERR, EM_VER_FRMT);
173 return (-1);
174 }
175 dtls->ver_min = strtoul(*p_str, p_str, 0);
176 if ((**p_str != '\0') && !isspace(**p_str)) {
177 SYSLOG(LOG_ERR, EM_VER_FRMT);
178 return (-1);
179 }
180 if ((dtls->ver_maj != 1) || (dtls->ver_min != 0)) {
181 SYSLOG(LOG_ERR, EM_WRNGVER, dtls->ver_maj, dtls->ver_min);
182 return (-1);
183 }
184 return (0);
185 }
186
187 /*
188 * get space to hold white-space terminated string at *p_str
189 * advance *p_str to point to terminator
190 * return copy of string, null terminated
191 */
192 static int
get_cstr(str * p_str,cstr * p_cstr_res)193 get_cstr(str *p_str, cstr *p_cstr_res)
194 {
195 int ch;
196 int len;
197 char *ptr;
198
199 while (isspace(**p_str))
200 (*p_str)++;
201 ptr = *p_str;
202
203 do {
204 ch = *++ptr;
205 } while ((ch != '\0') && (!isspace(ch)));
206
207 len = ptr - *p_str;
208 if (*p_cstr_res != NULL)
209 free((void *)(*p_cstr_res));
210 ptr = malloc(len + 1);
211 *p_cstr_res = ptr;
212 if (ptr == NULL) {
213 return (ENOMEM);
214 }
215 (void) memcpy(ptr, *p_str, len);
216 ptr[len] = '\0';
217 (*p_str) += len;
218 return (0);
219 }
220
221 static int
act_leds_board(str * p_str,led_dtls_t * dtls)222 act_leds_board(str *p_str, led_dtls_t *dtls)
223 {
224 int res = get_cstr(p_str, &dtls->fcal_leds);
225 if (res == 0) {
226 if (dtls->fcal_leds[0] != '/') {
227 free((void *)dtls->fcal_leds);
228 dtls->fcal_leds = NULL;
229 SYSLOG(LOG_ERR, EM_REL_PATH);
230 return (-1);
231 }
232 }
233 return (res);
234 }
235
236 static int
act_status_board(str * p_str,led_dtls_t * dtls)237 act_status_board(str *p_str, led_dtls_t *dtls)
238 {
239 int res = get_cstr(p_str, &dtls->fcal_status);
240 if (res == 0) {
241 if (dtls->fcal_status[0] != '/') {
242 free((void *)dtls->fcal_status);
243 dtls->fcal_status = NULL;
244 SYSLOG(LOG_ERR, EM_REL_PATH);
245 return (-1);
246 }
247 }
248 return (res);
249 }
250
251 static int
act_disk_driver(str * p_str,led_dtls_t * dtls)252 act_disk_driver(str *p_str, led_dtls_t *dtls)
253 {
254 return (get_cstr(p_str, &dtls->fcal_driver));
255 }
256
257 static int
act_disk_parent(str * p_str,led_dtls_t * dtls)258 act_disk_parent(str *p_str, led_dtls_t *dtls)
259 {
260 return (get_cstr(p_str, &dtls->fcal_disk_parent));
261 }
262
263 static int
act_unit_parent(str * p_str,led_dtls_t * dtls)264 act_unit_parent(str *p_str, led_dtls_t *dtls)
265 {
266 return (get_cstr(p_str, &dtls->disk_unit_parent));
267 }
268
269 static int
act_led_nodes(str * p_str,led_dtls_t * dtls)270 act_led_nodes(str *p_str, led_dtls_t *dtls)
271 {
272 return (get_cstr(p_str, &dtls->disk_led_nodes));
273 }
274
275 /*
276 * A number of fields in the led_dtls_t structure have per-disk copies.
277 * This action routine creates the space for all such fields.
278 * Following any failure, an error is returned and the calling routine
279 * must handle the fact that only a subset of these fields are populated.
280 * In practice, this function is only called by get_token() on behalf of
281 * fc_led_parse(). fc_led_parse calls free_led_dtls() after any error.
282 */
283 static int
act_n_disks(str * p_str,led_dtls_t * dtls)284 act_n_disks(str *p_str, led_dtls_t *dtls)
285 {
286 int i;
287
288 if (dtls->n_disks != 0) {
289 SYSLOG(LOG_ERR, EM_NDISKS_DBL);
290 return (-1);
291 }
292 dtls->n_disks = strtoul(*p_str, p_str, 0);
293 if ((**p_str != '\0') && !isspace(**p_str)) {
294 SYSLOG(LOG_ERR, EM_NUM_TERM);
295 return (-1);
296 }
297 if (dtls->n_disks < 1) {
298 SYSLOG(LOG_ERR, EM_NO_DISKS);
299 return (-1);
300 }
301 dtls->presence = calloc(dtls->n_disks, sizeof (int));
302 if (dtls->presence == NULL)
303 return (ENOMEM);
304 dtls->faults = calloc(dtls->n_disks, sizeof (int));
305 if (dtls->faults == NULL)
306 return (ENOMEM);
307 dtls->disk_detected = calloc(dtls->n_disks, sizeof (int));
308 if (dtls->disk_detected == NULL)
309 return (ENOMEM);
310 dtls->disk_ready = calloc(dtls->n_disks, sizeof (int));
311 if (dtls->disk_ready == NULL)
312 return (ENOMEM);
313 dtls->disk_prev = calloc(dtls->n_disks, sizeof (int));
314 if (dtls->disk_prev == NULL)
315 return (ENOMEM);
316 dtls->led_test_end = calloc(dtls->n_disks, sizeof (int));
317 if (dtls->led_test_end == NULL)
318 return (ENOMEM);
319 dtls->picl_retry = calloc(dtls->n_disks, sizeof (boolean_t));
320 if (dtls->picl_retry == NULL)
321 return (ENOMEM);
322 dtls->disk_port = calloc(dtls->n_disks, sizeof (char *));
323 if (dtls->disk_port == NULL) {
324 return (ENOMEM);
325 }
326 for (i = 0; i < FCAL_LED_CNT; i++) {
327 dtls->led_addr[i] = calloc(dtls->n_disks, sizeof (int));
328 if (dtls->led_addr[i] == NULL)
329 return (ENOMEM);
330 dtls->led_state[i] = calloc(dtls->n_disks,
331 sizeof (led_state_t));
332 if (dtls->led_state[i] == NULL)
333 return (ENOMEM);
334 }
335 return (0);
336 }
337
338 static int
get_assert(str * p_str,int * assert)339 get_assert(str *p_str, int *assert)
340 {
341 int i = strtoul(*p_str, p_str, 0);
342 if ((**p_str != '\0') && !isspace(**p_str)) {
343 SYSLOG(LOG_ERR, EM_NUM_TERM);
344 return (-1);
345 }
346 if ((i != 0) && (i != 1)) {
347 SYSLOG(LOG_ERR, EM_LOGIC_LVL);
348 return (-1);
349 }
350 *assert = i;
351 return (0);
352 }
353
354 static int
get_pnz(str * p_str,int * pnz)355 get_pnz(str *p_str, int *pnz)
356 {
357 int i = strtoul(*p_str, p_str, 0);
358 if ((**p_str != '\0') && !isspace(**p_str)) {
359 SYSLOG(LOG_ERR, EM_NUM_TERM);
360 return (-1);
361 }
362 if (i < 1) {
363 SYSLOG(LOG_ERR, EM_NOTPOS);
364 return (-1);
365 }
366 *pnz = i;
367 return (0);
368 }
369
370 static int
act_asrt_pres(str * p_str,led_dtls_t * dtls)371 act_asrt_pres(str *p_str, led_dtls_t *dtls)
372 {
373 return (get_assert(p_str, &dtls->assert_presence));
374 }
375
376 static int
act_asrt_fault(str * p_str,led_dtls_t * dtls)377 act_asrt_fault(str *p_str, led_dtls_t *dtls)
378 {
379 return (get_assert(p_str, &dtls->assert_fault));
380 }
381
382 static int
act_led_on(str * p_str,led_dtls_t * dtls)383 act_led_on(str *p_str, led_dtls_t *dtls)
384 {
385 return (get_assert(p_str, &dtls->assert_led_on));
386 }
387
388 static int
get_mask(str * p_str,int n_disks,int * p_intarray)389 get_mask(str *p_str, int n_disks, int *p_intarray)
390 {
391 int i;
392 int j = strtoul(*p_str, p_str, 0);
393 if (*(*p_str)++ != ',') {
394 SYSLOG(LOG_ERR, EM_NUM_TERM);
395 return (-1);
396 }
397 if ((j < 0) || (j > n_disks)) {
398 SYSLOG(LOG_ERR, EM_DISK_RANGE);
399 return (-1);
400 }
401 i = strtoul(*p_str, p_str, 0);
402 if ((**p_str != '\0') && !isspace(**p_str)) {
403 SYSLOG(LOG_ERR, EM_NUM_TERM);
404 return (-1);
405 }
406 p_intarray[j] = i;
407 return (0);
408 }
409
410 static int
act_disk_present(str * p_str,led_dtls_t * dtls)411 act_disk_present(str *p_str, led_dtls_t *dtls)
412 {
413 return (get_mask(p_str, dtls->n_disks, dtls->presence));
414 }
415
416 static int
act_disk_fault(str * p_str,led_dtls_t * dtls)417 act_disk_fault(str *p_str, led_dtls_t *dtls)
418 {
419 return (get_mask(p_str, dtls->n_disks, dtls->faults));
420 }
421
422 static int
act_led_id(str * p_str,led_dtls_t * dtls)423 act_led_id(str *p_str, led_dtls_t *dtls)
424 {
425 token_t tok;
426 actfun_t action;
427 int i;
428 int j = strtoul(*p_str, p_str, 0);
429
430 if (*(*p_str)++ != ',') {
431 SYSLOG(LOG_ERR, EM_NUM_TERM);
432 return (-1);
433 }
434 if ((j < 0) || (j >= dtls->n_disks)) {
435 SYSLOG(LOG_ERR, EM_DISK_RANGE);
436 return (-1);
437 }
438 tok = get_token(p_str, 0, &action);
439 if ((tok <= LED_PROPS_START) || (tok >= LED_PROPS_END)) {
440 SYSLOG(LOG_ERR, EM_NO_LED_PROP);
441 return (-1);
442 }
443 if (*(*p_str)++ != ',') {
444 SYSLOG(LOG_ERR, EM_PROP_TERM);
445 return (-1);
446 }
447 i = strtoul(*p_str, p_str, 0);
448 if ((**p_str != '\0') && !isspace(**p_str)) {
449 SYSLOG(LOG_ERR, EM_NUM_TERM);
450 return (-1);
451 }
452 dtls->led_addr[tok - FCAL_REMOK_LED][j] = i;
453 return (0);
454 }
455
456 static int
act_slow_poll(str * p_str,led_dtls_t * dtls)457 act_slow_poll(str *p_str, led_dtls_t *dtls)
458 {
459 return (get_pnz(p_str, &dtls->slow_poll_ticks));
460 }
461
462 static int
act_fast_poll(str * p_str,led_dtls_t * dtls)463 act_fast_poll(str *p_str, led_dtls_t *dtls)
464 {
465 return (get_pnz(p_str, &dtls->fast_poll));
466 }
467
468 static int
act_relax_interval(str * p_str,led_dtls_t * dtls)469 act_relax_interval(str *p_str, led_dtls_t *dtls)
470 {
471 return (get_pnz(p_str, &dtls->relax_time_ticks));
472 }
473
474 static int
act_test_interval(str * p_str,led_dtls_t * dtls)475 act_test_interval(str *p_str, led_dtls_t *dtls)
476 {
477 return (get_pnz(p_str, &dtls->led_test_time));
478 }
479
480 /*
481 * Create a led_dtls_t structure
482 * Parse configuration file and populate the led_dtls_t
483 * In the event of an error, free the structure and return an error
484 */
485 int
fc_led_parse(FILE * fp,led_dtls_t ** p_dtls)486 fc_led_parse(FILE *fp, led_dtls_t **p_dtls)
487 {
488 int lineNo = 0;
489 int err = 0;
490 char linebuf[160];
491 char *ptr;
492 led_dtls_t *dtls = calloc(1, sizeof (led_dtls_t));
493 actfun_t action;
494 token_t tok;
495
496 *p_dtls = dtls;
497 if (dtls == NULL) {
498 return (ENOMEM);
499 }
500 dtls->ver_min = -1; /* mark as version unknown */
501
502 while ((ptr = fgets(linebuf, sizeof (linebuf), fp)) != NULL) {
503 lineNo++;
504 tok = get_token(&ptr, lineNo, &action);
505 if (tok == NO_TOKEN)
506 continue;
507 if (tok == TOKEN_ERROR) {
508 err = -1;
509 break;
510 }
511 if (tok == FCAL_VERSION) {
512 if ((err = (*action)(&ptr, dtls)) != 0)
513 break;
514 else
515 continue;
516 }
517 if (dtls->ver_min < 0) {
518 SYSLOG(LOG_ERR, EM_NOVERS);
519 err = -1;
520 break;
521 }
522 if (tok <= LINE_DEFS) {
523 SYSLOG(LOG_ERR, EM_INVAL_TOK, lineNo);
524 err = -1;
525 break;
526 }
527 if (*ptr++ != ':') {
528 SYSLOG(LOG_ERR, EM_NOCOLON, lineNo);
529 err = -1;
530 break;
531 }
532 if ((err = (*action)(&ptr, dtls)) != 0) {
533 SYSLOG(LOG_ERR, EM_ERRLINE, lineNo);
534 break;
535 }
536 else
537 continue;
538 }
539
540 if (err == 0) {
541 err = -1; /* just in case */
542 if (dtls->ver_min < 0) {
543 SYSLOG(LOG_ERR, EM_NOVERS);
544 } else if (dtls->n_disks == 0) {
545 SYSLOG(LOG_ERR, EM_NO_DISKS);
546 } else if (dtls->fcal_leds == NULL) {
547 SYSLOG(LOG_ERR, EM_STR_NOT_SET, "fcal-leds");
548 } else if (dtls->fcal_status == NULL) {
549 SYSLOG(LOG_ERR, EM_STR_NOT_SET, "fcal-status");
550 } else if (dtls->fcal_driver == NULL) {
551 SYSLOG(LOG_ERR, EM_STR_NOT_SET, "fcal-driver");
552 } else
553 err = 0;
554 }
555
556 if (err != 0) {
557 /*
558 * clean up after error detected
559 */
560 free_led_dtls(dtls);
561 *p_dtls = NULL;
562 return (err);
563 }
564
565 /*
566 * set any unset timers to default time
567 */
568 if (dtls->slow_poll_ticks == 0)
569 dtls->slow_poll_ticks = DFLT_SLOW_POLL;
570 if (dtls->fast_poll == 0)
571 dtls->fast_poll = DFLT_FAST_POLL;
572 if (dtls->relax_time_ticks == 0)
573 dtls->relax_time_ticks = DFLT_RELAX_TIME;
574 if (dtls->led_test_time == 0)
575 dtls->led_test_time = DFLT_TEST_TIME;
576
577 /*
578 * set polling flag to avoid a start-up glitch
579 * it will be cleared again if the poll thread fails
580 */
581 dtls->polling = B_TRUE;
582
583 /*
584 * convert derived timers to multiples of fast poll time
585 */
586 dtls->slow_poll_ticks += dtls->fast_poll - 1; /* for round up */
587 dtls->slow_poll_ticks /= dtls->fast_poll;
588 dtls->relax_time_ticks += dtls->fast_poll - 1;
589 dtls->relax_time_ticks /= dtls->fast_poll;
590 dtls->led_test_time += dtls->fast_poll - 1;
591 dtls->led_test_time /= dtls->fast_poll;
592 return (0);
593 }
594
595 void
free_led_dtls(led_dtls_t * dtls)596 free_led_dtls(led_dtls_t *dtls)
597 {
598 int i;
599
600 if (dtls == NULL)
601 return;
602 if (dtls->fcal_leds != NULL)
603 free((void *)dtls->fcal_leds);
604 if (dtls->fcal_status != NULL)
605 free((void *)dtls->fcal_status);
606 if (dtls->fcal_driver != NULL)
607 free((void *)dtls->fcal_driver);
608 if (dtls->presence != NULL)
609 free((void *)dtls->presence);
610 if (dtls->faults != NULL)
611 free((void *)dtls->faults);
612 if (dtls->disk_detected != NULL)
613 free((void *)dtls->disk_detected);
614 if (dtls->disk_ready != NULL)
615 free((void *)dtls->disk_ready);
616 if (dtls->disk_prev != NULL)
617 free((void *)dtls->disk_prev);
618 if (dtls->led_test_end != NULL)
619 free((void *)dtls->led_test_end);
620 if (dtls->picl_retry != NULL)
621 free((void *)dtls->picl_retry);
622 if (dtls->disk_port != NULL) {
623 for (i = 0; i < dtls->n_disks; i++) {
624 if (dtls->disk_port[i] != NULL)
625 free(dtls->disk_port[i]);
626 }
627 free(dtls->disk_port);
628 }
629 for (i = 0; i < FCAL_LED_CNT; i++) {
630 if (dtls->led_addr[i] != NULL)
631 free((void *)dtls->led_addr[i]);
632 if (dtls->led_state[i] != NULL)
633 free((void *)dtls->led_state[i]);
634 }
635
636 free(dtls);
637 }
638