ack/plat/pc86/boot.s
George Koehler 3dcc3bd1cf Allow pc86 bootloader to boot from more floppy drives.
When the bootloader probes the drive geometry, the BIOS can clobber the
es register.  If this happens, the bootloader loads the program to the
wrong address, and jumps off the code.  This happens with an emulated
floppy drive in Bochs or QEMU, but not with an emulated hard disk.
2012-09-23 14:43:22 -04:00

334 lines
6.5 KiB
ArmAsm

#
! $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