Disk: DIR | Index

Write Your Own OS from Scratch - Chapter 4. Disk

Disk: COM Loader

We will start off buy adding the build match string that will take command line input and turn it into a match string.

;-------------------------------------------------------
; Build match string
; Input:
;       es:di = Buffer
; Output:
;       matchname = Match Name
;-------------------------------------------------------
dskbuildmatch:
	;mov	bx, di			; save our place beginning of buffer
	mov	cx, 0x0B
	mov	si, matchname
dskbuildmatchnext:
	mov	al, [es:di]
	inc	di
	cmp	al, 0x0D
	jz	dskbuildmatchfill
	cmp	al, ' '
	jz	dskbuildmatchfill
	cmp	al, '.'			; dot crashes
	jz	dskbuildmatchfill
	call	toup
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchnext
        ret
dskbuildmatchfill:
	mov	al, '?'
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfill
	ret
dskbuildmatchfill8:
	sub	cx, 0x03
dskbuildmatchfillloop:
	mov	al, '?'
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfillloop
	add	cx, 0x03
	jmp     dskbuildmatchnext
		

disk.asm

;=======================================================
; Disk - disk.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================

;-------------------------------------------------------
; Convert current drive to drive letter
; Input:
;       None
; Output:
;       AL = Drive Letter 
;-------------------------------------------------------
dskdriveletter:
    mov    al, [drivenumber]
    test   al, byte 080h
    jz     dskdriveletternoadj
    sub    al, 078h
dskdriveletternoadj:
    add    al, 'A'
    ret

;-------------------------------------------------------
; Get first directory match
; Input:
;       None
; Output:
;       ES:BX Points to directory entry
;-------------------------------------------------------
dskfirstentry:
    mov    [diroffset], word 0                          ; Clear offset for new read
    mov    [ldrsectorload], word 0
                                                        ; drops into next entry
;-------------------------------------------------------
; Get next directory match
; Input:
;       None
; Output:
;       ES:BX Points to directory entry
;-------------------------------------------------------
dsknextentry:
    push   ax
    push   cx
    push   dx
    push   di
    xor    bx, bx                                      ; es:bx = scratch area
    mov    es, bx
dsknextentrynext:
    cmp    [dircurrent], word 0                        ; check if root directory
    jnz    dsknextentryfile                            ; if not process as file
    mov    ax, 32                                      ; Calc size of root
    mul    word [rootdirents]
    mov    bx,[bytespersec]
    add    ax, bx
    cmp    [diroffset], ax                              ; check if we are past the end
    jge    dsknextentryerror                            ; we have reached the end
    jmp    dsknextentrydone
dsknextentryfile:
        ; *** this needs to be written when we can change directory ***
	; calulate which cluster to read
        ; crawl fat for current cluster
	; calulate which sector of cluster
dsknextentrydone:
    mov    ax, [diroffset]                             ; read sector = diroffset / BYTES PER SECTOR
    xor    dx, dx
    div    word [bytespersec]                           
    push   dx                                          ; dx should equal index into sector
    add    ax, [root]                                  ; add directory base
    cmp    [ldrsectorload], ax                          ; Check if this sector is already in scratch
    jz     dsknextentryskprd                           ;    If it is don't load it again
    mov    [ldrsectorload], ax                          ; Set current loaded FAT sector
    mov    bx, 00500h
    xor    dx, dx
    call   calcchs                                     ; Calc chs
    jc     dsknextentryerrorpop
    call   readsec                                     ; read sector
    jc     dsknextentryerrorpop
dsknextentryskprd:
    pop    bx                                          ; restore index       
    add    bx, 00500h
    cmp    [es:bx], byte 0                             ; check if entry is blank
    jz     dsknextentryerror                           ; if so we reached the end
    mov    di, bx
    call   dskcmpmatch                                 ; see if it matches our match string
    jnc    dsknextentryexit
    add    [diroffset], word 32                        ; point to next dir entry
    jmp    dsknextentrynext
dsknextentryexit:
    add    [diroffset], word 32                        ; point to next dir entry
    pop    di
    pop    dx
    pop    cx
    pop    ax
    clc
    ret
dsknextentryerrorpop:
    pop    dx
dsknextentryerror:
    pop    di
    pop    dx
    pop    cx
    pop    ax
    stc
    ret

;-------------------------------------------------------
; Build blank match string
; Input:
;       es:di = Buffer
; Output:
;       matchname = Match Name
;-------------------------------------------------------
dskmatchblank:
    push   cx
    push   si
    mov    cx,    11
    mov    si, matchname
dskmatchblanknext:
    mov    [ds:si], byte '?'  
    inc    si
    loop   dskmatchblanknext
    pop    si
    pop    cx
    ret

matchname:        db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

;-------------------------------------------------------
; Build match string
; Input:
;       es:di = Buffer
; Output:
;       matchname = Match Name
;-------------------------------------------------------
dskbuildmatch:
	;mov	bx, di			; save our place beginning of buffer
	mov	cx, 0x0B
	mov	si, matchname
dskbuildmatchnext:
	mov	al, [es:di]
	inc	di
	cmp	al, 0x0D
	jz	dskbuildmatchfill
	cmp	al, ' '
	jz	dskbuildmatchfill
	cmp	al, '.'			; dot crashes
	jz	dskbuildmatchfill
	call	toup
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchnext
        ret
dskbuildmatchfill:
	mov	al, '?'
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfill
	ret
dskbuildmatchfill8:
	sub	cx, 0x03
dskbuildmatchfillloop:
	mov	al, '?'
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfillloop
	add	cx, 0x03
	jmp     dskbuildmatchnext

;-------------------------------------------------------
; Compare match string
; Input:
;       es:di = Directory entry
; Output:
;       Carry Set = not match
;       Carry Clear = match
;-------------------------------------------------------
dskcmpmatch:
    mov    si, matchname
    mov    cx, 00Bh
dskcmpmatchnext:
    mov    al, [si]
    inc    si
    cmp    al, '?'
    jz     dskcmpmatchauto
    cmp    [es:di], al
    jnz    dskcmpmatchfail
dskcmpmatchauto:
    inc    di
    loop   dskcmpmatchnext
    clc
    ret
dskcmpmatchfail:
    stc
    ret


diroffset:		dw 0
dircurrent:		dw 0
		

Exit Program

The next code we will add to int.asm is exit program.

;-------------------------------------------------------
; 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
		

Then we will add the service exit program to inthook.

    cmp    ah, 0x4C                                     ; Service 4Ch: exit program
    jnz    inthookdone4C
    jmp    exitprogram
inthookdone4C:
		

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, 025h                                     ; Service 25h: setint
    jnz    inthookdone25
    call   intset
    jmp    inthookdone
inthookdone25:
    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:
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

;-------------------------------------------------------
; 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 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

First we will need a way to verify a COM file when we type a command we don't want to accidentally try to execute a non executable file.

commatch:       dw      "COM"
		

Then to main we will add the following code. it creates a match string. searches through the directory. Loads the program into memory. Allocates memory. Creates a psp at the head of the program with what was typed after the program on the command line. Sets up all the registes then pushes an interrupt return structure onto the stack and then jumps to our program with an iret

    call   dskbuildmatch                                ; Make directory match string

    call   dskfirstentry
    jc     loaddirfail

loaddirdone:
    mov    di, bx                                       ; Check if com file
    add    di, 8                                        ; Pass first 8 characters of name
    mov    si, commatch                                 ; Compare to "COM"
    mov    cx, 3                                        ; Number of characters to match
    repz   cmpsb                                        ; Compare 3 all at once
    jz     loadcom                                      ; If not com check rest of directory
    call   dsknextentry
    jc     loaddirfail
    jmp    loaddirdone

loadcom:

    push   bx                                           ; Save pointer to directory entry

    mov    ah, 048h                                     ; Allocate 64K of memory for COM
    mov    bx, 01000h
    int    021h

    mov    es, ax                                       ; Build PSP
    mov    di, 081h
    mov    si, buffer+2                                 ; Point to buffer data
    mov    cx, [buffer+1]                               ; Get length of buffer
    mov    bx, di                                       ; Save di for count
loadskip:
    lodsb                                               ; Skip first word of buffer
    cmp    al, ' '                                      ; If space we passed first word
    jz     loadcopy                                     ; Copy rest of buffer to psp
    cmp    al, 00Dh                                     ; If 0D we reached the end
    jz     loadnocopy                                   ; Nothing to copy
    loop   loadskip                                     ; Get next byte of skip
loadcopy:
    mov    bx, di                                       ; Save di new for count
    dec    si                                           ; Move back 1 space
    inc    cx                                           ; Move back 1 space
    rep    movsb                                        ; Move remaining buffer to psp
loadnocopy:
    sub    di, bx                                       ; Count of characters copied to psp
    mov    ax, di                                       ; Save count in ax
    mov    di, 080h                                     ; Point to count location in psp
    mov    [es:di], al                                  ; Write count to PSP

    pop    bx                                           ; Restore pointer to directory entry

    push   es                                           ; Save COM load segment
    xor    ax, ax                                       ; Get cluster to load
    mov    es, ax
    mov    ax, [es:bx+01Ah]

    pop    es                                           ; Restore COM load segment
    mov    bx, 00100h                                   ; Offset to load at past PSP
    call   ldrchainload                                 ; Load FAT chain for file

    mov    ax, es                                       ; Setup registers
    mov    ds, ax                                       ; DS = COM load segment
    mov    ss, ax                                       ; SS = COM load segment
    mov    sp, 0FFFFh                                   ; SP = End of segment
    pushf                                               ; Push flags for iret
    push   es                                           ; Push segment for iret
    mov    ax, es
    mov    ax, 00100h                                   ; Push offset for iret
    push   ax
    xor    ax, ax                                       ; Clear remaning registers
    xor    bx, bx
    xor    cx, cx
    xor    dx, dx
    xor    di, di
    xor    si, si
    xor    bp, bp
    iret                                                ; jump to program, CS = COM load segment

loaddirfail:
    push   cs                                           ; fix es
    pop    es
		

Then change:

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

to

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

demoos.asm

;=======================================================
; Demo OS - demoos.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 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
    call   dskdriveletter                               ; Print drive letter
    mov    dl, al
    int    021h
    mov    dl, ':'                                      ; Print ':'
    int    021h
    mov    dl, '\'                                      ; Print '\'
    int    021h
    mov    dl, '>'                                      ; Print '>'
    int    021h
    mov    dl, ' '                                      ; Print Space
    int    021h
    mov    ax, es
    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    si, volstr                                   ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdnvol                                      ; Carry set if not equal
    call   vol                                          ; Call function
    jmp    main                                         ; We're done checking
cmdnvol:

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

    call   dskbuildmatch                                ; Make directory match string

    call   dskfirstentry
    jc     loaddirfail

loaddirdone:
    mov    di, bx                                       ; Check if com file
    add    di, 8                                        ; Pass first 8 characters of name
    mov    si, commatch                                 ; Compare to "COM"
    mov    cx, 3                                        ; Number of characters to match
    repz   cmpsb                                        ; Compare 3 all at once
    jz     loadcom                                      ; If not com check rest of directory
    call   dsknextentry
    jc     loaddirfail
    jmp    loaddirdone

loadcom:

    push   bx                                           ; Save pointer to directory entry

    mov    ah, 048h                                     ; Allocate 64K of memory for COM
    mov    bx, 01000h
    int    021h

    mov    es, ax                                       ; Build PSP
    mov    di, 081h
    mov    si, buffer+2                                 ; Point to buffer data
    mov    cx, [buffer+1]                               ; Get length of buffer
    mov    bx, di                                       ; Save di for count
loadskip:
    lodsb                                               ; Skip first word of buffer
    cmp    al, ' '                                      ; If space we passed first word
    jz     loadcopy                                     ; Copy rest of buffer to psp
    cmp    al, 00Dh                                     ; If 0D we reached the end
    jz     loadnocopy                                   ; Nothing to copy
    loop   loadskip                                     ; Get next byte of skip
loadcopy:
    mov    bx, di                                       ; Save di new for count
    dec    si                                           ; Move back 1 space
    inc    cx                                           ; Move back 1 space
    rep    movsb                                        ; Move remaining buffer to psp
loadnocopy:
    sub    di, bx                                       ; Count of characters copied to psp
    mov    ax, di                                       ; Save count in ax
    mov    di, 080h                                     ; Point to count location in psp
    mov    [es:di], al                                  ; Write count to PSP

    pop    bx                                           ; Restore pointer to directory entry

    push   es                                           ; Save COM load segment
    xor    ax, ax                                       ; Get cluster to load
    mov    es, ax
    mov    ax, [es:bx+01Ah]

    pop    es                                           ; Restore COM load segment
    mov    bx, 00100h                                   ; Offset to load at past PSP
    call   ldrchainload                                 ; Load FAT chain for file

    mov    ax, es                                       ; Setup registers
    mov    ds, ax                                       ; DS = COM load segment
    mov    ss, ax                                       ; SS = COM load segment
    mov    sp, 0FFFFh                                   ; SP = End of segment
    pushf                                               ; Push flags for iret
    push   es                                           ; Push segment for iret
    mov    ax, es
    mov    ax, 00100h                                   ; Push offset for iret
    push   ax
    xor    ax, ax                                       ; Clear remaning registers
    xor    bx, bx
    xor    cx, cx
    xor    dx, dx
    xor    di, di
    xor    si, si
    xor    bp, bp
    iret                                                ; jump to program, CS = COM load segment

loaddirfail:
    push   cs                                           ; fix es
    pop    es

    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

;-------------------------------------------------------
; Volume Info Command
; Input:
;       None
; Output:
;       None
;-------------------------------------------------------
vol:
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   di
    push   es
    ; read boot sector into scratch
    xor    ax, ax                                       ; Point to scratch area
    mov    es, ax
    mov    bx, 00500h
    xor    dx, dx                                       ; AX = 0
    call   calcchs                                      ; Calculate CHS
    jc     volerror
    call   readsec                                      ; Read sector
    jc     volerror
    ; print data from boot sector
    mov    ah, 009h                                     ; Print string
    mov    dx, volname
    int    021h
    mov    ah, 002h                                     ; drive letter
    call   dskdriveletter
    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   dx
    pop   cx
    pop   bx
    pop   ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Directory List
; Input:
;       None
; Output:
;       None
;-------------------------------------------------------
dir:
    push   ax                                           ; Save registers
    push   cx
    push   dx
    push   si
    push   es
    call   vol                                          ; Print volume information
    xor    ax, ax                                       ; Point to scratch area
    mov    es, ax
    mov    [seccount], ax                               ; Clear sector count
dirsecnext:
    mov    ax, [root]                                   ; Get root start
    add    ax, word [seccount]                          ; Add sector count
    inc    word [seccount]                              ; Increment sector count
    cmp    [seccount], word 00Eh                        ; (needs math for root size)
    jge    dirdone                                      ; We are done
    mov    bx, 00500h                                   ; Point to scratch area
    xor    dx, dx                                       ; Clear DX for calcchs
    call   calcchs                                      ; Calculate CHS
    jc     dirdone
    call   readsec                                      ; Read sector
    jc     dirdone
dirnext:
    call   crlf                                         ; Print CRLF
    mov    ax, [bytespersec]                            ; Get bytes per sector
    add    ax, 00500h                                   ; Add scratch area
    cmp    bx, ax                                       ; Check if bx hit the end
    jg     dirsecnext                                   ; If so load next sector
    mov    ax, [es:bx]                                  ; Check if name feild empty
    or     ax, ax                                       ; Check if there is data in name
    jz     dirdone                                      ; If not we are done
    mov    cx, 0000Bh                                   ; Name length
    mov    si, bx                                       ; Copy bx
dirname:
    mov    ah, 002h                                     ; Service stdout
    mov    dl, [es:si]                                  ; Get filename character
    inc    si                                           ; Point to next character
    int    021h                                         ; Print the character we got
    loop   dirname                                      ; Get next character, dec cx
    add    bx, 020h                                     ; Point to next directory entry
    jmp    dirnext                                      ; Get next directory entry
dirdone:
    pop    es                                           ; Restore registers
    pop    si
    pop    dx
    pop    cx
    pop    ax
    ret                                                 ; Return from subroutine


%include "io.asm"
%include "int.asm"
%include "mem.asm"
%include "disk.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
volstr:         db      "VOL", 0
dirstr:         db      "DIR", 0

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

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

volname:	db	0x0D, 0x0A, " Volume in drive $"
volname2:	db	" is $"
volserial:	db	0x0D, 0x0A, " Volume Serial Number is $"

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

commatch:       dw      "COM"
mcbfirst:       dw      0
seccount:       dw      0
		

Our First Program

Now we need to create a test program to see if it works.

hello.asm

;=======================================================
; Hello, World! - hello.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================

    org     00100h                                      ; Assemble code for this offset

    mov     ah, 009h                                    ; DEMOOS service string output
    mov     dx, message                                 ; dx is set to offset Message
    int     021h                                        ; Call demoos service

    mov     ax, 04C00h                                  ; exit to demoos
    int     021h

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

message:        db      00Dh, 00Ah, "Hello, World!"     ; Message to be printed
                db      00Dh, 00Ah, "$"
		

Modify the disk image

We start by adding the fat for hello.com

                times (034h*3/2)+512-($-$$) db 0       ; Fat for hello.com 034h is Cluster
                FAT12 034h, 30                         ; 034h Cluster, filesize
		

Now add the entry for our hello program to the root directory.

                db      "HELLO   COM"                   ; Filename
                db      020h				; 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      00034h                          ; First Cluster Address Low
                dd      helloend-hellostart             ; File Size
		

Then add our hello.com program to the disk image.

                times ((034h-2+021h)*512)-($-$$) db 0x00
hellostart:                                             ; Cluster 34h
incbin "hello.com"                                      ; hello.com                                        
helloend:
		

osdisk.asm

;=======================================================
; Disk Image - osdisk.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 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, 2526                       ; 002h Cluster, file size (MAX 8K)


                times (034h*3/2)+512-($-$$) db 0       ; Fat for hello.com 034h is Cluster
                FAT12 034h, 30                         ; 034h Cluster, filesize

;-------------------------------------------------------
; 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

                db      "HELLO   COM"                   ; Filename
                db      020h				; 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      00034h                          ; First Cluster Address Low
                dd      helloend-hellostart             ; File Size

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

                times ((034h-2+021h)*512)-($-$$) db 0x00
hellostart:                                             ; Cluster 34h
incbin "hello.com"                                      ; hello.com                                        
helloend:

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

		

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 hello.asm -o hello.com
> nasm osdisk.asm -o osdisk.img
		

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

DEMO OS V0.15

A:\> dir

 Volume in drive A is DEMO OS DSK
 Volume Serial Number is 2394-C726

DEMOOS  BIN
HELLO   COM

A:\> hello

Hello, World!

A:\>
		
Disk: DIR | Index

Support This Project On Patreon

Copyright © 2021 Marcus Kelly