To disk.asm we will add first entry and next entry. These functions return directory entries. The main routine is next entry. First entry clears a couple variables then drops into next entry.
;------------------------------------------------------- ; 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
Next we will add match blank this creates a blank match string of all question marks to compare to.
;------------------------------------------------------- ; 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
After that we will add compare match. this will match a directory entry to our generated match name.
;------------------------------------------------------- ; 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
Then we will finish it off with a couple of variables.
diroffset: dw 0 dircurrent: dw 0
;======================================================= ; 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 ;------------------------------------------------------- ; 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
In demoos.asm we will add a new command string
dirstr: db "DIR", 0
Now we will add a veriable to keep track of the sector we are on in the directory.
seccount: dw 0
Then we will add the directory list code
;------------------------------------------------------- ; Directory List ; Input: ; None ; Output: ; None ;------------------------------------------------------- dir: push ax ; Save registers push bx push cx push dx push es call vol ; Print volume information call crlf call dskmatchblank ; create a blank match string call dskfirstentry ; get first entry jc dirdone dirnext: mov cx, 0000Bh ; Name length dirname: mov ah, 002h ; Service stdout mov dl, [es:bx] ; Get filename character inc bx ; Point to next character int 021h ; Print the character we got loop dirname ; Get next character, dec cx call crlf call dsknextentry jc dirdone loop dirnext dirdone: pop es ; Restore registers pop dx pop cx pop bx pop ax ret
Add the code to check if it is the DIR command to main.
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:
Then we will replace:
version: db 00Dh, 00Ah, "DEMO OS V0.13" ; Message to be printed db 00Dh, 00Ah, '$'
with
version: db 00Dh, 00Ah, "DEMO OS V0.14" ; Message to be printed db 00Dh, 00Ah, '$'
;======================================================= ; 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 driveletter ; 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: 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 driveletter 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 bx push cx push dx push es call vol ; Print volume information call crlf call dskmatchblank ; create a blank match string call dskfirstentry ; get first entry jc dirdone dirnext: mov cx, 0000Bh ; Name length dirname: mov ah, 002h ; Service stdout mov dl, [es:bx] ; Get filename character inc bx ; Point to next character int 021h ; Print the character we got loop dirname ; Get next character, dec cx call crlf call dsknextentry jc dirdone loop dirnext dirdone: pop es ; Restore registers pop dx pop cx pop bx pop ax ret %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.14" ; 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 mcbfirst: dw 0 seccount: dw 0
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.14 A:\> dir Volume in drive A is DEMO OS DSK Volume Serial Number is 2394-C726 DEMOOS BIN A:\>Disk: VOL | Index | Disk: COM Loader