I've created a very small test program that prints an "x" on the screen every second (ish);
Code: Select all
CPU 386 ; Architecture we're using
BITS 16 ; MBRs are 16-bit code
org 7C00h ; The location the BIOS loads the MBR
; Offset of int 0x1C - 4 bytes per entry
%define TIMER_IVT_OFFSET 0x1C * 4
main:
; Perform this jump to make sure cs is set to 0x0000 and ip is set to 0x7c00(ish) - in line with our org.
; BIOS behaviour on this is unpredictable
jmp 0x0000:setcs
print_char db 0 ; Flag to see if we need to print or not
tick_count db 0 ; Tick count for 1Ch
setcs:
; Base setup
cld ; Set the direction flag forward, used for movs, stos, etc.
; Setting this forward will increment cx on operations instead of decrementing it.
xor ax, ax ; Set ES=DS=0 - this can be changed later for rep operations
mov ds, ax
mov es, ax
mov ss, ax ; Stack segment is 0
mov sp, 0x7c00 ; This sets the stack (with segment 0, so ss:sp = 0000:7c00) to grow below the
; bootloader. Nothing is relocated or loaded there, so this should be fine.
; Set up the screen for displaying error messages and load the remaining sectors
; These calls could be defined as callable functions, but since they are only used once, it doesn't
; make that much sense to add that overhead and complexity.
; Clear the screen
mov ah, 0x07 ; Function 0x07: scroll window
mov al, 0x00 ; Clear entire window
mov bh, 0x07 ; White on black
mov cx, 0x00 ; Specifies top left of screen as (0,0)
mov dh, 0x18 ; 18h = 24 rows of chars
mov dl, 0x4f ; 4fh = 79 cols of chars
int 0x10 ; Video interrupt
; Move cursor to position 0,0
mov ah, 0x02 ; Function 0x02: set cursor position
mov dh, 0x00 ; Row 0
mov dl, 0x00 ; Col 0
mov bh, 0x00 ; Page 0
int 0x10 ; Video interrupt
; Stop all interrupts while we change an entry
cli
; IVT in real mode is at 0x0000:0x0000
; Each entry is 2 words; offset and address with address first,
; then offset. We are using offset 0 in this entire MBR and the
; address is the defined anim_timer_isr below.
mov word [TIMER_IVT_OFFSET], tick
mov word [TIMER_IVT_OFFSET+2], 0x0000
; Enable interrupts again
sti
main_loop:
cmp byte [print_char], 1
je .print_char
jmp main_loop
.print_char:
mov byte [print_char], 0
mov bh, 0
mov bl, 0
mov ah, 0Eh
mov al, 'x'
int 10h
jmp main_loop
tick:
; If we're on tick 5
cmp byte [tick_count], 5
je .set_print
; Increase tick count
inc byte [tick_count]
jmp .end_tick
.set_print:
; Set print_char to true and reset tick count
mov byte [tick_count], 0
mov byte [print_char], 1
.end_tick:
iret
; Padding bytes until the partition table.
; $$ = address of start of section, $ = current position, $-$$ = length of section thus far.
; We use 510, so the last 2 bytes can be specifically set (total sector size = 512).
times 510-($-$$) db 0
; MBR signature
db 55h, 0AAh
Code: Select all
nasm -fbin mbr.asm -o mbr.bin
Code: Select all
qemu-system-i386 -drive "format=raw,file=mbr.bin,if=floppy"
When I create a VirtualBox disk, like so;
Code: Select all
VBoxManage internalcommands createrawvmdk -filename ./mbr.vmdk -rawdisk ./mbr.bin
The actual MBR is far more complex and uses the timer for some animations. When I saw that didn't work, I suspected it might be because VirtualBox does not handle 1Ch. I've searched around for other cases of this issue, but could not find any.
Am I missing something here? Is there a way to fix this, or another way to get a timer working inside VirtualBox?
I also tried int 08h with the same result...