boot2.c revision 333049
1/*- 2 * Copyright (c) 2008-2009 TAKAHASHI Yoshihiro 3 * Copyright (c) 1998 Robert Nordier 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms are freely 7 * permitted provided that the above copyright notice and this 8 * paragraph and the following disclaimer are duplicated in all 9 * such forms. 10 * 11 * This software is provided "AS IS" and without any express or 12 * implied warranties, including, without limitation, the implied 13 * warranties of merchantability and fitness for a particular 14 * purpose. 15 */ 16 17#include <sys/cdefs.h> 18__FBSDID("$FreeBSD: stable/11/stand/pc98/boot2/boot2.c 333049 2018-04-27 02:39:36Z nyan $"); 19 20#include <sys/param.h> 21#include <sys/disklabel.h> 22#include <sys/diskpc98.h> 23#include <sys/dirent.h> 24#include <sys/reboot.h> 25 26#include <machine/bootinfo.h> 27#include <machine/cpufunc.h> 28#include <machine/elf.h> 29 30#include <stdarg.h> 31 32#include <a.out.h> 33 34#include <btxv86.h> 35 36#include "boot2.h" 37#include "lib.h" 38#include "paths.h" 39#include "rbx.h" 40 41/* Define to 0 to omit serial support */ 42#ifndef SERIAL 43#define SERIAL 0 44#endif 45 46#define IO_KEYBOARD 1 47#define IO_SERIAL 2 48 49#if SERIAL 50#define DO_KBD (ioctrl & IO_KEYBOARD) 51#define DO_SIO (ioctrl & IO_SERIAL) 52#else 53#define DO_KBD (1) 54#define DO_SIO (0) 55#endif 56 57#define SECOND 1 /* Circa that many ticks in a second. */ 58 59#define ARGS 0x900 60#define NOPT 14 61#define NDEV 3 62 63#define DRV_DISK 0xf0 64#define DRV_UNIT 0x0f 65 66#define TYPE_AD 0 67#define TYPE_DA 1 68#define TYPE_FD 2 69 70extern uint32_t _end; 71 72static const char optstr[NOPT] = "DhaCcdgmnpqrsv"; /* Also 'P', 'S' */ 73static const unsigned char flags[NOPT] = { 74 RBX_DUAL, 75 RBX_SERIAL, 76 RBX_ASKNAME, 77 RBX_CDROM, 78 RBX_CONFIG, 79 RBX_KDB, 80 RBX_GDB, 81 RBX_MUTE, 82 RBX_NOINTR, 83 RBX_PAUSE, 84 RBX_QUIET, 85 RBX_DFLTROOT, 86 RBX_SINGLE, 87 RBX_VERBOSE 88}; 89 90static const char *const dev_nm[NDEV] = {"ad", "da", "fd"}; 91static const unsigned char dev_maj[NDEV] = {30, 4, 2}; 92static const unsigned char dev_daua[NDEV] = {0x80, 0xa0, 0x90}; 93 94static struct dsk { 95 unsigned daua; 96 unsigned type; 97 unsigned disk; 98 unsigned unit; 99 unsigned head; 100 unsigned sec; 101 uint8_t slice; 102 uint8_t part; 103 unsigned start; 104} dsk; 105static char cmd[512], cmddup[512], knamebuf[1024]; 106static const char *kname; 107uint32_t opts; 108static struct bootinfo bootinfo; 109#if SERIAL 110static int comspeed = SIOSPD; 111static uint8_t ioctrl = IO_KEYBOARD; 112#endif 113 114int main(void); 115void exit(int); 116static void load(void); 117static int parse(void); 118static int dskread(void *, unsigned, unsigned); 119static void printf(const char *,...); 120static void putchar(int); 121static int drvread(void *, unsigned); 122static int keyhit(unsigned); 123static int xputc(int); 124static int xgetc(int); 125static inline int getc(int); 126 127static void memcpy(void *, const void *, int); 128static void 129memcpy(void *dst, const void *src, int len) 130{ 131 const char *s = src; 132 char *d = dst; 133 134 while (len--) 135 *d++ = *s++; 136} 137 138static inline int 139strcmp(const char *s1, const char *s2) 140{ 141 142 for (; *s1 == *s2 && *s1; s1++, s2++); 143 return ((unsigned char)*s1 - (unsigned char)*s2); 144} 145 146#define UFS_SMALL_CGBASE 147#include "ufsread.c" 148 149static inline int 150xfsread(ufs_ino_t inode, void *buf, size_t nbyte) 151{ 152 153 if ((size_t)fsread(inode, buf, nbyte) != nbyte) { 154 printf("Invalid %s\n", "format"); 155 return (-1); 156 } 157 return (0); 158} 159 160static inline void 161getstr(void) 162{ 163 char *s; 164 int c; 165 166 s = cmd; 167 for (;;) { 168 switch (c = xgetc(0)) { 169 case 0: 170 break; 171 case '\177': 172 case '\b': 173 if (s > cmd) { 174 s--; 175 printf("\b \b"); 176 } 177 break; 178 case '\n': 179 case '\r': 180 *s = 0; 181 return; 182 default: 183 if (s - cmd < sizeof(cmd) - 1) 184 *s++ = c; 185 putchar(c); 186 } 187 } 188} 189 190static inline void 191putc(int c) 192{ 193 194 v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS; 195 v86.addr = PUTCORG; /* call to putc in boot1 */ 196 v86.eax = c; 197 v86int(); 198 v86.ctl = V86_FLAGS; 199} 200 201static inline int 202is_scsi_hd(void) 203{ 204 205 if ((*(u_char *)PTOV(0x482) >> dsk.unit) & 0x01) 206 return 1; 207 208 return 0; 209} 210 211static inline void 212fix_sector_size(void) 213{ 214 u_char *p; 215 216 p = (u_char *)PTOV(0x460 + dsk.unit * 4); /* SCSI equipment parameter */ 217 218 if ((p[0] & 0x1f) == 7) { /* SCSI MO */ 219 if (!(p[3] & 0x30)) { /* 256B / sector */ 220 p[3] |= 0x10; /* forced set 512B / sector */ 221 p[3 + 0xa1000] |= 0x10; 222 } 223 } 224} 225 226static inline uint32_t 227get_diskinfo(void) 228{ 229 230 if (dsk.disk == 0x30) { /* 1440KB FD */ 231 /* 80 cylinders, 2 heads, 18 sectors */ 232 return (80 << 16) | (2 << 8) | 18; 233 } else if (dsk.disk == 0x90) { /* 1200KB FD */ 234 /* 80 cylinders, 2 heads, 15 sectors */ 235 return (80 << 16) | (2 << 8) | 15; 236 } else if (dsk.disk == 0x80 || is_scsi_hd()) { /* IDE or SCSI HDD */ 237 v86.addr = 0x1b; 238 v86.eax = 0x8400 | dsk.daua; 239 v86int(); 240 return (v86.ecx << 16) | v86.edx; 241 } 242 243 /* SCSI MO or CD */ 244 fix_sector_size(); /* SCSI MO */ 245 246 /* other SCSI devices */ 247 return (65535 << 16) | (8 << 8) | 32; 248} 249 250static void 251set_dsk(void) 252{ 253 uint32_t di; 254 255 di = get_diskinfo(); 256 257 dsk.head = (di >> 8) & 0xff; 258 dsk.sec = di & 0xff; 259 dsk.start = 0; 260} 261 262#ifdef GET_BIOSGEOM 263static uint32_t 264bd_getbigeom(int bunit) 265{ 266 int hds = 0; 267 int unit = 0x80; /* IDE HDD */ 268 u_int addr = 0x55d; 269 270 while (unit < 0xa7) { 271 if (*(u_char *)PTOV(addr) & (1 << (unit & 0x0f))) 272 if (hds++ == bunit) 273 break; 274 275 if (unit >= 0xA0) { 276 int media = ((unsigned *)PTOV(0x460))[unit & 0x0F] & 0x1F; 277 278 if (media == 7 && hds++ == bunit) /* SCSI MO */ 279 return(0xFFFE0820); /* C:65535 H:8 S:32 */ 280 } 281 if (++unit == 0x84) { 282 unit = 0xA0; /* SCSI HDD */ 283 addr = 0x482; 284 } 285 } 286 if (unit == 0xa7) 287 return 0x4F020F; /* 1200KB FD C:80 H:2 S:15 */ 288 v86.addr = 0x1b; 289 v86.eax = 0x8400 | unit; 290 v86int(); 291 if (V86_CY(v86.efl)) 292 return 0x4F020F; /* 1200KB FD C:80 H:2 S:15 */ 293 return ((v86.ecx & 0xffff) << 16) | (v86.edx & 0xffff); 294} 295#endif 296 297static int 298check_slice(void) 299{ 300 struct pc98_partition *dp; 301 char *sec; 302 unsigned i, cyl; 303 304 sec = dmadat->secbuf; 305 cyl = *(uint16_t *)PTOV(ARGS); 306 set_dsk(); 307 308 if (dsk.type == TYPE_FD) 309 return (WHOLE_DISK_SLICE); 310 if (drvread(sec, PC98_BBSECTOR)) 311 return (WHOLE_DISK_SLICE); /* Read error */ 312 dp = (void *)(sec + PC98_PARTOFF); 313 for (i = 0; i < PC98_NPARTS; i++) { 314 if (dp[i].dp_mid == DOSMID_386BSD) { 315 if (dp[i].dp_scyl <= cyl && cyl <= dp[i].dp_ecyl) 316 return (BASE_SLICE + i); 317 } 318 } 319 320 return (WHOLE_DISK_SLICE); 321} 322 323int 324main(void) 325{ 326#ifdef GET_BIOSGEOM 327 int i; 328#endif 329 uint8_t autoboot; 330 ufs_ino_t ino; 331 size_t nbyte; 332 333 dmadat = (void *)(roundup2(__base + (int32_t)&_end, 0x10000) - __base); 334 v86.ctl = V86_FLAGS; 335 v86.efl = PSL_RESERVED_DEFAULT | PSL_I; 336 dsk.daua = *(uint8_t *)PTOV(0x584); 337 dsk.disk = dsk.daua & DRV_DISK; 338 dsk.unit = dsk.daua & DRV_UNIT; 339 if (dsk.disk == 0x80) 340 dsk.type = TYPE_AD; 341 else if (dsk.disk == 0xa0) 342 dsk.type = TYPE_DA; 343 else /* if (dsk.disk == 0x30 || dsk.disk == 0x90) */ 344 dsk.type = TYPE_FD; 345 dsk.slice = check_slice(); 346#ifdef GET_BIOSGEOM 347 for (i = 0; i < N_BIOS_GEOM; i++) 348 bootinfo.bi_bios_geom[i] = bd_getbigeom(i); 349#endif 350 bootinfo.bi_version = BOOTINFO_VERSION; 351 bootinfo.bi_size = sizeof(bootinfo); 352 353 /* Process configuration file */ 354 355 autoboot = 1; 356 357 if ((ino = lookup(PATH_CONFIG)) || 358 (ino = lookup(PATH_DOTCONFIG))) { 359 nbyte = fsread(ino, cmd, sizeof(cmd) - 1); 360 cmd[nbyte] = '\0'; 361 } 362 363 if (*cmd) { 364 memcpy(cmddup, cmd, sizeof(cmd)); 365 if (parse()) 366 autoboot = 0; 367 if (!OPT_CHECK(RBX_QUIET)) 368 printf("%s: %s", PATH_CONFIG, cmddup); 369 /* Do not process this command twice */ 370 *cmd = 0; 371 } 372 373 /* 374 * Try to exec stage 3 boot loader. If interrupted by a keypress, 375 * or in case of failure, try to load a kernel directly instead. 376 */ 377 378 if (!kname) { 379 kname = PATH_LOADER; 380 if (autoboot && !keyhit(3*SECOND)) { 381 load(); 382 kname = PATH_KERNEL; 383 } 384 } 385 386 /* Present the user with the boot2 prompt. */ 387 388 for (;;) { 389 if (!autoboot || !OPT_CHECK(RBX_QUIET)) 390 printf("\nFreeBSD/pc98 boot\n" 391 "Default: %u:%s(%u,%c)%s\n" 392 "boot: ", 393 dsk.unit, dev_nm[dsk.type], dsk.unit, 394 'a' + dsk.part, kname); 395 if (DO_SIO) 396 sio_flush(); 397 if (!autoboot || keyhit(3*SECOND)) 398 getstr(); 399 else if (!autoboot || !OPT_CHECK(RBX_QUIET)) 400 putchar('\n'); 401 autoboot = 0; 402 if (parse()) 403 putchar('\a'); 404 else 405 load(); 406 } 407} 408 409/* XXX - Needed for btxld to link the boot2 binary; do not remove. */ 410void 411exit(int x) 412{ 413 414} 415 416static void 417load(void) 418{ 419 union { 420 struct exec ex; 421 Elf32_Ehdr eh; 422 } hdr; 423 static Elf32_Phdr ep[2]; 424 static Elf32_Shdr es[2]; 425 caddr_t p; 426 ufs_ino_t ino; 427 uint32_t addr; 428 int k; 429 uint8_t i, j; 430 431 if (!(ino = lookup(kname))) { 432 if (!ls) 433 printf("No %s\n", kname); 434 return; 435 } 436 if (xfsread(ino, &hdr, sizeof(hdr))) 437 return; 438 439 if (N_GETMAGIC(hdr.ex) == ZMAGIC) { 440 addr = hdr.ex.a_entry & 0xffffff; 441 p = PTOV(addr); 442 fs_off = PAGE_SIZE; 443 if (xfsread(ino, p, hdr.ex.a_text)) 444 return; 445 p += roundup2(hdr.ex.a_text, PAGE_SIZE); 446 if (xfsread(ino, p, hdr.ex.a_data)) 447 return; 448 } else if (IS_ELF(hdr.eh)) { 449 fs_off = hdr.eh.e_phoff; 450 for (j = k = 0; k < hdr.eh.e_phnum && j < 2; k++) { 451 if (xfsread(ino, ep + j, sizeof(ep[0]))) 452 return; 453 if (ep[j].p_type == PT_LOAD) 454 j++; 455 } 456 for (i = 0; i < 2; i++) { 457 p = PTOV(ep[i].p_paddr & 0xffffff); 458 fs_off = ep[i].p_offset; 459 if (xfsread(ino, p, ep[i].p_filesz)) 460 return; 461 } 462 p += roundup2(ep[1].p_memsz, PAGE_SIZE); 463 bootinfo.bi_symtab = VTOP(p); 464 if (hdr.eh.e_shnum == hdr.eh.e_shstrndx + 3) { 465 fs_off = hdr.eh.e_shoff + sizeof(es[0]) * 466 (hdr.eh.e_shstrndx + 1); 467 if (xfsread(ino, &es, sizeof(es))) 468 return; 469 for (i = 0; i < 2; i++) { 470 *(Elf32_Word *)p = es[i].sh_size; 471 p += sizeof(es[i].sh_size); 472 fs_off = es[i].sh_offset; 473 if (xfsread(ino, p, es[i].sh_size)) 474 return; 475 p += es[i].sh_size; 476 } 477 } 478 addr = hdr.eh.e_entry & 0xffffff; 479 bootinfo.bi_esymtab = VTOP(p); 480 } else { 481 printf("Invalid %s\n", "format"); 482 return; 483 } 484 485 bootinfo.bi_kernelname = VTOP(kname); 486 bootinfo.bi_bios_dev = dsk.daua; 487 __exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK), 488 MAKEBOOTDEV(dev_maj[dsk.type], dsk.slice, dsk.unit, dsk.part), 489 0, 0, 0, VTOP(&bootinfo)); 490} 491 492static int 493parse() 494{ 495 char *arg = cmd; 496 char *ep, *p, *q; 497 const char *cp; 498 unsigned int drv; 499 int c, i, j; 500 size_t k; 501 502 while ((c = *arg++)) { 503 if (c == ' ' || c == '\t' || c == '\n') 504 continue; 505 for (p = arg; *p && *p != '\n' && *p != ' ' && *p != '\t'; p++); 506 ep = p; 507 if (*p) 508 *p++ = 0; 509 if (c == '-') { 510 while ((c = *arg++)) { 511 if (c == 'P') { 512 if (*(uint8_t *)PTOV(0x481) & 0x48) { 513 cp = "yes"; 514 } else { 515 opts |= OPT_SET(RBX_DUAL) | 516 OPT_SET(RBX_SERIAL); 517 cp = "no"; 518 } 519 printf("Keyboard: %s\n", cp); 520 continue; 521#if SERIAL 522 } else if (c == 'S') { 523 j = 0; 524 while ((unsigned int)(i = *arg++ - '0') <= 9) 525 j = j * 10 + i; 526 if (j > 0 && i == -'0') { 527 comspeed = j; 528 break; 529 } 530 /* 531 * Fall through to error below 532 * ('S' not in optstr[]). 533 */ 534#endif 535 } 536 for (i = 0; c != optstr[i]; i++) 537 if (i == NOPT - 1) 538 return (-1); 539 opts ^= OPT_SET(flags[i]); 540 } 541#if SERIAL 542 ioctrl = OPT_CHECK(RBX_DUAL) ? (IO_SERIAL|IO_KEYBOARD) : 543 OPT_CHECK(RBX_SERIAL) ? IO_SERIAL : IO_KEYBOARD; 544 if (DO_SIO) { 545 if (sio_init(115200 / comspeed) != 0) 546 ioctrl &= ~IO_SERIAL; 547 } 548#endif 549 } else { 550 for (q = arg--; *q && *q != '('; q++); 551 if (*q) { 552 drv = -1; 553 if (arg[1] == ':') { 554 drv = *arg - '0'; 555 if (drv > 9) 556 return (-1); 557 arg += 2; 558 } 559 if (q - arg != 2) 560 return (-1); 561 for (i = 0; arg[0] != dev_nm[i][0] || 562 arg[1] != dev_nm[i][1]; i++) 563 if (i == NDEV - 1) 564 return (-1); 565 dsk.type = i; 566 arg += 3; 567 dsk.unit = *arg - '0'; 568 if (arg[1] != ',' || dsk.unit > 9) 569 return (-1); 570 arg += 2; 571 dsk.slice = WHOLE_DISK_SLICE; 572 if (arg[1] == ',') { 573 dsk.slice = *arg - '0' + 1; 574 if (dsk.slice > PC98_NPARTS + 1) 575 return (-1); 576 arg += 2; 577 } 578 if (arg[1] != ')') 579 return (-1); 580 dsk.part = *arg - 'a'; 581 if (dsk.part > 7) 582 return (-1); 583 arg += 2; 584 if (drv == -1) 585 drv = dsk.unit; 586 dsk.disk = dev_daua[dsk.type]; 587 dsk.daua = dsk.disk | dsk.unit; 588 dsk_meta = 0; 589 } 590 k = ep - arg; 591 if (k > 0) { 592 if (k >= sizeof(knamebuf)) 593 return (-1); 594 memcpy(knamebuf, arg, k + 1); 595 kname = knamebuf; 596 } 597 } 598 arg = p; 599 } 600 return (0); 601} 602 603static int 604dskread(void *buf, unsigned lba, unsigned nblk) 605{ 606 struct pc98_partition *dp; 607 struct disklabel *d; 608 char *sec; 609 unsigned i; 610 uint8_t sl; 611 u_char *p; 612 const char *reason; 613 614 if (!dsk_meta) { 615 sec = dmadat->secbuf; 616 set_dsk(); 617 if (dsk.type == TYPE_FD) 618 goto unsliced; 619 if (drvread(sec, PC98_BBSECTOR)) 620 return -1; 621 dp = (void *)(sec + PC98_PARTOFF); 622 sl = dsk.slice; 623 if (sl < BASE_SLICE) { 624 for (i = 0; i < PC98_NPARTS; i++) 625 if (dp[i].dp_mid == DOSMID_386BSD) { 626 sl = BASE_SLICE + i; 627 break; 628 } 629 dsk.slice = sl; 630 } 631 if (sl != WHOLE_DISK_SLICE) { 632 dp += sl - BASE_SLICE; 633 if (dp->dp_mid != DOSMID_386BSD) { 634 reason = "slice"; 635 goto error; 636 } 637 dsk.start = dp->dp_scyl * dsk.head * dsk.sec + 638 dp->dp_shd * dsk.sec + dp->dp_ssect; 639 } 640 if (drvread(sec, dsk.start + LABELSECTOR)) 641 return -1; 642 d = (void *)(sec + LABELOFFSET); 643 if (d->d_magic != DISKMAGIC || d->d_magic2 != DISKMAGIC) { 644 if (dsk.part != RAW_PART) { 645 reason = "label"; 646 goto error; 647 } 648 } else { 649 if (dsk.part >= d->d_npartitions || 650 !d->d_partitions[dsk.part].p_size) { 651 reason = "partition"; 652 goto error; 653 } 654 dsk.start += d->d_partitions[dsk.part].p_offset; 655 dsk.start -= d->d_partitions[RAW_PART].p_offset; 656 } 657 unsliced: ; 658 } 659 for (p = buf; nblk; p += 512, lba++, nblk--) { 660 if ((i = drvread(p, dsk.start + lba))) 661 return (i); 662 } 663 return (0); 664error: 665 printf("Invalid %s\n", reason); 666 return (-1); 667} 668 669static void 670printf(const char *fmt,...) 671{ 672 va_list ap; 673 static char buf[10]; 674 char *s; 675 unsigned u; 676 int c; 677 678 va_start(ap, fmt); 679 while ((c = *fmt++)) { 680 if (c == '%') { 681 c = *fmt++; 682 switch (c) { 683 case 'c': 684 putchar(va_arg(ap, int)); 685 continue; 686 case 's': 687 for (s = va_arg(ap, char *); *s; s++) 688 putchar(*s); 689 continue; 690 case 'u': 691 u = va_arg(ap, unsigned); 692 s = buf; 693 do 694 *s++ = '0' + u % 10U; 695 while (u /= 10U); 696 while (--s >= buf) 697 putchar(*s); 698 continue; 699 } 700 } 701 putchar(c); 702 } 703 va_end(ap); 704 return; 705} 706 707static void 708putchar(int c) 709{ 710 711 if (c == '\n') 712 xputc('\r'); 713 xputc(c); 714} 715 716static int 717drvread(void *buf, unsigned lba) 718{ 719 static unsigned c = 0x2d5c7c2f; 720 unsigned bpc, x, cyl, head, sec; 721 722 bpc = dsk.sec * dsk.head; 723 cyl = lba / bpc; 724 x = lba % bpc; 725 head = x / dsk.sec; 726 sec = x % dsk.sec; 727 728 if (!OPT_CHECK(RBX_QUIET)) { 729 xputc(c = c << 8 | c >> 24); 730 xputc('\b'); 731 } 732 v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS; 733 v86.addr = READORG; /* call to read in boot1 */ 734 v86.ecx = cyl; 735 v86.edx = (head << 8) | sec; 736 v86.edi = lba; 737 v86.ebx = 512; 738 v86.es = VTOPSEG(buf); 739 v86.ebp = VTOPOFF(buf); 740 v86int(); 741 v86.ctl = V86_FLAGS; 742 if (V86_CY(v86.efl)) { 743 printf("error %u c/h/s %u/%u/%u lba %u\n", v86.eax >> 8 & 0xff, 744 cyl, head, sec, lba); 745 return (-1); 746 } 747 return (0); 748} 749 750static inline void 751delay(void) 752{ 753 int i; 754 755 i = 800; 756 do { 757 outb(0x5f, 0); /* about 600ns */ 758 } while (--i >= 0); 759} 760 761static int 762keyhit(unsigned sec) 763{ 764 unsigned i; 765 766 if (OPT_CHECK(RBX_NOINTR)) 767 return (0); 768 for (i = 0; i < sec * 1000; i++) { 769 if (xgetc(1)) 770 return (1); 771 delay(); 772 } 773 return (0); 774} 775 776static int 777xputc(int c) 778{ 779 780 if (DO_KBD) 781 putc(c); 782 if (DO_SIO) 783 sio_putc(c); 784 return (c); 785} 786 787static int 788getc(int fn) 789{ 790 791 v86.addr = 0x18; 792 v86.eax = fn << 8; 793 v86int(); 794 if (fn) 795 return ((v86.ebx >> 8) & 0x01); 796 else 797 return (v86.eax & 0xff); 798} 799 800static int 801xgetc(int fn) 802{ 803 804 if (OPT_CHECK(RBX_NOINTR)) 805 return (0); 806 for (;;) { 807 if (DO_KBD && getc(1)) 808 return (fn ? 1 : getc(0)); 809 if (DO_SIO && sio_ischar()) 810 return (fn ? 1 : sio_getc()); 811 if (fn) 812 return (0); 813 } 814} 815