Memory: Resize | Index | Disk: VOL

Write Your Own OS from Scratch - Chapter 4. Disk

Disk: Loader

The boot sector only loads 3 sectors. This was enough to get quite a bit done. However, we still need to write more code and we are out of space. So now we will write a loader.

Create a file called loader.asm. We will then copy some boot sector code to the loader. First copy the header and modify as follows:

    org     00000h

    jmp     short ldrstart                              ; Jump to boot sector start
    nop                                                 ; place holder

oemname:        db      "        "                      ; Operating system name
bytespersec:    dw      0                               ; Bytes per sector
secperclust:    db      0                               ; Sectors per cluster
ressectors:     dw      0                               ; Reserved Sectors
fats:           db      0                               ; Number of FATs on disk
rootdirents:    dw      0                               ; Number of directory entries
sectors:        dw      0                               ; Sectors per disk
media:          db      0                               ; Always F0 hex for floppy
fatsecs:        dw      0                               ; Sectors per FAT
secpertrack:    dw      0                               ; Sectors per track
heads:          dw      0                               ; Number of heads
hiddensecs:     dd      0                               ; Hidden sectors
hugesectors:    dd      0                               ; Huge sectors
drivenumber:    db      0                               ; Drive number 000h for floppy
currenthead:    db      0                               ; Reserved byte we are using for disk read
bootsignature:  db      0                               ; Boot signature
volumeid:       dd      0                               ; Serial Number
vlumelabel:     db      "           "                   ; Disk label
filesystype:    db      "        "                      ; File system
track:          dw      0                               ; Track for disk read
sector:         db      0                               ; Sector for disk read
root:           dw      0                               ; lba for root
rootmod:        dw      0                               ; lba modulous for root
data:           dw      0                               ; lba for data
datamod:        dw      0                               ; lba modulous for data

ldrstart:
		

Then copy over the strout, calcchs, and readsec subroutines from the bios. We already have a strout in our os so rename the strout routine print and modify the jumps and other labels.

;-------------------------------------------------------
; String Print
; Input:
;    DS:SI = Message offset
; Output:
;    AX, BX, SI are destroyed
;-------------------------------------------------------
print:
    lodsb                                               ; Get character from string
    or      al, al                                      ; Check if our character is zero
    jz      print done                                  ; if it is zero we have finished
    mov     ah, 0Eh                                     ; BIOS print call
    mov     bx, 00007h                                  ; Character attribute
    int     010h                                        ; Call the video BIOS
    jmp     print                                       ; Get next character
printdone:
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Calculate CHS
; 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
calcchs:
    cmp     dx, [secpertrack]                           ; Check within track
    jnc     calcchsfail                                 ; if not fail
    div     word [secpertrack]                          ; LBA / secpertrack
    inc     dl                                          ; LBA % secpertrack + 1
    mov     [sector], dl                                ; Save in sector
    xor     dx, dx                                      ; Clear the modulous
    div     word [heads]                                ; lba / secpertrack / heads
    mov     [currenthead], dl                           ; lba / secpertrack % heads
    mov     [track], ax                                 ; lba / secpertrack / heads
    clc                                                 ; Clear carry to report success
    ret                                                 ; Return from subroutine
calcchsfail:
    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
;-------------------------------------------------------
readsec:
    mov     dx, [track]	                                ; Get the track
    mov     cl, 006h                                    ; Rotate the bits 8 and 9 into position
    shl     dh, cl
    or      dh, [sector]                                ; 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, [drivenumber]                           ; Drive number
    mov     dh, [currenthead]                           ; Current Head
    mov     ax, 00201h                                  ; BIOS Disk Read 1 Sector
    int     013h
    ret                                                 ; Return from subroutine
		

We will also copy the loaderror code from the boot sector and rename it.

ldrfatal:
    mov    si, ldrfatalmsg                              ; Get offset to fetal message
    call   print                                        ; Print fatal message
    xor    ax, ax                                       ; Wait for key press
    int    016h
    int    019h                                         ; Bootstrap (Reboot)
		

Let's not forget the message for it to print

ldrfatalmsg:    db      00Dh, 00Ah, "Fatal Error: Loader...", 00Dh, 00Ah, 0
		

Next we will add the code to read a fat12 byte and load a sector into the scratch area. This is because fat 12 data can cross a sector boundry.

;-------------------------------------------------------
; Get byte from fat (FAT12)
; Input:
;     AX = Byte to read
; Output:
;     AL = Byte read
;-------------------------------------------------------
ldrfatread12:
    push   bx                                           ; Save registers
    push   cx
    push   es
    push   ax                                           ; Set ES to 0000h
    xor    ax, ax
    mov    es, ax
    pop    ax
    mov    bx, 00500h                                   ; Read into scratch area
    xor    dx, dx                                       ; Clear for divide
    div    word [bytespersec]                           ; Sector of file = FAT index / bytes per sector 
    inc    ax                                           ; FAT starts at sector 2 advance ax
    cmp    [ldrsectorload], ax                          ; Check if this sector is already in scratch
    jz     ldrfatread12noload                           ;    If it is don't load it again
    mov    [ldrsectorload], ax                          ; Set current loaded FAT sector
    push   dx                                           ; Save FAT index MOD bytes per sector
    xor    dx, dx                                       ; Clear for divide in calcchs
    call   calcchs                                      ; Calculate Cylinder Head Sector
    jc     ldrfatread12error                            ;    If error exit routine
    call   readsec                                      ; Read sector
    jc     ldrfatread12error                            ;    If error exit routine
    pop    dx                                           ; Restore FAT index MOD bytes per sector
ldrfatread12noload:
    add    bx, dx                                       ; Byte to read = file index % bytes per sector + scratch
    mov    al, [es:bx]                                  ; Get byte
    pop    es                                           ; Restore registers
    pop    cx
    pop    bx
    clc                                                 ; Carry clear on success
    ret                                                 ; Return from subroutine
ldrfatread12error:
    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 also need some place to keep track of which sector is loaded. To the header add the following

ldrsectorload:  dw      0
		

Then we will need the code to get the next cluster from the fat

;-------------------------------------------------------
; Get Next Cluster (FAT12)
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
ldrnextcl12:
    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   ldrfatread12                                 ; Read first byte of fat12
    jc     ldrnextcl12errorpop                          ;   error exit routine
    mov    cl, al                                       ; Save byte read
    pop    ax                                           ; Restore input byte to read
    inc    ax                                           ; Get next byte
    call   ldrfatread12                                 ; Read second byte of fat12
    jc     ldrnextcl12error                             ;   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     ldrnextcl12done                              ; If even do nothing
    mov    cl, 4                                        ; If odd we need to shift by 4
    shr    ax, cl
ldrnextcl12done:
    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
ldrnextcl12errorpop:
    pop    ax                                           ; We had an extra push clear it
ldrnextcl12error:
    pop    ax                                           ; Restore input cluster
    pop    cx                                           ; Restore register
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine
		

Now we need a way to read an entire cluster.

;-------------------------------------------------------
; Read Data Cluster
; Input:
;     AX = Cluster start
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
ldrreaddatacluster:
    xor    ch, ch                                       ; Clear ch
    mov    cl, [secperclust]                            ; CX = Sectors per cluster
ldrreadclusternext:
    push   ax                                           ; Save cluster start
    mul    byte [secperclust]
    add    ax, [data]                                   ; Add the data start cluster
    dec    ax                                           ; Subtract 2 because FAT starts at 2
    dec    ax
    push   cx                                           ; Save count read will destroy it
    xor    dx, dx                                       ; Clear for calcchs
    call   calcchs                                      ; Calculate Cylinder Head Sector
    jc     ldrreaddataclusterfail                       ;   If error exit routine
    call   readsec                                      ; Read sector into buffer
    jc     ldrreaddataclusterfail                       ;   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   ldrreadclusternext                           ; Get next sector of cluster
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
ldrreaddataclusterfail:
    pop    cx                                           ; Restore registers
    pop    ax
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine
		

Next we need a chain loader this is going to walk through the entire fat chain and load every cluster in the chain.

;-------------------------------------------------------
; FAT Chain Loader
; Input:
;     AX = First Cluster
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
ldrchainload:
    mov    [ldrsectorload], word 0
ldrchainloadnext:
    cmp    ax, 00FFFh                                   ; Last cluster found
    jz     ldrchainloaddone                             ; We are done
    push   ax                                           ; Save cluster
    call   ldrreaddatacluster                           ; Read cluster from data area
    jc     ldrchainloadfailpop
    pop    ax                                           ; Restore cluster
    call   ldrnextcl12                                  ; Get next cluster
    jc     ldrchainloadfail
    jmp    ldrchainloadnext                             ; Loop until last cluster
ldrchainloaddone:
    clc                                                 ; Carry clear for success
    ret                                                 ; Return from subroutine
ldrchainloadfailpop:
    pop    ax
ldrchainloadfail:
    stc
    ret
		

Let's do some basic setup of registers.

    cli                                                 ; Turn off interrupts
    cld							; Make sure we are going forward
    push   cs                                           ; Set ES to current segment
    pop    es
    mov    ss, ax                                       ; Make sure stack segment is zero
    mov    sp, 07C00h                                   ; Place stack ptr before boot sector
    sti                                                 ; Turn on interrupts
		

We also need a bunch of data from the boot sector we will add code to copy the header

    mov    di, oemname                                  ; Copy disk header to oemname
    mov    si, 07C03h                                   ; From oemname of disk header
    mov    cx, ldrsectorload-oemname                    ; Header size
    rep    movsb                                        ; Copy all bytes
		

Now let's grab the first cluster to load from the root directory left by the boot sector.

    mov    si, 0051Ah                                   ; BIOS left root directory at 500h
    mov    ax, [si]                                     ; Read cluster of first file
		

We are done using 0 as our data segment let's update ds.

    push   cs                                           ; Set DS to current segment
    pop    ds
		

We have already loaded 3 sectors. We will have to fix this for clusters in the future. but for now we will skip 3 clusters.

    mov    cx, 3                                        ; Read first 3 clusters from FAT
ldrnextskipcluster:
    call   ldrnextcl12                                  ; Load next cluster from fat
    jc     ldrfatal                                     ;    If error goto fatal
    cmp    ax, 00FFFh                                   ; Last cluster found
    jz     start                                        ; We are loaded goto demoos
    loop   ldrnextskipcluster                           ; Check next cluster
		

Now let's load the rest of the fat chain and jump to demoos start.

    mov    bx, 00600h                                   ; Start loading at 70:600h
    call   ldrchainload
    jc     ldrfatal
    jmp    start
		

loader.asm

;=======================================================
; Loader - loader.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
;=======================================================
    org    00000h                                       ; Assemble code for this offset
    jmp    short ldrstart

oemname:        db      "        "                      ; Operating system name
bytespersec:    dw      0                               ; Bytes per sector
secperclust:    db      0                               ; Sectors per cluster
ressectors:     dw      0                               ; Reserved Sectors
fats:           db      0                               ; Number of FATs on disk
rootdirents:    dw      0                               ; Number of directory entries
sectors:        dw      0                               ; Sectors per disk
media:          db      0                               ; Always F0 hex for floppy
fatsecs:        dw      0                               ; Sectors per FAT
secpertrack:    dw      0                               ; Sectors per track
heads:          dw      0                               ; Number of heads
hiddensecs:     dd      0                               ; Hidden sectors
hugesectors:    dd      0                               ; Huge sectors
drivenumber:    db      0                               ; Drive number 000h for floppy
currenthead:    db      0                               ; Reserved byte we are using for disk read
bootsignature:  db      0                               ; Boot signature
volumeid:       dd      0                               ; Serial Number
vlumelabel:     db      "           "                   ; Disk label
filesystype:    db      "        "                      ; File system
track:          dw      0                               ; Track for disk read
sector:         db      0                               ; Sector for disk read
root:           dw      0                               ; lba for root
rootmod:        dw      0                               ; lba modulous for root
data:           dw      0                               ; lba for data
datamod:        dw      0                               ; lba modulous for data
ldrsectorload:  dw      0
ldrfatalmsg:    db      00Dh, 00Ah, "Fatal Error: Loader...", 00Dh, 00Ah, 0

ldrstart:
    cli                                                 ; Turn off interrupts
    cld							; Make sure we are going forward
    push   cs                                           ; Set ES to current segment
    pop    es
    mov    ss, ax                                       ; Make sure stack segment is zero
    mov    sp, 07C00h                                   ; Place stack ptr before boot sector
    sti                                                 ; Turn on interrupts

    mov    di, oemname                                  ; Copy disk header to oemname
    mov    si, 07C03h                                   ; From oemname of disk header
    mov    cx, ldrsectorload-oemname                    ; Header size
    rep    movsb                                        ; Copy all bytes

    mov    si, 0051Ah                                   ; BIOS left root directory at 500h
    mov    ax, [si]                                     ; Read cluster of first file

    push   cs                                           ; Set DS to current segment
    pop    ds

    mov    cx, 3                                        ; Read first 3 clusters from FAT
ldrnextskipcluster:
    call   ldrnextcl12                                  ; Load next cluster from fat
    jc     ldrfatal                                     ;    If error goto fatal
    cmp    ax, 00FFFh                                   ; Last cluster found
    jz     start                                        ; We are loaded goto demoos
    loop   ldrnextskipcluster                           ; Check next cluster

    mov    bx, 00600h                                   ; Start loading at 70:600h
    call   ldrchainload
    jc     ldrfatal
    jmp    start

ldrfatal:
    mov    si, ldrfatalmsg                              ; Get offset to fetal message
    call   print                                        ; Print fatal message
    xor    ax, ax                                       ; Wait for key press
    int    016h
    int    019h                                         ; Bootstrap (Reboot)

;-------------------------------------------------------
; FAT Chain Loader
; Input:
;     AX = First Cluster
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
ldrchainload:
    mov    [ldrsectorload], word 0
ldrchainloadnext:
    cmp    ax, 00FFFh                                   ; Last cluster found
    jz     ldrchainloaddone                             ; We are done
    push   ax                                           ; Save cluster
    call   ldrreaddatacluster                           ; Read cluster from data area
    jc     ldrchainloadfailpop
    pop    ax                                           ; Restore cluster
    call   ldrnextcl12                                  ; Get next cluster
    jc     ldrchainloadfail
    jmp    ldrchainloadnext                             ; Loop until last cluster
ldrchainloaddone:
    clc                                                 ; Carry clear for success
    ret                                                 ; Return from subroutine
ldrchainloadfailpop:
    pop    ax
ldrchainloadfail:
    stc
    ret

;-------------------------------------------------------
; Get Next Cluster (FAT12)
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
ldrnextcl12:
    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   ldrfatread12                                 ; Read first byte of fat12
    jc     ldrnextcl12errorpop                          ;   error exit routine
    mov    cl, al                                       ; Save byte read
    pop    ax                                           ; Restore input byte to read
    inc    ax                                           ; Get next byte
    call   ldrfatread12                                 ; Read second byte of fat12
    jc     ldrnextcl12error                             ;   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     ldrnextcl12done                              ; If even do nothing
    mov    cl, 4                                        ; If odd we need to shift by 4
    shr    ax, cl
ldrnextcl12done:
    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
ldrnextcl12errorpop:
    pop    ax                                           ; We had an extra push clear it
ldrnextcl12error:
    pop    ax                                           ; Restore input cluster
    pop    cx                                           ; Restore register
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine


;-------------------------------------------------------
; Get byte from fat (FAT12)
; Input:
;     AX = Byte to read
; Output:
;     AL = Byte read
;-------------------------------------------------------
ldrfatread12:
    push   bx                                           ; Save registers
    push   cx
    push   es
    push   ax                                           ; Set ES to 0000h
    xor    ax, ax
    mov    es, ax
    pop    ax
    mov    bx, 00500h                                   ; Read into scratch area
    xor    dx, dx                                       ; Clear for divide
    div    word [bytespersec]                           ; Sector of file = FAT index / bytes per sector 
    inc    ax                                           ; FAT starts at sector 2 advance ax
    cmp    [ldrsectorload], ax                          ; Check if this sector is already in scratch
    jz     ldrfatread12noload                           ;    If it is don't load it again
    mov    [ldrsectorload], ax                          ; Set current loaded FAT sector
    push   dx                                           ; Save FAT index MOD bytes per sector
    xor    dx, dx                                       ; Clear for divide in calcchs
    call   calcchs                                      ; Calculate Cylinder Head Sector
    jc     ldrfatread12error                            ;    If error exit routine
    call   readsec                                      ; Read sector
    jc     ldrfatread12error                            ;    If error exit routine
    pop    dx                                           ; Restore FAT index MOD bytes per sector
ldrfatread12noload:
    add    bx, dx                                       ; Byte to read = file index % bytes per sector + scratch
    mov    al, [es:bx]                                  ; Get byte
    pop    es                                           ; Restore registers
    pop    cx
    pop    bx
    clc                                                 ; Carry clear on success
    ret                                                 ; Return from subroutine
ldrfatread12error:
    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

;-------------------------------------------------------
; Read Data Cluster
; Input:
;     AX = Cluster start
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
ldrreaddatacluster:
    xor    ch, ch                                       ; Clear ch
    mov    cl, [secperclust]                            ; CX = Sectors per cluster
ldrreadclusternext:
    push   ax                                           ; Save cluster start
    mul    byte [secperclust]
    add    ax, [data]                                   ; Add the data start cluster
    dec    ax                                           ; Subtract 2 because FAT starts at 2
    dec    ax
    push   cx                                           ; Save count read will destroy it
    xor    dx, dx                                       ; Clear for calcchs
    call   calcchs                                      ; Calculate Cylinder Head Sector
    jc     ldrreaddataclusterfail                       ;   If error exit routine
    call   readsec                                      ; Read sector into buffer
    jc     ldrreaddataclusterfail                       ;   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   ldrreadclusternext                           ; Get next sector of cluster
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
ldrreaddataclusterfail:
    pop    cx                                           ; Restore registers
    pop    ax
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; String Output
; Input:
;    DS:SI = Message offset
; Output:
;    AX, BX, SI are destroyed
;-------------------------------------------------------
print:
    lodsb                                               ; Get character from string
    or      al, al                                      ; Check if our character is zero
    jz      printdone                                   ; if it is zero we have finished
    mov     ah, 0Eh                                     ; BIOS print call
    mov     bx, 00007h                                  ; Character attribute
    int     010h                                        ; Call the video BIOS
    jmp     print                                       ; Get next character
printdone:
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Calculate CHS
; 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
calcchs:
    cmp     dx, [secpertrack]                           ; Check within track
    jnc     calcchsfail                                 ; if not fail
    div    word [secpertrack]                           ; LBA / secpertrack
    inc     dl                                          ; LBA % secpertrack + 1
    mov     [sector], dl                                ; Save in sector
    xor     dx, dx                                      ; Clear the modulous
    div     word [heads]                                ; lba / secpertrack / heads
    mov     [currenthead], dl                           ; lba / secpertrack % heads
    mov     [track], ax                                 ; lba / secpertrack / heads
    clc                                                 ; Clear carry to report success
    ret                                                 ; Return from subroutine
calcchsfail:
    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
;-------------------------------------------------------
readsec:
    mov     dx, [track]	                                ; Get the track
    mov     cl, 006h                                    ; Rotate the bits 8 and 9 into position
    shl     dh, cl
    or      dh, [sector]                                ; 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, [drivenumber]                           ; Drive number
    mov     dh, [currenthead]                           ; Current Head
    mov     ax, 00201h                                  ; BIOS Disk Read 1 Sector
    int     013h
    ret


		

Demo OS

Now we need to fix a couple of things in demoos.asm.

Remove the following code:

    cli                                                 ; Turn off interrupts
    cld							; Make sure we are going forward
    mov     ax, cs                                      ; Set all segments to code segment
    mov     ds, ax                                      ; Make sure data segment is zero
    mov     es, ax                                      ; Make sure extra segment is zero
    mov     ss, ax                                      ; Make sure stack segment is zero
    mov     sp, 07C00h                                  ; Place stack ptr before boot sector
    sti                                                 ; Turn on interrupts
		

Replace the following:

    org     00000h                                      ; Assemble code for this offset
		

with

%include "loader.asm"
		

Also replace the following:

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

with

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

demoos.asm

;=======================================================
; Demo OS - demoos.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
;=======================================================

%include "loader.asm"

start:
    call   intinit                                      ; Initialize interrupts
    push   ds                                           ; Save DS
    mov    ax, 00FFFh                                   ; Set first MCB
    mov    [mcbfirst], ax                               ; Save first MCB
    mov    ds, ax
    mov    bx, 07000h                                   ; Size to initialize
    call   meminit                                      ; Initialize memory
    pop    ds                                           ; Restore DS
    call   ver

main:
    call   crlf                                         ; Print carriage return / line feed
    mov    ah, 002h                                     ; Service standard output
    mov    dl, '>'                                      ; Print '>'
    int    021h
    mov    dl, ' '                                      ; Print Space
    int    021h
    mov    ah, 00Ah                                     ; Service string input
    mov    dx, buffer                                   ; Set dx to our buffer
    int    021h
    call   crlf                                         ; Print carriage return / line feed

    mov    di, buffer+2                                 ; Point to the string we typed

    mov    si, verstr                                   ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdnver                                      ; Carry set if not equal
    call   ver                                          ; Call function
    jmp    main                                         ; We're done checking
cmdnver:

    mov    si, clsstr                                   ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdncls                                      ; Carry set if not equal
    call   cls                                          ; Call function
    jmp    main                                         ; We're done checking
cmdncls:

    mov    si, echostr                                  ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdnecho                                     ; Carry set if not equal
    call   echo                                         ; Call function
    jmp    main                                         ; We're done checking
cmdnecho:

    mov    si, mcbstr                                   ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdnmcb                                      ; Carry set if not equal
    call   mcb                                          ; Call function
    jmp    main                                         ; We're done checking
cmdnmcb:

    mov    si, allocstr                                 ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdnalloc                                    ; Carry set if not equal
    call   alloc                                        ; Call function
    jmp    main                                         ; We're done checking
cmdnalloc:

    mov    si, freestr                                  ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdnfree                                     ; Carry set if not equal
    call   free                                         ; Call function
    jmp    main                                         ; We're done checking
cmdnfree:

    mov    si, resizestr                                ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdnresize                                   ; Carry set if not equal
    call   resize                                       ; Call function
    jmp    main                                         ; We're done checking
cmdnresize:


    mov    ah, 009h                                     ; Service Print String
    mov    dx, invalid                                  ; Invalid command
    int    021h                                         ; Our Service
    call   crlf                                         ; New line
    jmp    main                                         ; Repeat forever

;-------------------------------------------------------
; Compare Command
; Input:
;      DS:SI = Command String
;      ES:DI = Buffer String
; Output:
;      Carry set if not equal
;      Carry clear if equal
;-------------------------------------------------------
cmpcmd:
    push   ax                                           ; Save registers
    push   si
    push   di
cmpcmdnext:
    mov    al, [es:di]                                  ; Load char from command line 
    inc    di                                           ; Point to next character
    call   toup                                         ; Convert to uppercase
    mov    ah, [ds:si]                                  ; Load char to test against
    inc    si                                           ; Point to next character
    cmp    ah, al                                       ; Compare both characters
    jz     cmpcmdnext                                   ; If they are the same check next
    dec    si                                           ; Adjust to point to last char
    dec    di
    cmp    [ds:si], byte 0                              ; Our test command end should be 0
    jnz    cmpcmdne                                     ; If it isn't not our command
    cmp    [es:di], byte ' '                            ; Our input cmd might be space
    jz     cmpcmde                                      ; If it is this is our command
    cmp    [es:di], byte 00Dh                           ; Our input cmd might be CR
    jz     cmpcmde                                      ; If it is this is our command
    jmp    cmpcmdne                                     ; Otherwise not our command
cmpcmde:
    pop    di                                           ; Restore registers
    pop    si
    pop    ax
    clc                                                 ; Clear carry for match
    ret                                                 ; Return from subroutine
cmpcmdne:
    pop    di                                           ; Restore registers
    pop    si
    pop    ax
    stc                                                 ; Set carry if not a match
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Version Command
; Input:
;      None
; Output:
;      AX, DX are Destroyed
;-------------------------------------------------------
ver:
    mov    ah, 009h                                     ; Service string output
    mov    dx, version                                  ; dx is set to offset version
    int    021h                                         ; Call the service
    ret

;-------------------------------------------------------
; Clear Screen Command
; Input:
;      None
; Output:
;      AX is Destroyed
;-------------------------------------------------------
cls:
    mov    ah, 00Fh                                     ; Get video mode
    int    010h
    mov    ah, 000h                                     ; Set video mode
    int    010h                                         ;  this automatically clears the screen
    ret

;-------------------------------------------------------
; Echo Command
; Input:
;      None
; Output:
;      None
;-------------------------------------------------------
echo:
    push   ax                                           ; Save registers
    push   dx
    push   di
    xor    ah, ah                                       ; Clear upper part of ax
    mov    al, [buffer+1]                               ; Get count from buffer
    mov    di, buffer+2                                 ; Get start of buffer
    add    di, ax                                       ; Add for end of buffer
    mov    al, '$'                                      ; Place '$' at end for printing
    mov    [di], al
    mov    ah, 009h                                     ; Service print string
    mov    dx, buffer+7                                 ; Get start of string to print
    int    021h                                         ;    7 to exclude echo
    call   crlf
    pop    di                                           ; Restore regsiters
    pop    dx
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Memory Control Block Command
; Input:
;      None
; Output:
;      AX is Destroyed
;-------------------------------------------------------
mcb:
    push   ax                                           ; Save Registers
    push   dx
    push   ds
    call   crlf                                         ; Print CR / LF
    mov    ax, [mcbfirst]                               ; DS = first mcb
    mov    ds, ax
mcbnext:
    mov    ax, ds                                       ; Print arena segment
    call   hexwordout
    mov    ah, 002h
    mov    dl, ' '                                      ; Print space
    int    021h
    mov    ah, 002h                                     ; Print signature
    mov    dl, [ds:memarena.signature]
    push   dx                                           ; Save signature
    int    021h
    mov    ah, 002h
    mov    dl, ' '                                      ; Print space
    int    021h
    mov    ax, [ds:memarena.owner]                      ; Print owner
    call   hexwordout
    mov    ah, 002h
    mov    dl, ' '                                      ; Print space
    int    021h
    mov    ax, [ds:memarena.size]                       ; Print size
    call   hexwordout
    mov    ah, 002h
    mov    dl, ' '                                      ; Print space
    int    021h
    xor    si, si                                       ; Print name
    mov    cx, 8
mcbnextname:
    mov    dl, [ds:si+memarena.name]                    ; Read name byte
    mov    ah, 002h                                     ; Print name byte
    int    021h
    inc    si                                           ; Increment name byte
    loop   mcbnextname                                  ; Get next name byte
    pop    dx                                           ; Restore signature
    cmp    dl, 'Z'                                      ; Compare to end arena
    jz     mcbdone                                      ;   if yes were done
    call   memarenanext                                 ; Get next arena
    jc     mcbdone                                      ;   failed were done
    call   crlf                                         ; Print CR / LF for next arena
    jmp    mcbnext                                      ; Next arena
mcbdone:
    call   crlf                                         ; Print CR / LF
    pop    ds                                           ; Restore registers
    pop    dx
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Memory Allocate Command
; Input:
;      Command line hex number
; Output:
;       None
;-------------------------------------------------------
alloc:
    push   ax                                           ; Save registers
    push   bx
    push   si
    mov    si, buffer+8                                 ; Get buffer + 8 point to hex input
    call   hexwordin                                    ; Get hex input
    mov    bx, ax                                       ; Allocate input size
    mov    ah, 048h                                     ; Service Allocate
    int    021h
    jc     allocerror
    call   crlf                                         ; Print CR / LF
    call   hexwordout                                   ; Show allocated page start
    call   crlf                                         ; Print CR / LF
allocerror:
    pop    si                                           ; Restore registers
    pop    bx
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Memory Free Command
; Input:
;      Command line hex number
; Output:
;       None
;-------------------------------------------------------
free:
    push   ax                                           ; Save registers
    push   si
    push   es
    mov    si, buffer+7                                 ; Get buffer + 8 point to hex input
    call   hexwordin                                    ; Get hex input
    mov    es, ax                                       ; Free page
    mov    ah, 049h                                     ; Service Free
    int    021h
    jc     freeerror
freeerror:
    pop    es
    pop    si                                           ; Restore registers
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Memory Resize Command
; Input:
;      Command line hex number
; Output:
;       None
;-------------------------------------------------------
resize:
    push   ax                                           ; Save registers
    push   si
    push   es
    mov    si, buffer+9                                 ; Get buffer + 8 point to hex input
    call   hexwordin                                    ; Get hex input
    mov    es, ax                                       ; Free page
    mov    si, buffer+14
    call   hexwordin                                    ; Get hex input
    mov    bx, ax
    mov    ah, 04Ah                                     ; Service Resize
    int    021h
    jc     resizeerror
resizeerror:
    pop    es
    pop    si                                           ; Restore registers
    pop    ax
    ret                                                 ; Return from subroutine


%include "io.asm"
%include "int.asm"
%include "mem.asm"

;-------------------------------------------------------
; Data Area
;-------------------------------------------------------

verstr:         db      "VER", 0
clsstr:         db      "CLS", 0
echostr:        db      "ECHO", 0
mcbstr:         db      "MCB", 0
allocstr:       db      "ALLOC", 0
freestr:        db      "FREE", 0
resizestr:      db      "RESIZE", 0

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

invalid:        db      00Dh, 00Ah, "Invalid Command"   ; Message to be printed
                db      00Dh, 00Ah, '$'

buffer:         db      64                              ; Buffer for string input
                db      0
                db      0, 0, 0, 0, 0, 0, 0, 0
                db      0, 0, 0, 0, 0, 0, 0, 0
                db      0, 0, 0, 0, 0, 0, 0, 0
                db      0, 0, 0, 0, 0, 0, 0, 0
                db      0, 0, 0, 0, 0, 0, 0, 0
                db      0, 0, 0, 0, 0, 0, 0, 0
                db      0, 0, 0, 0, 0, 0, 0, 0
                db      0, 0, 0, 0, 0, 0, 0, 0

mcbfirst:       dw      0
		

OS Disk

Now that we have all the code to read the fat we need a fat in our disk image.

The following routine is a macro to generate a fat chain so we don't have to type it manually.

;-------------------------------------------------------
; FAT12 Generator
; Usage:
;      FAT12 Cluster, Filesize
;-------------------------------------------------------
%macro FAT12 2		                                ; FAT STARTBLOCK FILESIZE
    %assign i %1                                        ; FAT Block Number
    %assign j %2                                        ; File Size
    %assign k 512                                       ; Cluster Size
    %assign m j
    %assign j 0                                         ; j = j mod k
    %rep 100                                            ; repeate 100 times or m is less than 512
        %assign j j+1                                   ; j ++
        %assign m m-k                                   ; subtract sector size
        %if m0                                     ; if m is still greater than zero increment j
                %assign j j+1
            %endif
            %exitrep                                    ; we are done with our divide
        %endif
    %endrep
    %assign l i+j-1                                     ; l = FAT Number + Number of FAT Blocks for file
    %assign j j*3/2                                     ; Position in FAT
    %rep j                                              ; Numver of blocks to write
        %if i=l
            %assign x 0FFFh                             ; Last FAT Block for File 0xFFF
            %if i&1                                     ; If odd
                db     ((x<<4)&0F0h)+(i>>8)
                db     (x>>4)&0FFh)
            %else                                       ; If even
                db     x&0FFh
                db     (x>>8)&00Fh
                db     000h
            %endif
            %exitrep
        %endif
        %if i&1                                         ; If odd
            db     ((i+1<<4)&0xF0)+(i>>8)
            db     (i+1>>4)&0xFF)
        %else
            db     i+1&0xFF
        %endif
        %assign i i+1                                   ; increment i
    %endrep
%endmacro
		

Replace the following:

incbin "demoos2.bin"                                    ; include our demoos2.bin file as binary
		

with

                times (002h*3/2)+512-($-$$) db 0       ; Fat for demoos.bin 002h is Cluster
                FAT12 002h, 1846                       ; 002h Cluster, 1846 file size
		

osdisk.asm

;=======================================================
; Disk Image - osdisk.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
;=======================================================

;-------------------------------------------------------
; FAT12 Generator
; Usage:
;      FAT12 Cluster, Filesize
;-------------------------------------------------------
%macro FAT12 2		                                ; FAT STARTBLOCK FILESIZE
    %assign i %1                                        ; FAT Block Number
    %assign j %2                                        ; File Size
    %assign k 512                                       ; Cluster Size
    %assign m j
    %assign j 0                                         ; j = j mod k
    %rep 100                                            ; repeate 100 times or m is less than 512
        %assign j j+1                                   ; j ++
        %assign m m-k                                   ; subtract sector size
        %if m0                                     ; if m is still greater than zero increment j
                %assign j j+1
            %endif
            %exitrep                                    ; we are done with our divide
        %endif
    %endrep
    %assign l i+j-1                                     ; l = FAT Number + Number of FAT Blocks for file
    %assign j j*3/2                                     ; Position in FAT
    %rep j                                              ; Numver of blocks to write
        %if i=l
            %assign x 0FFFh                             ; Last FAT Block for File 0xFFF
            %if i&1                                     ; If odd
                db     ((x<<4)&0F0h)+(i>>8)
                db     (x>>4)&0FFh)
            %else                                       ; If even
                db     x&0FFh
                db     (x>>8)&00Fh
                db     000h
            %endif
            %exitrep
        %endif
        %if i&1                                         ; If odd
            db     ((i+1<<4)&0xF0)+(i>>8)
            db     (i+1>>4)&0xFF)
        %else
            db     i+1&0xFF
        %endif
        %assign i i+1                                   ; increment i
    %endrep
%endmacro


;-------------------------------------------------------
; Boot Sector
;-------------------------------------------------------
incbin "boot.bin"                                       ; include our boot.bin file as binary

;-------------------------------------------------------
; FAT Table 1
;-------------------------------------------------------
                db      0F0h, 0FFh, 0FFh

                times (002h*3/2)+512-($-$$) db 0       ; Fat for demoos.bin 002h is Cluster
                FAT12 002h, 1846                       ; 002h Cluster, 1846 file size

;-------------------------------------------------------
; Root Directory
;-------------------------------------------------------
                times 9728-($-$$) db 0                  ; Place our file at 9728
                db      "DEMOOS  BIN"                   ; Filename
                db      021h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                db      0C0h, 032h, 0BFh, 01Ch          ; Written Date/Time
                dw      00002h                          ; First Cluster Address Low
                dd      demoosend-demoosstart           ; File Size

;-------------------------------------------------------
; Data Area
;-------------------------------------------------------
                times 16896-($-$$) db 0                 ; (32 * 224 + 512 - 1) / 512 = 14
demoosstart:
incbin "demoos.bin"                                     ; 14 * 512 + 9728 = 16896
demoosend:

;-------------------------------------------------------
; Correct Disk Size
;-------------------------------------------------------
                times 1474560-($-$$) db 0               ; Make file size 1,474,560 bytes long
                                                        ;   This is the size of a floppy disk
		

Modify the disk image

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.12

> 
		
Memory: Resize | Index | Disk: VOL

Support This Project On Patreon

Copyright © 2021 Marcus Kelly