xref: /illumos-gate/usr/src/boot/common/console.c (revision 1f0978acc6d6a43e779975f28651c252ffbefaab)
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
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
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 *
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
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
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
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
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
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
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
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
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
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 *
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
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
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
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