xref: /illumos-gate/usr/src/boot/common/console.c (revision e3ae4b35c024af1196582063ecee3ab79367227d)
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
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
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
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
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 	env_setenv("twiddle_divisor", EV_VOLATILE, "1", 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 	env_setenv("console", EV_VOLATILE, console, cons_set,
182 	    env_nounset);
183 
184 	free(prefconsole);
185 	free(list);
186 }
187 
188 void
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
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
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
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
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
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
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 *
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
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
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 	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
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