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