1 /*
2 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27 #include <sys/cdefs.h>
28
29 #include <stand.h>
30 #include <string.h>
31
32 #include "bootstrap.h"
33 /*
34 * Core console support
35 */
36
37 static int cons_set(struct env_var *ev, int flags, const void *value);
38 static int cons_find(const char *name);
39 static int cons_check(const char *string);
40 static int cons_change(const char *string, char **);
41 static int twiddle_set(struct env_var *ev, int flags, const void *value);
42
43 static int last_input = -1; /* input device index */
44
45 /*
46 * With multiple active console devices, return index of last input
47 * device, so we can set up os_console variable to denote console
48 * device for kernel.
49 *
50 * Please note, this feature can not really work with UEFI, because
51 * efi console input is returned from any device listed in ConIn,
52 * and we have no way to check which device from ConIn actually was
53 * generating input.
54 */
55 int
cons_inputdev(void)56 cons_inputdev(void)
57 {
58 int cons;
59 int flags = C_PRESENTIN | C_ACTIVEIN;
60 int active = 0;
61
62 for (cons = 0; consoles[cons] != NULL; cons++)
63 if ((consoles[cons]->c_flags & flags) == flags)
64 active++;
65
66 /* With just one active console, we will not set os_console */
67 if (active == 1)
68 return (-1);
69
70 return (last_input);
71 }
72
73 /*
74 * Return number of array slots.
75 */
76 uint_t
cons_array_size(void)77 cons_array_size(void)
78 {
79 uint_t n;
80
81 if (consoles == NULL)
82 return (0);
83
84 for (n = 0; consoles[n] != NULL; n++)
85 ;
86 return (n + 1);
87 }
88
89 static void
cons_add_dev(struct console * dev)90 cons_add_dev(struct console *dev)
91 {
92 uint_t c = cons_array_size();
93 uint_t n = 1;
94 struct console **tmp;
95
96 if (c == 0)
97 n++;
98 tmp = realloc(consoles, (c + n) * sizeof (struct console *));
99 if (tmp == NULL)
100 return;
101 if (c > 0)
102 c--;
103 consoles = tmp;
104 consoles[c] = dev;
105 consoles[c + 1] = NULL;
106 }
107
108 /*
109 * Detect possible console(s) to use. If preferred console(s) have been
110 * specified, mark them as active. Else, mark the first probed console
111 * as active. Also create the console variable.
112 */
113 void
cons_probe(void)114 cons_probe(void)
115 {
116 int cons;
117 int active;
118 char *prefconsole, *list, *console;
119
120 /* Build list of consoles */
121 consoles = NULL;
122 for (cons = 0;; cons++) {
123 if (ct_list[cons].ct_dev != NULL) {
124 cons_add_dev(ct_list[cons].ct_dev);
125 continue;
126 }
127 if (ct_list[cons].ct_init != NULL) {
128 ct_list[cons].ct_init();
129 continue;
130 }
131 break;
132 }
133
134 /* We want a callback to install the new value when this var changes. */
135 (void) env_setenv("twiddle_divisor", EV_VOLATILE, "16", twiddle_set,
136 env_nounset);
137
138 /* Do all console probes */
139 for (cons = 0; consoles[cons] != NULL; cons++) {
140 consoles[cons]->c_flags = 0;
141 consoles[cons]->c_probe(consoles[cons]);
142 }
143 /* Now find the first working one */
144 active = -1;
145 for (cons = 0; consoles[cons] != NULL; cons++) {
146 if (consoles[cons]->c_flags == (C_PRESENTIN | C_PRESENTOUT)) {
147 active = cons;
148 break;
149 }
150 }
151
152 /* Force a console even if all probes failed */
153 if (active == -1)
154 active = 0;
155
156 /* Check to see if a console preference has already been registered */
157 list = NULL;
158 prefconsole = getenv("console");
159 if (prefconsole != NULL)
160 prefconsole = strdup(prefconsole);
161 if (prefconsole == NULL)
162 prefconsole = strdup(consoles[active]->c_name);
163
164 /*
165 * unset "console", we need to create one with callbacks.
166 */
167 unsetenv("console");
168 cons_change(prefconsole, &list);
169
170 printf("Consoles: ");
171 for (cons = 0; consoles[cons] != NULL; cons++)
172 if (consoles[cons]->c_flags & (C_ACTIVEIN | C_ACTIVEOUT))
173 printf("%s ", consoles[cons]->c_desc);
174 printf("\n");
175
176 if (list != NULL)
177 console = list;
178 else
179 console = prefconsole;
180
181 (void) env_setenv("console", EV_VOLATILE, console, cons_set,
182 env_nounset);
183
184 free(prefconsole);
185 free(list);
186 }
187
188 void
cons_mode(int raw)189 cons_mode(int raw)
190 {
191 int cons;
192
193 for (cons = 0; consoles[cons] != NULL; cons++) {
194 if (raw == 0)
195 consoles[cons]->c_flags &= ~C_MODERAW;
196 else
197 consoles[cons]->c_flags |= C_MODERAW;
198 }
199 }
200
201 int
getchar(void)202 getchar(void)
203 {
204 int cons;
205 int flags = C_PRESENTIN | C_ACTIVEIN;
206 int rv;
207
208 /*
209 * Loop forever polling all active consoles. Somewhat strangely,
210 * this code expects all ->c_in() implementations to effectively do an
211 * ischar() check first, returning -1 if there's not a char ready.
212 */
213 for (;;) {
214 for (cons = 0; consoles[cons] != NULL; cons++) {
215 if ((consoles[cons]->c_flags & flags) == flags) {
216 rv = consoles[cons]->c_in(consoles[cons]);
217 if (rv != -1) {
218 #ifndef EFI
219 last_input = cons;
220 #endif
221 return (rv);
222 }
223 }
224 }
225 delay(30 * 1000); /* delay 30ms */
226 }
227 }
228
229 int
ischar(void)230 ischar(void)
231 {
232 int cons;
233
234 for (cons = 0; consoles[cons] != NULL; cons++)
235 if ((consoles[cons]->c_flags & (C_PRESENTIN | C_ACTIVEIN)) ==
236 (C_PRESENTIN | C_ACTIVEIN) &&
237 (consoles[cons]->c_ready(consoles[cons]) != 0))
238 return (1);
239 return (0);
240 }
241
242 void
putchar(int c)243 putchar(int c)
244 {
245 int cons;
246
247 /* Expand newlines if not in raw mode */
248 for (cons = 0; consoles[cons] != NULL; cons++)
249 if ((consoles[cons]->c_flags & (C_PRESENTOUT | C_ACTIVEOUT)) ==
250 (C_PRESENTOUT | C_ACTIVEOUT)) {
251 if (c == '\n' &&
252 (consoles[cons]->c_flags & C_MODERAW) == 0)
253 consoles[cons]->c_out(consoles[cons], '\r');
254 consoles[cons]->c_out(consoles[cons], c);
255 }
256 }
257
258 /*
259 * Find the console with the specified name.
260 */
261 static int
cons_find(const char * name)262 cons_find(const char *name)
263 {
264 int cons;
265
266 for (cons = 0; consoles[cons] != NULL; cons++)
267 if (strcmp(consoles[cons]->c_name, name) == 0)
268 return (cons);
269 return (-1);
270 }
271
272 /*
273 * Select one or more consoles.
274 */
275 static int
cons_set(struct env_var * ev,int flags,const void * value)276 cons_set(struct env_var *ev, int flags, const void *value)
277 {
278 int ret;
279 char *list;
280
281 if ((value == NULL) || (cons_check(value) == 0)) {
282 /*
283 * Return CMD_OK instead of CMD_ERROR to prevent forth syntax
284 * error, which would prevent it processing any further
285 * loader.conf entries.
286 */
287 return (CMD_OK);
288 }
289
290 list = NULL;
291 ret = cons_change(value, &list);
292 if (ret != CMD_OK)
293 return (ret);
294
295 /*
296 * set console variable.
297 */
298 if (list != NULL) {
299 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, list,
300 NULL, NULL);
301 } else {
302 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value,
303 NULL, NULL);
304 }
305 free(list);
306 return (ret);
307 }
308
309 /*
310 * Check that at least one the consoles listed in *string is valid
311 */
312 static int
cons_check(const char * string)313 cons_check(const char *string)
314 {
315 int cons, found, failed;
316 char *curpos, *dup, *next;
317
318 dup = next = strdup(string);
319 found = failed = 0;
320 while (next != NULL) {
321 curpos = strsep(&next, " ,");
322 if (*curpos != '\0') {
323 cons = cons_find(curpos);
324 if (cons == -1) {
325 printf("console %s is invalid!\n", curpos);
326 failed++;
327 } else {
328 if ((consoles[cons]->c_flags &
329 (C_PRESENTIN | C_PRESENTOUT)) !=
330 (C_PRESENTIN | C_PRESENTOUT)) {
331 failed++;
332 } else
333 found++;
334 }
335 }
336 }
337
338 free(dup);
339
340 if (found == 0)
341 printf("no valid consoles!\n");
342
343 if (found == 0 || failed != 0) {
344 printf("Available consoles:\n");
345 for (cons = 0; consoles[cons] != NULL; cons++) {
346 printf(" %s", consoles[cons]->c_name);
347 if (consoles[cons]->c_devinfo != NULL)
348 consoles[cons]->c_devinfo(consoles[cons]);
349 printf("\n");
350 }
351 }
352
353 return (found);
354 }
355
356 /*
357 * Helper function to build string with list of console names.
358 */
359 static char *
cons_add_list(char * list,const char * value)360 cons_add_list(char *list, const char *value)
361 {
362 char *tmp;
363
364 if (list == NULL)
365 return (strdup(value));
366
367 if (asprintf(&tmp, "%s,%s", list, value) > 0) {
368 free(list);
369 list = tmp;
370 }
371 return (list);
372 }
373
374 /*
375 * Activate all the valid consoles listed in string and disable all others.
376 * Return comma separated string with list of activated console names.
377 */
378 static int
cons_change(const char * string,char ** list)379 cons_change(const char *string, char **list)
380 {
381 int cons, active, rv;
382 char *curpos, *dup, *next;
383
384 /* Disable all consoles */
385 for (cons = 0; consoles[cons] != NULL; cons++) {
386 consoles[cons]->c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT);
387 }
388
389 /* Enable selected consoles */
390 dup = next = strdup(string);
391 active = 0;
392 *list = NULL;
393 rv = CMD_OK;
394 while (next != NULL) {
395 curpos = strsep(&next, " ,");
396 if (*curpos == '\0')
397 continue;
398 cons = cons_find(curpos);
399 if (cons >= 0) {
400 consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT;
401 consoles[cons]->c_init(consoles[cons], 0);
402 if ((consoles[cons]->c_flags &
403 (C_ACTIVEIN | C_ACTIVEOUT)) ==
404 (C_ACTIVEIN | C_ACTIVEOUT)) {
405 active++;
406 *list = cons_add_list(*list, curpos);
407 continue;
408 }
409
410 if (active != 0) {
411 /*
412 * If no consoles have initialised we wouldn't
413 * see this.
414 */
415 printf("console %s failed to initialize\n",
416 consoles[cons]->c_name);
417 }
418 }
419 }
420
421 free(dup);
422
423 if (active == 0) {
424 /*
425 * All requested consoles failed to initialise, try to recover.
426 */
427 for (cons = 0; consoles[cons] != NULL; cons++) {
428 consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT;
429 consoles[cons]->c_init(consoles[cons], 0);
430 if ((consoles[cons]->c_flags &
431 (C_ACTIVEIN | C_ACTIVEOUT)) ==
432 (C_ACTIVEIN | C_ACTIVEOUT)) {
433 active++;
434 *list = cons_add_list(*list,
435 consoles[cons]->c_name);
436 }
437 }
438
439 if (active == 0)
440 rv = CMD_ERROR; /* Recovery failed. */
441 }
442
443 return (rv);
444 }
445
446 /*
447 * Change the twiddle divisor.
448 *
449 * The user can set the twiddle_divisor variable to directly control how fast
450 * the progress twiddle spins, useful for folks with slow serial consoles. The
451 * code to monitor changes to the variable and propagate them to the twiddle
452 * routines has to live somewhere. Twiddling is console-related so it's here.
453 */
454 static int
twiddle_set(struct env_var * ev,int flags,const void * value)455 twiddle_set(struct env_var *ev, int flags, const void *value)
456 {
457 ulong_t tdiv;
458 char *eptr;
459
460 tdiv = strtoul(value, &eptr, 0);
461 if (*(const char *)value == 0 || *eptr != 0) {
462 printf("invalid twiddle_divisor '%s'\n", (const char *)value);
463 return (CMD_ERROR);
464 }
465 twiddle_divisor((uint_t)tdiv);
466 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
467
468 return (CMD_OK);
469 }
470
471 COMMAND_SET(console, "console", "console info", command_console);
472
473 static int
command_console(int argc,char * argv[])474 command_console(int argc, char *argv[])
475 {
476 if (argc > 1)
477 printf("%s: list info about available consoles\n", argv[0]);
478
479 printf("Current console: %s\n", getenv("console"));
480 printf("Available consoles:\n");
481 for (int cons = 0; consoles[cons] != NULL; cons++) {
482 printf(" %s", consoles[cons]->c_name);
483 if (consoles[cons]->c_devinfo != NULL)
484 consoles[cons]->c_devinfo(consoles[cons]);
485 printf("\n");
486 }
487
488 return (CMD_OK);
489 }
490