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