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 struct console *
cons_get_console(const char * name)90 cons_get_console(const char *name)
91 {
92 char port[5];
93
94 (void) strlcpy(port, name, sizeof (port));
95 for (uint_t i = 0; consoles[i] != NULL; i++) {
96 if (strcmp(port, consoles[i]->c_name) == 0)
97 return (consoles[i]);
98 }
99
100 printf("No such port: %s\n", port);
101 return (NULL);
102 }
103
104 static void
cons_add_dev(struct console * dev)105 cons_add_dev(struct console *dev)
106 {
107 uint_t c = cons_array_size();
108 uint_t n = 1;
109 struct console **tmp;
110
111 if (c == 0)
112 n++;
113 tmp = realloc(consoles, (c + n) * sizeof (struct console *));
114 if (tmp == NULL)
115 return;
116 if (c > 0)
117 c--;
118 consoles = tmp;
119 consoles[c] = dev;
120 consoles[c + 1] = NULL;
121 }
122
123 /*
124 * Detect possible console(s) to use. If preferred console(s) have been
125 * specified, mark them as active. Else, mark the first probed console
126 * as active. Also create the console variable.
127 */
128 void
cons_probe(void)129 cons_probe(void)
130 {
131 int cons;
132 int active;
133 char *prefconsole, *list, *console;
134
135 /* Build list of consoles */
136 consoles = NULL;
137 for (cons = 0;; cons++) {
138 if (ct_list[cons].ct_dev != NULL) {
139 cons_add_dev(ct_list[cons].ct_dev);
140 continue;
141 }
142 if (ct_list[cons].ct_init != NULL) {
143 ct_list[cons].ct_init();
144 continue;
145 }
146 break;
147 }
148
149 /* We want a callback to install the new value when this var changes. */
150 (void) env_setenv("twiddle_divisor", EV_VOLATILE, "16", twiddle_set,
151 env_nounset);
152
153 /* Do all console probes */
154 for (cons = 0; consoles[cons] != NULL; cons++) {
155 consoles[cons]->c_flags = 0;
156 consoles[cons]->c_probe(consoles[cons]);
157 }
158 /* Now find the first working one */
159 active = -1;
160 for (cons = 0; consoles[cons] != NULL; cons++) {
161 if (consoles[cons]->c_flags == (C_PRESENTIN | C_PRESENTOUT)) {
162 active = cons;
163 break;
164 }
165 }
166
167 /* Force a console even if all probes failed */
168 if (active == -1)
169 active = 0;
170
171 /* Check to see if a console preference has already been registered */
172 list = NULL;
173 prefconsole = getenv("console");
174 if (prefconsole != NULL)
175 prefconsole = strdup(prefconsole);
176 if (prefconsole == NULL)
177 prefconsole = strdup(consoles[active]->c_name);
178
179 /*
180 * unset "console", we need to create one with callbacks.
181 */
182 unsetenv("console");
183 cons_change(prefconsole, &list);
184
185 printf("Consoles: ");
186 for (cons = 0; consoles[cons] != NULL; cons++)
187 if (consoles[cons]->c_flags & (C_ACTIVEIN | C_ACTIVEOUT))
188 printf("%s ", consoles[cons]->c_desc);
189 printf("\n");
190
191 if (list != NULL)
192 console = list;
193 else
194 console = prefconsole;
195
196 (void) env_setenv("console", EV_VOLATILE, console, cons_set,
197 env_nounset);
198
199 free(prefconsole);
200 free(list);
201 }
202
203 void
cons_mode(int raw)204 cons_mode(int raw)
205 {
206 int cons;
207
208 for (cons = 0; consoles[cons] != NULL; cons++) {
209 if (raw == 0)
210 consoles[cons]->c_flags &= ~C_MODERAW;
211 else
212 consoles[cons]->c_flags |= C_MODERAW;
213 }
214 }
215
216 int
getchar(void)217 getchar(void)
218 {
219 int cons;
220 int flags = C_PRESENTIN | C_ACTIVEIN;
221 int rv;
222
223 /*
224 * Loop forever polling all active consoles. Somewhat strangely,
225 * this code expects all ->c_in() implementations to effectively do an
226 * ischar() check first, returning -1 if there's not a char ready.
227 */
228 for (;;) {
229 for (cons = 0; consoles[cons] != NULL; cons++) {
230 if ((consoles[cons]->c_flags & flags) == flags) {
231 rv = consoles[cons]->c_in(consoles[cons]);
232 if (rv != -1) {
233 #ifndef EFI
234 last_input = cons;
235 #endif
236 return (rv);
237 }
238 }
239 }
240 delay(30 * 1000); /* delay 30ms */
241 }
242 }
243
244 int
ischar(void)245 ischar(void)
246 {
247 int cons;
248
249 for (cons = 0; consoles[cons] != NULL; cons++)
250 if ((consoles[cons]->c_flags & (C_PRESENTIN | C_ACTIVEIN)) ==
251 (C_PRESENTIN | C_ACTIVEIN) &&
252 (consoles[cons]->c_ready(consoles[cons]) != 0))
253 return (1);
254 return (0);
255 }
256
257 void
putchar(int c)258 putchar(int c)
259 {
260 int cons;
261
262 /* Expand newlines if not in raw mode */
263 for (cons = 0; consoles[cons] != NULL; cons++)
264 if ((consoles[cons]->c_flags & (C_PRESENTOUT | C_ACTIVEOUT)) ==
265 (C_PRESENTOUT | C_ACTIVEOUT)) {
266 if (c == '\n' &&
267 (consoles[cons]->c_flags & C_MODERAW) == 0)
268 consoles[cons]->c_out(consoles[cons], '\r');
269 consoles[cons]->c_out(consoles[cons], c);
270 }
271 }
272
273 /*
274 * Find the console with the specified name.
275 */
276 static int
cons_find(const char * name)277 cons_find(const char *name)
278 {
279 int cons;
280
281 for (cons = 0; consoles[cons] != NULL; cons++)
282 if (strcmp(consoles[cons]->c_name, name) == 0)
283 return (cons);
284 return (-1);
285 }
286
287 /*
288 * Select one or more consoles.
289 */
290 static int
cons_set(struct env_var * ev,int flags,const void * value)291 cons_set(struct env_var *ev, int flags, const void *value)
292 {
293 int ret;
294 char *list;
295
296 if ((value == NULL) || (cons_check(value) == 0)) {
297 /*
298 * Return CMD_OK instead of CMD_ERROR to prevent forth syntax
299 * error, which would prevent it processing any further
300 * loader.conf entries.
301 */
302 return (CMD_OK);
303 }
304
305 list = NULL;
306 ret = cons_change(value, &list);
307 if (ret != CMD_OK)
308 return (ret);
309
310 /*
311 * set console variable.
312 */
313 if (list != NULL) {
314 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, list,
315 NULL, NULL);
316 } else {
317 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value,
318 NULL, NULL);
319 }
320 free(list);
321 return (ret);
322 }
323
324 /*
325 * Check that at least one the consoles listed in *string is valid
326 */
327 static int
cons_check(const char * string)328 cons_check(const char *string)
329 {
330 int cons, found, failed;
331 char *curpos, *dup, *next;
332
333 dup = next = strdup(string);
334 found = failed = 0;
335 while (next != NULL) {
336 curpos = strsep(&next, " ,");
337 if (*curpos != '\0') {
338 cons = cons_find(curpos);
339 if (cons == -1) {
340 printf("console %s is invalid!\n", curpos);
341 failed++;
342 } else {
343 if ((consoles[cons]->c_flags &
344 (C_PRESENTIN | C_PRESENTOUT)) !=
345 (C_PRESENTIN | C_PRESENTOUT)) {
346 failed++;
347 } else
348 found++;
349 }
350 }
351 }
352
353 free(dup);
354
355 if (found == 0)
356 printf("no valid consoles!\n");
357
358 if (found == 0 || failed != 0) {
359 printf("Available consoles:\n");
360 for (cons = 0; consoles[cons] != NULL; cons++) {
361 printf(" %s", consoles[cons]->c_name);
362 if (consoles[cons]->c_devinfo != NULL)
363 consoles[cons]->c_devinfo(consoles[cons]);
364 printf("\n");
365 }
366 }
367
368 return (found);
369 }
370
371 /*
372 * Helper function to build string with list of console names.
373 */
374 static char *
cons_add_list(char * list,const char * value)375 cons_add_list(char *list, const char *value)
376 {
377 char *tmp;
378
379 if (list == NULL)
380 return (strdup(value));
381
382 if (asprintf(&tmp, "%s,%s", list, value) > 0) {
383 free(list);
384 list = tmp;
385 }
386 return (list);
387 }
388
389 /*
390 * Activate all the valid consoles listed in string and disable all others.
391 * Return comma separated string with list of activated console names.
392 */
393 static int
cons_change(const char * string,char ** list)394 cons_change(const char *string, char **list)
395 {
396 int cons, active, rv;
397 char *curpos, *dup, *next;
398
399 /* Disable all consoles */
400 for (cons = 0; consoles[cons] != NULL; cons++) {
401 consoles[cons]->c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT);
402 }
403
404 /* Enable selected consoles */
405 dup = next = strdup(string);
406 active = 0;
407 *list = NULL;
408 rv = CMD_OK;
409 while (next != NULL) {
410 curpos = strsep(&next, " ,");
411 if (*curpos == '\0')
412 continue;
413 cons = cons_find(curpos);
414 if (cons >= 0) {
415 consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT;
416 consoles[cons]->c_init(consoles[cons], 0);
417 if ((consoles[cons]->c_flags &
418 (C_ACTIVEIN | C_ACTIVEOUT)) ==
419 (C_ACTIVEIN | C_ACTIVEOUT)) {
420 active++;
421 *list = cons_add_list(*list, curpos);
422 continue;
423 }
424
425 if (active != 0) {
426 /*
427 * If no consoles have initialised we wouldn't
428 * see this.
429 */
430 printf("console %s failed to initialize\n",
431 consoles[cons]->c_name);
432 }
433 }
434 }
435
436 free(dup);
437
438 if (active == 0) {
439 /*
440 * All requested consoles failed to initialise, try to recover.
441 */
442 for (cons = 0; consoles[cons] != NULL; cons++) {
443 consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT;
444 consoles[cons]->c_init(consoles[cons], 0);
445 if ((consoles[cons]->c_flags &
446 (C_ACTIVEIN | C_ACTIVEOUT)) ==
447 (C_ACTIVEIN | C_ACTIVEOUT)) {
448 active++;
449 *list = cons_add_list(*list,
450 consoles[cons]->c_name);
451 }
452 }
453
454 if (active == 0)
455 rv = CMD_ERROR; /* Recovery failed. */
456 }
457
458 return (rv);
459 }
460
461 /*
462 * Change the twiddle divisor.
463 *
464 * The user can set the twiddle_divisor variable to directly control how fast
465 * the progress twiddle spins, useful for folks with slow serial consoles. The
466 * code to monitor changes to the variable and propagate them to the twiddle
467 * routines has to live somewhere. Twiddling is console-related so it's here.
468 */
469 static int
twiddle_set(struct env_var * ev,int flags,const void * value)470 twiddle_set(struct env_var *ev, int flags, const void *value)
471 {
472 ulong_t tdiv;
473 char *eptr;
474
475 tdiv = strtoul(value, &eptr, 0);
476 if (*(const char *)value == 0 || *eptr != 0) {
477 printf("invalid twiddle_divisor '%s'\n", (const char *)value);
478 return (CMD_ERROR);
479 }
480 twiddle_divisor((uint_t)tdiv);
481 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
482
483 return (CMD_OK);
484 }
485
486 COMMAND_SET(console, "console", "console info", command_console);
487
488 static int
command_console(int argc,char * argv[])489 command_console(int argc, char *argv[])
490 {
491 if (argc > 1)
492 printf("%s: list info about available consoles\n", argv[0]);
493
494 printf("Current console: %s\n", getenv("console"));
495 printf("Available consoles:\n");
496 for (int cons = 0; consoles[cons] != NULL; cons++) {
497 printf(" %s", consoles[cons]->c_name);
498 if (consoles[cons]->c_devinfo != NULL)
499 consoles[cons]->c_devinfo(consoles[cons]);
500 printf("\n");
501 }
502
503 return (CMD_OK);
504 }
505