cpu 8086 org 0x500 jmp 0:(init+0x7C00-$$) %include "inc/bpb.asm" ; Far call via interrupt vector %macro intcall 1 call far [cs:%1*4] %endmacro ; Far jump via interrupt vector %macro intjump 1 jump far [cs:%1*4] %endmacro ; stack size %define stacks 512 banner: db "rdos", 0xA, 0xD, '$' ; Alias for Int 21h,AH=0h int20h: xor ah, ah ; allow extenders to get away with only hooking 21h jmp [cs:(0x21 * 4)] int21h: ; inside of kernel, direction always goes up ; the iret will restore it to the user value later cld ; set sfptr from ah push bx xor bx, bx mov bl, ah add bl, bl add bx, sftab mov bx, [cs:bx] mov [cs:sfptr], bx pop bx ; do the actual subfunction call call [cs:sfptr] ; inherit the lower 8 flag bits to userspace iretfl: push ax push bp mov bp, sp lahf mov [bp+8], ah pop bp pop ax ; iret frame: IP CS FLAGS iret: iret ; Subfunction ptr ; this is used as extra register in int21h sfptr: dw 0 ; Subfunction table sftab: dw sferr, getc, putc, sferr dw sferr, sferr, conout, conin dw sferr, puts, sferr, sferr dw sferr, sferr, sferr, sferr ; 10 dw sferr, sferr, sferr, sferr dw sferr, sferr, sferr, sferr dw sferr, sferr, sferr, sferr dw sferr, sferr, sferr, sferr ; 20 dw sferr, sferr, sferr, sferr dw sferr, setint, sferr, sferr dw sferr, sferr, sferr, sferr dw sferr, sferr, sferr, sferr ; 30 dw sferr, sferr, sferr, sferr dw sferr, getint, sferr, sferr dw sferr, sferr, sferr, sferr dw sferr, sferr, sferr, sferr ; OUT al character read getc: xor ax, ax int 0x16 push ax jmp putc.2 ; IN dl character to write putc: push ax mov al, dl .2: int 0x29 pop ax ret ; console output ; IN dl character conout: cmp dl, 0xFF je conine push ax mov al, dl int 0x29 pop ax ret ; console input with echo ; OUT al character ; zf clear when character available conine: mov ah, 1 int 0x16 jnz .has xor al, al ret .has: xor ax, ax int 0x16 test al, al jz conine int 0x29 test ax, ax ret ; console input without echo ; OUT al character ; zf clear when character available conin: mov ah, 1 int 0x16 jnz .has xor al, al ret .has: xor ax, ax int 0x16 test al, al jz conin test ax, ax ret ; DOS 1+ 9h - WRITE STRING TO STANDARD OUTPUT ; IN ds:dx '$'-terminated string puts: push si .loop: lodsb cmp al, '$' je .end int 0x29 jmp .loop .end: pop si ret ; DOS 1+ 25h - SET INTERRUPT VECTOR ; IN al interrupt number ; ds:dx entry point setint: push bx xor bx, bx ; BX=AL*4 mov bl, al add bl, bl add bl, bl ; save DS:DX into vector mov [cs:bx], dx mov [cs:bx+2], ds pop bx ret ; DOS 2+ 35h - GET INTERRUPT VECTOR ; IN al interrupt number ; OUT es:bx current interrupt handler getint: xor bx, bx ; BX=AL*4 mov bl, al add bl, bl add bl, bl ; load vector into ES:BX les bx, [cs:bx] ret ; Fallback for non-existant subfunctions ; The carry flag is inherited to user sferr: stc ret ; Convert between drive number and BIOS dl ; Bidirectional mapping ; 0 <-> 0, 1 <-> 1, 2 <-> 0x80, 3 <-> 0x81 dnconv: mov cx, 7 ror dx, 1 rol dl, 1 dncl: rcl dl, 1 rcr dh, 1 loop dncl xchg dh, dl ret ; Set sector number seek: mov [drvpos], ax mov [drvpos+2], dx ret ; Read sector from disk read: ; this uses the EBIOS extensions ; qword sector number xor ax, ax push ax push ax mov ax, [cs:drvpos+2] push ax mov ax, [cs:drvpos] push ax ; dword target buffer mov ax, buffer push cs push ax ; word number of sectors mov ax, 1 push ax ; size & passing mov di, 0x10 push di mov si, sp mov ah, 0x42 mov dl, [cs:drvnum] call dnconv stc int 0x13 add sp, di ret ; Select active drive ; IN dl drive number A=0, B=1, C=2, ... select: mov [cs:drvnum], dl ret push es xor ax, ax push cs pop es mov si, drvoff stosw stosw stosw stosw pop es call read ; load bpb from buffer to bpb ldbpb: push ds push es mov ax, cs mov ds, ax mov es, ax mov si, buffer+0x0B mov di, bpb mov cx, bpb_len rep movsb pop es pop ds ret ; defines for CHS table chs_spt: equ 0 ; word sectors per track chs_nos: equ 2 ; word number of sides chs_po: equ 4 ; dword partition offset chs_siz: equ 8 fndchs: ; find CHS table entry ; IN dl DOS drive number (A=0, B=1, ...) ; OUT cs:si ptr to chs table entry push ax mov ax, chs_siz mul dl add ax, chstab mov si, ax pop ax ret ; compare sector number in disk access packet with zero ; sets zero flag accordingly iszero: push ax mov ax, [bx] or ax, [bx+2] pop ax ret ; calculate CHS from dap and CHS table entry ; IN ds:bx disk access packet ; cs:si chstab table entry ; OUT cx,dh chs data for int 13h calchs: push ax ; load linear sector number mov dx, [bx+2] mov ax, [bx] ; if any word of it is non-zero, we need to do calculation call iszero jz .zero ; ax is track number (lba / spt) ; dx is sector (lba % spt) + 1 div word [cs:si+chs_spt] inc dx ; sector number mov cl, dl ; ax is cylinder (track / heads) ; dx is head (track % heads) xor dx, dx div word [cs:si+chs_nos] ; set up cylinder and head number mov ch, al mov dh, dl pop ax ret ; set up CHS data for int 13h for reading sector zero .zero: mov cx, 1 ; C=0, S=1 mov dh, 0 ; H=0 pop ax ret ; ABSOLUTE DISK READ / DISK WRITE int25h: mov ah, 2 jmp adisk int26h: mov ah, 3 adisk: push bp push ds push es mov bp, sp cmp cx, 0xFFFF je .islrg ; build a disk access packet on the stack push ds ; dword transfer buffer push bx push cx ; word number of sectors push cs ; dword starting sector number (CS always 0) push dx ; set our freshly created dap as DS:BX mov bx, sp push ss pop ds .islrg: mov dl, al call fndchs call calchs mov dl, al mov al, [bx+4] mov es, [bx+8] mov bx, [bx+6] int 0x13 jc .ret .ret: mov sp, bp pop es pop ds pop bp retf: retf ; DOS IDLE INTERRUPT ; Usually hooked by TSRs idle: sti ; sti takes one instruction to take effect nop ; Wait until next interrupt hlt iret ; FAST CONSOLE OUTPUT ; IN al character to print fputc: push ax push bx mov ah, 0x0E xor bx, bx int 0x10 pop bx pop ax iret vects: dw int20h, int21h, retf, retf dw retf, int25h, int26h, int20h dw idle, fputc main: ; zero out first 64kb except our code ; bss and boot sector area gets cleared by this mov di, init mov cx, di neg cx xor ax, ax rep movsb ; install interrupt vectors mov si, vects mov di, (0x20*4) mov cx, 0x0A intlp: movsw mov ax, cs stosw loop intlp ; print banner to indicate we are booted mov si, banner mov ah, 9 int 0x21 call dnconv mov al, dl mov cx, 1 mov dx, 0 mov bx, buffer int 3 intcall 0x25 int 3 loop: int 0x28 jmp loop init: cli xor ax, ax mov ds, ax mov es, ax mov ss, ax mov sp, ( stack+stacks ) ; relocate mov si, 0x7C00 mov di, $$ mov cx, (init-$$) rep movsb jmp 0:main section .bss ; stack to be used during init and disk i/o stack: resw stacks ; default int 25h/26h handler use a cache to keep geometry for ; the first 4 drives chstab: resw (4*chs_siz) bpb: resb bpb_len drvnum: resb 1 align 4 drvoff: resd 1; partition offset drvpos: resd 1; absolute physical sector number buffer: resb 512