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 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <sys/types.h> 27 #include <sys/systm.h> 28 #include <sys/archsystm.h> 29 #include <sys/boot_console.h> 30 #include <sys/panic.h> 31 #include <sys/ctype.h> 32 #if defined(__xpv) 33 #include <sys/hypervisor.h> 34 #endif /* __xpv */ 35 36 #include "boot_serial.h" 37 #include "boot_vga.h" 38 39 #if defined(_BOOT) 40 #include <dboot/dboot_asm.h> 41 #include <dboot/dboot_xboot.h> 42 #else /* _BOOT */ 43 #include <sys/bootconf.h> 44 #if defined(__xpv) 45 #include <sys/evtchn_impl.h> 46 #endif /* __xpv */ 47 static char *defcons_buf; 48 static char *defcons_cur; 49 #endif /* _BOOT */ 50 51 #if defined(__xpv) 52 extern void bcons_init_xen(char *); 53 extern void bcons_putchar_xen(int); 54 extern int bcons_getchar_xen(void); 55 extern int bcons_ischar_xen(void); 56 #endif /* __xpv */ 57 58 static int cons_color = CONS_COLOR; 59 int console = CONS_SCREEN_TEXT; 60 #if defined(__xpv) 61 static int console_hypervisor_redirect = B_FALSE; 62 int console_hypervisor_device = CONS_INVALID; 63 #endif /* __xpv */ 64 65 static int serial_ischar(void); 66 static int serial_getchar(void); 67 static void serial_putchar(int); 68 static void serial_adjust_prop(void); 69 70 static char *boot_line = NULL; 71 72 #if !defined(_BOOT) 73 /* Set if the console or mode are expressed in the boot line */ 74 static int console_set, console_mode_set; 75 #endif 76 77 /* Clear the screen and initialize VIDEO, XPOS and YPOS. */ 78 void 79 clear_screen(void) 80 { 81 /* 82 * XXX should set vga mode so we don't depend on the 83 * state left by the boot loader. Note that we have to 84 * enable the cursor before clearing the screen since 85 * the cursor position is dependant upon the cursor 86 * skew, which is initialized by vga_cursor_display() 87 */ 88 vga_cursor_display(); 89 vga_clear(cons_color); 90 vga_setpos(0, 0); 91 } 92 93 /* Put the character C on the screen. */ 94 static void 95 screen_putchar(int c) 96 { 97 int row, col; 98 99 vga_getpos(&row, &col); 100 switch (c) { 101 case '\t': 102 col += 8 - (col % 8); 103 if (col == VGA_TEXT_COLS) 104 col = 79; 105 vga_setpos(row, col); 106 break; 107 108 case '\r': 109 vga_setpos(row, 0); 110 break; 111 112 case '\b': 113 if (col > 0) 114 vga_setpos(row, col - 1); 115 break; 116 117 case '\n': 118 if (row < VGA_TEXT_ROWS - 1) 119 vga_setpos(row + 1, col); 120 else 121 vga_scroll(cons_color); 122 break; 123 124 default: 125 vga_drawc(c, cons_color); 126 if (col < VGA_TEXT_COLS -1) 127 vga_setpos(row, col + 1); 128 else if (row < VGA_TEXT_ROWS - 1) 129 vga_setpos(row + 1, 0); 130 else { 131 vga_setpos(row, 0); 132 vga_scroll(cons_color); 133 } 134 break; 135 } 136 } 137 138 static int port; 139 140 static void 141 serial_init(void) 142 { 143 switch (console) { 144 case CONS_TTYA: 145 port = 0x3f8; 146 break; 147 case CONS_TTYB: 148 port = 0x2f8; 149 break; 150 } 151 152 outb(port + ISR, 0x20); 153 if (inb(port + ISR) & 0x20) { 154 /* 155 * 82510 chip is present 156 */ 157 outb(port + DAT+7, 0x04); /* clear status */ 158 outb(port + ISR, 0x40); /* set to bank 2 */ 159 outb(port + MCR, 0x08); /* IMD */ 160 outb(port + DAT, 0x21); /* FMD */ 161 outb(port + ISR, 0x00); /* set to bank 0 */ 162 } else { 163 /* 164 * set the UART in FIFO mode if it has FIFO buffers. 165 * use 16550 fifo reset sequence specified in NS 166 * application note. disable fifos until chip is 167 * initialized. 168 */ 169 outb(port + FIFOR, 0x00); /* clear */ 170 outb(port + FIFOR, FIFO_ON); /* enable */ 171 outb(port + FIFOR, FIFO_ON|FIFORXFLSH); /* reset */ 172 outb(port + FIFOR, 173 FIFO_ON|FIFODMA|FIFOTXFLSH|FIFORXFLSH|0x80); 174 if ((inb(port + ISR) & 0xc0) != 0xc0) { 175 /* 176 * no fifo buffers so disable fifos. 177 * this is true for 8250's 178 */ 179 outb(port + FIFOR, 0x00); 180 } 181 } 182 183 /* disable interrupts */ 184 outb(port + ICR, 0); 185 186 #if !defined(_BOOT) 187 if (IN_XPV_PANIC()) 188 return; 189 #endif 190 191 /* adjust setting based on tty properties */ 192 serial_adjust_prop(); 193 194 #if defined(_BOOT) 195 /* 196 * Do a full reset to match console behavior. 197 * 0x1B + c - reset everything 198 */ 199 serial_putchar(0x1B); 200 serial_putchar('c'); 201 #endif 202 } 203 204 /* Advance str pointer past white space */ 205 #define EAT_WHITE_SPACE(str) { \ 206 while ((*str != '\0') && ISSPACE(*str)) \ 207 str++; \ 208 } 209 210 /* 211 * boot_line is set when we call here. Search it for the argument name, 212 * and if found, return a pointer to it. 213 */ 214 static char * 215 find_boot_line_prop(const char *name) 216 { 217 char *ptr; 218 char *ret = NULL; 219 char end_char; 220 size_t len; 221 222 if (boot_line == NULL) 223 return (NULL); 224 225 len = strlen(name); 226 227 /* 228 * We have two nested loops here: the outer loop discards all options 229 * except -B, and the inner loop parses the -B options looking for 230 * the one we're interested in. 231 */ 232 for (ptr = boot_line; *ptr != '\0'; ptr++) { 233 EAT_WHITE_SPACE(ptr); 234 235 if (*ptr == '-') { 236 ptr++; 237 while ((*ptr != '\0') && (*ptr != 'B') && 238 !ISSPACE(*ptr)) 239 ptr++; 240 if (*ptr == '\0') 241 goto out; 242 else if (*ptr != 'B') 243 continue; 244 } else { 245 while ((*ptr != '\0') && !ISSPACE(*ptr)) 246 ptr++; 247 if (*ptr == '\0') 248 goto out; 249 continue; 250 } 251 252 do { 253 ptr++; 254 EAT_WHITE_SPACE(ptr); 255 256 if ((strncmp(ptr, name, len) == 0) && 257 (ptr[len] == '=')) { 258 ptr += len + 1; 259 if ((*ptr == '\'') || (*ptr == '"')) { 260 ret = ptr + 1; 261 end_char = *ptr; 262 ptr++; 263 } else { 264 ret = ptr; 265 end_char = ','; 266 } 267 goto consume_property; 268 } 269 270 /* 271 * We have a property, and it's not the one we're 272 * interested in. Skip the property name. A name 273 * can end with '=', a comma, or white space. 274 */ 275 while ((*ptr != '\0') && (*ptr != '=') && 276 (*ptr != ',') && (!ISSPACE(*ptr))) 277 ptr++; 278 279 /* 280 * We only want to go through the rest of the inner 281 * loop if we have a comma. If we have a property 282 * name without a value, either continue or break. 283 */ 284 if (*ptr == '\0') 285 goto out; 286 else if (*ptr == ',') 287 continue; 288 else if (ISSPACE(*ptr)) 289 break; 290 ptr++; 291 292 /* 293 * Is the property quoted? 294 */ 295 if ((*ptr == '\'') || (*ptr == '"')) { 296 end_char = *ptr; 297 ptr++; 298 } else { 299 /* 300 * Not quoted, so the string ends at a comma 301 * or at white space. Deal with white space 302 * later. 303 */ 304 end_char = ','; 305 } 306 307 /* 308 * Now, we can ignore any characters until we find 309 * end_char. 310 */ 311 consume_property: 312 for (; (*ptr != '\0') && (*ptr != end_char); ptr++) { 313 if ((end_char == ',') && ISSPACE(*ptr)) 314 break; 315 } 316 if (*ptr && (*ptr != ',') && !ISSPACE(*ptr)) 317 ptr++; 318 } while (*ptr == ','); 319 } 320 out: 321 return (ret); 322 } 323 324 325 #define MATCHES(p, pat) \ 326 (strncmp(p, pat, strlen(pat)) == 0 ? (p += strlen(pat), 1) : 0) 327 328 #define SKIP(p, c) \ 329 while (*(p) != 0 && *p != (c)) \ 330 ++(p); \ 331 if (*(p) == (c)) \ 332 ++(p); 333 334 /* 335 * find a tty mode property either from cmdline or from boot properties 336 */ 337 static char * 338 get_mode_value(char *name) 339 { 340 /* 341 * when specified on boot line it looks like "name" "=".... 342 */ 343 if (boot_line != NULL) { 344 return (find_boot_line_prop(name)); 345 } 346 347 #if defined(_BOOT) 348 return (NULL); 349 #else 350 /* 351 * if we're running in the full kernel we check the bootenv.rc settings 352 */ 353 { 354 static char propval[20]; 355 356 propval[0] = 0; 357 if (do_bsys_getproplen(NULL, name) <= 0) 358 return (NULL); 359 (void) do_bsys_getprop(NULL, name, propval); 360 return (propval); 361 } 362 #endif 363 } 364 365 /* 366 * adjust serial port based on properties 367 * These come either from the cmdline or from boot properties. 368 */ 369 static void 370 serial_adjust_prop(void) 371 { 372 char propname[20]; 373 char *propval; 374 char *p; 375 ulong_t baud; 376 uchar_t lcr = 0; 377 uchar_t mcr = DTR | RTS; 378 379 (void) strcpy(propname, "ttyX-mode"); 380 propname[3] = 'a' + console - CONS_TTYA; 381 propval = get_mode_value(propname); 382 if (propval == NULL) 383 propval = "9600,8,n,1,-"; 384 #if !defined(_BOOT) 385 else 386 console_mode_set = 1; 387 #endif 388 389 /* property is of the form: "9600,8,n,1,-" */ 390 p = propval; 391 if (MATCHES(p, "110,")) 392 baud = ASY110; 393 else if (MATCHES(p, "150,")) 394 baud = ASY150; 395 else if (MATCHES(p, "300,")) 396 baud = ASY300; 397 else if (MATCHES(p, "600,")) 398 baud = ASY600; 399 else if (MATCHES(p, "1200,")) 400 baud = ASY1200; 401 else if (MATCHES(p, "2400,")) 402 baud = ASY2400; 403 else if (MATCHES(p, "4800,")) 404 baud = ASY4800; 405 else if (MATCHES(p, "19200,")) 406 baud = ASY19200; 407 else if (MATCHES(p, "38400,")) 408 baud = ASY38400; 409 else if (MATCHES(p, "57600,")) 410 baud = ASY57600; 411 else if (MATCHES(p, "115200,")) 412 baud = ASY115200; 413 else { 414 baud = ASY9600; 415 SKIP(p, ','); 416 } 417 outb(port + LCR, DLAB); 418 outb(port + DAT + DLL, baud & 0xff); 419 outb(port + DAT + DLH, (baud >> 8) & 0xff); 420 421 switch (*p) { 422 case '5': 423 lcr |= BITS5; 424 ++p; 425 break; 426 case '6': 427 lcr |= BITS6; 428 ++p; 429 break; 430 case '7': 431 lcr |= BITS7; 432 ++p; 433 break; 434 case '8': 435 ++p; 436 default: 437 lcr |= BITS8; 438 break; 439 } 440 441 SKIP(p, ','); 442 443 switch (*p) { 444 case 'n': 445 lcr |= PARITY_NONE; 446 ++p; 447 break; 448 case 'o': 449 lcr |= PARITY_ODD; 450 ++p; 451 break; 452 case 'e': 453 ++p; 454 default: 455 lcr |= PARITY_EVEN; 456 break; 457 } 458 459 460 SKIP(p, ','); 461 462 switch (*p) { 463 case '1': 464 /* STOP1 is 0 */ 465 ++p; 466 break; 467 default: 468 lcr |= STOP2; 469 break; 470 } 471 /* set parity bits */ 472 outb(port + LCR, lcr); 473 474 (void) strcpy(propname, "ttyX-rts-dtr-off"); 475 propname[3] = 'a' + console - CONS_TTYA; 476 propval = get_mode_value(propname); 477 if (propval == NULL) 478 propval = "false"; 479 if (propval[0] != 'f' && propval[0] != 'F') 480 mcr = 0; 481 /* set modem control bits */ 482 outb(port + MCR, mcr | OUT2); 483 } 484 485 /* 486 * A structure to map console names to values. 487 */ 488 typedef struct { 489 char *name; 490 int value; 491 } console_value_t; 492 493 console_value_t console_devices[] = { 494 { "ttya", CONS_TTYA }, 495 { "ttyb", CONS_TTYB }, 496 { "text", CONS_SCREEN_TEXT }, 497 { "graphics", CONS_SCREEN_GRAPHICS }, 498 #if defined(__xpv) 499 { "hypervisor", CONS_HYPERVISOR }, 500 #endif 501 #if !defined(_BOOT) 502 { "usb-serial", CONS_USBSER }, 503 #endif 504 { "", CONS_INVALID } 505 }; 506 507 void 508 bcons_init(char *bootstr) 509 { 510 console_value_t *consolep; 511 size_t len, cons_len; 512 char *cons_str; 513 514 boot_line = bootstr; 515 console = CONS_INVALID; 516 517 #if defined(__xpv) 518 bcons_init_xen(bootstr); 519 #endif /* __xpv */ 520 521 cons_str = find_boot_line_prop("console"); 522 if (cons_str == NULL) 523 cons_str = find_boot_line_prop("output-device"); 524 525 /* 526 * Go through the console_devices array trying to match the string 527 * we were given. The string on the command line must end with 528 * a comma or white space. 529 */ 530 if (cons_str != NULL) { 531 cons_len = strlen(cons_str); 532 consolep = console_devices; 533 for (; consolep->name[0] != '\0'; consolep++) { 534 len = strlen(consolep->name); 535 if ((len <= cons_len) && ((cons_str[len] == '\0') || 536 (cons_str[len] == ',') || (cons_str[len] == '\'') || 537 (cons_str[len] == '"') || ISSPACE(cons_str[len])) && 538 (strncmp(cons_str, consolep->name, len) == 0)) { 539 console = consolep->value; 540 break; 541 } 542 } 543 } 544 545 #if defined(__xpv) 546 /* 547 * domU's always use the hypervisor regardless of what 548 * the console variable may be set to. 549 */ 550 if (!DOMAIN_IS_INITDOMAIN(xen_info)) { 551 console = CONS_HYPERVISOR; 552 console_hypervisor_redirect = B_TRUE; 553 } 554 #endif /* __xpv */ 555 556 /* 557 * If no console device specified, default to text. 558 * Remember what was specified for second phase. 559 */ 560 if (console == CONS_INVALID) 561 console = CONS_SCREEN_TEXT; 562 #if !defined(_BOOT) 563 else 564 console_set = 1; 565 #endif 566 567 #if defined(__xpv) 568 if (DOMAIN_IS_INITDOMAIN(xen_info)) { 569 switch (HYPERVISOR_console_io(CONSOLEIO_get_device, 0, NULL)) { 570 case XEN_CONSOLE_COM1: 571 console_hypervisor_device = CONS_TTYA; 572 break; 573 case XEN_CONSOLE_COM2: 574 console_hypervisor_device = CONS_TTYB; 575 break; 576 case XEN_CONSOLE_VGA: 577 /* 578 * Currently xen doesn't really support 579 * keyboard/display console devices. 580 * What this setting means is that 581 * "vga=keep" has been enabled, which is 582 * more of a xen debugging tool that a 583 * true console mode. Hence, we're going 584 * to ignore this xen "console" setting. 585 */ 586 /*FALLTHROUGH*/ 587 default: 588 console_hypervisor_device = CONS_INVALID; 589 } 590 } 591 592 /* 593 * if the hypervisor is using the currently selected serial 594 * port then default to using the hypervisor as the console 595 * device. 596 */ 597 if (console == console_hypervisor_device) { 598 console = CONS_HYPERVISOR; 599 console_hypervisor_redirect = B_TRUE; 600 } 601 #endif /* __xpv */ 602 603 switch (console) { 604 case CONS_TTYA: 605 case CONS_TTYB: 606 serial_init(); 607 break; 608 609 case CONS_HYPERVISOR: 610 break; 611 612 #if !defined(_BOOT) 613 case CONS_USBSER: 614 /* 615 * We can't do anything with the usb serial 616 * until we have memory management. 617 */ 618 break; 619 #endif 620 case CONS_SCREEN_GRAPHICS: 621 kb_init(); 622 break; 623 case CONS_SCREEN_TEXT: 624 default: 625 #if defined(_BOOT) 626 clear_screen(); /* clears the grub or xen screen */ 627 #endif /* _BOOT */ 628 kb_init(); 629 break; 630 } 631 boot_line = NULL; 632 } 633 634 #if !defined(_BOOT) 635 /* 636 * 2nd part of console initialization. 637 * In the kernel (ie. fakebop), this can be used only to switch to 638 * using a serial port instead of screen based on the contents 639 * of the bootenv.rc file. 640 */ 641 /*ARGSUSED*/ 642 void 643 bcons_init2(char *inputdev, char *outputdev, char *consoledev) 644 { 645 int cons = CONS_INVALID; 646 char *devnames[] = { consoledev, outputdev, inputdev, NULL }; 647 console_value_t *consolep; 648 int i; 649 650 if (console != CONS_USBSER && console != CONS_SCREEN_GRAPHICS) { 651 if (console_set) { 652 /* 653 * If the console was set on the command line, 654 * but the ttyX-mode was not, we only need to 655 * check bootenv.rc for that setting. 656 */ 657 if ((!console_mode_set) && 658 (console == CONS_TTYA || console == CONS_TTYB)) 659 serial_init(); 660 return; 661 } 662 663 for (i = 0; devnames[i] != NULL; i++) { 664 consolep = console_devices; 665 for (; consolep->name[0] != '\0'; consolep++) { 666 if (strcmp(devnames[i], consolep->name) == 0) { 667 cons = consolep->value; 668 } 669 } 670 if (cons != CONS_INVALID) 671 break; 672 } 673 674 #if defined(__xpv) 675 /* 676 * if the hypervisor is using the currently selected console 677 * device then default to using the hypervisor as the console 678 * device. 679 */ 680 if (cons == console_hypervisor_device) { 681 cons = CONS_HYPERVISOR; 682 console_hypervisor_redirect = B_TRUE; 683 } 684 #endif /* __xpv */ 685 686 if ((cons == CONS_INVALID) || (cons == console)) { 687 /* 688 * we're sticking with whatever the current setting is 689 */ 690 return; 691 } 692 693 console = cons; 694 if (cons == CONS_TTYA || cons == CONS_TTYB) { 695 serial_init(); 696 return; 697 } 698 } else { 699 /* 700 * USB serial and GRAPHICS console 701 * we just collect data into a buffer 702 */ 703 extern void *defcons_init(size_t); 704 defcons_buf = defcons_cur = defcons_init(MMU_PAGESIZE); 705 } 706 } 707 708 #if defined(__xpv) 709 boolean_t 710 bcons_hypervisor_redirect(void) 711 { 712 return (console_hypervisor_redirect); 713 } 714 715 void 716 bcons_device_change(int new_console) 717 { 718 if (new_console < CONS_MIN || new_console > CONS_MAX) 719 return; 720 721 /* 722 * If we are asked to switch the console to the hypervisor, that 723 * really means to switch the console to whichever device the 724 * hypervisor is/was using. 725 */ 726 if (new_console == CONS_HYPERVISOR) 727 new_console = console_hypervisor_device; 728 729 console = new_console; 730 731 if (new_console == CONS_TTYA || new_console == CONS_TTYB) 732 serial_init(); 733 } 734 #endif /* __xpv */ 735 736 static void 737 defcons_putchar(int c) 738 { 739 if (defcons_buf != NULL && 740 defcons_cur + 1 - defcons_buf < MMU_PAGESIZE) { 741 *defcons_cur++ = c; 742 *defcons_cur = 0; 743 } 744 } 745 #endif /* _BOOT */ 746 747 static void 748 serial_putchar(int c) 749 { 750 int checks = 10000; 751 752 while (((inb(port + LSR) & XHRE) == 0) && checks--) 753 ; 754 outb(port + DAT, (char)c); 755 } 756 757 static int 758 serial_getchar(void) 759 { 760 uchar_t lsr; 761 762 while (serial_ischar() == 0) 763 ; 764 765 lsr = inb(port + LSR); 766 if (lsr & (SERIAL_BREAK | SERIAL_FRAME | 767 SERIAL_PARITY | SERIAL_OVERRUN)) { 768 if (lsr & SERIAL_OVERRUN) { 769 return (inb(port + DAT)); 770 } else { 771 /* Toss the garbage */ 772 (void) inb(port + DAT); 773 return (0); 774 } 775 } 776 return (inb(port + DAT)); 777 } 778 779 static int 780 serial_ischar(void) 781 { 782 return (inb(port + LSR) & RCA); 783 } 784 785 static void 786 _doputchar(int c) 787 { 788 switch (console) { 789 case CONS_TTYA: 790 case CONS_TTYB: 791 serial_putchar(c); 792 return; 793 case CONS_SCREEN_TEXT: 794 screen_putchar(c); 795 return; 796 case CONS_SCREEN_GRAPHICS: 797 #if !defined(_BOOT) 798 case CONS_USBSER: 799 defcons_putchar(c); 800 #endif /* _BOOT */ 801 return; 802 } 803 } 804 805 void 806 bcons_putchar(int c) 807 { 808 static int bhcharpos = 0; 809 810 #if defined(__xpv) 811 if (!DOMAIN_IS_INITDOMAIN(xen_info) || 812 console == CONS_HYPERVISOR) { 813 bcons_putchar_xen(c); 814 return; 815 } 816 #endif /* __xpv */ 817 818 if (c == '\t') { 819 do { 820 _doputchar(' '); 821 } while (++bhcharpos % 8); 822 return; 823 } else if (c == '\n' || c == '\r') { 824 bhcharpos = 0; 825 _doputchar('\r'); 826 _doputchar(c); 827 return; 828 } else if (c == '\b') { 829 if (bhcharpos) 830 bhcharpos--; 831 _doputchar(c); 832 return; 833 } 834 835 bhcharpos++; 836 _doputchar(c); 837 } 838 839 /* 840 * kernel character input functions 841 */ 842 int 843 bcons_getchar(void) 844 { 845 #if defined(__xpv) 846 if (!DOMAIN_IS_INITDOMAIN(xen_info) || 847 console == CONS_HYPERVISOR) 848 return (bcons_getchar_xen()); 849 #endif /* __xpv */ 850 851 switch (console) { 852 case CONS_TTYA: 853 case CONS_TTYB: 854 return (serial_getchar()); 855 default: 856 return (kb_getchar()); 857 } 858 } 859 860 #if !defined(_BOOT) 861 862 int 863 bcons_ischar(void) 864 { 865 866 #if defined(__xpv) 867 if (!DOMAIN_IS_INITDOMAIN(xen_info) || 868 console == CONS_HYPERVISOR) 869 return (bcons_ischar_xen()); 870 #endif /* __xpv */ 871 872 switch (console) { 873 case CONS_TTYA: 874 case CONS_TTYB: 875 return (serial_ischar()); 876 default: 877 return (kb_ischar()); 878 } 879 } 880 881 #endif /* _BOOT */ 882