hammer2 - Add hammer2 boot support
[dragonfly.git] / sys / boot / pc32 / boot2 / boot2.c
1 /*
2  * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
3  * 
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  * 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  * 
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  * 
34  * Copyright (c) 1998 Robert Nordier
35  * All rights reserved.
36  *
37  * Redistribution and use in source and binary forms are freely
38  * permitted provided that the above copyright notice and this
39  * paragraph and the following disclaimer are duplicated in all
40  * such forms.
41  *
42  * This software is provided "AS IS" and without any express or
43  * implied warranties, including, without limitation, the implied
44  * warranties of merchantability and fitness for a particular
45  * purpose.
46  *
47  * $FreeBSD: src/sys/boot/i386/boot2/boot2.c,v 1.64 2003/08/25 23:28:31 obrien Exp $
48  */
49
50 #define AOUT_H_FORCE32
51 #include <sys/param.h>
52 #ifdef DISKLABEL64
53 #include <sys/disklabel64.h>
54 #else
55 #include <sys/disklabel32.h>
56 #endif
57 #include <sys/diskslice.h>
58 #include <sys/diskmbr.h>
59 #include <sys/dtype.h>
60 #include <sys/dirent.h>
61 #include <machine/bootinfo.h>
62 #include <machine/elf.h>
63 #include <machine/psl.h>
64
65 #include <stdarg.h>
66
67 #include <a.out.h>
68
69 #include <btxv86.h>
70
71 #ifdef DISKLABEL64
72 #include "boot2_64.h"
73 #else
74 #include "boot2_32.h"
75 #endif
76 #include "boot2.h"
77 #include "lib.h"
78 #include "../bootasm.h"
79
80 #define SECOND          18      /* Circa that many ticks in a second. */
81
82 #define RBX_ASKNAME     0x0     /* -a */
83 #define RBX_SINGLE      0x1     /* -s */
84 #define RBX_DFLTROOT    0x5     /* -r */
85 #define RBX_KDB         0x6     /* -d */
86 #define RBX_CONFIG      0xa     /* -c */
87 #define RBX_VERBOSE     0xb     /* -v */
88 #define RBX_SERIAL      0xc     /* -h */
89 #define RBX_CDROM       0xd     /* -C */
90 #define RBX_GDB         0xf     /* -g */
91 #define RBX_MUTE        0x10    /* -m */
92 #define RBX_PAUSE       0x12    /* -p */
93 #define RBX_NOINTR      0x1c    /* -n */
94 #define RBX_VIDEO       0x1d    /* -V */
95 #define RBX_PROBEKBD    0x1e    /* -P */
96 /* 0x1f is reserved for the historical RB_BOOTINFO option */
97
98 #define RBF_MUTE        (1 << RBX_MUTE)
99 #define RBF_SERIAL      (1 << RBX_SERIAL)
100 #define RBF_VIDEO       (1 << RBX_VIDEO)
101 #define RBF_NOINTR      (1 << RBX_NOINTR)
102 #define RBF_PROBEKBD    (1 << RBX_PROBEKBD)
103
104 /* pass: -a, -s, -r, -d, -c, -v, -h, -C, -g, -m, -p, -V */
105 #define RBX_MASK        0x2005ffff
106
107 #define PATH_CONFIG     "/boot.config"
108 #define PATH_BOOT3      "/loader"               /* /boot is dedicated */
109 #define PATH_BOOT3_ALT  "/boot/loader"          /* /boot in root */
110 #define PATH_KERNEL     "/kernel"
111
112 #define NOPT            12
113 #define NDEV            3
114 #define MEM_BASE        0x12
115 #define MEM_EXT         0x15
116 #define V86_CY(x)       ((x) & PSL_C)
117 #define V86_ZR(x)       ((x) & PSL_Z)
118
119 #define DRV_HARD        0x80
120 #define DRV_MASK        0x7f
121
122 #define TYPE_AD         0
123 #define TYPE_DA         1
124 #define TYPE_MAXHARD    TYPE_DA
125 #define TYPE_FD         2
126
127 #define INVALID_S       "Bad %s\n"
128
129 extern uint32_t _end;
130
131 static const char optstr[NOPT] = { "VhaCgmnPprsv" };
132 static const unsigned char flags[NOPT] = {
133     RBX_VIDEO,
134     RBX_SERIAL,
135     RBX_ASKNAME,
136     RBX_CDROM,
137     RBX_GDB,
138     RBX_MUTE,
139     RBX_NOINTR,
140     RBX_PROBEKBD,
141     RBX_PAUSE,
142     RBX_DFLTROOT,
143     RBX_SINGLE,
144     RBX_VERBOSE
145 };
146
147 static const char *const dev_nm[NDEV] = {"ad", "da", "fd"};
148 static const unsigned char dev_maj[NDEV] = {30, 4, 2};
149
150 static struct dsk {
151     unsigned drive;
152     unsigned type;
153     unsigned unit;
154     uint8_t slice;
155     uint8_t part;
156     unsigned start;
157     int init;
158 } dsk;
159
160 static char cmd[512];
161 static const char *kname;
162 static uint32_t opts = RBF_VIDEO;
163 static struct bootinfo bootinfo;
164
165 /*
166  * boot2 encapsulated ABI elements provided to *fsread.c
167  *
168  * NOTE: boot2_dmadat is extended by per-filesystem APIs
169  */
170 uint32_t fs_off;
171 int     no_io_error;
172 int     ls;
173 struct boot2_dmadat *boot2_dmadat;
174
175 void exit(int);
176 static void load(void);
177 static int parse(void);
178 static int dskprobe(void);
179 static int xfsread(boot2_ino_t, void *, size_t);
180 static int drvread(void *, unsigned, unsigned);
181 static int keyhit(unsigned);
182 static int xputc(int);
183 static int xgetc(int);
184 static int getc(int);
185
186 void
187 memcpy(void *d, const void *s, int len)
188 {
189     char *dd = d;
190     const char *ss = s;
191
192         while (--len >= 0)
193             dd[len] = ss[len];
194 }
195
196 int
197 strcmp(const char *s1, const char *s2)
198 {
199     for (; *s1 == *s2 && *s1; s1++, s2++)
200         ;
201     return ((int)((unsigned char)*s1 - (unsigned char)*s2));
202 }
203
204 #if defined(UFS) && defined(HAMMER2FS)
205
206 const struct boot2_fsapi *fsapi;
207
208 #elif defined(UFS)
209
210 #define fsapi   (&boot2_ufs_api)
211
212 #elif defined(HAMMER2FS)
213
214 #define fsapi   (&boot2_hammer2_api)
215
216 #endif
217
218 static int
219 xfsread(boot2_ino_t inode, void *buf, size_t nbyte)
220 {
221     if ((size_t)fsapi->fsread(inode, buf, nbyte) != nbyte) {
222         printf(INVALID_S, "format");
223         return -1;
224     }
225     return 0;
226 }
227
228 static inline void
229 getstr(void)
230 {
231     char *s;
232     int c;
233
234     s = cmd;
235     for (;;) {
236         switch (c = xgetc(0)) {
237         case 0:
238             break;
239         case '\177':
240         case '\b':
241             if (s > cmd) {
242                 s--;
243                 printf("\b \b");
244             }
245             break;
246         case '\n':
247         case '\r':
248             *s = 0;
249             return;
250         default:
251             if (s - cmd < sizeof(cmd) - 1)
252                 *s++ = c;
253             putchar(c);
254         }
255     }
256 }
257
258 static inline void
259 putc(int c)
260 {
261     v86.addr = 0x10;
262     v86.eax = 0xe00 | (c & 0xff);
263     v86.ebx = 0x7;
264     v86int();
265 }
266
267 int
268 main(void)
269 {
270     uint8_t autoboot;
271     boot2_ino_t ino;
272
273     kname = NULL;
274     boot2_dmadat =
275                 (void *)(roundup2(__base + (int32_t)&_end, 0x10000) - __base);
276     v86.ctl = V86_FLAGS;
277     v86.efl = PSL_RESERVED_DEFAULT | PSL_I;
278     dsk.drive = *(uint8_t *)PTOV(MEM_BTX_USR_ARG);
279     dsk.type = dsk.drive & DRV_HARD ? TYPE_AD : TYPE_FD;
280     dsk.unit = dsk.drive & DRV_MASK;
281     dsk.slice = *(uint8_t *)PTOV(MEM_BTX_USR_ARG + 1) + 1;
282     bootinfo.bi_version = BOOTINFO_VERSION;
283     bootinfo.bi_size = sizeof(bootinfo);
284
285     autoboot = 1;
286
287     /*
288      * Probe the default disk and process the configuration file if
289      * successful.
290      */
291     if (dskprobe() == 0) {
292         if ((ino = fsapi->fslookup(PATH_CONFIG)))
293             fsapi->fsread(ino, cmd, sizeof(cmd));
294     }
295
296     /*
297      * Parse config file if present.  parse() will re-probe if necessary.
298      */
299     if (cmd[0]) {
300         printf("%s: %s", PATH_CONFIG, cmd);
301         if (parse())
302             autoboot = 0;
303         /* Do not process this command twice */
304         *cmd = 0;
305     }
306
307     /*
308      * Setup our (serial) console after processing the config file.  If
309      * the initialization fails, don't try to use the serial port.  This
310      * can happen if the serial port is unmaped (happens on new laptops a lot).
311      */
312     if ((opts & (RBF_MUTE|RBF_SERIAL|RBF_VIDEO)) == 0)
313         opts |= RBF_SERIAL|RBF_VIDEO;
314     if (opts & RBF_SERIAL) {
315         if (sio_init())
316             opts = RBF_VIDEO;
317     }
318
319
320     /*
321      * Try to exec stage 3 boot loader. If interrupted by a keypress,
322      * or in case of failure, try to load a kernel directly instead.
323      *
324      * We have to try boot /boot/loader and /loader to support booting
325      * from a /boot partition instead of a root partition.
326      */
327     if (autoboot && !kname) {
328         kname = PATH_BOOT3;
329         if (!keyhit(3*SECOND)) {
330             load();
331             kname = PATH_BOOT3_ALT;
332             load();
333             kname = PATH_KERNEL;
334         }
335     }
336
337     /* Present the user with the boot2 prompt. */
338
339     for (;;) {
340         printf("\nDragonFly boot\n"
341                "%u:%s(%u,%c)%s: ",
342                dsk.drive & DRV_MASK, dev_nm[dsk.type], dsk.unit,
343                'a' + dsk.part, kname);
344         if (!autoboot || keyhit(5*SECOND))
345             getstr();
346         else
347             putchar('\n');
348         autoboot = 0;
349         if (parse())
350             putchar('\a');
351         else
352             load();
353     }
354 }
355
356 /* XXX - Needed for btxld to link the boot2 binary; do not remove. */
357 void
358 exit(int x)
359 {
360 }
361
362 static void
363 load(void)
364 {
365     union {
366         struct exec ex;
367         Elf32_Ehdr eh;
368     } hdr;
369     static Elf32_Phdr ep[2];
370     static Elf32_Shdr es[2];
371     caddr_t p;
372     boot2_ino_t ino;
373     uint32_t addr;
374     int i, j;
375
376     if (!(ino = fsapi->fslookup(kname))) {
377         if (!ls)
378             printf("No %s\n", kname);
379         return;
380     }
381     if (xfsread(ino, &hdr, sizeof(hdr)))
382         return;
383
384     if (N_GETMAGIC(hdr.ex) == ZMAGIC) {
385         addr = hdr.ex.a_entry & 0xffffff;
386         p = PTOV(addr);
387         fs_off = PAGE_SIZE;
388         if (xfsread(ino, p, hdr.ex.a_text))
389             return;
390         p += roundup2(hdr.ex.a_text, PAGE_SIZE);
391         if (xfsread(ino, p, hdr.ex.a_data))
392             return;
393     } else if (IS_ELF(hdr.eh)) {
394         fs_off = hdr.eh.e_phoff;
395         for (j = i = 0; i < hdr.eh.e_phnum && j < 2; i++) {
396             if (xfsread(ino, ep + j, sizeof(ep[0])))
397                 return;
398             if (ep[j].p_type == PT_LOAD)
399                 j++;
400         }
401         for (i = 0; i < 2; i++) {
402             p = PTOV(ep[i].p_paddr & 0xffffff);
403             fs_off = ep[i].p_offset;
404             if (xfsread(ino, p, ep[i].p_filesz))
405                 return;
406         }
407         p += roundup2(ep[1].p_memsz, PAGE_SIZE);
408         bootinfo.bi_symtab = VTOP(p);
409         if (hdr.eh.e_shnum == hdr.eh.e_shstrndx + 3) {
410             fs_off = hdr.eh.e_shoff + sizeof(es[0]) *
411                 (hdr.eh.e_shstrndx + 1);
412             if (xfsread(ino, &es, sizeof(es)))
413                 return;
414             for (i = 0; i < 2; i++) {
415                 *(Elf32_Word *)p = es[i].sh_size;
416                 p += sizeof(es[i].sh_size);
417                 fs_off = es[i].sh_offset;
418                 if (xfsread(ino, p, es[i].sh_size))
419                     return;
420                 p += es[i].sh_size;
421             }
422         }
423         addr = hdr.eh.e_entry & 0xffffff;
424         bootinfo.bi_esymtab = VTOP(p);
425     } else {
426         printf(INVALID_S, "format");
427         return;
428     }
429
430     bootinfo.bi_kernelname = VTOP(kname);
431     bootinfo.bi_bios_dev = dsk.drive;
432     __exec((caddr_t)addr, opts & RBX_MASK,
433            MAKEBOOTDEV(dev_maj[dsk.type], 0, dsk.slice, dsk.unit, dsk.part),
434            0, 0, 0, VTOP(&bootinfo));
435 }
436
437 static int
438 parse(void)
439 {
440     char *arg = cmd;
441     char *p, *q;
442     unsigned int drv;
443     int c, i;
444
445     while ((c = *arg++)) {
446         if (c == ' ' || c == '\t' || c == '\n')
447             continue;
448         for (p = arg; *p && *p != '\n' && *p != ' ' && *p != '\t'; p++)
449             ;
450         if (*p)
451             *p++ = 0;
452         if (c == '-') {
453             while ((c = *arg++)) {
454                 for (i = NOPT - 1; i >= 0; --i) {
455                     if (optstr[i] == c) {
456                         opts ^= 1 << flags[i];
457                         goto ok;
458                     }
459                 }
460                 return(-1);
461                 ok: ;   /* ugly but save space */
462             }
463             if (opts & RBF_PROBEKBD) {
464                 i = *(uint8_t *)PTOV(0x496) & 0x10;
465                 if (!i) {
466                     printf("NO KB\n");
467                     opts |= RBF_VIDEO | RBF_SERIAL;
468                 }
469                 opts &= ~RBF_PROBEKBD;
470             }
471         } else {
472             for (q = arg--; *q && *q != '('; q++);
473             if (*q) {
474                 drv = -1;
475                 if (arg[1] == ':') {
476                     drv = *arg - '0';
477                     if (drv > 9)
478                         return (-1);
479                     arg += 2;
480                 }
481                 if (q - arg != 2)
482                     return -1;
483                 for (i = 0; arg[0] != dev_nm[i][0] ||
484                             arg[1] != dev_nm[i][1]; i++)
485                     if (i == NDEV - 1)
486                         return -1;
487                 dsk.type = i;
488                 arg += 3;
489                 dsk.unit = *arg - '0';
490                 if (arg[1] != ',' || dsk.unit > 9)
491                     return -1;
492                 arg += 2;
493                 dsk.slice = WHOLE_DISK_SLICE;
494                 if (arg[1] == ',') {
495                     dsk.slice = *arg - '0' + 1;
496                     if (dsk.slice > NDOSPART + 1)
497                         return -1;
498                     arg += 2;
499                 }
500                 if (arg[1] != ')')
501                     return -1;
502                 dsk.part = *arg - 'a';
503                 if (dsk.part > 7)
504                     return (-1);
505                 arg += 2;
506                 if (drv == -1)
507                     drv = dsk.unit;
508                 dsk.drive = (dsk.type <= TYPE_MAXHARD
509                              ? DRV_HARD : 0) + drv;
510             }
511             kname = arg;
512         }
513         arg = p;
514     }
515     return dskprobe();
516 }
517
518 static int
519 dskprobe(void)
520 {
521     struct dos_partition *dp;
522 #ifdef DISKLABEL64
523     struct disklabel64 *d;
524 #else
525     struct disklabel32 *d;
526 #endif
527     char *sec;
528     unsigned i;
529     uint8_t sl;
530
531     /*
532      * Probe slice table
533      */
534     sec = boot2_dmadat->secbuf;
535     dsk.start = 0;
536     if (drvread(sec, DOSBBSECTOR, 1))
537         return -1;
538     dp = (void *)(sec + DOSPARTOFF);
539     sl = dsk.slice;
540     if (sl < BASE_SLICE) {
541         for (i = 0; i < NDOSPART; i++)
542             if (dp[i].dp_typ == DOSPTYP_386BSD &&
543                 (dp[i].dp_flag & 0x80 || sl < BASE_SLICE)) {
544                 sl = BASE_SLICE + i;
545                 if (dp[i].dp_flag & 0x80 ||
546                     dsk.slice == COMPATIBILITY_SLICE)
547                     break;
548             }
549         if (dsk.slice == WHOLE_DISK_SLICE)
550             dsk.slice = sl;
551     }
552     if (sl != WHOLE_DISK_SLICE) {
553         if (sl != COMPATIBILITY_SLICE)
554             dp += sl - BASE_SLICE;
555         if (dp->dp_typ != DOSPTYP_386BSD) {
556             printf(INVALID_S, "slice");
557             return -1;
558         }
559         dsk.start = dp->dp_start;
560     }
561
562     /*
563      * Probe label and partition table
564      */
565 #ifdef DISKLABEL64
566     if (drvread(sec, dsk.start, (sizeof(struct disklabel64) + 511) / 512))
567             return -1;
568     d = (void *)sec;
569     if (d->d_magic != DISKMAGIC64) {
570         printf(INVALID_S, "label");
571         return -1;
572     } else {
573         if (dsk.part >= d->d_npartitions || d->d_partitions[dsk.part].p_bsize == 0) {
574             printf(INVALID_S, "partition");
575             return -1;
576         }
577         dsk.start += d->d_partitions[dsk.part].p_boffset / 512;
578     }
579 #else
580     if (drvread(sec, dsk.start + LABELSECTOR32, 1))
581             return -1;
582     d = (void *)(sec + LABELOFFSET32);
583     if (d->d_magic != DISKMAGIC32 || d->d_magic2 != DISKMAGIC32) {
584         if (dsk.part != RAW_PART) {
585             printf(INVALID_S, "label");
586             return -1;
587         }
588     } else {
589         if (!dsk.init) {
590             if (d->d_type == DTYPE_SCSI)
591                 dsk.type = TYPE_DA;
592             dsk.init++;
593         }
594         if (dsk.part >= d->d_npartitions ||
595             !d->d_partitions[dsk.part].p_size) {
596             printf(INVALID_S, "partition");
597             return -1;
598         }
599         dsk.start += d->d_partitions[dsk.part].p_offset;
600         dsk.start -= d->d_partitions[RAW_PART].p_offset;
601     }
602 #endif
603     /*
604      * Probe filesystem
605      */
606 #if defined(UFS) && defined(HAMMER2FS)
607     if (boot2_ufs_api.fsinit() == 0) {
608         fsapi = &boot2_ufs_api;
609     } else if (boot2_hammer2_api.fsinit() == 0) {
610         fsapi = &boot2_hammer2_api;
611     } else {
612         printf("fs probe failed\n");
613         fsapi = &boot2_ufs_api;
614         return -1;
615     }
616     return 0;
617 #else
618     return fsapi->fsinit();
619 #endif
620 }
621
622
623 /*
624  * Read from the probed disk.  We have established the slice and partition
625  * base sector.
626  */
627 int
628 dskread(void *buf, unsigned lba, unsigned nblk)
629 {
630     return drvread(buf, dsk.start + lba, nblk);
631 }
632
633 /*
634  * boot encapsulated ABI
635  */
636 void
637 printf(const char *fmt,...)
638 {
639     va_list ap;
640     static char buf[10];
641     char *s;
642     unsigned u;
643 #ifdef HAMMER2FS
644     uint64_t q;
645 #endif
646     int c;
647
648     va_start(ap, fmt);
649     while ((c = *fmt++)) {
650         if (c == '%') {
651             c = *fmt++;
652             switch (c) {
653             case 'c':
654                 putchar(va_arg(ap, int));
655                 continue;
656             case 's':
657                 for (s = va_arg(ap, char *); *s; s++)
658                     putchar(*s);
659                 continue;
660 #ifdef HAMMER2FS
661             case 'q':
662                 ++fmt;  /* skip the 'x' */
663                 q = va_arg(ap, uint64_t);
664                 s = buf;
665                 do {
666                     if ((q & 15) < 10)
667                         *s++ = '0' + (q & 15);
668                     else
669                         *s++ = 'a' + (q & 15) - 10;
670                 } while (q >>= 4);
671                 while (--s >= buf)
672                     putchar(*s);
673                 continue;
674             case 'x':
675                 u = va_arg(ap, unsigned);
676                 s = buf;
677                 do {
678                     if ((u & 15) < 10)
679                         *s++ = '0' + (u & 15);
680                     else
681                         *s++ = 'a' + (u & 15) - 10;
682                 } while (u >>= 4);
683                 while (--s >= buf)
684                     putchar(*s);
685                 continue;
686 #endif
687             case 'u':
688                 u = va_arg(ap, unsigned);
689                 s = buf;
690                 do
691                     *s++ = '0' + u % 10U;
692                 while (u /= 10U);
693                 while (--s >= buf)
694                     putchar(*s);
695                 continue;
696             }
697         }
698         putchar(c);
699     }
700     va_end(ap);
701     return;
702 }
703
704 /*
705  * boot encapsulated ABI
706  */
707 void
708 putchar(int c)
709 {
710     if (c == '\n')
711         xputc('\r');
712     xputc(c);
713 }
714
715 /*
716  * boot encapsulated ABI
717  */
718 static int
719 drvread(void *buf, unsigned lba, unsigned nblk)
720 {
721     static unsigned c = 0x2d5c7c2f;     /* twiddle */
722
723     c = (c << 8) | (c >> 24);
724     xputc(c);
725     xputc('\b');
726     v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS;
727     v86.addr = XREADORG;                /* call to xread in boot1 */
728     v86.es = VTOPSEG(buf);
729     v86.eax = lba;
730     v86.ebx = VTOPOFF(buf);
731     v86.ecx = lba >> 16;
732     v86.edx = nblk << 8 | dsk.drive;
733     v86int();
734     v86.ctl = V86_FLAGS;
735     if (V86_CY(v86.efl) && !no_io_error) {
736         printf("error %u lba %u\n", v86.eax >> 8 & 0xff, lba);
737         return -1;
738     }
739     return 0;
740 }
741
742 static int
743 keyhit(unsigned ticks)
744 {
745     uint32_t t0, t1;
746
747     if (opts & RBF_NOINTR)
748         return 0;
749     t0 = 0;
750     for (;;) {
751         if (xgetc(1))
752             return 1;
753         t1 = *(uint32_t *)PTOV(0x46c);
754         if (!t0)
755             t0 = t1;
756         if ((uint32_t)(t1 - t0) >= ticks)
757             return 0;
758     }
759 }
760
761 static int
762 xputc(int c)
763 {
764     if (opts & RBF_VIDEO)
765         putc(c);
766     if (opts & RBF_SERIAL)
767         sio_putc(c);
768     return c;
769 }
770
771 static int
772 getc(int fn)
773 {
774     v86.addr = 0x16;
775     v86.eax = fn << 8;
776     v86int();
777     return fn == 0 ? v86.eax & 0xff : !V86_ZR(v86.efl);
778 }
779
780 static int
781 xgetc(int fn)
782 {
783     if (opts & RBF_NOINTR)
784         return 0;
785     for (;;) {
786         if ((opts & RBF_VIDEO) && getc(1))
787             return fn ? 1 : getc(0);
788         if ((opts & RBF_SERIAL) && sio_ischar())
789             return fn ? 1 : sio_getc();
790         if (fn)
791             return 0;
792     }
793 }