1 /* 2 * Copyright (c) 1998 Michael Smith <msmith@freebsd.org> 3 * All rights reserved. 4 * Copyright 2025 Edgecast Cloud LLC. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 30 #include <stand.h> 31 #include <string.h> 32 33 #include "bootstrap.h" 34 /* 35 * Core console support 36 */ 37 38 static int cons_set(struct env_var *ev, int flags, const void *value); 39 static int cons_find(const char *name); 40 static int cons_check(const char *string); 41 static int cons_change(const char *string, char **); 42 static int twiddle_set(struct env_var *ev, int flags, const void *value); 43 44 static int last_input = -1; /* input device index */ 45 46 /* 47 * With multiple active console devices, return index of last input 48 * device, so we can set up os_console variable to denote console 49 * device for kernel. 50 * 51 * Please note, this feature can not really work with UEFI, because 52 * efi console input is returned from any device listed in ConIn, 53 * and we have no way to check which device from ConIn actually was 54 * generating input. 55 */ 56 int 57 cons_inputdev(void) 58 { 59 int cons; 60 int flags = C_PRESENTIN | C_ACTIVEIN; 61 int active = 0; 62 63 for (cons = 0; consoles[cons] != NULL; cons++) 64 if ((consoles[cons]->c_flags & flags) == flags) 65 active++; 66 67 /* With just one active console, we will not set os_console */ 68 if (active == 1) 69 return (-1); 70 71 return (last_input); 72 } 73 74 /* 75 * Return number of array slots. 76 */ 77 uint_t 78 cons_array_size(void) 79 { 80 uint_t n; 81 82 if (consoles == NULL) 83 return (0); 84 85 for (n = 0; consoles[n] != NULL; n++) 86 ; 87 return (n + 1); 88 } 89 90 struct console * 91 cons_get_console(const char *name) 92 { 93 char port[5]; 94 95 (void) strlcpy(port, name, sizeof (port)); 96 for (uint_t i = 0; consoles[i] != NULL; i++) { 97 if (strcmp(port, consoles[i]->c_name) == 0) 98 return (consoles[i]); 99 } 100 101 printf("No such port: %s\n", port); 102 return (NULL); 103 } 104 105 static void 106 cons_add_dev(struct console *dev) 107 { 108 uint_t c = cons_array_size(); 109 uint_t n = 1; 110 struct console **tmp; 111 112 if (c == 0) 113 n++; 114 tmp = realloc(consoles, (c + n) * sizeof (struct console *)); 115 if (tmp == NULL) 116 return; 117 if (c > 0) 118 c--; 119 consoles = tmp; 120 consoles[c] = dev; 121 consoles[c + 1] = NULL; 122 } 123 124 /* 125 * Detect possible console(s) to use. If preferred console(s) have been 126 * specified, mark them as active. Else, mark the first probed console 127 * as active. Also create the console variable. 128 */ 129 void 130 cons_probe(void) 131 { 132 int cons; 133 int active; 134 char *prefconsole, *list, *console; 135 136 /* Build list of consoles */ 137 consoles = NULL; 138 for (cons = 0;; cons++) { 139 if (ct_list[cons].ct_dev != NULL) { 140 cons_add_dev(ct_list[cons].ct_dev); 141 continue; 142 } 143 if (ct_list[cons].ct_init != NULL) { 144 ct_list[cons].ct_init(); 145 continue; 146 } 147 break; 148 } 149 150 /* We want a callback to install the new value when this var changes. */ 151 (void) env_setenv("twiddle_divisor", EV_VOLATILE, "16", twiddle_set, 152 env_nounset); 153 154 /* Do all console probes */ 155 for (cons = 0; consoles[cons] != NULL; cons++) { 156 consoles[cons]->c_flags = 0; 157 consoles[cons]->c_probe(consoles[cons]); 158 } 159 /* Now find the first working one */ 160 active = -1; 161 for (cons = 0; consoles[cons] != NULL; cons++) { 162 if (consoles[cons]->c_flags == (C_PRESENTIN | C_PRESENTOUT)) { 163 active = cons; 164 break; 165 } 166 } 167 168 /* Force a console even if all probes failed */ 169 if (active == -1) 170 active = 0; 171 172 /* Check to see if a console preference has already been registered */ 173 list = NULL; 174 prefconsole = getenv("console"); 175 if (prefconsole != NULL) 176 prefconsole = strdup(prefconsole); 177 if (prefconsole == NULL) 178 prefconsole = strdup(consoles[active]->c_name); 179 180 /* 181 * unset "console", we need to create one with callbacks. 182 */ 183 unsetenv("console"); 184 cons_change(prefconsole, &list); 185 186 printf("Consoles:"); 187 bool first = true; 188 for (cons = 0; consoles[cons] != NULL; cons++) { 189 if (consoles[cons]->c_flags & (C_ACTIVEIN | C_ACTIVEOUT)) { 190 if (first) 191 first = false; 192 else 193 putchar(','); 194 printf(" %s", consoles[cons]->c_desc); 195 } 196 } 197 printf("\n"); 198 199 if (list != NULL) 200 console = list; 201 else 202 console = prefconsole; 203 204 (void) env_setenv("console", EV_VOLATILE, console, cons_set, 205 env_nounset); 206 207 free(prefconsole); 208 free(list); 209 } 210 211 void 212 cons_mode(int raw) 213 { 214 int cons; 215 216 for (cons = 0; consoles[cons] != NULL; cons++) { 217 if (raw == 0) 218 consoles[cons]->c_flags &= ~C_MODERAW; 219 else 220 consoles[cons]->c_flags |= C_MODERAW; 221 } 222 } 223 224 int 225 getchar(void) 226 { 227 int cons; 228 int flags = C_PRESENTIN | C_ACTIVEIN; 229 int rv; 230 231 /* 232 * Loop forever polling all active consoles. Somewhat strangely, 233 * this code expects all ->c_in() implementations to effectively do an 234 * ischar() check first, returning -1 if there's not a char ready. 235 */ 236 for (;;) { 237 for (cons = 0; consoles[cons] != NULL; cons++) { 238 if ((consoles[cons]->c_flags & flags) == flags) { 239 rv = consoles[cons]->c_in(consoles[cons]); 240 if (rv != -1) { 241 #ifndef EFI 242 last_input = cons; 243 #endif 244 return (rv); 245 } 246 } 247 } 248 delay(30 * 1000); /* delay 30ms */ 249 } 250 } 251 252 int 253 ischar(void) 254 { 255 int cons; 256 257 for (cons = 0; consoles[cons] != NULL; cons++) 258 if ((consoles[cons]->c_flags & (C_PRESENTIN | C_ACTIVEIN)) == 259 (C_PRESENTIN | C_ACTIVEIN) && 260 (consoles[cons]->c_ready(consoles[cons]) != 0)) 261 return (1); 262 return (0); 263 } 264 265 void 266 putchar(int c) 267 { 268 int cons; 269 270 /* Expand newlines if not in raw mode */ 271 for (cons = 0; consoles[cons] != NULL; cons++) 272 if ((consoles[cons]->c_flags & (C_PRESENTOUT | C_ACTIVEOUT)) == 273 (C_PRESENTOUT | C_ACTIVEOUT)) { 274 if (c == '\n' && 275 (consoles[cons]->c_flags & C_MODERAW) == 0) 276 consoles[cons]->c_out(consoles[cons], '\r'); 277 consoles[cons]->c_out(consoles[cons], c); 278 } 279 } 280 281 /* 282 * Find the console with the specified name. 283 */ 284 static int 285 cons_find(const char *name) 286 { 287 int cons; 288 289 for (cons = 0; consoles[cons] != NULL; cons++) 290 if (strcmp(consoles[cons]->c_name, name) == 0) 291 return (cons); 292 return (-1); 293 } 294 295 /* 296 * Select one or more consoles. 297 */ 298 static int 299 cons_set(struct env_var *ev, int flags, const void *value) 300 { 301 int ret; 302 char *list; 303 304 if ((value == NULL) || (cons_check(value) == 0)) { 305 /* 306 * Return CMD_OK instead of CMD_ERROR to prevent forth syntax 307 * error, which would prevent it processing any further 308 * loader.conf entries. 309 */ 310 return (CMD_OK); 311 } 312 313 list = NULL; 314 ret = cons_change(value, &list); 315 if (ret != CMD_OK) 316 return (ret); 317 318 /* 319 * set console variable. 320 */ 321 if (list != NULL) { 322 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, list, 323 NULL, NULL); 324 } else { 325 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value, 326 NULL, NULL); 327 } 328 free(list); 329 return (ret); 330 } 331 332 /* 333 * Check that at least one the consoles listed in *string is valid 334 */ 335 static int 336 cons_check(const char *string) 337 { 338 int cons, found, failed; 339 char *curpos, *dup, *next; 340 341 dup = next = strdup(string); 342 found = failed = 0; 343 while (next != NULL) { 344 curpos = strsep(&next, " ,"); 345 if (*curpos != '\0') { 346 cons = cons_find(curpos); 347 if (cons == -1) { 348 printf("console %s is invalid!\n", curpos); 349 failed++; 350 } else { 351 if ((consoles[cons]->c_flags & 352 (C_PRESENTIN | C_PRESENTOUT)) != 353 (C_PRESENTIN | C_PRESENTOUT)) { 354 failed++; 355 } else 356 found++; 357 } 358 } 359 } 360 361 free(dup); 362 363 if (found == 0) 364 printf("no valid consoles!\n"); 365 366 if (found == 0 || failed != 0) { 367 printf("Available consoles:\n"); 368 for (cons = 0; consoles[cons] != NULL; cons++) { 369 printf(" %s", consoles[cons]->c_name); 370 if (consoles[cons]->c_devinfo != NULL) 371 consoles[cons]->c_devinfo(consoles[cons]); 372 printf("\n"); 373 } 374 } 375 376 return (found); 377 } 378 379 /* 380 * Helper function to build string with list of console names. 381 */ 382 static char * 383 cons_add_list(char *list, const char *value) 384 { 385 char *tmp; 386 387 if (list == NULL) 388 return (strdup(value)); 389 390 if (asprintf(&tmp, "%s,%s", list, value) > 0) { 391 free(list); 392 list = tmp; 393 } 394 return (list); 395 } 396 397 /* 398 * Activate all the valid consoles listed in string and disable all others. 399 * Return comma separated string with list of activated console names. 400 */ 401 static int 402 cons_change(const char *string, char **list) 403 { 404 int cons, active, rv; 405 char *curpos, *dup, *next; 406 407 /* Disable all consoles */ 408 for (cons = 0; consoles[cons] != NULL; cons++) { 409 consoles[cons]->c_flags &= ~(C_ACTIVEIN | C_ACTIVEOUT); 410 } 411 412 /* Enable selected consoles */ 413 dup = next = strdup(string); 414 active = 0; 415 *list = NULL; 416 rv = CMD_OK; 417 while (next != NULL) { 418 curpos = strsep(&next, " ,"); 419 if (*curpos == '\0') 420 continue; 421 cons = cons_find(curpos); 422 if (cons >= 0) { 423 consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT; 424 consoles[cons]->c_init(consoles[cons], 0); 425 if ((consoles[cons]->c_flags & 426 (C_ACTIVEIN | C_ACTIVEOUT)) == 427 (C_ACTIVEIN | C_ACTIVEOUT)) { 428 active++; 429 *list = cons_add_list(*list, curpos); 430 continue; 431 } 432 433 if (active != 0) { 434 /* 435 * If no consoles have initialised we wouldn't 436 * see this. 437 */ 438 printf("console %s failed to initialize\n", 439 consoles[cons]->c_name); 440 } 441 } 442 } 443 444 free(dup); 445 446 if (active == 0) { 447 /* 448 * All requested consoles failed to initialise, try to recover. 449 */ 450 for (cons = 0; consoles[cons] != NULL; cons++) { 451 consoles[cons]->c_flags |= C_ACTIVEIN | C_ACTIVEOUT; 452 consoles[cons]->c_init(consoles[cons], 0); 453 if ((consoles[cons]->c_flags & 454 (C_ACTIVEIN | C_ACTIVEOUT)) == 455 (C_ACTIVEIN | C_ACTIVEOUT)) { 456 active++; 457 *list = cons_add_list(*list, 458 consoles[cons]->c_name); 459 } 460 } 461 462 if (active == 0) 463 rv = CMD_ERROR; /* Recovery failed. */ 464 } 465 466 return (rv); 467 } 468 469 /* 470 * Change the twiddle divisor. 471 * 472 * The user can set the twiddle_divisor variable to directly control how fast 473 * the progress twiddle spins, useful for folks with slow serial consoles. The 474 * code to monitor changes to the variable and propagate them to the twiddle 475 * routines has to live somewhere. Twiddling is console-related so it's here. 476 */ 477 static int 478 twiddle_set(struct env_var *ev, int flags, const void *value) 479 { 480 ulong_t tdiv; 481 char *eptr; 482 483 tdiv = strtoul(value, &eptr, 0); 484 if (*(const char *)value == 0 || *eptr != 0) { 485 printf("invalid twiddle_divisor '%s'\n", (const char *)value); 486 return (CMD_ERROR); 487 } 488 twiddle_divisor((uint_t)tdiv); 489 (void) env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); 490 491 return (CMD_OK); 492 } 493 494 COMMAND_SET(console, "console", "console info", command_console); 495 496 static int 497 command_console(int argc, char *argv[]) 498 { 499 if (argc > 1) 500 printf("%s: list info about available consoles\n", argv[0]); 501 502 printf("Current console: %s\n", getenv("console")); 503 printf("Available consoles:\n"); 504 for (int cons = 0; consoles[cons] != NULL; cons++) { 505 printf(" %s", consoles[cons]->c_name); 506 if (consoles[cons]->c_devinfo != NULL) 507 consoles[cons]->c_devinfo(consoles[cons]); 508 printf("\n"); 509 } 510 511 return (CMD_OK); 512 } 513