Disk: DIR | Index | Disk: CD (Change Directory)

Write Your Own OS from Scratch - Chapter 4. Disk

Disk: Drives, FAT16

Disk Structures

We will start by adding a couple structures in an include file

disk.inc

;=======================================================
; Disk - disk.inc
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================
struc BSDATA
    .jump            resb        3
    .oemname         resb        8
    .bytespersec     resw        1
    .secperclust     resb        1
    .ressectors      resw        1
    .fats            resb        1
    .rootdirents     resw        1
    .sectors         resw        1
    .media           resb        1
    .fatsecs         resw        1
    .secpertrack     resw        1
    .heads           resw        1
    .hiddensecs      resd        1
    .hugesectors     resd        1
    .drivenumber     resb        1
    .reserved1       resb        1
    .bootsignature   resb        1
    .volumeid        resd        1
    .volumelabel     resb        11
    .filesystype     resb        8
endstruc

struc PARTITION
    .status          resb        1
    .starthead       resb        1
    .startsecenc     resb        1
    .startcyl        resb        1
    .type            resb        1
    .endhead         resb        1
    .endsecenc       resb        1
    .endcyl          resb        1
    .startlba        resd        1
    .endlba          resd        1
endstruc

DSKPART1             EQU         001BEh
		

IO

To io.asm we will add a print decimal numbers function.

;-------------------------------------------------------
; Decimal Out
; Input:
;       AX = value
;       CX = places to print
;       DL = fill character
;       DH = Commas  0 = No, 1 = Yes (not implemented)
; Output:
;       None
;-------------------------------------------------------
decfmtout: 
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   si
    push   di
    mov    [decbuffend], byte '$'                       ; String terminator
    mov    si, decbuffend                               ; Point to end of string
    push   dx                                           ; Save fmt Settings
    mov    bx, 10                                       ; We will divide by 10
decfmtoutstr:
    xor    dx, dx                                       ; Zero DX for divide
    div    bx                                           ; ax = ax / 10
    add    dx, 48                                       ; Add 48 to dx to change to ascii 
    dec    si                                           ; Store in reverse order
    mov    [si], dl                                     ; Store character
    dec    cx                                           ; Place counter
    cmp    ax, 0                                        ; If ax is zero we are done           
    jz     decfmtoutstrdone                             ;    with conversion
    jmp    decfmtoutstr                                 ; Get next decimal place
decfmtoutstrdone:
    pop    dx                                           ; Get comma setting
    push   dx                                           ; Resave fmt setting
    test   dh, 01h                                      ; Is comma Yes
    jz     decfmtoutnocomma                             ;    If not no comma
    push   cx                                           ; Save decimal place counter
    mov    ax, 1                                        ; Start second counter at 1
    mov    cx, decbuffend                               ; setup length
    sub    cx, si                                       ; length = end - si
    mov    di, si                                       ; keep start of string place
    mov    si, decbuffend-1                             ; setup si for walk through
    dec    di                                           ; adjust di to just before start
decfmtoutnextcomma:
    push   ax                                           ; save our place counter
    mov    bx, 3                                        ; check if we have moved 3 places
    xor    dx, dx
    div    bx
    cmp    dx, 0                                        ; if place counter MOD 3 != 0
    jnz    decfmtoutnottime                             ;   then not time to place a comma
    push   si                                           ; Save our place for copy
    dec    di                                           ; Create 1 extra space
    mov    si, di                                       ; Si = start of string
    inc    si                                   
    push   di                                           ; Save our place
    push   cx
    push   ax
decfmtoutcopy:
    mov    al, [si]                                     ; Copy byte
    inc    si
    mov    [di], al
    inc    di
    loop   decfmtoutcopy
    pop    ax                                           ; Restore our place
    pop    cx
    pop    di
    pop    si
    mov    [si-1], byte ','                             ; Insert comma
    cmp    cx, 1                                        ; Check if length remaning
    jnz    decfmtoutnottime                             ;   is 1 if not check next comma
    pop    ax                                           ; restore counters
    pop    cx
    inc    cx                                           ; adjust length by 1
    jmp    decfmtoutnocomma
decfmtoutnottime:     
    dec    si                                           ; Point to next place
    pop    ax
    inc    ax                                           ; Increment place counter
    loop   decfmtoutnextcomma                           ; Check next place
    pop    cx                                           ; Restore original places
decfmtoutnocomma:
    pop    dx                                           ; Restore settings
    cmp    cx, 0                                        ; If length fmt is zero exit
    jle    decfmtoutdone
decfmtoutnextfill:
    dec    si                                           ; Add extra characters until
    mov    [si], dl                                     ;    Desired length
    loop   decfmtoutnextfill
decfmtoutdone:
    mov    dx, si                                       ; Print decimal string
    call   strout
    pop    di                                           ; Restore registers
    pop    si
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    ret                                                 ; Return from subroutine

decbuff:          db     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
decbuffend:       db     0
		

io.asm

;=======================================================
; Input / Output - io.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================

;-------------------------------------------------------
; Standard Output
; Input:
;       DL = Character to print
; Output:
;       AL = Input Character
;-------------------------------------------------------
stdout:
    mov    al, dl                                       ; Place character in al 
    push   ax						; Save ax
    push   bx						; Save bx
    mov    ah, 00Eh                                     ; BIOS Service Print Character
    mov    bx, 00007h
    int    010h
    pop    bx                                           ; Restore bx
    pop    ax                                           ; Restore ax
    ret							; Return from subroutine

;-------------------------------------------------------
; String Output
; Input:
;       DS:DX = String to print
; Output:
;       AL = '$'
;-------------------------------------------------------
strout:
    push   dx                                           ; Save dx
    push   si                                           ; Save si
    mov    si, dx                                       ; Move string pointer to si
stroutnext:
    lodsb                                               ; Get character from string
    cmp    al, '$'                                      ; Check end of string
    jz     stroutdone                                   ;    if yes we are done
    mov    dl, al                                       ; Move char to dl for stdout
    call   stdout                                       ; Call stdout
    jmp    stroutnext                                   ; Next character
stroutdone:
    pop    si                                           ; Restore si
    pop    dx                                           ; Restore di
    mov    ah, 9                                        ; Place 9 back into ah
    ret							; Return from subroutine


;-------------------------------------------------------
; Standard Input
; Input:
;       None
; Output:
;       AL = Character
;-------------------------------------------------------
stdin:
    xor    ax, ax                                       ; Wait for key BIOS Call
    int    016h
    mov    ah, 8                                        ; Restore ah to 8
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; String Input
; Input:
;       DS:DX = Input Buffer Structure
;       db Buffer Size
;       dw 0  (This has a function we aren't using)
;       db rep[Buffer Size]
; Output:
;       DS:DX+3 = Input String
;-------------------------------------------------------
strin:
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   di
    push   es
    push   ds                                           ; Move ds to es
    pop    es
    mov    di, dx                                       ; Move dx to di
    xor    ch, ch
    mov    cl, [es:di]                                  ; cl = buffer size remaining
    or     cl, cl                                       ; if cl is zero we are done
    jz     strindone
    add    di, 2                                        ; Skip to start of buffer
    mov    bx, di                                       ; Save start of buffer
strinnext:
    call   stdin                                        ; Wait for key press
    cmp    al, 0x0d                                     ; Check if enter was pressed
    jz     strinenter                                   ;   if yes exit routine
    cmp    al, 0x08                                     ; Check if backspace was pressed
    jz     strinbs                                      ;   if yes goto backspace
    or     cl, cl                                       ; Check if buffer is full
    jz     strinbf                                      ;   if yes goto buffer full
    mov    dl, al                                       ; Otherwise output character
    call   stdout
    stosb                                               ; Store character in the buffer
    dec    cl                                           ; Decriment buffer size remaining
    jmp    strinnext                                    ; Get next character
strinbs:
    cmp    di, bx                                      ; Check if we are at buffer start
    jz     strinnext                                   ;   if yes skip and get next key
    dec    di                                          ; Overwrite previous character 
    mov    al, 0                                       ;    in memory
    stosb
    dec    di                                          ; Backup 1 space in memory
    mov    dl, 8                                       ; Overwrite previous character
    call   stdout                                      ;   on the screen
    mov    dl, 0
    call   stdout
    mov    dl, 8                                       ; Backup 1 space on screen
    call   stdout
    inc    cl                                          ; Add 1 to buffer size remaining
    jmp    strinnext                                   ; Get next character
strinbf:
    jmp    strinnext                                   ; Buffer full get next character
strinenter:
    stosb                                              ; Store 00Dh in the buffer
    dec    cl                                          ; Add it to the count
    mov    ax, [es:bx-2]                               ; get buffer size
    sub    ax, cx                                      ; subtract remaining to get length
    dec    ax                                          ; 0D does not count in lenght
    mov    [es:bx-1], al                               ; Store length in buffer
strindone:
    pop    es                                          ; Restore registers
    pop    di
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    ret                                                ; Return from subroutine

;-------------------------------------------------------
; Carrige Return / Line Feed
; Input:
;       None
; Output:
;       None
;-------------------------------------------------------
crlf:
    push   ax                                           ; Save registers
    push   dx
    mov    dl, 00Dh                                     ; Output carriage return
    call   stdout
    mov    dl, 00Ah                                     ; Output line feed
    call   stdout
    pop    dx                                           ; Restore registers
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; To Upper Case
; Input:
;      al = Character
; Output:
;      al = Character
;-------------------------------------------------------
toup:
    cmp    al, 'a'                                      ; Compare to "a"
    jl     toupdone                                     ; If less than a don't change
    cmp    al, 'z'                                      ; Compare to "z"
    jg     toupdone                                     ; If greater than z don't change
    and    al, 0DFh                                     ; Remove uppercase bit
toupdone:
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Hex Word Output
; Input:
;       AX = Value
; Output:
;       None
;-------------------------------------------------------
hexwordout:
    push   ax                                           ; Save AX
    xchg   ah, al                                       ; Exchange high and low bytes
    call   hexbyteout                                   ; Call hex byte out
    pop    ax                                           ; Restore AX
                                                        ; Drop into hexbyteout for low byte
;-------------------------------------------------------
; Hex Byte Output
; Input:
;       AL = Value
; Output:
;       None
;-------------------------------------------------------
hexbyteout:
    push   ax                                           ; Save registers
    push   cx
    mov    cl, 4                                        ; Shift in upper nibble
    shr    al, cl
    call   hexnibbleout                                 ; Output upper nibble
    pop    cx                                           ; Restore registers
    pop    ax
                                                        ; Drop into hexnibbleout for low nibble
;-------------------------------------------------------
; Hex Nibble Output
; Input:
;       AL[0:3] = Value
; Output:
;       None
;-------------------------------------------------------
hexnibbleout:
    push   ax                                           ; Save registers
    push   dx
    and    al, 00Fh                                     ; Clear upper bits
    cmp    al, 00Ah                                     ; Check greater than "9"
    jl     hexnibbleout2                                ;  if greater add 7
    add    al, 007h
hexnibbleout2:
    add    al, '0'                                      ; Add '0' for number to print
    mov    dl, al                                       ; Move to dl for stdout
    call   stdout
    pop    dx                                           ; Restore registers
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Hex Word In
; Input:
;       ds:si = pointer to string
; Output:
;       ax = value
;       ds:si = pointer to string past hex word
;-------------------------------------------------------
hexwordin:
    push   cx                                           ; Save registers
    push   bx
    xor    bx, bx                                       ; Zero our end value
    xor    cx, cx                                       ; Zero our counter
hexwordinnext:	
    lodsb                                               ; Get character
    call   ishex                                        ; Make sure it is a hex number
    jc     hexwordindone
    shl    bx, 1                                        ; Rotate end value
    shl    bx, 1                                        ;   first pass is pointless
    shl    bx, 1                                        ;   there is no value yet
    shl    bx, 1
    call   hexnibblein                                  ; Convert from ascii to binary
    or     bl, al                                       ; Merge it with end value
    inc    cx                                           ; Increment counter
    jmp    hexwordinnext                                ; Next character
hexwordindone:
    dec    si                                           ; Adjust our string pointer
    mov    ax, bx                                       ; Final value in ax
    pop    bx                                           ; Restore BX
    cmp    cx, 4                                        ; Check less than 4 digits
    jg     hexwordinfail                                ;    if not then fail
    cmp    cx, 0                                        ; Check more than 0 digits
    je     hexwordinfail                                ;    if not then fail
    pop    cx                                           ; Restore CX
    clc                                                 ; Carry clear for success
    ret                                                 ; Return from subroutine
hexwordinfail:
    pop    cx                                           ; Restore CX
    stc                                                 ; Carry set for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Decimal Out
; Input:
;       AX = value
;       CX = places to print
;       DL = fill character
;       DH = Commas  0 = No, 1 = Yes (not implemented)
; Output:
;       None
;-------------------------------------------------------
decfmtout: 
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   si
    push   di
    mov    [decbuffend], byte '$'                       ; String terminator
    mov    si, decbuffend                               ; Point to end of string
    push   dx                                           ; Save fmt Settings
    mov    bx, 10                                       ; We will divide by 10
decfmtoutstr:
    xor    dx, dx                                       ; Zero DX for divide
    div    bx                                           ; ax = ax / 10
    add    dx, 48                                       ; Add 48 to dx to change to ascii 
    dec    si                                           ; Store in reverse order
    mov    [si], dl                                     ; Store character
    dec    cx                                           ; Place counter
    cmp    ax, 0                                        ; If ax is zero we are done           
    jz     decfmtoutstrdone                             ;    with conversion
    jmp    decfmtoutstr                                 ; Get next decimal place
decfmtoutstrdone:
    pop    dx                                           ; Get comma setting
    push   dx                                           ; Resave fmt setting
    test   dh, 01h                                      ; Is comma Yes
    jz     decfmtoutnocomma                             ;    If not no comma
    push   cx                                           ; Save decimal place counter
    mov    ax, 1                                        ; Start second counter at 1
    mov    cx, decbuffend                               ; setup length
    sub    cx, si                                       ; length = end - si
    mov    di, si                                       ; keep start of string place
    mov    si, decbuffend-1                             ; setup si for walk through
    dec    di                                           ; adjust di to just before start
decfmtoutnextcomma:
    push   ax                                           ; save our place counter
    mov    bx, 3                                        ; check if we have moved 3 places
    xor    dx, dx
    div    bx
    cmp    dx, 0                                        ; if place counter MOD 3 != 0
    jnz    decfmtoutnottime                             ;   then not time to place a comma
    push   si                                           ; Save our place for copy
    dec    di                                           ; Create 1 extra space
    mov    si, di                                       ; Si = start of string
    inc    si                                   
    push   di                                           ; Save our place
    push   cx
    push   ax
decfmtoutcopy:
    mov    al, [si]                                     ; Copy byte
    inc    si
    mov    [di], al
    inc    di
    loop   decfmtoutcopy
    pop    ax                                           ; Restore our place
    pop    cx
    pop    di
    pop    si
    mov    [si-1], byte ','                             ; Insert comma
    cmp    cx, 1                                        ; Check if length remaning
    jnz    decfmtoutnottime                             ;   is 1 if not check next comma
    pop    ax                                           ; restore counters
    pop    cx
    inc    cx                                           ; adjust length by 1
    jmp    decfmtoutnocomma
decfmtoutnottime:     
    dec    si                                           ; Point to next place
    pop    ax
    inc    ax                                           ; Increment place counter
    loop   decfmtoutnextcomma                           ; Check next place
    pop    cx                                           ; Restore original places
decfmtoutnocomma:
    pop    dx                                           ; Restore settings
    cmp    cx, 0                                        ; If length fmt is zero exit
    jle    decfmtoutdone
decfmtoutnextfill:
    dec    si                                           ; Add extra characters until
    mov    [si], dl                                     ;    Desired length
    loop   decfmtoutnextfill
decfmtoutdone:
    mov    dx, si                                       ; Print decimal string
    call   strout
    pop    di                                           ; Restore registers
    pop    si
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    ret                                                 ; Return from subroutine

decbuff:          db     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
decbuffend:       db     0

;-------------------------------------------------------
; Hex Nibble In
; Input:
;       al = Ascii
; Output:
;       al = Binary
;-------------------------------------------------------
hexnibblein:
    call   toup                                         ; Convert to upper case
    sub    al, '0'                                      ; Subtract character '0'
    cmp    al, 00Ah                                     ; If above 9
    jl     hexnibbleindone
    sub    al, 007h                                     ; Subtract difference between '0' and 'A'
hexnibbleindone:
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Is Hex
; Input:
;       al = Ascii
; Output:
;       Carry Set = No
;       Carry Clear = Yes
;-------------------------------------------------------
ishex:
    cmp    al, '0'                                      ; If less than '0'
    jl     ishexno                                      ;   Not hex
    cmp    al, '9'                                      ; If between '0' and '9'
    jle    ishexyes                                     ;   Is hex
    cmp    al, 'A'                                      ; If !'0'-'9' & < 'A'
    jl     ishexno                                      ;   Not hex
    cmp    al, 'F'                                      ; If !'0'-'9' & 'A'-'F'
    jle    ishexyes                                      ;   Is hex
    cmp    al, 'a'                                      ; If !'0'-'9' & < 'a'
    jl     ishexno                                      ;   Not hex
    cmp    al, 'f'                                      ; If !'0'-'9' & !'a'-'f'
    jg     ishexno                                      ;   Not hex
ishexyes:
    clc                                                 ; Carry clear for yes
    ret                                                 ; Return from subroutine
ishexno:
    stc                                                 ; Carry set for no
    ret                                                 ; Return from subroutine
		

Disk

Add the include file to disk.asm

%include "disk.inc"
		

We will add two services getmaindrive and setmaindrive

;-------------------------------------------------------
; SELECT DEFAULT DRIVE (compensate for entering 80h)
; SERVICE 0EH
;-------------------------------------------------------
dsksetmaindrive:
    cmp    dl, 002h                                     ; Check if input is less than 2
    jl     dsksetmaindrivedone                          ; If it is no conversion needed
    test   dl, 080h                                     ; Test if bit 7 is set
    jnz    dsksetmaindrivedone                          ; If it is no conversion needed
    add    dl, 07Eh                                     ; Drive 2+ start at 080h+
dsksetmaindrivedone:
    mov    [dskdrive], dl                               ; Set current drive
    call   dskloadboot                                  ; Load boot data and calculate
    mov    al, [dskmaxdrive]                            ; Return max drives
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; GET CURRENT DEFAULT DRIVE
; SERVICE 19H
;-------------------------------------------------------
dskgetmaindrive:
    mov    al, [dskdrive]                               ; Get current drive
    test   al, 80h                                      ; Test if it is a hard drive
    jz     dskgetmaindrivedone
    sub    al, 07Eh                                     ; Make hard drives start at #2
dskgetmaindrivedone:
    ret                                                 ; Return from subroutine
		

We will add code to read the boot sector

;-------------------------------------------------------
; Load Boot Sector
; ES:BX = Scratch
;-------------------------------------------------------
dskloadboot:
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   es
    xor    ax, ax                                       ; Point to scratch area
    mov    es, ax
    mov    bx, 00500h
    mov    [dskbootlba], word 0                         ; Set normal boot lba
    mov    [dskbootlba+2], word 0
    mov    [dskfatlba], word 1                          ; Set normal fat lba
    mov    [dskfatlba+2], word 0
    mov    [dskcylinder], word 0                        ; Read boot sector
    mov    [dsksector], byte 1
    mov    [dskhead], byte 0
    call   dskreadsec
    jc     dskloadbootfail
    test   [dskdrive], byte 080h                        ; If hard disk use partition info
    jz     dskloadbootdone
    mov    ax, [es:bx+DSKPART1+PARTITION.startlba]      ; Adjust boot lba to start of partition
    mov    [dskbootlba], ax
    inc    ax                                           ; Adjust fat lba 1 lba past boot
    mov    [dskfatlba], ax
    mov    ax, [es:bx+DSKPART1+PARTITION.startlba+2]
    mov    [dskbootlba+2], ax
    adc    ax, 0
    mov    [dskfatlba+2], ax
    mov    ch, [es:bx+DSKPART1+PARTITION.startcyl]      ; Read real boot sector
    mov    cl, [es:bx+DSKPART1+PARTITION.startsecenc]
    mov    dh, [es:bx+DSKPART1+PARTITION.starthead]
    mov    dl, [dskdrive]
    mov    ax, 00201h
    int    013h
    jc     dskloadbootfail
dskloadbootdone:
    call   dskcopyboot                                  ; Copy boot sector header
    pop    es                                           ; Restore registers
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dskloadbootfail:
    pop    es                                           ; Restore registers
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine
		

And code to copy the boot sector header

;-------------------------------------------------------
; Copy Boot
;-------------------------------------------------------
dskcopyboot:
    push   ds                                           ; Save registers
    push   es
    push   si
    push   di
    xor    ax, ax                                       ; DS = 0
    mov    ds, ax
    mov    ax, 00070h                                   ; ES = DEMOOS disk header
    mov    es, ax
    mov    di, oemname                                  ; Copy disk header to oemname
    mov    si, 0503h                                    ; From oemname of disk header

    mov    cx, track-oemname                            ; Header size
    rep    movsb                                        ; Do copy

    push   cs                                           ; Set DS local
    pop    ds

    push    bx
    mov     al, [fats]                                  ; Calculate root location
    mul     word [fatsecs]
    add     ax, [hiddensecs]
    adc     dx, [hiddensecs+2]
    add     ax, [ressectors]
    adc     dx, byte 0                                  ; if carry increment modulous
    mov     [root], ax                                  ; Save result in root
    mov     [rootmod], dx                               ; Save modulous in rootmod
    mov     [data], ax                                  ; Seve result in data we will just add
    mov     [datamod], dx                               ; Save modulous in datamod

    mov     ax, 32					; Calculate data location
    mul     word [rootdirents]
    mov     bx,[bytespersec]
    add     ax, bx
    dec     ax
    div     bx
    add     [data], ax					; save result in data
    adc     word [datamod], byte 0                      ; save modulous in datamod
    pop     bx

    pop    di                                           ; Restore registers
    pop    si
    pop    es
    pop    ds
    ret                                                 ; Return from subroutine
		

We also need the code to calculate chs and read sectors. Taken from boot sector or loader and renamed.

;-------------------------------------------------------
; Calculate CHS (limit of 33.5MB)
; Input:
;     AX:DX = Logical Sector
; Output:
;     sector
;     head
;     track/cylinder
;-------------------------------------------------------
; sector = (lba % sectors per track) + 1
; head = lba / sectors per track % number of heads
; track = lba / sectors per track / number of heads
dskcalcchs:
    cmp     dx, [secpertrack]                           ; Check within track
    jnc     dskcalcchsfail                              ; if not fail
    div    word [secpertrack]                           ; LBA / secpertrack
    inc     dl                                          ; LBA % secpertrack + 1
    mov     [dsksector], dl                             ; Save in sector
    xor     dx, dx                                      ; Clear the modulous
    div     word [heads]                                ; lba / secpertrack / heads
    mov     [dskhead], dl                               ; lba / secpertrack % heads
    mov     [dskcylinder], ax                           ; lba / secpertrack / heads
    clc                                                 ; Clear carry to report success
    ret                                                 ; Return from subroutine
dskcalcchsfail:
    stc                                                 ; Set carry to report failure
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Read Sector
; Input:
;    bsTrack = Track
;    bsSector = Sector
;    bsHead = Head
;    ES:BX = Location to load sector at
; Output:
;       AX, BX, CX, DX are destroyed
;-------------------------------------------------------
dskreadsec:
    mov     dx, [dskcylinder]	                        ; Get the track
    mov     cl, 006h                                    ; Rotate the bits 8 and 9 into position
    shl     dh, cl
    or      dh, [dsksector]                             ; Get the sector with out changing high bits
    mov     cx, dx                                      ; Place in the correct register
    xchg    ch, cl                                      ; Swap bytes to complete the process
    mov     dl, [dskdrive]                              ; Drive number
    mov     dh, [dskhead]                               ; Current Head
    mov     ax, 00201h                                  ; BIOS Disk Read 1 Sector
    int     013h
    ret
		

We also will add read fat byte from the loader and rename it

;-------------------------------------------------------
; Get byte from fat
; Input:
;     AX = Byte to read
; Output:
;     AL = Byte read
;-------------------------------------------------------
dskfatread:
    push   bx                                           ; Save registers
    push   cx
    push   es
    xor    bx, bx                                       ; Set ES to 0000h
    mov    es, bx
    mov    bx, 00500h                                   ; Read into scratch area
    xor    dx, dx                                       ; Clear for divide
    div    word [bytespersec]                           ; Sector of file = FAT index / bytes per sector 
    add    ax, [dskfatlba]
    cmp    [dsksectorload], ax                          ; Check if this sector is already in scratch
    jz     dskfatread12noload                           ;    If it is don't load it again
    mov    [dsksectorload], ax                          ; Set current loaded FAT sector
    push   dx                                           ; Save FAT index MOD bytes per sector
    xor    dx, dx                                       ; Clear for divide in calcchs
    call   dskcalcchs                                      ; Calculate Cylinder Head Sector
    jc     dskfatread12error                            ;    If error exit routine
    call   dskreadsec                                      ; Read sector
    jc     dskfatread12error                            ;    If error exit routine
    pop    dx                                           ; Restore FAT index MOD bytes per sector
dskfatread12noload:
    add    bx, dx                                       ; Byte to read = file index % bytes per sector + scratch
    mov    ax, [es:bx]                                  ; Get byte

    pop    es                                           ; Restore registers
    pop    cx
    pop    bx
    clc                                                 ; Carry clear on success
    ret                                                 ; Return from subroutine
dskfatread12error:
    pop    dx                                           ; Restore FAT index MOD bytes per sector
    pop    es                                           ; Restore registers
    pop    cx
    pop    bx
    stc                                                 ; Set carry on fail
    ret                                                 ; Return from subroutine
		

We will also add the code for next cluster. Modified for fat 16 and 32

;-------------------------------------------------------
; Get Next Cluster
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
dsknextcl:
    cmp   [filesystype+3], word 03233h                    ; Check fat 32
    jnz    dsknextclnot32
    call   dsknextcl32                                    ; Call fat nextcl32
    jc     dsknextclfail
    jmp    dsknextcldone                                  ; Done
dsknextclnot32:
    cmp    [filesystype+3], word 03631h 		  ; Check fat 16
    jnz    dsknextclnot16
    call   dsknextcl16                                    ; Call fat nextcl16
    jc     dsknextclfail
    cmp    ax,  0FFFFh                                    ; Check end of chain
    jnz    dsknextcldone
    mov    eax, 0FFFFFFFFh                                ; Translate to fat32 end of chain
    jmp    dsknextcldone
dsknextclnot16:
    cmp    [filesystype+3], word 03231h                   ; Check fat 12
    jnz    dsknextclnot12
    call   dsknextcl12                                    ; Call fat nextcl12
    jc     dsknextclfail
    cmp    ax, 000FFFh                                    ; Check end of chain
    jnz    dsknextcldone
    mov    eax, 0FFFFFFFFh                                ; Translate to fat32 end of chain
    jmp    dsknextcldone
dsknextclnot12:
    jmp    dsknextclfail                                  ; Not a fat partition
dsknextcldone:
    clc                                                   ; Clear carry for succes
    ret                                                   ; Return from subroutine
dsknextclfail:
    stc                                                   ; Set carry for fail
    ret                                                   ; Return from subroutine

;-------------------------------------------------------
; Get Next Cluster (FAT16)
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
dsknextcl32:
    push   cx                                           ; Save cx
    mov    cl, 2
    shl    ax, cl                                       ; Multiply by 4
    pop    cx
    jmp    dsknextcl16read                              ; Rest is the same as nectcl16
dsknextcl16:
    shl    ax, 1                                        ; Multiply by 2
dsknextcl16read:
    call   dskfatread                                   ; Read first byte of fat12
    jc     dsknextcl16error                             ;   error exit routine
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dsknextcl16error:
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Get Next Cluster (FAT12)
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
dsknextcl12:
    push   cx                                           ; Save register
    push   ax                                           ; Save a copy of input cluster
    mov    cx, ax                                       ; FAT index = n*3/2
    shl    ax, 1                                        ; Multiply by 2
    add    ax, cx                                       ; Add 1
    shr    ax, 1                                        ; Divide by 2
    push   ax                                           ; Save input byte to read
    call   dskfatread                                   ; Read first byte of fat12
    jc     dsknextcl12errorpop                          ;   error exit routine
    mov    cl, al                                       ; Save byte read
    pop    ax                                           ; Restore input byte to read
    inc    ax                                           ; Get next byte
    call   dskfatread                                   ; Read second byte of fat12
    jc     dsknextcl12error                             ;   error exit routine
    xchg   ah, al                                       ; Make second byte high
    mov    al, cl                                       ; Make first byte low
    pop    cx                                           ; Restore the saved input cluster
    test   cx, 0x0001                                   ; Check if it was odd or even
    jz     dsknextcl12done                              ; If even do nothing
    mov    cl, 4                                        ; If odd we need to shift by 4
    shr    ax, cl
dsknextcl12done:
    and    ax, 0x0FFF                                   ; Clear top 4 bits we only need 12 bits
    pop    cx                                           ; Restore register
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dsknextcl12errorpop:
    pop    ax                                           ; We had an extra push clear it
dsknextcl12error:
    pop    ax                                           ; Restore input cluster
    pop    cx                                           ; Restore register
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine
		

Add the code to read a data cluster

;-------------------------------------------------------
; Read Data Cluster  
; Input:
;     AX = Cluster start
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
dskreaddatacluster:
    push   ax                                           ; Save registers
    push   cx
    push   dx
    xor    ch, ch                                       ; Clear ch
    mov    cl, [secperclust]                            ; CX = Sectors per cluster
;dskreadclusternext:
    push   bx                                           ; Save load location
    dec    ax                                           ; Subtract 2 because FAT starts at 2
    dec    ax
    mov    bl, [secperclust]
    xor    bh, bh
    mul    bx
    pop    bx                                           ; Restore load location
    add    ax, [data]                                   ; Add the data start cluster
dskreadclusternext:
    push   ax
    push   cx                                           ; Save count read will destroy it
    xor    dx, dx                                       ; Clear for calcchs
    call   dskcalcchs                                      ; Calculate Cylinder Head Sector
    jc     dskreaddataclusterfail                       ;   If error exit routine
    call   dskreadsec                                      ; Read sector into buffer
    jc     dskreaddataclusterfail                       ;   If error exit routine
    pop    cx                                           ; Restore counter
    pop    ax                                           ; Restore cluster start
    add    bx, word [bytespersec]                       ; Add bytes per sec for next read
    inc    ax                                           ; Increment sector
    loop   dskreadclusternext                           ; Get next sector of cluster
    pop    dx                                           ; Restore registers
    pop    cx
    pop    ax
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dskreaddataclusterfail:
    pop    cx                                           ; Restore registers
    pop    ax
    pop    dx                                           ; Restore registers
    pop    cx
    pop    ax
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine
		

Add the fat chain loader

;-------------------------------------------------------
; FAT Chain Loader
; Input:
;     AX = First Cluster
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
dskchainload:
    mov    [dsksectorload], word 0
dskchainloadnext:
    cmp    eax, 0FFFFFFFFh                               ; Last cluster found
    jz     dskchainloaddone                             ; We are done
    call   dskreaddatacluster                           ; Read cluster from data area
    jc     dskchainloadfailpop
    call   dsknextcl                                    ; Get next cluster
    jc     dskchainloadfail
    jmp    dskchainloadnext                             ; Loop until last cluster
dskchainloaddone:
    clc                                                 ; Carry clear for success
    ret                                                 ; Return from subroutine
dskchainloadfailpop:
    pop    ax                                           ; Clear extra push
dskchainloadfail:
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine
		

And lastly add a few new variables

dskbootlba:             dd   0
dskfatlba:              dd   0
dskcylinder             dw   0
dsksector               db   0
dskhead                 db   0
dsksectorload:          dw   0
dskdrive:               db   0
dskmaxdrive:            db   16
		

Service

In inthook in the file int.asm add the following:

    cmp    ah, 00Eh                                     ; Service 0Eh: setmaindrive
    jnz    inthookdone0E
    call   dsksetmaindrive
    jmp    inthookdone
inthookdone0E:
    cmp    ah, 019h                                     ; Service 19h: getmaindrive
    jnz    inthookdone19
    call   dskgetmaindrive
    jmp    inthookdone
inthookdone19:
		

int.asm

;=======================================================
; Service - int.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================

;-------------------------------------------------------
; Interrupt Hook
;-------------------------------------------------------
inthook:
    cmp    ah, 002h                                     ; Service 02h: stdout
    jnz    inthookdone02
    call   stdout
    jmp    inthookdone
inthookdone02:
    cmp    ah, 008h                                     ; Service 08h: stdin
    jnz    inthookdone08
    call   stdin
    jmp    inthookdone
inthookdone08:
    cmp    ah, 009h                                     ; Service 09h: strout
    jnz    inthookdone09
    call   strout
    jmp    inthookdone
inthookdone09:
    cmp    ah, 00Ah                                     ; Service 0Ah: strin
    jnz    inthookdone0A
    call   strin
    jmp    inthookdone
inthookdone0A:
    cmp    ah, 00Eh                                     ; Service 0Eh: setmaindrive
    jnz    inthookdone0E
    call   dsksetmaindrive
    jmp    inthookdone
inthookdone0E:
    cmp    ah, 019h                                     ; Service 19h: getmaindrive
    jnz    inthookdone19
    call   dskgetmaindrive
    jmp    inthookdone
inthookdone19:
    cmp    ah, 025h                                     ; Service 25h: setint
    jnz    inthookdone25
    call   intset
    jmp    inthookdone
inthookdone25:
;    cmp    ah, 031h                                    ; Service 31h: TSR
;    jnz    inthookdone31
;    jmp    tsr
;inthookdone31:
    cmp    ah, 035h                                     ; Service 35h: getint
    jnz    inthookdone35
    call   intget
    jmp    inthookdone
inthookdone35:
    cmp    ah, 048h                                     ; Service 48h: alloc
    jnz    inthookdone48
    call   memalloc
    jmp    inthookdone
inthookdone48:
    cmp    ah, 049h                                     ; Service 49h: free
    jnz    inthookdone49
    call   memfree
    jmp    inthookdone
inthookdone49:
    cmp    ah, 04Ah                                     ; Service 4Ah: resize
    jnz    inthookdone4A
    call   memresize
    jmp    inthookdone
inthookdone4A:
    cmp    ah, 0x4C                                     ; Service 4Ch: exit program
    jnz    inthookdone4C
    jmp    exitprogram
inthookdone4C:

    stc
inthookdone:
    iret


;-------------------------------------------------------
; Exit program
; Input:
;       SP+2 = owner (return address from interrupt call)
; Output:
;       None
;-------------------------------------------------------
exitprogram:
    mov    bp, sp                                      ; Copy stack pointer
    mov    ax, [ss:bp+0x02]                            ; Get memory owner
    mov    es, ax                                      ; Free program memory
    mov    ah, 049h
    int    021h
    xor    ax, ax                                      ; SS = 0
    mov    ss, ax
    mov    sp, 07C00h                                  ; Reset SP to 7C00
    push   cs                                          ; DS = CS
    pop    ds
    push   cs                                          ; ES = CS
    pop    es
    jmp    main                                        ; Restart command prompt

;-------------------------------------------------------
; Terminate and stay resident
; Input:
;       SP+2 = owner (return address from interrupt call)
; Output:
;       None
;-------------------------------------------------------
tsr:
    mov    bp, sp                                      ; Copy stack pointer
    mov    ax, [ss:bp+0x02]                            ; Get memory owner
    mov    es, ax                                      ; Free program memory
    mov    bx, dx
    mov    ah, 04Ah
    int    021h
    xor    ax, ax                                      ; SS = 0
    mov    ss, ax
    mov    sp, 07C00h                                  ; Reset SP to 7C00
    push   cs                                          ; DS = CS
    pop    ds
    push   cs                                          ; ES = CS
    pop    es
    jmp    main                                        ; Restart command prompt

;-------------------------------------------------------
; Interrupt Set
; Input:
;       AL = Interrupt Number
;       DS = Segment of Interrupt
;       DX = Offset of Interrupt
; Output:
;       None
;-------------------------------------------------------
intset:
    push   ax                                           ; Save registers
    push   cx
    push   di
    push   es
    xor    ah, ah                                       ; DI = INT << 2
    mov    cl, 2                                        ; Essentially multiply by 4
    shl    ax, cl
    mov    di, ax                                       ; di is the address of our int
    xor    ax, ax                                       ; ES = 0 The int table is at 00000h
    mov    es, ax
    mov    ax, dx                                       ; Store offset
    stosw
    mov    ax, ds                                       ; Store segment
    stosw
    pop    es                                           ; Restore registers
    pop    di
    pop    cx
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Interrupt Get
; Input:
;       AL = Interrupt Number
; Output:
;       ES = Segment of Interrupt
;       BX = Offset of Interrupt
;-------------------------------------------------------
intget:
    push   ax                                           ; Save registers
    push   cx
    push   si
    push   ds
    xor    ah, ah                                       ; DI = INT << 2
    mov    cl, 2                                        ; Essentially multiply by 4
    shl    ax, cl
    mov    si, ax                                       ; di is the address of our int
    xor    ax, ax                                       ; DS = 0 The int table is at 00000h
    mov    ds, ax
    lodsw
    mov    bx, ax                                       ; Get offset
    lodsw
    mov    es, ax                                       ; Get segment
    pop    ds                                           ; Restore registers
    pop    si
    pop    cx
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Interrupt Initialize
; Input:
;       None
; Output:
;       DX, AX are destroyped
;-------------------------------------------------------
intinit:
    mov    dx, inthook                                  ; Offset to our service routine
                                                        ;    DS should already be the
                                                        ;    correct segment
    mov    al, 021h                                     ; Service 21h
    call   intset                                       ; Set Interrupt 21h
    ret                                                 ; Return from subroutine
		

DEMO OS

In demoos.asm we will start by replacing the dir routine

;-------------------------------------------------------
; Directory List
; Input:
;       None
; Output:
;       None
;-------------------------------------------------------
dir:
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   es
    call   vol                                          ; Print volume information
    call   crlf
    call   dskmatchblank                                ; create a blank match string
    call   dskfirstentry                                ; get first entry
    jc     dirdone
    mov    cx, 20
dirnext:
    push   bx
    push   cx
    mov    cx, 0000Bh                                   ; Name length
dirname:
    mov    ah, 002h                                     ; Service stdout
    mov    dl, [es:bx]                                  ; Get filename character
    inc    bx                                           ; Point to next character
    int    021h                                         ; Print the character we got
    loop   dirname                                      ; Get next character, dec cx
    pop    cx
    pop    bx

    mov    ah, 002h                                     ; Print space
    mov    dl, ' '
    int    021h

    test   [es:bx+11], byte 010h                        ; Check if directory
    jz     dirnodir
    mov    ah, 002h                                     ; Print space
    mov    dl, ' '
    int    021h
    mov    ah, 009h                                     ; Print 
    mov    dx, dirmsg
    int    021h
    jmp    dirisdir
dirnodir:
    push   cx                                           ; Print file size
    mov    ax, [es:bx+28]
    mov    cx, 12
    mov    dh, 1
    mov    dl, ' '
    call   decfmtout
    pop    cx
dirisdir:
    mov    ah, 002h                                     ; Print space
    mov    dl, ' '
    int    021h
    mov    ax, [es:bx+24]                               ; Print date
    call   dskdateout
    mov    ah, 002h                                     ; Print space space
    mov    dl, ' '
    int    021h
    mov    ah, 002h
    mov    dl, ' '
    int    021h
    mov    ax, [es:bx+22]                               ; Print time
    call   dsktimeout

    call   crlf

    dec    cx                                           ; Display 20 items and wait for key
    jnz    dirskipwait
    xor    ax, ax
    int    016h
    mov    cx, 20
dirskipwait:

    call   dsknextentry                                 ; Get next directory entry
    jc     dirdone

    jmp    dirnext                                      ; Show next

dirdone:
    pop    es                                           ; Restore registers
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    ret                                                 ; Return from subroutine
		

Next we will replace the vol routine

;-------------------------------------------------------
; Volume Info Command
; Input:
;       None
; Output:
;       None
;-------------------------------------------------------
vol:
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   si
    push   di
    push   es

    call   dskloadboot                                  ; Load boot sector

    xor    ax, ax                                       ; Point to scratch area
    mov    es, ax
    mov    bx, 00500h
    mov    ah, 009h                                     ; Print string
    mov    dx, volname
    int    021h
    mov    ah, 019h                                     ; drive letter
    int    021h
    add    al, 'A'
    mov    ah, 002h
    mov    dl, al
    int    021h
    mov    ah, 009h                                     ; Print string
    mov    dx, volname2
    int    021h
    mov    di, bx                                       ; Print volume label
    add    di, 0002Bh
    mov    cx, 11
vollabelnext:
    mov    dl, [es:di]
    inc    di
    call   stdout
    loop   vollabelnext
    mov    ah, 009h                                     ; Print string
    mov    dx, volserial
    int    021h
    mov    di, bx                                       ; Print serial first part
    add    di, 00029h
    mov    ax, [es:di]
    call   hexwordout
    mov    ah, 002h                                     ; Print -
    mov    dl, '-'
    int    021h
    mov    di, bx                                       ; Print serial second part
    add    di, 027h
    mov    ax, [es:di]
    call   hexwordout
    call   crlf                                         ; Print crlf
volerror:
    pop   es                                            ; Restore registers
    pop   di
    pop   si
    pop   dx
    pop   cx
    pop   bx
    pop   ax
    ret                                                 ; Return from subroutine
		

In the main function change:

    call   ldrchainload                                 ; Load FAT chain for file
		

to

    call   dskchainload                                 ; Load FAT chain for file
		

Add the following code for drive changes

    cmp   [di-1], byte 02h                              ; Check change drive
    jg    loadstart
    cmp   [di+1], byte ':'
    jne   loadstart
    mov   al, [di]
    call  toup
    sub   al, 'A'

    push  ax
    mov   dl, al                                        ; Set default drive
    mov   ah, 00Eh
    int   021h
    pop   ax

    jmp   main
		

Change where we print the drive in the prompt to

    mov    ah, 019h                                     ; Get default drive
    int    021h
    add    al, 'A'
    mov    ah, 002h                                     ; Service standard output
    mov    dl, al
    int    021h
		

Under start after intinit add the following to initialize the drive current drive.

    mov    dl, [drivenumber]                            ; Set default drive
    mov    ah, 00Eh
    int    021h
		

Then change:

version:        db      00Dh, 00Ah, "DEMO OS V0.15"     ; Message to be printed
                db      00Dh, 00Ah, '$'
		

to

version:        db      00Dh, 00Ah, "DEMO OS V0.16"     ; Message to be printed
                db      00Dh, 00Ah, '$'
		

Modify the disk image

In osdisk.asm update the file size of demoos.bin to 4152 for the fat generator

Now lets build our disk. If you are running the previous tutorial in VirtualBox Remember to remove the disk or close virtual box before assembling the disk image.

> nasm demoos.asm -o demoos.bin
> nasm osdisk.asm -o osdisk.img
		

Your osdisk.img can now be loaded with VirtualBox. The result should be as follows:

DEMO OS V0.16

A:\>  B:

B:\>
		

You also now have minimal fat16 support up to 32MB

Disk: DIR | Index | Disk: CD (Change Directory)

Support This Project On Patreon

Copyright © 2021 Marcus Kelly