Coding a DOS node

UniDOS is easily expandable by adding DOS nodes; the page bellow is explaining how you can create your own DOS node to provide support for new boards or simply to create virtual drives.

UniDOS is handling paths, errors and AMSDOS compatibility on its own, the DOS node just has to care about low level routines to actually access files, which considerably reduces the amount of work required to integrate all the peripherals management inside the same DOS.

Structure of a DOS node ROM

A DOS node is a standard ROM; you can refer to documentation explaining how to create ROMs for CPC for more details.

A DOS node can be either a background ROM (type 1) or an extension ROM (type 2), in which a RSX named “DOS Node” shall be declared at address &C009. In addition, this RSX shall be followed by 31 RSX CTRL+0 (&80) which are corresponding to entry point for the various routines a DOS node can implement.

Here is how a DOS node ROM header should look like1) :

        Org &c000

ROMType db 2    ; 1 (background ROM) or 2 (extension ROM)

ROMMark db 1    ; Usage is that a ROM version
ROMVer  db 4    ; is displayed in the form M.VR
ROMRev  db 1    ; (1.41 in this case)

ROMRSX  dw RSXTable

        jp InitROM
        jp DOSNode_Init
        jp DOSNode_CheckDrive
        jp DOSNode_GetStatus
        jp DOSNode_GetName
        jp DOSNode_GetDesc
        jp DOSNode_GetFreeSpace
        jp DOSNode_InOpenStream
        jp DOSNode_InReadStream
        jp DOSNode_InCloseStream
        jp DOSNode_InSeekStream
        jp DOSNode_OutOpenStream
        jp DOSNode_OutWriteStream
        jp DOSNode_OutCloseStream
        jp DOSNode_OutSeekStream
        jp DOSNode_Examine
        jp DOSNode_ExamineNext
        jp DOSNode_Rename
        jp DOSNode_Delete
        jp DOSNode_CreateDir
        jp DOSNode_SetProtection
        jp DOSNode_Format
        jp DOSNode_SetFileDate
        jp DOSNode_Void
        jp DOSNode_Void
        jp DOSNode_Void
        jp DOSNode_ReadRTC
        jp DOSNode_WriteRTC
        jp DOSNode_OpenNVRAM
        jp DOSNode_CloseNVRAM
        jp DOSNode_ReadNVRAM
        jp DOSNode_WriteNVRAM
        jp DOSNode_SeekNVRAM
        ; You can add your personal RSX here

RSXTable
        str "ROM  name"
        str "DOS Node"
Repeat  31
        db &80
REnd
        ; You can add your personal RSX here
        db 0 ; End of RSX table

        ; Actual ROM code begins here
InitROM cp a
        ret

Pratically, you will create a type 1 ROM if you need to allocate some additional memory for the DOS node2). If you do not need additional memory, a type 2 ROM is enough3).

Each DOS node then provides 32 routines (6 are not allocated yet). You are free to implement totally or partially these routines depending on your requirements and on the capabilities of the board your DOS node is managing.

In anycase, when a routine is implemented, it shall returns with the Carry set; a Carry at 0 indicates to UniDOS that the routine is not implemented.

Don't hesitate to refer to the source code of the DOS node “Zero” which is quite trivial (it is only implementing a subset of the routines to handle reading) or the “Albireo” one for an full features example implementing almost all routines.

Hereafter is a description of the input and output conditions of all the routines which can be implemented and a memory map of the area they are allowed to use. It is mandatory that you roughly follow the output conditions of each routine you decide to implement.

The list of the error codes you can use as well as the value of various other constants is provided in the file “UniDOS.i” available from UniDOS source code archive. You will also find there the files “SystemCalls.i” and “SystemData.i” which are containing other useful definitions (these files are designed for RASM but should be quite easy to adapt to any other assembler).

In all the descriptions bellow:

  • A normalized file name has the form NNNNNNNNEEE where:
    • NNNNNNNN is the 8 characters name (padded with spaces when shorter).
    • EEE is the 3 characters extensions (padded with spaces when shorter).
  • A normalized path is a structure which contains:
    • One byte n with the path depth (0 if the path is empty, 6 maximum).
    • A succession of n normalized names.

Routines handling the DOS node

It is a set of routines that UniDOS will use to initalize the DOS node and to deal with the additional drives it provides. It is recommended to implement all of them to have a better integration of your DOS node to the system.

Each DOS node do have access to two bytes at offset +&18E4). The contents of these two bytes is saved then restored between two calls to your DOS node routines and is then always valid while your ROM code is executed. In addition to these global bytes, some routines also have access to dedicated memory areas for their internal mangement.

DOSNode_Init

This routine is called during UniDOS initialization phase while detecting existing DOS nodes. It is the good place to detect the board your DOS node have to support and respond accordingly.

It is also the place where you have to tell UniDOS if your DOS node if provided the non volatile memory feature or not.

;
; Initialize the DOS node
;
; Input  - A = initialization options (see input flags Init_*)
; Output - If Carry = 1 then the node was initialized
;               A = information about existing features (see output flags Init_*)
;           If Carry = 0 then the node could not be initialized
;               All registers are preserved
; Altered - AF

DOSNode_CheckDrive

This routine will be called everytime UniDOS want to determine in a drive name exists or not within a node. The first DOS node with a positive reply will be assign to the drive management.

Each DOS node can have up to 8 drives which have to be numbered from 0 to 7.

;
; Check if a drive name is handled by the DOS node
;
; Input   - HL = pointer to the drive name
;                (bit 7 could be set on some character and must be ignored)
;           C = length of the drive name
; Output - If Carry = 1 a drive was found in the node
;                A = physical drive number
;           If Carry = 0 the drive was not found in the node
;               All registers are preserved
; Altered - AF 

DOSNode_GetStatus

This routine is used by UniDOS to determine the capabilities of a drive. Depending on the returned value, UniDOS will handle some further operations differently. For instance, si a drive is of type “stream” then UniDOS will not handle BAK and $$$ files.

;
; Return a drive status
;
; Input   - A = drive number
; Output  - If Carry = 1 a status was returned
;                A = status of the drive (see flags flags Media_*)
;                    Bit0 = 1 if a media is inserted in the drive
;                    Bit1 = 1 if the media support directories
;                    Bit2 = 1 if the media is write protected
;                    Bit3 = 1 if the media is removable
;                    Bit4 = 1 if the media is a stream (linear read/write only)
;                        $$$ and BAK handling files is then disabled
;                    Other bits are unused
;           If Carry = 0 then the drive is unknown and no status was returned
;               All registers are preserved
; Alteted - AF  

DOSNode_GetName

This routine is the opposite of DOSNode_CheckDrive. It provides to UniDOS the way to retrieve a drive name from its identifier.

;
; Return the name corresponding to a physical drive
;
; Input   - A = drive number
;           DE = address of a buffer of 8 bytes when to store the name
; Output  - If Carry = 1 the name was found
;                DE points to the first character after the end of the copied string
;                (the string is stored with the bit 7 of its last character set)
;          If Carry = 0 not description was found and the buffer is left unchanged.
;               All registers are preserved
; Altered - AF,DE  

DOSNode_GetDesc

This routine should return a textual description of the provided drive number.

;
; Return the description corresponding to a physical drive
;
; Input  - A = drive number
;          DE = addess of the 32 bytes buffer where to store the description
; Ouput  - If Carry = 1 a description was found
;                DE points to the first character after the end of the copied string
;                (the string is stored with the bit 7 of its last character set)
;          If Carry = 0 not description was found and the buffer is left unchanged.
;               All registers are preserved
; Altered - AF,DE 

DOSNode_GetFreeSpace

UniDOS call this routines when it wants to know about the free space on a media.

;
; Return the free space on a physical drive
;
; Input   - A = drive number
; Output  - If Carry = 1 the routine is supported for this drive
;                If Z then the free space could be obtained
;                    BCDE = free space in kilo-bytes
;                If NZ then an error occured
;                    A = error code
;           If Carry = 0 then the routines is invalid for this drive
;               All registers are preserved
; Altered - AF,BC,DE   

Routines handling the input file

Because of the operating system, only one file at once can be opened as output, which makes the implementation on these routines quite simple.

The DOS node can use 80 bytes at offset +&3305) to handle this file.

DOSNode_InOpenStream

;
; Open the input stream
;
; Input   - A = drive number
;           HL = pointer to the normalized name
;               note: if the drive is of type stream then this name can
;               contain 11x&ff in case where no file name was provided
;               by the user (when he uses the anonymous reference ".");
;               the routine should then just open the just encountered
;               file on the stream and can optionally update the name
;               if it could be obtained from the stream itself
;          DE = pointer the normalized path
;          The pointed memory is always located in the current ROM/RAM space area
; Ouput  - If Carry = 1 the routine is supported for the provided drive
;                If Z then a file was opened
;                If NZ then no file could be opened
;                    A = error code
;               In any case, the routine might truncate the provided normalized path to match the nearest parent
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_InReadStream

;
; Read from the input stream
;
; Input  - A = drive number
;           HL = address where to stored the read data
;           DE = number of bytes to read
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then data could be read
;                    DE = number of bytes read
;                If NZ then a error occured
;                    A = error code
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF,DE

DOSNode_InCloseStream

;
; Close the input stream
;
; Input   - A = drive number
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the stream was properly closed
;                If NZ then the stream was closed with an error
;                    A = error code
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_InSeekStream

;
; Change the position into the input stream
;
; Input   - A = drive number
;           DEHL = new position in the input stream
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the new position could be reached
;                If NZ then an error occured
;                    A = error code
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

Routines handling the ouput file

As requested by the operating system (and like AMSDOS), only one file at once can be opened as output, which makes the implementation on these routines quite simple.

The DOS nodes can use 80 bytes at offset +&3806) to handle this file.

DOSNode_OutOpenStream

;
; Open the output stream
;
; Input   - A = drive number
;           HL = pointer to the normalized name
;           DE = pointer to the normalized path
;          The pointed memory is always located in the current ROM/RAM space area
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then a file was created
;                If NZ then no file was created
;                    A = error code
;               In any case, the routine might truncate the provided normalized path to match the nearest parent
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_OutWriteStream

;
; Write into the ouput stream
;
; Input   - A = drive number
;           HL = address where are located the data to write
;           DE = number of bytes to write
; Sortie - If Carry = 1 the routine is supported for the provided drive
;                If Z then data could be written
;                    DE = nomber of written bytes
;                If NZ then an error occured
;                    A = error code
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF,DE  

DOSNode_OutCloseStream

;
; Close the output stream
;
; Input   - A = drive number
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the stream was properly closed
;                If NZ then the stream was closed with an error
;                    A = error code
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_OutSeekStream

;
; Change the position into the output stream
;
; Input   - A = drive number
;           DEHL = new position in the ouput stream
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the new position could be reached
;                If NZ then an error occured
;                    A = error code
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

Routines analyzing files and directories

Only one analyze at once can be started.

The node can use 80 bytes at offset +&3D07) to manage files and directory to examine.

DOSNode_Examine

;
; Check that a file or directory exists and return associated information
;
; Input   - A = drive number
;           HL = pointer to the normalized name
;               If HL = 0 then it is the contents of the directory which has to be analyzed
;               and ExamineNext will be called next to retrieve each entry
;           DE = pointer to the normalized path
;           IX = buffer where to store last modification time and date
;           The pointed memory is always located in the current ROM/RAM space area
; Output  - If Carry = 1 the routine is supported for the provided drive
;
;                If HL was not 0 in input
;                    If Z then the file or directory exists
;                        A = protection bits of the file
;                            Bit 0 - Read-only
;                            Bit 1 - Hidden
;                            Bit 2 - System
;                            Bit 4 = Directory
;                            Bit 5 = Archived
;                        BCDE = Length of the file
;                        IX = buffer where last modification time and date of the entry were stored
;                            One 16-bit word with year (1978..9999)
;                            One byte with number of month (1..12)
;                            One byte with number of day in the month (1..28,29,30 or 31)
;                            One byte with hours (0..23)
;                            One byte with minutes (0..59)
;                            One byte with seconds (0..59)
;                    If NZ then the file or directory was not found
;                        A = error code
;
;                If HL was 0 in input
;                    If Z then the directory is ready to be examined through ExamineNext
;                    If NZ then an error occurred
;                        A = error code
;
;                In any case, the routine might truncate the provided normalized path to match the nearest parent
;
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_ExamineNext

;
; Get the next entry from a directory being examined
;
; Input   - A = drive number
;           HL = pointer to the memory where to store the normalize name
;           IX = buffer where to store last modification time and date
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then an entry was found
;                    HL = pointer to the memory updated with the found normalized name
;                        A = protection bits of the file
;                            Bit 0 - Read-only
;                            Bit 1 - Hidden
;                            Bit 2 - System
;                            Bit 4 = Directory
;                            Bit 5 = Archived
;                        BCDE = Length of the file
;                        IX = buffer where last modification time and date of the entry were stored
;                            One 16-bit word with year (1978..9999)
;                            One byte with number of month (1..12)
;                            One byte with number of day in the month (1..28,29,30 or 31)
;                            One byte with hours (0..23)
;                            One byte with minutes (0..59)
;                            One byte with seconds (0..59)
;                If NZ then an error occurred
;                    A = error code (dsk_err_file_not_found indicates that all entries were examined)
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

Routines manipulating files

It is a set of routines which are dedicated to rename, delete and modify files and directories.

DOSNode_Rename

;
; Rename a file or a directory
;
; Input   - A = drive number
;           HL = pointer to the normalized name
;           DE = pointer to the normalized path
;           IX = pointer to the new normalized name
;           BC = pointer to the new normalized path
;           The pointed memory is always located in the current ROM/RAM space area
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the file or directory was renamed
;                If NZ then the file or directory could not be renamed
;                    A = error code
;                In any case, the routine might truncate the provided normalized path to match the nearest parent
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_Delete

;
; Delete a file or a directory
;
; Input  - A = drive number
;           HL = pointer to the normalized name
;           DE = pointer to the normalized path
;           The pointed memory is always located in the current ROM/RAM space area
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the file or directory was deleted
;                If NZ then the file or directory could not be deleted
;                    A = error code
;                In any case, the routine might truncate the provided normalized path to match the nearest parent
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_CreateDir

;
; Create a directory
;
; Input  - A = drive number
;           HL = pointer to the normalized name
;           DE = pointer to the normalized path
;           The pointed memory is always located in the current ROM/RAM space area
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the directory was created
;                If NZ then the directory could not be created
;                    A = error code
;                In any case, the routine might truncate the provided normalized path to match the nearest parent
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_SetProtection

;
; Change the protection bits of a file
;
; Input  - A = drive number
;           HL = pointer to the normalized name
;           DE = pointer to the normalized path
;           B = Protections to modify
;           C = New protections
;                Bit 0 - Read-only
;                Bit 1 - Hidden
;                Bit 5 = Archived
;           Other bits are ignored
;           The pointed memory is always located in the current ROM/RAM space area
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the protections were modified
;                If NZ then the protections could not be modified
;                    A = error code
;                In any case, the routine might truncate the provided normalized path to match the nearest parent
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

DOSNode_SetFileDate

;
; Change last modification time and date of a file or a directory
;
; Input  - A = drive number
;           HL = pointer to the normalized name
;           DE = pointer to the normalized path
;           IX = buffer where last modification time and date to use for the entry are stored
;               One 16-bit word with year (1978..9999)
;               One byte with number of month (1..12)
;               One byte with number of day in the month (1..28,29,30 or 31)
;               One byte with hours (0..23)
;               One byte with minutes (0..59)
;               One byte with seconds (0..59)
;           The pointed memory is always located in the current ROM/RAM space area
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the time and date were modified
;                If NZ then the time and date could not be modified
;                    A = error code
;                In any case, the routine might truncate the provided normalized path to match the nearest parent
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

Format routine

DOSNode_Format

;
; Format a drive
;
; Input  - A = drive number
; Output  - If Carry = 1 the routine is supported for the provided drive
;                If Z then the drive was formatted
;                If NZ then format failed
;                    A = error code
;           If Carry = 0 then the routine is invalid for the provided drive
;               All registers are preserved
; Altered - AF

Routines de gestion de l'horloge

DOSNode_ReadRTC

;
; Read the contents of the real time clock
;
; Input   - IX = buffer where to store current time and date (7 octets)
; Output  - If Carry = 1 the the DOS node handles a relax time clock
;                IX = buffer where current time and date were stored
;                    One 16-bit word with year (1978..9999)
;                    One byte with number of the month (1..12)
;                    One byte with the number of the day in the month (1..28,29,30 or 31)
;                    One byte with hours (0..23)
;                    One byte with minutes (0..59)
;                    One byte with seconds (0..59)
;                 A = number of the day in the week (1..7, from Monday to Sunday)
;           If Carry = 0 the the DOS node do not handle a real time clock
;               All registers are preserved
; Altered - AF

DOSNode_WriteRTC

;
; Write into the real time clock
;
; Input   - IX = buffer containing time and date to write into real time clock (7 bytes)
;               One 16-bit word with year (1978..9999)
;               One byte with number of the month (1..12)
;               One byte with the number of the day in the month (1..28,29,30 or 31)
;               One byte with hours (0..23)
;               One byte with minutes (0..59)
;               One byte with seconds (0..59)
; Output  - If Carry = 1 then the DOS node handles a real time clock
;               If Z then the contents of the real time clock was updated
;               If NZ then an error occurred
;                   A = error code
;          If Carry = 1 then the DOS node do not handle a real time clock
;               All registers are preserved
; Altered - AF

Routines handling non volatile memory

These routines have to be implemented only if your DOS node supports the non volatiile memory feature. If it is the case, it is mandatory to implement the entire set of the routines bellow.

The non volatile memory shall be at least of 2KiB.

The DOS nodes can use 6 bytes at offset +&4F08) to handle non volatile memory.

DOSNode_OpenNVRAM

;
; Open the non volatile memory
;
; Input  - C = opening mode
;                If 0 then the non volatile memory shall be opened with its current contents
;                If 1 then the non volatile memory shall be opened with its contents reset
; Output  - If Carry = 1 then the DOS node provides non volatile memory
;                If Z then the non volatile memory has been opened and is ready for usage
;                If NZ then a error occured
;                    A = error code
;           If Carry = 0 then the DOS node do not provide non volatile memory
;               All registers are preserved
; Altered - AF 

DOSNode_CloseNVRAM

;
; Close and update the non volatile memory memory contents
;
; Input  - None
; Output  - If Carry = 1 then the DOS node provides non volatile memory
;                If Z then the non volatile memory was properly updated
;                If NZ then an error occured
;           If Carry = 0 then the DOS node do not provide non volatile memory
;               All registers are preserved
; Altered - AF 

DOSNode_ReadNVRAM

;
; Read data from the non volatile memory
;
; Input  - HL = address where to write read data
;           DE = number of bytes to read
; Output  - If Carry = 1 then the DOS node provides non volatile memory
;                If Z then data were read
;                    DE = number of bytes read
;                If NZ then a error occured
;                    A = error code
;           If Carry = 0 then the DOS node do not provide non volatile memory
;               All registers are preserved
; Altered - AF,DE 

DOSNode_WriteNVRAM

;
; Write data to the non volatile memory
;
; Input  - HL = address where a located data to write
;           DE = number of bytes to write
; Output  - If Carry = 1 then the DOS node provides non volatile memory
;                If Z then data were written
;                    DE = number of bytes written
;                If NZ then a error occured
;                    A = error code
;           If Carry = 0 then the DOS node do not provide non volatile memory
;               All registers are preserved
; Altered - AF,DE 

DOSNode_SeekNVRAM

;
; Change the position in the non volatile memory
;
; Input  - DEHL = new position
; Output  - If Carry = 1 then the DOS node provides non volatile memory
;                If Z then the new position was reached
;                If NZ then a error occured
;                    A = error code
;           If Carry = 0 then the DOS node do not provide non volatile memory
;               All registers are preserved
; Altered - AF,DE 
1)
RASM formalism
2)
but in this case, your ROM will have to be installed in a slot lower than 16
3)
which makes it possible to install it beyond slot 15
4) , 5) , 6) , 7) , 8)
relative to the address stored at _DOS_ROM_BASE_ADDRESS