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