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 33 #include "boot_serial.h" 34 #include "boot_vga.h" 35 36 #if defined(_BOOT) 37 #include "../dboot/dboot_xboot.h" 38 #include <util/string.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 /* Clear the screen and initialize VIDEO, XPOS and YPOS. */ 56 static void 57 clear_screen(void) 58 { 59 /* 60 * XXX should set vga mode so we don't depend on the 61 * state left by the boot loader 62 */ 63 vga_clear(cons_color); 64 vga_setpos(0, 0); 65 } 66 67 /* Put the character C on the screen. */ 68 static void 69 screen_putchar(int c) 70 { 71 int row, col; 72 73 vga_getpos(&row, &col); 74 switch (c) { 75 case '\t': 76 col += 8 - (col % 8); 77 if (col == VGA_TEXT_COLS) 78 col = 79; 79 vga_setpos(row, col); 80 break; 81 82 case '\r': 83 vga_setpos(row, 0); 84 break; 85 86 case '\b': 87 if (col > 0) 88 vga_setpos(row, col - 1); 89 break; 90 91 case '\n': 92 if (row < VGA_TEXT_ROWS - 1) 93 vga_setpos(row + 1, col); 94 else 95 vga_scroll(cons_color); 96 break; 97 98 default: 99 vga_drawc(c, cons_color); 100 if (col < VGA_TEXT_COLS -1) 101 vga_setpos(row, col + 1); 102 else if (row < VGA_TEXT_ROWS - 1) 103 vga_setpos(row + 1, 0); 104 else { 105 vga_setpos(row, 0); 106 vga_scroll(cons_color); 107 } 108 break; 109 } 110 } 111 112 /* serial port stuff */ 113 static int port; 114 115 static void 116 serial_init(void) 117 { 118 switch (console) { 119 case CONS_TTYA: 120 port = 0x3f8; 121 break; 122 case CONS_TTYB: 123 port = 0x2f8; 124 break; 125 } 126 127 outb(port + ISR, 0x20); 128 if (inb(port + ISR) & 0x20) { 129 /* 130 * 82510 chip is present 131 */ 132 outb(port + DAT+7, 0x04); /* clear status */ 133 outb(port + ISR, 0x40); /* set to bank 2 */ 134 outb(port + MCR, 0x08); /* IMD */ 135 outb(port + DAT, 0x21); /* FMD */ 136 outb(port + ISR, 0x00); /* set to bank 0 */ 137 } else { 138 /* 139 * set the UART in FIFO mode if it has FIFO buffers. 140 * use 16550 fifo reset sequence specified in NS 141 * application note. disable fifos until chip is 142 * initialized. 143 */ 144 outb(port + FIFOR, 0x00); /* clear */ 145 outb(port + FIFOR, FIFO_ON); /* enable */ 146 outb(port + FIFOR, FIFO_ON|FIFORXFLSH); /* reset */ 147 outb(port + FIFOR, 148 FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80); 149 if ((inb(port + ISR) & 0xc0) != 0xc0) { 150 /* 151 * no fifo buffers so disable fifos. 152 * this is true for 8250's 153 */ 154 outb(port + FIFOR, 0x00); 155 } 156 } 157 158 /* disable interrupts */ 159 outb(port + ICR, 0); 160 161 /* adjust setting based on tty properties */ 162 serial_adjust_prop(); 163 164 #if defined(_BOOT) 165 /* 166 * Do a full reset to match console behavior. 167 * 0x1B + c - reset everything 168 */ 169 serial_putchar(0x1B); 170 serial_putchar('c'); 171 #endif 172 } 173 174 175 #define MATCHES(p, pat) \ 176 (strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0) 177 178 #define SKIP(p, c) \ 179 while (*(p) != 0 && *p != (c)) \ 180 ++(p); \ 181 if (*(p) == (c)) \ 182 ++(p); 183 184 /* 185 * find a tty mode property either from cmdline or from boot properties 186 */ 187 static char * 188 get_mode_value(char *name) 189 { 190 char *p; 191 192 /* 193 * when specified on boot line it looks like "name" "=".... 194 */ 195 if (boot_line != NULL) { 196 p = strstr(boot_line, name); 197 if (p == NULL) 198 return (NULL); 199 SKIP(p, '='); 200 return (p); 201 } 202 203 #if defined(_BOOT) 204 return (NULL); 205 #else 206 /* 207 * if we're running in the full kernel we check the bootenv.rc settings 208 */ 209 { 210 static char propval[20]; 211 212 propval[0] = 0; 213 if (bootops == NULL || BOP_GETPROPLEN(bootops, name) == 0) 214 return (NULL); 215 (void) BOP_GETPROP(bootops, name, propval); 216 return (propval); 217 } 218 #endif 219 } 220 221 /* 222 * adjust serial port based on properties 223 * These come either from the cmdline or from boot properties. 224 */ 225 static void 226 serial_adjust_prop(void) 227 { 228 char propname[20]; 229 char *propval; 230 char *p; 231 ulong_t baud; 232 uchar_t lcr = 0; 233 uchar_t mcr = DTR | RTS; 234 235 (void) strcpy(propname, "ttyX-mode"); 236 propname[3] = 'a' + console - CONS_TTYA; 237 propval = get_mode_value(propname); 238 if (propval == NULL) 239 propval = "9600,8,n,1,-"; 240 241 /* property is of the form: "9600,8,n,1,-" */ 242 p = propval; 243 if (MATCHES(p, "110,")) 244 baud = ASY110; 245 else if (MATCHES(p, "150,")) 246 baud = ASY150; 247 else if (MATCHES(p, "300,")) 248 baud = ASY300; 249 else if (MATCHES(p, "600,")) 250 baud = ASY600; 251 else if (MATCHES(p, "1200,")) 252 baud = ASY1200; 253 else if (MATCHES(p, "2400,")) 254 baud = ASY2400; 255 else if (MATCHES(p, "4800,")) 256 baud = ASY4800; 257 else if (MATCHES(p, "19200,")) 258 baud = ASY19200; 259 else if (MATCHES(p, "38400,")) 260 baud = ASY38400; 261 else if (MATCHES(p, "57600,")) 262 baud = ASY57600; 263 else if (MATCHES(p, "115200,")) 264 baud = ASY115200; 265 else { 266 baud = ASY9600; 267 SKIP(p, ','); 268 } 269 outb(port + LCR, DLAB); 270 outb(port + DAT + DLL, baud & 0xff); 271 outb(port + DAT + DLH, (baud >> 8) & 0xff); 272 273 switch (*p) { 274 case '5': 275 lcr |= BITS5; 276 ++p; 277 break; 278 case '6': 279 lcr |= BITS6; 280 ++p; 281 break; 282 case '7': 283 lcr |= BITS7; 284 ++p; 285 break; 286 case '8': 287 ++p; 288 default: 289 lcr |= BITS8; 290 break; 291 } 292 293 SKIP(p, ','); 294 295 switch (*p) { 296 case 'n': 297 lcr |= PARITY_NONE; 298 ++p; 299 break; 300 case 'o': 301 lcr |= PARITY_ODD; 302 ++p; 303 break; 304 case 'e': 305 ++p; 306 default: 307 lcr |= PARITY_EVEN; 308 break; 309 } 310 311 312 SKIP(p, ','); 313 314 switch (*p) { 315 case '1': 316 /* STOP1 is 0 */ 317 ++p; 318 break; 319 default: 320 lcr |= STOP2; 321 break; 322 } 323 /* set parity bits */ 324 outb(port + LCR, lcr); 325 326 (void) strcpy(propname, "ttyX-rts-dtr-off"); 327 propname[3] = 'a' + console - CONS_TTYA; 328 propval = get_mode_value(propname); 329 if (propval == NULL) 330 propval = "false"; 331 if (propval[0] != 'f' && propval[0] != 'F') 332 mcr = 0; 333 /* set modem control bits */ 334 outb(port + MCR, mcr | OUT2); 335 } 336 337 void 338 bcons_init(char *bootstr) 339 { 340 boot_line = bootstr; 341 console = CONS_INVALID; 342 343 if (strstr(bootstr, "console=ttya") != 0) 344 console = CONS_TTYA; 345 else if (strstr(bootstr, "console=ttyb") != 0) 346 console = CONS_TTYB; 347 else if (strstr(bootstr, "console=text") != 0) 348 console = CONS_SCREEN_TEXT; 349 350 /* 351 * If no console device specified, default to text. 352 * Remember what was specified for second phase. 353 */ 354 if (console == CONS_INVALID) 355 console = CONS_SCREEN_TEXT; 356 357 switch (console) { 358 case CONS_TTYA: 359 case CONS_TTYB: 360 serial_init(); 361 break; 362 363 case CONS_SCREEN_TEXT: 364 default: 365 #if defined(_BOOT) 366 clear_screen(); /* clears the grub screen */ 367 #endif 368 kb_init(); 369 break; 370 } 371 boot_line = NULL; 372 } 373 374 /* 375 * 2nd part of console initialization. 376 * In the kernel (ie. fakebop), this can be used only to switch to 377 * using a serial port instead of screen based on the contents 378 * of the bootenv.rc file. 379 */ 380 /*ARGSUSED*/ 381 void 382 bcons_init2(char *inputdev, char *outputdev, char *consoledev) 383 { 384 #if !defined(_BOOT) 385 int cons = CONS_INVALID; 386 387 if (consoledev) { 388 if (strstr(consoledev, "ttya") != 0) 389 cons = CONS_TTYA; 390 else if (strstr(consoledev, "ttyb") != 0) 391 cons = CONS_TTYB; 392 else if (strstr(consoledev, "usb-serial") != 0) 393 cons = CONS_USBSER; 394 } 395 396 if (cons == CONS_INVALID && inputdev) { 397 if (strstr(inputdev, "ttya") != 0) 398 cons = CONS_TTYA; 399 else if (strstr(inputdev, "ttyb") != 0) 400 cons = CONS_TTYB; 401 else if (strstr(inputdev, "usb-serial") != 0) 402 cons = CONS_USBSER; 403 } 404 405 if (cons == CONS_INVALID && outputdev) { 406 if (strstr(outputdev, "ttya") != 0) 407 cons = CONS_TTYA; 408 else if (strstr(outputdev, "ttyb") != 0) 409 cons = CONS_TTYB; 410 else if (strstr(outputdev, "usb-serial") != 0) 411 cons = CONS_USBSER; 412 } 413 414 if (cons == CONS_INVALID) 415 return; 416 if (cons == console) 417 return; 418 419 console = cons; 420 if (cons == CONS_TTYA || cons == CONS_TTYB) { 421 serial_init(); 422 return; 423 } 424 425 /* 426 * USB serial -- we just collect data into a buffer 427 */ 428 if (cons == CONS_USBSER) { 429 extern void *usbser_init(size_t); 430 usbser_buf = usbser_cur = usbser_init(MMU_PAGESIZE); 431 } 432 #endif /* _BOOT */ 433 } 434 435 #if !defined(_BOOT) 436 static void 437 usbser_putchar(int c) 438 { 439 if (usbser_cur - usbser_buf < MMU_PAGESIZE) 440 *usbser_cur++ = c; 441 } 442 #endif /* _BOOT */ 443 444 static void 445 serial_putchar(int c) 446 { 447 int checks = 10000; 448 449 while (((inb(port + LSR) & XHRE) == 0) && checks--) 450 ; 451 outb(port + DAT, (char)c); 452 } 453 454 static int 455 serial_getchar(void) 456 { 457 uchar_t lsr; 458 459 while (serial_ischar() == 0) 460 ; 461 462 lsr = inb(port + LSR); 463 if (lsr & (SERIAL_BREAK | SERIAL_FRAME | 464 SERIAL_PARITY | SERIAL_OVERRUN)) { 465 if (lsr & SERIAL_OVERRUN) { 466 return (inb(port + DAT)); 467 } else { 468 /* Toss the garbage */ 469 (void) inb(port + DAT); 470 return (0); 471 } 472 } 473 return (inb(port + DAT)); 474 } 475 476 static int 477 serial_ischar(void) 478 { 479 return (inb(port + LSR) & RCA); 480 } 481 482 static void 483 _doputchar(int c) 484 { 485 switch (console) { 486 case CONS_TTYA: 487 case CONS_TTYB: 488 serial_putchar(c); 489 return; 490 case CONS_SCREEN_TEXT: 491 screen_putchar(c); 492 return; 493 #if !defined(_BOOT) 494 case CONS_USBSER: 495 usbser_putchar(c); 496 return; 497 #endif /* _BOOT */ 498 } 499 } 500 501 void 502 bcons_putchar(int c) 503 { 504 static int bhcharpos = 0; 505 506 if (c == '\t') { 507 do { 508 _doputchar(' '); 509 } while (++bhcharpos % 8); 510 return; 511 } else if (c == '\n' || c == '\r') { 512 bhcharpos = 0; 513 _doputchar('\r'); 514 _doputchar(c); 515 return; 516 } else if (c == '\b') { 517 if (bhcharpos) 518 bhcharpos--; 519 _doputchar(c); 520 return; 521 } 522 523 bhcharpos++; 524 _doputchar(c); 525 } 526 527 /* 528 * kernel character input functions 529 */ 530 int 531 bcons_getchar(void) 532 { 533 switch (console) { 534 case CONS_TTYA: 535 case CONS_TTYB: 536 return (serial_getchar()); 537 default: 538 return (kb_getchar()); 539 } 540 } 541 542 #if !defined(_BOOT) 543 544 int 545 bcons_ischar(void) 546 { 547 switch (console) { 548 case CONS_TTYA: 549 case CONS_TTYB: 550 return (serial_ischar()); 551 default: 552 return (kb_ischar()); 553 } 554 } 555 556 #endif /* _BOOT */ 557