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.
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:
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.
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
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
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
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
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
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
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.
; ; 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
; ; 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
; ; 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
; ; 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
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.
; ; 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
; ; 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
; ; 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
; ; 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
Only one analyze at once can be started.
The node can use 80 bytes at offset +&3D07) to manage files and directory to 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
; ; 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
It is a set of routines which are dedicated to rename, delete and modify files and directories.
; ; 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
; ; 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
; ; 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
; ; 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
; ; 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 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
; ; 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
; ; 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
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.
; ; 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
; ; 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
; ; 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
; ; 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
; ; 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