gptboot.c revision 96424
1/*
2 * Copyright (c) 1998 Robert Nordier
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms are freely
6 * permitted provided that the above copyright notice and this
7 * paragraph and the following disclaimer are duplicated in all
8 * such forms.
9 *
10 * This software is provided "AS IS" and without any express or
11 * implied warranties, including, without limitation, the implied
12 * warranties of merchantability and fitness for a particular
13 * purpose.
14 */
15
16/*
17 * $FreeBSD: head/sys/boot/i386/gptboot/gptboot.c 96424 2002-05-11 21:39:59Z peter $
18 */
19
20#include <sys/param.h>
21#include <sys/reboot.h>
22#include <sys/diskslice.h>
23#include <sys/disklabel.h>
24#include <sys/dirent.h>
25#include <machine/bootinfo.h>
26#include <machine/elf.h>
27
28#include <ufs/ffs/fs.h>
29#include <ufs/ufs/dinode.h>
30
31#include <stdarg.h>
32
33#include <a.out.h>
34
35#include <btxv86.h>
36
37#include "boot2.h"
38#include "lib.h"
39
40#define IO_KEYBOARD	1
41#define IO_SERIAL	2
42
43#define SECOND		18	/* Circa that many ticks in a second. */
44
45#define RBX_ASKNAME	0x0	/* -a */
46#define RBX_SINGLE	0x1	/* -s */
47#define RBX_DFLTROOT	0x5	/* -r */
48#define RBX_KDB 	0x6	/* -d */
49#define RBX_CONFIG	0xa	/* -c */
50#define RBX_VERBOSE	0xb	/* -v */
51#define RBX_SERIAL	0xc	/* -h */
52#define RBX_CDROM	0xd	/* -C */
53#define RBX_GDB 	0xf	/* -g */
54#define RBX_MUTE	0x10	/* -m */
55#define RBX_PAUSE	0x12	/* -p */
56#define RBX_DUAL	0x1d	/* -D */
57#define RBX_PROBEKBD	0x1e	/* -P */
58#define RBX_NOINTR	0x1f	/* -n */
59
60#define RBX_MASK	0x2005ffff
61
62#define PATH_CONFIG	"/boot.config"
63#define PATH_BOOT3	"/boot/loader"
64#define PATH_KERNEL	"/kernel"
65
66#define ARGS		0x900
67#define NOPT		14
68#define NDEV		5
69#define MEM_BASE	0x12
70#define MEM_EXT 	0x15
71#define V86_CY(x)	((x) & 1)
72#define V86_ZR(x)	((x) & 0x40)
73
74/*
75 * We use 4k `virtual' blocks for filesystem data, whatever the actual
76 * filesystem block size. FFS blocks are always a multiple of 4k.
77 */
78#define VBLKSIZE	4096
79#define VBLKMASK	(VBLKSIZE - 1)
80#define DBPERVBLK	(VBLKSIZE / DEV_BSIZE)
81#define IPERVBLK	(VBLKSIZE / sizeof(struct dinode))
82#define INDIRPERVBLK	(VBLKSIZE / sizeof(ufs_daddr_t))
83#define INO_TO_VBA(fs, x) (fsbtodb(fs, ino_to_fsba(fs, x)) + \
84    (ino_to_fsbo(fs, x) / IPERVBLK) * DBPERVBLK)
85#define INO_TO_VBO(fs, x) (ino_to_fsbo(fs, x) % IPERVBLK)
86#define FS_TO_VBA(fs, fsb, off) (fsbtodb(fs, fsb) + \
87    ((off) / VBLKSIZE) * DBPERVBLK)
88#define FS_TO_VBO(fs, fsb, off) ((off) & VBLKMASK)
89
90#define DRV_HARD	0x80
91#define DRV_MASK	0x7f
92
93#define TYPE_AD		0
94#define TYPE_WD		1
95#define TYPE_WFD 	2
96#define TYPE_FD		3
97#define TYPE_DA		4
98
99/* Buffers that must not span a 64k boundary. */
100static struct dmadat {
101	char blkbuf[VBLKSIZE];				/* filesystem blocks */
102	ufs_daddr_t indbuf[VBLKSIZE / sizeof(ufs_daddr_t)]; /* indir blocks */
103	char sbbuf[SBSIZE];				/* superblock */
104	char secbuf[DEV_BSIZE];				/* for MBR/disklabel */
105} *dmadat;
106
107extern uint32_t _end;
108
109static const char optstr[NOPT] = "DhaCcdgmnPprsv";
110static const unsigned char flags[NOPT] = {
111    RBX_DUAL,
112    RBX_SERIAL,
113    RBX_ASKNAME,
114    RBX_CDROM,
115    RBX_CONFIG,
116    RBX_KDB,
117    RBX_GDB,
118    RBX_MUTE,
119    RBX_NOINTR,
120    RBX_PROBEKBD,
121    RBX_PAUSE,
122    RBX_DFLTROOT,
123    RBX_SINGLE,
124    RBX_VERBOSE
125};
126
127static const char *const dev_nm[] = {"ad", "wd", "  ", "fd", "da"};
128static const unsigned dev_maj[] = {30, 0, 1, 2, 4};
129
130static struct dsk {
131    unsigned drive;
132    unsigned type;
133    unsigned unit;
134    unsigned slice;
135    unsigned part;
136    unsigned start;
137    int init;
138    int meta;
139} dsk;
140static char cmd[512];
141static char kname[1024];
142static uint32_t opts;
143static struct bootinfo bootinfo;
144static int ls;
145static uint32_t fs_off;
146static uint8_t ioctrl = IO_KEYBOARD;
147
148void exit(int);
149static void load(const char *);
150static int parse(char *);
151static ino_t lookup(const char *);
152static int xfsread(ino_t, void *, size_t);
153static ssize_t fsread(ino_t, void *, size_t);
154static int dskread(void *, unsigned, unsigned);
155static int printf(const char *,...);
156static int putchar(int);
157static void *memcpy(void *, const void *, size_t);
158static uint32_t memsize(int);
159static int drvread(void *, unsigned, unsigned);
160static int keyhit(unsigned);
161static int xputc(int);
162static int xgetc(int);
163static int getc(int);
164
165static inline void
166readfile(const char *fname, void *buf, size_t size)
167{
168    ino_t ino;
169
170    if ((ino = lookup(fname)))
171	fsread(ino, buf, size);
172}
173
174static inline int
175strcmp(const char *s1, const char *s2)
176{
177    for (; *s1 == *s2 && *s1; s1++, s2++);
178    return (u_char)*s1 - (u_char)*s2;
179}
180
181static inline int
182fsfind(const char *name, ino_t * ino)
183{
184    char buf[DEV_BSIZE];
185    struct dirent *d;
186    char *s;
187    ssize_t n;
188
189    fs_off = 0;
190    while ((n = fsread(*ino, buf, DEV_BSIZE)) > 0)
191	for (s = buf; s < buf + DEV_BSIZE;) {
192	    d = (void *)s;
193	    if (ls)
194		printf("%s ", d->d_name);
195	    else if (!strcmp(name, d->d_name)) {
196		*ino = d->d_fileno;
197		return d->d_type;
198	    }
199	    s += d->d_reclen;
200	}
201    if (n != -1 && ls)
202	putchar('\n');
203    return 0;
204}
205
206static inline int
207getchar(void)
208{
209    int c;
210
211    c = xgetc(0);
212    if (c == '\r')
213	c = '\n';
214    return c;
215}
216
217static inline void
218getstr(char *str, int size)
219{
220    char *s;
221    int c;
222
223    s = str;
224    do {
225	switch (c = getchar()) {
226	case 0:
227	    break;
228	case '\b':
229	case '\177':
230	    if (s > str) {
231		s--;
232		putchar('\b');
233		putchar(' ');
234	    } else
235		c = 0;
236	    break;
237	case '\n':
238	    *s = 0;
239	    break;
240	default:
241	    if (s - str < size - 1)
242		*s++ = c;
243	}
244	if (c)
245	    putchar(c);
246    } while (c != '\n');
247}
248
249static inline uint32_t
250drvinfo(int drive)
251{
252    v86.addr = 0x13;
253    v86.eax = 0x800;
254    v86.edx = DRV_HARD + drive;
255    v86int();
256    if (V86_CY(v86.efl))
257	return 0x4f010f;
258    return ((v86.ecx & 0xc0) << 18) | ((v86.ecx & 0xff00) << 8) |
259	   (v86.edx & 0xff00) | (v86.ecx & 0x3f);
260}
261
262static inline void
263putc(int c)
264{
265    v86.addr = 0x10;
266    v86.eax = 0xe00 | (c & 0xff);
267    v86.ebx = 0x7;
268    v86int();
269}
270
271int
272main(void)
273{
274    int autoboot, i;
275
276    dmadat = (void *)(roundup2(__base + _end, 0x10000) - __base);
277    v86.ctl = V86_FLAGS;
278    dsk.drive = *(uint8_t *)PTOV(ARGS);
279    dsk.type = dsk.drive & DRV_HARD ? TYPE_AD : TYPE_FD;
280    dsk.unit = dsk.drive & DRV_MASK;
281    dsk.slice = *(uint8_t *)PTOV(ARGS + 1) + 1;
282    bootinfo.bi_version = BOOTINFO_VERSION;
283    bootinfo.bi_size = sizeof(bootinfo);
284    bootinfo.bi_basemem = memsize(MEM_BASE);
285    bootinfo.bi_extmem = memsize(MEM_EXT);
286    bootinfo.bi_memsizes_valid++;
287    for (i = 0; i < N_BIOS_GEOM; i++)
288	bootinfo.bi_bios_geom[i] = drvinfo(i);
289
290    /* Process configuration file */
291
292    autoboot = 1;
293    readfile(PATH_CONFIG, cmd, sizeof(cmd));
294    if (*cmd) {
295	printf("%s: %s", PATH_CONFIG, cmd);
296	if (parse(cmd))
297	    autoboot = 0;
298	/* Do not process this command twice */
299	*cmd = 0;
300    }
301
302    /*
303     * Try to exec stage 3 boot loader. If interrupted by a keypress,
304     * or in case of failure, try to load a kernel directly instead.
305     */
306
307    if (autoboot && !*kname) {
308	memcpy(kname, PATH_BOOT3, sizeof(PATH_BOOT3));
309	if (!keyhit(3*SECOND)) {
310	    load(kname);
311	    memcpy(kname, PATH_KERNEL, sizeof(PATH_KERNEL));
312	}
313    }
314
315    /* Present the user with the boot2 prompt. */
316
317    for (;;) {
318	printf(" \n>> FreeBSD/i386 BOOT\n"
319	       "Default: %u:%s(%u,%c)%s\n"
320	       "boot: ",
321	       dsk.drive & DRV_MASK, dev_nm[dsk.type], dsk.unit,
322	       'a' + dsk.part, kname);
323	if (ioctrl & IO_SERIAL)
324	    sio_flush();
325	if (!autoboot || keyhit(5*SECOND))
326	    getstr(cmd, sizeof(cmd));
327	else
328	    putchar('\n');
329	autoboot = 0;
330	if (parse(cmd))
331	    putchar('\a');
332	else
333	    load(kname);
334    }
335}
336
337/* XXX - Needed for btxld to link the boot2 binary; do not remove. */
338void
339exit(int x)
340{
341}
342
343static void
344load(const char *fname)
345{
346    union {
347	struct exec ex;
348	Elf32_Ehdr eh;
349    } hdr;
350    Elf32_Phdr ep[2];
351    Elf32_Shdr es[2];
352    caddr_t p;
353    ino_t ino;
354    uint32_t addr, x;
355    int fmt, i, j;
356
357    if (!(ino = lookup(fname))) {
358	if (!ls)
359	    printf("No %s\n", fname);
360	return;
361    }
362    if (xfsread(ino, &hdr, sizeof(hdr)))
363	return;
364    if (N_GETMAGIC(hdr.ex) == ZMAGIC)
365	fmt = 0;
366    else if (IS_ELF(hdr.eh))
367	fmt = 1;
368    else {
369	printf("Invalid %s\n", "format");
370	return;
371    }
372    if (fmt == 0) {
373	addr = hdr.ex.a_entry & 0xffffff;
374	p = PTOV(addr);
375	fs_off = PAGE_SIZE;
376	if (xfsread(ino, p, hdr.ex.a_text))
377	    return;
378	p += roundup2(hdr.ex.a_text, PAGE_SIZE);
379	if (xfsread(ino, p, hdr.ex.a_data))
380	    return;
381	p += hdr.ex.a_data + roundup2(hdr.ex.a_bss, PAGE_SIZE);
382	bootinfo.bi_symtab = VTOP(p);
383	memcpy(p, &hdr.ex.a_syms, sizeof(hdr.ex.a_syms));
384	p += sizeof(hdr.ex.a_syms);
385	if (hdr.ex.a_syms) {
386	    if (xfsread(ino, p, hdr.ex.a_syms))
387		return;
388	    p += hdr.ex.a_syms;
389	    if (xfsread(ino, p, sizeof(int)))
390		return;
391	    x = *(uint32_t *)p;
392	    p += sizeof(int);
393	    x -= sizeof(int);
394	    if (xfsread(ino, p, x))
395		return;
396	    p += x;
397	}
398    } else {
399	fs_off = hdr.eh.e_phoff;
400	for (j = i = 0; i < hdr.eh.e_phnum && j < 2; i++) {
401	    if (xfsread(ino, ep + j, sizeof(ep[0])))
402		return;
403	    if (ep[j].p_type == PT_LOAD)
404		j++;
405	}
406	for (i = 0; i < 2; i++) {
407	    p = PTOV(ep[i].p_paddr & 0xffffff);
408	    fs_off = ep[i].p_offset;
409	    if (xfsread(ino, p, ep[i].p_filesz))
410		return;
411	}
412	p += roundup2(ep[1].p_memsz, PAGE_SIZE);
413	bootinfo.bi_symtab = VTOP(p);
414	if (hdr.eh.e_shnum == hdr.eh.e_shstrndx + 3) {
415	    fs_off = hdr.eh.e_shoff + sizeof(es[0]) *
416		(hdr.eh.e_shstrndx + 1);
417	    if (xfsread(ino, &es, sizeof(es)))
418		return;
419	    for (i = 0; i < 2; i++) {
420		memcpy(p, &es[i].sh_size, sizeof(es[i].sh_size));
421		p += sizeof(es[i].sh_size);
422		fs_off = es[i].sh_offset;
423		if (xfsread(ino, p, es[i].sh_size))
424		    return;
425		p += es[i].sh_size;
426	    }
427	}
428	addr = hdr.eh.e_entry & 0xffffff;
429    }
430    bootinfo.bi_esymtab = VTOP(p);
431    bootinfo.bi_kernelname = VTOP(fname);
432    bootinfo.bi_bios_dev = dsk.drive;
433    __exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK),
434	   MAKEBOOTDEV(dev_maj[dsk.type], 0, dsk.slice, dsk.unit, dsk.part),
435	   0, 0, 0, VTOP(&bootinfo));
436}
437
438static int
439parse(char *arg)
440{
441    char *p, *q;
442    int drv, c, i;
443
444    while ((c = *arg++)) {
445	if (c == ' ' || c == '\t' || c == '\n')
446	    continue;
447	for (p = arg; *p && *p != '\n' && *p != ' ' && *p != '\t'; p++);
448	if (*p)
449	    *p++ = 0;
450	if (c == '-') {
451	    while ((c = *arg++)) {
452		for (i = 0; c != optstr[i]; i++)
453		    if (i == NOPT - 1)
454			return -1;
455		opts ^= 1 << flags[i];
456	    }
457	    if (opts & 1 << RBX_PROBEKBD) {
458		i = *(uint8_t *)PTOV(0x496) & 0x10;
459		/* printf("Keyboard: %s\n", i ? "yes" : "no"); XXX GCC31 size */
460		if (!i)
461		    opts |= 1 << RBX_DUAL | 1 << RBX_SERIAL;
462		opts &= ~(1 << RBX_PROBEKBD);
463	    }
464	    ioctrl = opts & 1 << RBX_DUAL ? (IO_SERIAL|IO_KEYBOARD) :
465		     opts & 1 << RBX_SERIAL ? IO_SERIAL : IO_KEYBOARD;
466	    if (ioctrl & IO_SERIAL)
467	        sio_init();
468	} else {
469	    for (q = arg--; *q && *q != '('; q++);
470	    if (*q) {
471		drv = -1;
472		if (arg[1] == ':') {
473		    if (*arg < '0' || *arg > '9')
474			return -1;
475		    drv = *arg - '0';
476		    arg += 2;
477		}
478		if (q - arg != 2)
479		    return -1;
480		for (i = 0; arg[0] != dev_nm[i][0] ||
481			    arg[1] != dev_nm[i][1]; i++)
482		    if (i == NDEV - 1)
483			return -1;
484		dsk.type = i;
485		arg += 3;
486		if (arg[1] != ',' || *arg < '0' || *arg > '9')
487		    return -1;
488		dsk.unit = *arg - '0';
489		arg += 2;
490		dsk.slice = WHOLE_DISK_SLICE;
491		if (arg[1] == ',') {
492		    if (*arg < '0' || *arg > '0' + NDOSPART)
493			return -1;
494		    if ((dsk.slice = *arg - '0'))
495			dsk.slice++;
496		    arg += 2;
497		}
498		if (arg[1] != ')' || *arg < 'a' || *arg > 'p')
499		    return -1;
500		dsk.part = *arg - 'a';
501		arg += 2;
502		if (drv == -1)
503		    drv = dsk.unit;
504		dsk.drive = (dsk.type == TYPE_WD ||
505			     dsk.type == TYPE_AD ||
506			     dsk.type == TYPE_DA ? DRV_HARD : 0) + drv;
507		dsk.meta = 0;
508		fsread(0, NULL, 0);
509	    }
510	    if ((i = p - arg - !*(p - 1))) {
511		if (i >= sizeof(kname))
512		    return -1;
513		memcpy(kname, arg, i + 1);
514	    }
515	}
516	arg = p;
517    }
518    return 0;
519}
520
521static ino_t
522lookup(const char *path)
523{
524    char name[MAXNAMLEN + 1];
525    const char *s;
526    ino_t ino;
527    ssize_t n;
528    int dt;
529
530    ino = ROOTINO;
531    dt = DT_DIR;
532    for (;;) {
533	if (*path == '/')
534	    path++;
535	if (!*path)
536	    break;
537	for (s = path; *s && *s != '/'; s++);
538	if ((n = s - path) > MAXNAMLEN)
539	    return 0;
540	ls = *path == '?' && n == 1 && !*s;
541	memcpy(name, path, n);
542	name[n] = 0;
543	if ((dt = fsfind(name, &ino)) <= 0)
544	    break;
545	path = s;
546    }
547    return dt == DT_REG ? ino : 0;
548}
549static int
550xfsread(ino_t inode, void *buf, size_t nbyte)
551{
552    if (fsread(inode, buf, nbyte) != nbyte) {
553	printf("Invalid %s\n", "format");
554	return -1;
555    }
556    return 0;
557}
558
559static ssize_t
560fsread(ino_t inode, void *buf, size_t nbyte)
561{
562    static struct dinode din;
563    static ino_t inomap;
564    static daddr_t blkmap, indmap;
565    char *blkbuf;
566    ufs_daddr_t *indbuf;
567    struct fs *fs;
568    char *s;
569    ufs_daddr_t lbn, addr;
570    daddr_t vbaddr;
571    size_t n, nb, off, vboff;
572
573    blkbuf = dmadat->blkbuf;
574    indbuf = dmadat->indbuf;
575    fs = (struct fs *)dmadat->sbbuf;
576    if (!dsk.meta) {
577	inomap = 0;
578	if (dskread(fs, SBOFF / DEV_BSIZE, SBSIZE / DEV_BSIZE))
579	    return -1;
580	if (fs->fs_magic != FS_MAGIC) {
581	    printf("Not ufs\n");
582	    return -1;
583	}
584	dsk.meta++;
585    }
586    if (!inode)
587	return 0;
588    if (inomap != inode) {
589	if (dskread(blkbuf, INO_TO_VBA(fs, inode), DBPERVBLK))
590	    return -1;
591	din = ((struct dinode *)blkbuf)[INO_TO_VBO(fs, inode)];
592	inomap = inode;
593	fs_off = 0;
594	blkmap = indmap = 0;
595    }
596    s = buf;
597    if (nbyte > (n = din.di_size - fs_off))
598	nbyte = n;
599    nb = nbyte;
600    while (nb) {
601	lbn = lblkno(fs, fs_off);
602	off = blkoff(fs, fs_off);
603	if (lbn < NDADDR)
604	    addr = din.di_db[lbn];
605	else {
606	    vbaddr = FS_TO_VBA(fs, din.di_ib[0], sizeof(indbuf[0]) *
607		((lbn - NDADDR) % NINDIR(fs)));
608	    if (indmap != vbaddr) {
609		if (dskread(indbuf, vbaddr, DBPERVBLK))
610		    return -1;
611		indmap = vbaddr;
612	    }
613	    addr = indbuf[(lbn - NDADDR) % INDIRPERVBLK];
614	}
615	vbaddr = FS_TO_VBA(fs, addr, off);
616	vboff = FS_TO_VBO(fs, addr, off);
617	n = dblksize(fs, &din, lbn) - (off & ~VBLKMASK);
618	if (n > VBLKSIZE)
619		n = VBLKSIZE;
620	if (blkmap != vbaddr) {
621	    if (dskread(blkbuf, vbaddr, n >> DEV_BSHIFT))
622		return -1;
623	    blkmap = vbaddr;
624	}
625	n -= vboff;
626	if (n > nb)
627	    n = nb;
628	memcpy(s, blkbuf + vboff, n);
629	s += n;
630	fs_off += n;
631	nb -= n;
632    }
633    return nbyte;
634}
635
636static int
637dskread(void *buf, unsigned lba, unsigned nblk)
638{
639    struct dos_partition *dp;
640    struct disklabel *d;
641    char *sec;
642    unsigned sl, i;
643
644    if (!dsk.meta) {
645	sec = dmadat->secbuf;
646	dsk.start = 0;
647	if (drvread(sec, DOSBBSECTOR, 1))
648	    return -1;
649	dp = (void *)(sec + DOSPARTOFF);
650	sl = dsk.slice;
651	if (sl < BASE_SLICE) {
652	    for (i = 0; i < NDOSPART; i++)
653		if (dp[i].dp_typ == DOSPTYP_386BSD &&
654		    (dp[i].dp_flag & 0x80 || sl < BASE_SLICE)) {
655		    sl = BASE_SLICE + i;
656		    if (dp[i].dp_flag & 0x80 ||
657			dsk.slice == COMPATIBILITY_SLICE)
658			break;
659		}
660	    if (dsk.slice == WHOLE_DISK_SLICE)
661		dsk.slice = sl;
662	}
663	if (sl != WHOLE_DISK_SLICE) {
664	    if (sl != COMPATIBILITY_SLICE)
665		dp += sl - BASE_SLICE;
666	    if (dp->dp_typ != DOSPTYP_386BSD) {
667		printf("Invalid %s\n", "slice");
668		return -1;
669	    }
670	    dsk.start = dp->dp_start;
671	}
672	if (drvread(sec, dsk.start + LABELSECTOR, 1))
673		return -1;
674	d = (void *)(sec + LABELOFFSET);
675	if (d->d_magic != DISKMAGIC || d->d_magic2 != DISKMAGIC) {
676	    if (dsk.part != RAW_PART) {
677		printf("Invalid %s\n", "label");
678		return -1;
679	    }
680	} else {
681	    if (!dsk.init) {
682		if (d->d_type == DTYPE_SCSI)
683		    dsk.type = TYPE_DA;
684		dsk.init++;
685	    }
686	    if (dsk.part >= d->d_npartitions ||
687		!d->d_partitions[dsk.part].p_size) {
688		printf("Invalid %s\n", "partition");
689		return -1;
690	    }
691	    dsk.start = d->d_partitions[dsk.part].p_offset;
692	}
693    }
694    return drvread(buf, dsk.start + lba, nblk);
695}
696
697static int
698printf(const char *fmt,...)
699{
700    static const char digits[16] = "0123456789abcdef";
701    va_list ap;
702    char buf[10];
703    char *s;
704    unsigned r, u;
705    int c;
706
707    va_start(ap, fmt);
708    while ((c = *fmt++)) {
709	if (c == '%') {
710	    c = *fmt++;
711	    switch (c) {
712	    case 'c':
713		putchar(va_arg(ap, int));
714		continue;
715	    case 's':
716		for (s = va_arg(ap, char *); *s; s++)
717		    putchar(*s);
718		continue;
719	    case 'u':
720	    case 'x':
721		r = c == 'u' ? 10U : 16U;
722		u = va_arg(ap, unsigned);
723		s = buf;
724		do
725		    *s++ = digits[u % r];
726		while (u /= r);
727		while (--s >= buf)
728		    putchar(*s);
729		continue;
730	    }
731	}
732	putchar(c);
733    }
734    va_end(ap);
735    return 0;
736}
737
738static int
739putchar(int c)
740{
741    if (c == '\n')
742	xputc('\r');
743    return xputc(c);
744}
745
746static void *
747memcpy(void *dst, const void *src, size_t size)
748{
749    const char *s;
750    char *d;
751
752    for (d = dst, s = src; size; size--)
753	*d++ = *s++;
754    return dst;
755}
756
757static uint32_t
758memsize(int type)
759{
760    v86.addr = type;
761    v86.eax = 0x8800;
762    v86int();
763    return v86.eax;
764}
765
766static int
767drvread(void *buf, unsigned lba, unsigned nblk)
768{
769    static unsigned c = 0x2d5c7c2f;
770
771    printf("%c\b", c = c << 8 | c >> 24);
772    v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS;
773    v86.addr = XREADORG;		/* call to xread in boot1 */
774    v86.es = VTOPSEG(buf);
775    v86.eax = lba;
776    v86.ebx = VTOPOFF(buf);
777    v86.ecx = lba >> 16;
778    v86.edx = nblk << 8 | dsk.drive;
779    v86int();
780    v86.ctl = V86_FLAGS;
781    if (V86_CY(v86.efl)) {
782	printf("Disk error 0x%x (lba=0x%x)\n", v86.eax >> 8 & 0xff,
783	       lba);
784	return -1;
785    }
786    return 0;
787}
788
789static int
790keyhit(unsigned ticks)
791{
792    uint32_t t0, t1;
793
794    if (opts & 1 << RBX_NOINTR)
795	return 0;
796    t0 = 0;
797    for (;;) {
798	if (xgetc(1))
799	    return 1;
800	t1 = *(uint32_t *)PTOV(0x46c);
801	if (!t0)
802	    t0 = t1;
803	if (t1 < t0 || t1 >= t0 + ticks)
804	    return 0;
805    }
806}
807
808static int
809xputc(int c)
810{
811    if (ioctrl & IO_KEYBOARD)
812	putc(c);
813    if (ioctrl & IO_SERIAL)
814	sio_putc(c);
815    return c;
816}
817
818static int
819xgetc(int fn)
820{
821    if (opts & 1 << RBX_NOINTR)
822	return 0;
823    for (;;) {
824	if (ioctrl & IO_KEYBOARD && getc(1))
825	    return fn ? 1 : getc(0);
826	if (ioctrl & IO_SERIAL && sio_ischar())
827	    return fn ? 1 : sio_getc();
828	if (fn)
829	    return 0;
830    }
831}
832
833static int
834getc(int fn)
835{
836    v86.addr = 0x16;
837    v86.eax = fn << 8;
838    v86int();
839    return fn == 0 ? v86.eax & 0xff : !V86_ZR(v86.efl);
840}
841