xref: /illumos-gate/usr/src/boot/common/console.c (revision 22028508)
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 
189 	if ((value == NULL) || (cons_check(value) == 0)) {
190 		/*
191 		 * Return CMD_OK instead of CMD_ERROR to prevent forth syntax
192 		 * error, which would prevent it processing any further
193 		 * loader.conf entries.
194 		 */
195 		return (CMD_OK);
196 	}
197 
198 	ret = cons_change(value);
199 	if (ret != CMD_OK)
200 		return (ret);
201 
202 	/*
203 	 * build list of active consoles.
204 	 */
205 	list = NULL;
206 	for (cons = 0; consoles[cons] != NULL; cons++) {
207 		if ((consoles[cons]->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) ==
208 		    (C_ACTIVEIN | C_ACTIVEOUT)) {
209 			if (list == NULL) {
210 				list = strdup(consoles[cons]->c_name);
211 			} else {
212 				if (asprintf(&tmp, "%s,%s", list,
213 				    consoles[cons]->c_name) > 0) {
214 					free(list);
215 					list = tmp;
216 				}
217 			}
218 		}
219 	}
220 
221 	/*
222 	 * set console variable.
223 	 */
224 	if (list != NULL) {
225 		(void) env_setenv(ev->ev_name, flags | EV_NOHOOK, list,
226 		    NULL, NULL);
227 	} else {
228 		(void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value,
229 		    NULL, NULL);
230 	}
231 	free(list);
232 	return (CMD_OK);
233 }
234 
235 /*
236  * Check that at least one the consoles listed in *string is valid
237  */
238 static int
239 cons_check(const char *string)
240 {
241 	int	cons, found, failed;
242 	char	*curpos, *dup, *next;
243 
244 	dup = next = strdup(string);
245 	found = failed = 0;
246 	while (next != NULL) {
247 		curpos = strsep(&next, " ,");
248 		if (*curpos != '\0') {
249 			cons = cons_find(curpos);
250 			if (cons == -1) {
251 				printf("console %s is invalid!\n", curpos);
252 				failed++;
253 			} else {
254 				if ((consoles[cons]->c_flags &
255 				    (C_PRESENTIN | C_PRESENTOUT)) !=
256 				    (C_PRESENTIN | C_PRESENTOUT)) {
257 					failed++;
258 				} else
259 					found++;
260 			}
261 		}
262 	}
263 
264 	free(dup);
265 
266 	if (found == 0)
267 		printf("no valid consoles!\n");
268 
269 	if (found == 0 || failed != 0) {
270 		printf("Available consoles:\n");
271 		for (cons = 0; consoles[cons] != NULL; cons++) {
272 			printf("    %s", consoles[cons]->c_name);
273 			if (consoles[cons]->c_devinfo != NULL)
274 				consoles[cons]->c_devinfo(consoles[cons]);
275 			printf("\n");
276 		}
277 	}
278 
279 	return (found);
280 }
281 
282 
283 /*
284  * Activate all the valid consoles listed in *string and disable all others.
285  */
286 static int
287 cons_change(const char *string)
288 {
289 	int	cons, active;
290 	char	*curpos, *dup, *next;
291 
292 	/* Disable all consoles */
293 	for (cons = 0; consoles[cons] != NULL; cons++) {
294 		consoles[cons]->c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT);
295 	}
296 
297 	/* Enable selected consoles */
298 	dup = next = strdup(string);
299 	active = 0;
300 	while (next != NULL) {
301 		curpos = strsep(&next, " ,");
302 		if (*curpos == '\0')
303 			continue;
304 		cons = cons_find(curpos);
305 		if (cons >= 0) {
306 			consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT;
307 			consoles[cons]->c_init(consoles[cons], 0);
308 			if ((consoles[cons]->c_flags &
309 			    (C_ACTIVEIN | C_ACTIVEOUT)) ==
310 			    (C_ACTIVEIN | C_ACTIVEOUT)) {
311 				active++;
312 				continue;
313 			}
314 
315 			if (active != 0) {
316 				/*
317 				 * If no consoles have initialised we wouldn't
318 				 * see this.
319 				 */
320 				printf("console %s failed to initialize\n",
321 				    consoles[cons]->c_name);
322 			}
323 		}
324 	}
325 
326 	free(dup);
327 
328 	if (active == 0) {
329 		/*
330 		 * All requested consoles failed to initialise, try to recover.
331 		 */
332 		for (cons = 0; consoles[cons] != NULL; cons++) {
333 			consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT;
334 			consoles[cons]->c_init(consoles[cons], 0);
335 			if ((consoles[cons]->c_flags &
336 			    (C_ACTIVEIN | C_ACTIVEOUT)) ==
337 			    (C_ACTIVEIN | C_ACTIVEOUT))
338 				active++;
339 		}
340 
341 		if (active == 0)
342 			return (CMD_ERROR); /* Recovery failed. */
343 	}
344 
345 	return (CMD_OK);
346 }
347 
348 /*
349  * Change the twiddle divisor.
350  *
351  * The user can set the twiddle_divisor variable to directly control how fast
352  * the progress twiddle spins, useful for folks with slow serial consoles.  The
353  * code to monitor changes to the variable and propagate them to the twiddle
354  * routines has to live somewhere.  Twiddling is console-related so it's here.
355  */
356 static int
357 twiddle_set(struct env_var *ev, int flags, const void *value)
358 {
359 	ulong_t tdiv;
360 	char *eptr;
361 
362 	tdiv = strtoul(value, &eptr, 0);
363 	if (*(const char *)value == 0 || *eptr != 0) {
364 		printf("invalid twiddle_divisor '%s'\n", (const char *)value);
365 		return (CMD_ERROR);
366 	}
367 	twiddle_divisor((uint_t)tdiv);
368 	env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL);
369 
370 	return (CMD_OK);
371 }
372 
373 COMMAND_SET(console, "console", "console info", command_console);
374 
375 static int
376 command_console(int argc, char *argv[])
377 {
378 	if (argc > 1)
379 		printf("%s: list info about available consoles\n", argv[0]);
380 
381 	printf("Current console: %s\n", getenv("console"));
382 	printf("Available consoles:\n");
383 	for (int cons = 0; consoles[cons] != NULL; cons++) {
384 		printf("    %s", consoles[cons]->c_name);
385 		if (consoles[cons]->c_devinfo != NULL)
386 			consoles[cons]->c_devinfo(consoles[cons]);
387 		printf("\n");
388 	}
389 
390 	return (CMD_OK);
391 }
392