1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <sys/types.h> 29 #include <sys/systm.h> 30 #include <sys/archsystm.h> 31 #include <sys/boot_console.h> 32 #include <sys/ctype.h> 33 34 #include "boot_serial.h" 35 #include "boot_vga.h" 36 37 #if defined(_BOOT) 38 #include <dboot/dboot_xboot.h> 39 #else 40 #include <sys/bootconf.h> 41 static char *usbser_buf; 42 static char *usbser_cur; 43 #endif 44 45 static int cons_color = CONS_COLOR; 46 int console = CONS_SCREEN_TEXT; 47 /* or CONS_TTYA, CONS_TTYB */ 48 static int serial_ischar(void); 49 static int serial_getchar(void); 50 static void serial_putchar(int); 51 static void serial_adjust_prop(void); 52 53 static char *boot_line = NULL; 54 55 /* Set if the console or mode are expressed in the boot line */ 56 static int console_set, console_mode_set; 57 58 /* Clear the screen and initialize VIDEO, XPOS and YPOS. */ 59 static void 60 clear_screen(void) 61 { 62 /* 63 * XXX should set vga mode so we don't depend on the 64 * state left by the boot loader 65 */ 66 vga_clear(cons_color); 67 vga_setpos(0, 0); 68 } 69 70 /* Put the character C on the screen. */ 71 static void 72 screen_putchar(int c) 73 { 74 int row, col; 75 76 vga_getpos(&row, &col); 77 switch (c) { 78 case '\t': 79 col += 8 - (col % 8); 80 if (col == VGA_TEXT_COLS) 81 col = 79; 82 vga_setpos(row, col); 83 break; 84 85 case '\r': 86 vga_setpos(row, 0); 87 break; 88 89 case '\b': 90 if (col > 0) 91 vga_setpos(row, col - 1); 92 break; 93 94 case '\n': 95 if (row < VGA_TEXT_ROWS - 1) 96 vga_setpos(row + 1, col); 97 else 98 vga_scroll(cons_color); 99 break; 100 101 default: 102 vga_drawc(c, cons_color); 103 if (col < VGA_TEXT_COLS -1) 104 vga_setpos(row, col + 1); 105 else if (row < VGA_TEXT_ROWS - 1) 106 vga_setpos(row + 1, 0); 107 else { 108 vga_setpos(row, 0); 109 vga_scroll(cons_color); 110 } 111 break; 112 } 113 } 114 115 /* serial port stuff */ 116 static int port; 117 118 static void 119 serial_init(void) 120 { 121 switch (console) { 122 case CONS_TTYA: 123 port = 0x3f8; 124 break; 125 case CONS_TTYB: 126 port = 0x2f8; 127 break; 128 } 129 130 outb(port + ISR, 0x20); 131 if (inb(port + ISR) & 0x20) { 132 /* 133 * 82510 chip is present 134 */ 135 outb(port + DAT+7, 0x04); /* clear status */ 136 outb(port + ISR, 0x40); /* set to bank 2 */ 137 outb(port + MCR, 0x08); /* IMD */ 138 outb(port + DAT, 0x21); /* FMD */ 139 outb(port + ISR, 0x00); /* set to bank 0 */ 140 } else { 141 /* 142 * set the UART in FIFO mode if it has FIFO buffers. 143 * use 16550 fifo reset sequence specified in NS 144 * application note. disable fifos until chip is 145 * initialized. 146 */ 147 outb(port + FIFOR, 0x00); /* clear */ 148 outb(port + FIFOR, FIFO_ON); /* enable */ 149 outb(port + FIFOR, FIFO_ON|FIFORXFLSH); /* reset */ 150 outb(port + FIFOR, 151 FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80); 152 if ((inb(port + ISR) & 0xc0) != 0xc0) { 153 /* 154 * no fifo buffers so disable fifos. 155 * this is true for 8250's 156 */ 157 outb(port + FIFOR, 0x00); 158 } 159 } 160 161 /* disable interrupts */ 162 outb(port + ICR, 0); 163 164 /* adjust setting based on tty properties */ 165 serial_adjust_prop(); 166 167 #if defined(_BOOT) 168 /* 169 * Do a full reset to match console behavior. 170 * 0x1B + c - reset everything 171 */ 172 serial_putchar(0x1B); 173 serial_putchar('c'); 174 #endif 175 } 176 177 /* Advance str pointer past white space */ 178 #define EAT_WHITE_SPACE(str) { \ 179 while ((*str != '\0') && ISSPACE(*str)) \ 180 str++; \ 181 } 182 183 /* 184 * boot_line is set when we call here. Search it for the argument name, 185 * and if found, return a pointer to it. 186 */ 187 static char * 188 find_boot_line_prop(const char *name) 189 { 190 char *ptr; 191 char end_char; 192 size_t len; 193 194 if (boot_line == NULL) 195 return (NULL); 196 197 len = strlen(name); 198 199 /* 200 * We have two nested loops here: the outer loop discards all options 201 * except -B, and the inner loop parses the -B options looking for 202 * the one we're interested in. 203 */ 204 for (ptr = boot_line; *ptr != '\0'; ptr++) { 205 EAT_WHITE_SPACE(ptr); 206 207 if (*ptr == '-') { 208 ptr++; 209 while ((*ptr != '\0') && (*ptr != 'B') && 210 !ISSPACE(*ptr)) 211 ptr++; 212 if (*ptr == '\0') 213 return (NULL); 214 else if (*ptr != 'B') 215 continue; 216 } else { 217 while ((*ptr != '\0') && !ISSPACE(*ptr)) 218 ptr++; 219 if (*ptr == '\0') 220 return (NULL); 221 continue; 222 } 223 224 do { 225 ptr++; 226 EAT_WHITE_SPACE(ptr); 227 228 if ((strncmp(ptr, name, len) == 0) && 229 (ptr[len] == '=')) { 230 ptr += len + 1; 231 if ((*ptr == '\'') || (*ptr == '"')) 232 return (ptr + 1); 233 else 234 return (ptr); 235 } 236 237 /* 238 * We have a property, and it's not the one we're 239 * interested in. Skip the property name. A name 240 * can end with '=', a comma, or white space. 241 */ 242 while ((*ptr != '\0') && (*ptr != '=') && 243 (*ptr != ',') && (!ISSPACE(*ptr))) 244 ptr++; 245 246 /* 247 * We only want to go through the rest of the inner 248 * loop if we have a comma. If we have a property 249 * name without a value, either continue or break. 250 */ 251 if (*ptr == '\0') 252 return (NULL); 253 else if (*ptr == ',') 254 continue; 255 else if (ISSPACE(*ptr)) 256 break; 257 ptr++; 258 259 /* 260 * Is the property quoted? 261 */ 262 if ((*ptr == '\'') || (*ptr == '"')) { 263 end_char = *ptr; 264 } else { 265 /* 266 * Not quoted, so the string ends at a comma 267 * or at white space. Deal with white space 268 * later. 269 */ 270 end_char = ','; 271 } 272 273 /* 274 * Now, we can ignore any characters until we find 275 * end_char. 276 */ 277 for (; (*ptr != '\0') && (*ptr != end_char); ptr++) { 278 if ((end_char == ',') && ISSPACE(*ptr)) 279 break; 280 } 281 if (*ptr && (*ptr != ',')) 282 ptr++; 283 } while (*ptr == ','); 284 } 285 return (NULL); 286 } 287 288 289 #define MATCHES(p, pat) \ 290 (strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0) 291 292 #define SKIP(p, c) \ 293 while (*(p) != 0 && *p != (c)) \ 294 ++(p); \ 295 if (*(p) == (c)) \ 296 ++(p); 297 298 /* 299 * find a tty mode property either from cmdline or from boot properties 300 */ 301 static char * 302 get_mode_value(char *name) 303 { 304 /* 305 * when specified on boot line it looks like "name" "=".... 306 */ 307 if (boot_line != NULL) { 308 return (find_boot_line_prop(name)); 309 } 310 311 #if defined(_BOOT) 312 return (NULL); 313 #else 314 /* 315 * if we're running in the full kernel we check the bootenv.rc settings 316 */ 317 { 318 static char propval[20]; 319 320 propval[0] = 0; 321 if (do_bsys_getproplen(NULL, name) <= 0) 322 return (NULL); 323 (void) do_bsys_getprop(NULL, name, propval); 324 return (propval); 325 } 326 #endif 327 } 328 329 /* 330 * adjust serial port based on properties 331 * These come either from the cmdline or from boot properties. 332 */ 333 static void 334 serial_adjust_prop(void) 335 { 336 char propname[20]; 337 char *propval; 338 char *p; 339 ulong_t baud; 340 uchar_t lcr = 0; 341 uchar_t mcr = DTR | RTS; 342 343 (void) strcpy(propname, "ttyX-mode"); 344 propname[3] = 'a' + console - CONS_TTYA; 345 propval = get_mode_value(propname); 346 if (propval == NULL) 347 propval = "9600,8,n,1,-"; 348 else 349 console_mode_set = 1; 350 351 /* property is of the form: "9600,8,n,1,-" */ 352 p = propval; 353 if (MATCHES(p, "110,")) 354 baud = ASY110; 355 else if (MATCHES(p, "150,")) 356 baud = ASY150; 357 else if (MATCHES(p, "300,")) 358 baud = ASY300; 359 else if (MATCHES(p, "600,")) 360 baud = ASY600; 361 else if (MATCHES(p, "1200,")) 362 baud = ASY1200; 363 else if (MATCHES(p, "2400,")) 364 baud = ASY2400; 365 else if (MATCHES(p, "4800,")) 366 baud = ASY4800; 367 else if (MATCHES(p, "19200,")) 368 baud = ASY19200; 369 else if (MATCHES(p, "38400,")) 370 baud = ASY38400; 371 else if (MATCHES(p, "57600,")) 372 baud = ASY57600; 373 else if (MATCHES(p, "115200,")) 374 baud = ASY115200; 375 else { 376 baud = ASY9600; 377 SKIP(p, ','); 378 } 379 outb(port + LCR, DLAB); 380 outb(port + DAT + DLL, baud & 0xff); 381 outb(port + DAT + DLH, (baud >> 8) & 0xff); 382 383 switch (*p) { 384 case '5': 385 lcr |= BITS5; 386 ++p; 387 break; 388 case '6': 389 lcr |= BITS6; 390 ++p; 391 break; 392 case '7': 393 lcr |= BITS7; 394 ++p; 395 break; 396 case '8': 397 ++p; 398 default: 399 lcr |= BITS8; 400 break; 401 } 402 403 SKIP(p, ','); 404 405 switch (*p) { 406 case 'n': 407 lcr |= PARITY_NONE; 408 ++p; 409 break; 410 case 'o': 411 lcr |= PARITY_ODD; 412 ++p; 413 break; 414 case 'e': 415 ++p; 416 default: 417 lcr |= PARITY_EVEN; 418 break; 419 } 420 421 422 SKIP(p, ','); 423 424 switch (*p) { 425 case '1': 426 /* STOP1 is 0 */ 427 ++p; 428 break; 429 default: 430 lcr |= STOP2; 431 break; 432 } 433 /* set parity bits */ 434 outb(port + LCR, lcr); 435 436 (void) strcpy(propname, "ttyX-rts-dtr-off"); 437 propname[3] = 'a' + console - CONS_TTYA; 438 propval = get_mode_value(propname); 439 if (propval == NULL) 440 propval = "false"; 441 if (propval[0] != 'f' && propval[0] != 'F') 442 mcr = 0; 443 /* set modem control bits */ 444 outb(port + MCR, mcr | OUT2); 445 } 446 447 /* 448 * A structure to map console names to values. 449 */ 450 typedef struct { 451 char *name; 452 int value; 453 } console_value_t; 454 455 console_value_t console_devices[] = { 456 { "ttya", CONS_TTYA }, 457 { "ttyb", CONS_TTYB }, 458 { "text", CONS_SCREEN_TEXT }, 459 #if !defined(_BOOT) 460 { "usb-serial", CONS_USBSER }, 461 #endif 462 { "", CONS_INVALID } 463 }; 464 465 void 466 bcons_init(char *bootstr) 467 { 468 console_value_t *consolep; 469 size_t len, cons_len; 470 char *cons_str; 471 472 boot_line = bootstr; 473 console = CONS_INVALID; 474 475 cons_str = find_boot_line_prop("console"); 476 if (cons_str == NULL) 477 cons_str = find_boot_line_prop("output-device"); 478 479 /* 480 * Go through the console_devices array trying to match the string 481 * we were given. The string on the command line must end with 482 * a comma or white space. 483 */ 484 if (cons_str != NULL) { 485 cons_len = strlen(cons_str); 486 consolep = console_devices; 487 for (; consolep->name[0] != '\0'; consolep++) { 488 len = strlen(consolep->name); 489 if ((len <= cons_len) && ((cons_str[len] == '\0') || 490 (cons_str[len] == ',') || (cons_str[len] == '\'') || 491 (cons_str[len] == '"') || ISSPACE(cons_str[len])) && 492 (strncmp(cons_str, consolep->name, len) == 0)) { 493 console = consolep->value; 494 break; 495 } 496 } 497 } 498 499 /* 500 * If no console device specified, default to text. 501 * Remember what was specified for second phase. 502 */ 503 if (console == CONS_INVALID) 504 console = CONS_SCREEN_TEXT; 505 else 506 console_set = 1; 507 508 switch (console) { 509 case CONS_TTYA: 510 case CONS_TTYB: 511 serial_init(); 512 break; 513 514 #if !defined(_BOOT) 515 case CONS_USBSER: 516 /* 517 * We can't do anything with the usb serial 518 * until we have memory management. 519 */ 520 break; 521 #endif 522 case CONS_SCREEN_TEXT: 523 default: 524 #if defined(_BOOT) 525 clear_screen(); /* clears the grub screen */ 526 #endif 527 kb_init(); 528 break; 529 } 530 boot_line = NULL; 531 } 532 533 /* 534 * 2nd part of console initialization. 535 * In the kernel (ie. fakebop), this can be used only to switch to 536 * using a serial port instead of screen based on the contents 537 * of the bootenv.rc file. 538 */ 539 /*ARGSUSED*/ 540 void 541 bcons_init2(char *inputdev, char *outputdev, char *consoledev) 542 { 543 #if !defined(_BOOT) 544 int cons = CONS_INVALID; 545 char *devnames[] = { consoledev, outputdev, inputdev, NULL }; 546 console_value_t *consolep; 547 int i; 548 549 if (console != CONS_USBSER) { 550 if (console_set) { 551 /* 552 * If the console was set on the command line, 553 * but the ttyX-mode was not, we only need to 554 * check bootenv.rc for that setting. 555 */ 556 if ((!console_mode_set) && 557 (console == CONS_TTYA || console == CONS_TTYB)) 558 serial_init(); 559 return; 560 } 561 562 for (i = 0; devnames[i] != NULL; i++) { 563 consolep = console_devices; 564 for (; consolep->name[0] != '\0'; consolep++) { 565 if (strcmp(devnames[i], consolep->name) == 0) { 566 cons = consolep->value; 567 } 568 } 569 if (cons != CONS_INVALID) 570 break; 571 } 572 573 if (cons == CONS_INVALID) 574 return; 575 if (cons == console) 576 return; 577 578 console = cons; 579 if (cons == CONS_TTYA || cons == CONS_TTYB) { 580 serial_init(); 581 return; 582 } 583 } 584 585 /* 586 * USB serial -- we just collect data into a buffer 587 */ 588 if (console == CONS_USBSER) { 589 extern void *usbser_init(size_t); 590 usbser_buf = usbser_cur = usbser_init(MMU_PAGESIZE); 591 } 592 #endif /* _BOOT */ 593 } 594 595 #if !defined(_BOOT) 596 static void 597 usbser_putchar(int c) 598 { 599 if (usbser_cur - usbser_buf < MMU_PAGESIZE) 600 *usbser_cur++ = c; 601 } 602 #endif /* _BOOT */ 603 604 static void 605 serial_putchar(int c) 606 { 607 int checks = 10000; 608 609 while (((inb(port + LSR) & XHRE) == 0) && checks--) 610 ; 611 outb(port + DAT, (char)c); 612 } 613 614 static int 615 serial_getchar(void) 616 { 617 uchar_t lsr; 618 619 while (serial_ischar() == 0) 620 ; 621 622 lsr = inb(port + LSR); 623 if (lsr & (SERIAL_BREAK | SERIAL_FRAME | 624 SERIAL_PARITY | SERIAL_OVERRUN)) { 625 if (lsr & SERIAL_OVERRUN) { 626 return (inb(port + DAT)); 627 } else { 628 /* Toss the garbage */ 629 (void) inb(port + DAT); 630 return (0); 631 } 632 } 633 return (inb(port + DAT)); 634 } 635 636 static int 637 serial_ischar(void) 638 { 639 return (inb(port + LSR) & RCA); 640 } 641 642 static void 643 _doputchar(int c) 644 { 645 switch (console) { 646 case CONS_TTYA: 647 case CONS_TTYB: 648 serial_putchar(c); 649 return; 650 case CONS_SCREEN_TEXT: 651 screen_putchar(c); 652 return; 653 #if !defined(_BOOT) 654 case CONS_USBSER: 655 usbser_putchar(c); 656 return; 657 #endif /* _BOOT */ 658 } 659 } 660 661 void 662 bcons_putchar(int c) 663 { 664 static int bhcharpos = 0; 665 666 if (c == '\t') { 667 do { 668 _doputchar(' '); 669 } while (++bhcharpos % 8); 670 return; 671 } else if (c == '\n' || c == '\r') { 672 bhcharpos = 0; 673 _doputchar('\r'); 674 _doputchar(c); 675 return; 676 } else if (c == '\b') { 677 if (bhcharpos) 678 bhcharpos--; 679 _doputchar(c); 680 return; 681 } 682 683 bhcharpos++; 684 _doputchar(c); 685 } 686 687 /* 688 * kernel character input functions 689 */ 690 int 691 bcons_getchar(void) 692 { 693 switch (console) { 694 case CONS_TTYA: 695 case CONS_TTYB: 696 return (serial_getchar()); 697 default: 698 return (kb_getchar()); 699 } 700 } 701 702 #if !defined(_BOOT) 703 704 int 705 bcons_ischar(void) 706 { 707 switch (console) { 708 case CONS_TTYA: 709 case CONS_TTYB: 710 return (serial_ischar()); 711 default: 712 return (kb_ischar()); 713 } 714 } 715 716 #endif /* _BOOT */ 717