Added 8086 PC bootable floppy support (pc86).

This commit is contained in:
dtrg 2007-02-20 00:25:12 +00:00
parent 1c7bb87041
commit df153ba299
15 changed files with 1028 additions and 0 deletions

41
plat/pc86/README Normal file
View file

@ -0,0 +1,41 @@
# $Source$
# $State$
The pc86 platform
=================
pc86 is an i86-based BSP that produces bootable floppy disk images that can
be run on most PCs. It is intended to be quick and dirty rather than actually
useful, although it may come in handy for hardware test purposes, boot
loaders, and the like.
The code runs in TINY mode, where CS, DS and SS all share the same segment.
This means that there's not very much memory available. It would be very easy
to change it to run in SMALL mode, where CS occupies one segment and DS and SS
another, which would give 64kB for nearly all programs; I just haven't done it.
This port uses a syscall interface, which means you get *all* of the ACK's
builtin libc with no shortcuts. File descriptors 0, 1 and 2 represent the
console. All reads block. There's enough TTY emulation to allow \n conversion
(set RAW to turn off) and local echo (unset ECHO to turn off); this is needed
to make Basic work.
Language support
================
Tested with C (both), Basic, Pascal, Occam, Modula-2.
Basic works, but because Basic programs require access to a data file to work,
and pc86 doesn't (of course) have a file system, programs won't start unless
you hack the compiler not to try and open it.
Example command line
====================
ack -mpc86 -O -ansi -o pc86.img test.c
The file pc86.img can then be copied onto a floppy and booted, or run via qemu
or somesuch emulator.
David Given
dg@cowlark.com

321
plat/pc86/boot.s Normal file
View file

@ -0,0 +1,321 @@
#
! $Source$
! $State$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Some definitions.
BOOT_SEGMENT = 0x07C0 ! Where we've been loaded
#define PRINT(N) push ax; push bx; movb ah, 0x0E; movb al, N; mov bx, 0x0007; int 0x10; pop bx; pop ax
begtext:
! This code makes up the PC boot sector, and is the first thing on the
! floppy disk. The PC will load this sector to 0x07C0:0000 and jump to it
! with dl set to our drive, but it won't necessarily do it in a sane way
! (some BIOSes jump to 0x0000:7C00 instead). So, we need to fix that.
jmpf BOOT_SEGMENT : start2
start2:
! Set up the segment descriptors. We're running in tiny mode, so it's just
! a matter of copying the contents of cs (already been set up by the jmpf)
! into the other registers.
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
! Initialise the stack, which will start at the top of our segment and work
! down.
mov sp, 0 ! the first push will wrap round to 0xFFFF
! Some more startup housekeeping.
sti
cld
! We're now set up for actual code. Write out our banner. Remember that
! at this point dl contains our drive number, which we want to keep.
mov si, banner_msg
call write_string
! Probe the drive to figure out its geometry.
push dx
mov ax, 0x0800 ! service number
int 0x13
pop ax
jc cant_boot
! At this point:
! al: current drive
! cl: maximum sector number (bottom six bits)
! dh: maximum head number
! We don't care about the rest.
andb cl, 0x3F
! We now need to go through a loop loading each sector in turn.
! During this loop, the registers will be set up as follows:
! al: current cylinder
! ah: maximum head
! bx: address
! cl: current sector (one based)
! ch: maximum sector (one based)
! dl: current drive
! dh: current head
! Why, yes, they are painstakingly shoehorned in to get all the data
! into registers.
movb dl, al
movb ch, cl
movb ah, dh
movb al, 0 ! start on cylinder 0
mov bx, 0x0200 ! don't overwrite boot sector
movb cl, 2 ! start on sector 2 (skip boot sector)
movb dh, 0 ! start on head 0
1:
call read_sector
! Next memory area.
add bx, 0x0200
cmp bx, enddata
ja finished
! Next sector.
incb cl
cmpb cl, ch
jle 1b
movb cl, 1 ! back to sector 1 again
! Next head.
incb dh
cmpb dh, ah
jle 1b
movb dh, 0 ! back to head 1 again
! Next cylinder.
incb al
jmp 1b
cant_boot:
mov si, bootfail_msg
call write_string
jmp EXIT
! Reads a sector into memory. The parameters are:
! al: cylinder
! bx: address
! cl: sector
! dl: drive
! dh: head
! If an error occurs, it'll automatically try again. And again.
! And again...
read_sector:
push ax
push bx
push cx
push dx
#if 0
push dx
xorb dh, dh
movb dl, cl
call write_hex4
pop dx
PRINT(0x20)
push dx
movb dl, dh
xorb dh, dh
call write_hex4
pop dx
PRINT(0x20)
push dx
movb dl, al
xorb dh, dh
call write_hex4
pop dx
#endif
1:
movb ch, al
mov ax, 0x0201 ! service 2, read one sector
int 0x13
jc 2f
mov ax, 0x0E2E ! write out a .
mov bx, 0x0007 ! page 0, white
int 0x10
pop dx
pop cx
pop bx
pop ax
ret
! If a read fail occurs, the spec (such as it is) states that we need
! to reset the fd controller and try again.
2:
push ax
push bx
mov ax, 0x0E21 ! write out a !
mov bx, 0x0007 ! page 0, white
int 0x10
mov ax, 0x0000
int 0x13
pop bx
pop ax
jmp 1b
! Waits for a keystroke (and then discards it).
pause:
push ax
xorb ah, ah
int 0x16
pop ax
ret
! This utility writes the string pointed to by ds:si out to the console.
write_string:
push ax
push bx
1:
lodsb
andb al, al
jz 2f
movb ah, 0xE ! service
mov bx, 0x0007 ! page 0, white
int 0x10
jmp 1b
2:
pop bx
pop ax
ret
! Writes out the contents of dx as hex.
write_hex4:
push ax
push cx
mov cx, 4 ! 4 hex digits
1:
rol dx, 1 ! rotate so that highest 4 bits are at the bottom
rol dx, 1
rol dx, 1
rol dx, 1
mov ax, 0xE0F ! ah = request, al = mask for nybble
andb al, dl
addb al, 0x90 ! convert al to ascii hex (four instructions)
daa
adcb al, 0x40
daa
int 0x10
loop 1b
pop cx
pop ax
ret
! Everything loaded successfully!
!
! We now need to do some setup and start the program itself.
finished:
mov si, running_msg
call write_string
call pause
! Wipe the bss. (I'm a little suprised that __m_a_i_n doesn't do this.)
mov di, begbss
mov cx, endbss
sub cx, di
mov ax, 0
rep stosb
! Push standard parameters onto the stack and go.
mov ax, 1
push ax ! argc
mov ax, 2
push ax ! argc
mov ax, 3
push ax ! envp
call __m_a_i_n
! fall through into the exit routine.
! Halts, waits for a keypress, and reboots. This also becomes the
! application termination routine.
.define __exit
.extern __exit
.define EXIT
.extern EXIT
__exit:
EXIT:
mov si, halted_msg
call write_string
1:
jmp 1b
xor ax, ax
int 0x16 ! get key
int 0x19 ! reboot
! Some text messages.
banner_msg: .asciz 'ACKBOOT\n\r'
nl_msg = banner_msg + 7 ! cheap trick
bootfail_msg: .asciz 'Unable to boot!\n\r'
loading_msg: .asciz '\n\rLoading...\n\r'
halted_msg: .asciz '\n\rHalted.\n\r'
running_msg: .asciz '\n\rRunning.\n\r'
! ...and we need this to fool the PC into booting our boot sector.
.align 510
.data2 0xAA55
.define begtext,begdata,begbss
.define hol0,.trppc,.ignmask
.define ERANGE,ESET,EHEAP,ECASE,EILLINS,EIDIVZ,EODDZ
.extern _end
.sect .data
hol0:
.data2 0,0
.data2 0,0
.ignmask:
.data2 0
.trppc:
.data2 0
! Define symbols at the beginning of our various segments, so that we can find
! them. (Except .text, which has already been done.)
.sect .data; begdata:
.sect .rom; begrom:
.sect .bss; begbss:

67
plat/pc86/descr Normal file
View file

@ -0,0 +1,67 @@
# $Revision$
var w=2
var p=2
var s=2
var l=4
var f=4
var d=8
var ARCH=i86
var PLATFORM=pc86
var PLATFORMDIR={EM}/lib/{PLATFORM}
var CPP_F=-D__unix
var ALIGN=-a0:1 -a1:1 -a2:1 -a3:1
var C_LIB={PLATFORMDIR}/tail_cc.1s {PLATFORMDIR}/tail_cc.2g
var OLD_C_LIB={C_LIB}
var MACHOPT_F=-m8
name be
from .m.g
to .s
program {EM}/lib.bin/{PLATFORM}/ncg
args <
stdout
need .e
end
name as
from .s.so
to .o
program {EM}/lib.bin/{PLATFORM}/as
args - -o > <
prep cond
end
name led
from .o.a
to .out
program {EM}/lib.bin/em_led
mapflag -l* LNAME={PLATFORMDIR}/tail_*
mapflag -i SEPID=-b1:0
mapflag -fp FLOATS={EM}/{ILIB}fp
mapflag -ansi C_LIB={PLATFORMDIR}/tail_ac
args {ALIGN} {SEPID?} \
(.e:{HEAD}={PLATFORMDIR}/boot.o) \
({RTS}:.ocm.b={PLATFORMDIR}/head_cc) \
({RTS}{ANSI?}:.c={PLATFORMDIR}/head_cc) \
({RTS}{ANSI?}:.cansi={PLATFORMDIR}/head_ac) \
({RTS}:.mod={PLATFORMDIR}/head_m2) \
({RTS}:.p={PLATFORMDIR}/head_pc) \
-o > < \
(.p:{TAIL}={PLATFORMDIR}/tail_pc) \
(.b:{TAIL}={PLATFORMDIR}/tail_bc) \
(.mod:{TAIL}={PLATFORMDIR}/tail_m2) \
(.ocm:{TAIL}={PLATFORMDIR}/tail_ocm) \
(.ocm.b:{TAIL}={OLD_C_LIB}) \
(.c:{TAIL}={C_LIB}) \
{FLOATS?} \
(.e:{TAIL}={PLATFORMDIR}/libsys.a \
{PLATFORMDIR}/tail_mon \
{PLATFORMDIR}/libsys.a \
{PLATFORMDIR}/libem.a \
{PLATFORMDIR}/libend.a)
linker
end
name cv
from .out
to .img
program {EM}/bin/aslod
args < >
outfile pc86.img
end

54
plat/pc86/libsys/_brk.s Normal file
View file

@ -0,0 +1,54 @@
#
! $Source$
! $State$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
! This file contains the code necessary to extend the ACK heap. This is called
! by a i86/libem helper function called .strhp, which takes care of updating
! some magic global variables --- defined here.
! Pointer to the current top of the heap.
.sect .data
.define .reghp
.reghp:
.data2 endbss
! Pointer to the current top of memory.
.sect .data
.define .limhp
.limhp:
.data2 endbss
! Claims more memory from the system, but does not actually change those
! global variables (.strhp does that). This does not use the C calling
! convention!
!
! Stack: ( desired_limhp : actual_limhp )
! Also returns: ax = -1 on failure
.sect .text
.define BRK
BRK:
pop bx ! holds return address
pop ax ! holds desired limhp
cmp ax, sp ! compare sp with si
jae fail ! si too big? (Overlaps stack?)
cmp ax, endbss ! compare with bottom of heap
jb fail ! si too small? (Overlaps bss?)
return:
push ax ! success
jmp bx
fail:
mov ax, -1
jmp return

165
plat/pc86/libsys/_mon.s Normal file
View file

@ -0,0 +1,165 @@
#
! $Source$
! $State$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .bss
.define __sys_params_in
.comm __sys_params_in, 6
.define __sys_params_out
.comm __sys_params_out, 2
.comm opcode, 2
.comm returnto, 2
.sect .text
! Called on system call. This does *not* use the C calling convention:
! ax: syscall number
! stack: ( param3 param2 param1 - result )
.define .mon
.mon:
mov (opcode), ax
pop (returnto)
cmp ax, 1
je exit
cmp ax, 3
je read
cmp ax, 4
je write
cmp ax, 5
je open
cmp ax, 6
je nop_1 ! close
cmp ax, 20
je nop_0 ! getpid
cmp ax, 35
je nop_1 ! time
cmp ax, 48
je sigtrp
cmp ax, 54
je ioctl
! Syscall not supported --- write out an error message and halt.
unsupported:
mov si, msg
1:
lodsb
andb al, al
jz 2f
movb ah, 0xE ! service
mov bx, 0x0007 ! page 0, white
int 0x10
jmp 1b
2:
! Write out the syscall number.
mov dx, (opcode)
mov cx, 4 ! 4 hex digits
1:
rol dx, 1 ! rotate so that highest 4 bits are at the bottom
rol dx, 1
rol dx, 1
rol dx, 1
mov ax, 0xE0F ! ah = request, al = mask for nybble
andb al, dl
addb al, 0x90 ! convert al to ascii hex (four instructions)
daa
adcb al, 0x40
daa
int 0x10
loop 1b
! Exit.
jmp EXIT
.sect .rom
msg:
.asciz 'NOSYS'
.sect .text
exit:
jmp EXIT
read:
mov ax, __sys_read
jmp in_3_out_1
write:
mov ax, __sys_write
jmp in_3_out_1
open:
add sp, 2*2
jmp unimplemented
ioctl:
mov ax, __sys_ioctl
jmp in_3_out_0
sigtrp:
add sp, 4
jmp unimplemented
in_3_out_0:
pop (__sys_params_in+0)
pop (__sys_params_in+2)
pop (__sys_params_in+4)
call ax
jmp out_0
in_3_out_1:
pop (__sys_params_in+0)
pop (__sys_params_in+2)
pop (__sys_params_in+4)
call ax
jmp out_1
out_0:
or ax, ax
jnz failed
push ax
jmp return
out_1:
or ax, ax
jnz failed
push (__sys_params_out)
push ax
jmp return
unimplemented:
mov ax, EBADMON
failed:
push ax
push ax
jmp return
nop_1:
add sp, 1*2
jmp nop_0
nop_3:
add sp, 3*2
nop_0:
mov ax, 0
push ax
jmp return
return:
jmp (returnto)

87
plat/pc86/libsys/_sbrk.c Normal file
View file

@ -0,0 +1,87 @@
/* There should be a header for brk and sbrk, but there isn't. */
#include <stdlib.h>
#include <errno.h>
/* #include <unistd.h> */
extern char _end[];
static char* brkpointer = _end;
static void prints(char* s)
{
for (;;)
{
char c = *s++;
if (!c)
break;
write(0, &c, 1);
}
}
static void printc(unsigned int n)
{
char c;
n &= 0xF;
if (n < 10)
c = n + '0';
else
c = n + 'A' - 10;
write(0, &c, 1);
}
static void printh(unsigned int n)
{
printc(n>>12);
printc(n>>8);
printc(n>>4);
printc(n);
}
static void waitforkey(void)
{
char c;
read(1, &c, 1);
}
int _brk(char* newend)
{
char dummy;
/* Ensure that newend is reasonable. */
if ((newend < _end) || (newend > (&dummy - 256)))
{
prints("[brk to ");
printh((unsigned int) newend);
prints(" failed]\n\r");
waitforkey();
errno = ENOMEM;
return -1;
}
prints("[brk to ");
printh((unsigned int) newend);
prints("]\n\r");
waitforkey();
brkpointer = newend;
return 0;
}
char* _sbrk(int delta)
{
char* oldpointer = brkpointer;
prints("[sbrk delta ");
printh((unsigned int) delta);
prints(" from ");
printh((unsigned int) oldpointer);
prints("]\n\r");
printh((unsigned int) brkpointer);
prints(" ");
printh((unsigned int) _end);
if (_brk(oldpointer + delta) == -1)
return (char*)-1;
return oldpointer;
}

View file

@ -0,0 +1,52 @@
#include <stdlib.h>
#include <errno.h>
#include <sgtty.h>
#include "libsys.h"
int _sys_ttyflags = ECHO;
extern struct
{
int fd;
int request;
void* argp;
} _sys_params_in;
extern struct
{
int result;
} _sys_params_out;
#define P _sys_params_in
static int tiocgetp(void)
{
struct sgttyb* s = P.argp;
s->sg_flags = _sys_ttyflags;
return 0;
}
static int tiocsetp(void)
{
struct sgttyb* s = P.argp;
_sys_ttyflags = s->sg_flags;
return 0;
}
int _sys_ioctl(void)
{
switch (P.request)
{
case TIOCGETP:
_sys_params_out.result = tiocgetp();
return 0;
case TIOCSETP:
_sys_params_out.result = tiocsetp();
return 0;
}
_sys_params_out.result = -1;
errno = EINVAL;
return 0;
}

View file

@ -0,0 +1,22 @@
#
! $Source$
! $State$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Reads a single byte.
.define __sys_rawread
__sys_rawread:
xorb ah, ah
int 0x16
xorb ah, ah
ret

View file

@ -0,0 +1,28 @@
#
! $Source$
! $State$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! Writes a single byte to the console.
.define __sys_rawwrite
.extern __sys_rawwrite
__sys_rawwrite:
push bp
mov bp, sp
movb al, 4(bp)
movb ah, 0x0E
mov bx, 0x0007
int 0x10
jmp .cret

View file

@ -0,0 +1,49 @@
#include <stdlib.h>
#include <errno.h>
#include <sgtty.h>
#include "libsys.h"
extern struct
{
int fd;
char* buffer;
size_t count;
} _sys_params_in;
extern struct
{
size_t bytesread;
} _sys_params_out;
#define P _sys_params_in
int _sys_read(void)
{
char i;
/* We're only allowed to read from fd 0, 1 or 2. */
if ((P.fd < 0) || (P.fd > 2))
return EBADF;
/* Empty buffer? */
if (P.count == 0)
{
_sys_params_out.bytesread = 0;
return 0;
}
/* Read one byte. */
i = _sys_rawread();
if ((i == '\r') && !(_sys_ttyflags & RAW))
i = '\n';
if (_sys_ttyflags & ECHO)
_sys_write_tty(i);
*P.buffer = i;
_sys_params_out.bytesread = 1;
return 0;
}

View file

@ -0,0 +1,50 @@
#include <stdlib.h>
#include <errno.h>
#include <sgtty.h>
#include "libsys.h"
extern struct
{
int fd;
const char* buffer;
size_t count;
} _sys_params_in;
extern struct
{
size_t byteswritten;
} _sys_params_out;
#define P _sys_params_in
void _sys_write_tty(char c)
{
_sys_rawwrite(c);
if ((c == '\n') && !(_sys_ttyflags & RAW))
_sys_rawwrite('\r');
}
int _sys_write(void)
{
int i;
/* We're only allowed to write to fd 0, 1 or 2. */
if ((P.fd < 0) || (P.fd > 2))
return EBADF;
/* Write all data. */
i = 0;
while (i < P.count)
{
_sys_write_tty(*P.buffer++);
i++;
}
/* No failures. */
_sys_params_out.byteswritten = P.count;
return 0;
}

24
plat/pc86/libsys/errno.s Normal file
View file

@ -0,0 +1,24 @@
#
! $Source$
! $State$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
#define D(e) .define e; e
.sect .data
D(ERANGE) = 1
D(ESET) = 2
D(EIDIVZ) = 6
D(EHEAP) = 17
D(EILLINS) = 18
D(EODDZ) = 19
D(ECASE) = 20
D(EBADMON) = 25

11
plat/pc86/libsys/libsys.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef LIBSYS_H
#define LIBSYS_H
extern void _sys_rawwrite(unsigned char b);
extern unsigned char _sys_rawread(void);
extern void _sys_write_tty(char c);
extern int _sys_ttyflags;
#endif

20
plat/pc86/libsys/pmfile Normal file
View file

@ -0,0 +1,20 @@
-- $Source$
-- $State$
local d = ROOTDIR.."plat/pc86/libsys/"
libsys_pc86 = acklibrary {
ACKBUILDFLAGS = {PARENT, "-ansi"},
ACKINCLUDES = {"%BINDIR%include"},
ackfile (d.."errno.s"),
ackfile (d.."_mon.s"),
ackfile (d.."_brk.s"),
ackfile (d.."_sys_rawread.s"),
ackfile (d.."_sys_rawwrite.s"),
ackfile (d.."_sys_read.c"),
ackfile (d.."_sys_write.c"),
ackfile (d.."_sys_ioctl.c"),
install = pm.install("%BINDIR%lib/%PLATFORM%/libsys.a"),
}

37
plat/pc86/pmfile Normal file
View file

@ -0,0 +1,37 @@
-- $Source$
-- $State$
local d = ROOTDIR.."plat/pc86/"
include (d.."libsys/pmfile")
local bootsector = ackfile {
file (d.."boot.s"),
install = pm.install("%BINDIR%lib/pc86/boot.o"),
}
local descr = group {
install = pm.install(d.."descr", "%BINDIR%%PLATIND%/%PLATFORM%/descr")
}
platform_pc86 = group {
ARCH = "i86",
PLATFORM = "pc86",
OPTIMISATION = "-O",
-- Ensure the descr file is installed first because we'll need it
-- to build the libraries.
descr,
-- Build the back-end support.
mach_i86,
support_i86,
lang_runtimes,
-- Build the PC standalone syscall library.
libsys_pc86,
bootsector,
}