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