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