ack/plat/pc86/boot.s

334 lines
6.5 KiB
ArmAsm
Raw Normal View History

#
! $Source$
! $State$
! $Revision$
! Declare segments (the order is important).
.sect .text
.sect .rom
.sect .data
.sect .bss
.sect .text
! ****** WARNING! ******
!
! The PC boot sector requires a magic number at the end to signify that the
! disk is bootable. Unfortunately, the ACK assembler is a bit simple and we
! can't tell it to put the 0xAA55 at a particular address without writing our
! own custom binary generator. As a result, we need to manually insert just
! the right amount of padding in order to make this work.
!
! If you ever need to change the boot code, this needs adjusting. I recommend
! a hex editor.
PADDING = 0xB7
! 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 ss, ax
! Defer setting es until after probing the drive.
! 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.
! This might clobber es.
push dx
mov ax, 0x0800 ! service number
int 0x13
mov ax, cs ! restore es
mov es, ax
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
! 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.
push envp ! envp
push argv ! argv
push 1 ! argc
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'
! The argv and env arrays.
argv: .data2 exename, 0
envp: .data2 0
exename: .asciz 'pc86.img'
! ...and we need this to fool the PC into booting our boot sector.
.space PADDING
.data2 0xAA55
! Define symbols at the beginning of our various segments, so that we can find
! them. (Except .text, which has already been done.)
.define begtext, begdata, begbss
.sect .data; begdata:
.sect .rom; begrom:
.sect .bss; begbss:
! Some magic data. All EM systems need these.
.define .trppc, .ignmask, _errno
.comm .trppc, 4
.comm .ignmask, 4
.comm _errno, 4