3dcc3bd1cf
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.
334 lines
6.5 KiB
ArmAsm
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
|