This article provided an overview of the RDISK, remote disk protocol for the MIC80. The RDISK protocol allows the MIC80 to mount a one megabyte file located on a remote host as a CPM2.2 disk. RDISK uses the UDP network protocol to transfer data between a client and host. The server side of the RDISK protocol is currently implemented in the Z80Emu program. The Z80Emu program can also be used to, create disk image files and manage the files contained in a disk image. The Z80Emu program can manage the files in a disk image either by mounting the disk under CPM or by using the built in file manager functionality.
I developed the RDISK protocol to replace the TFTP server I was using to backup and restore my MIC80 project files. While developing the MIC80 EBIOS I often use Turbo Pascal to prototype and test EBIOS code before converting it to assembler code. The MIC80 BIOS supports using the 512K byte RAM on the Zilog development board as a RAM disk. I use the RAM disk to hold my test programs and source code while developing on the MIC80 system. The RAM disk will survive a system reset but not a power down of the system. To solve the power down problem I developed two simple TFTP client programs in Turbo Pascal called "PUT.COM" and "GET.COM". At the start of a development session I would reformat the MIC80 RAM disk then using the TFTP program "GET.COM" copy my last saved files to the MIC80 RAM disk. At the end of the development session I used the TFTP program "PUT.COM" to save the RAM disk contents back to my home computer. The required programs to start the MIC80 network stack and the TFTP client programs and source code can be found on the ROM disk image supplied with the MIC80 project files.
Compared to using the serial port to transfer files between my home computer and the MIC80 the TFTP support was a major improvement . The biggest problem with using TFTP as my backup system was the need to constantly backup my work incase the MIC80 code I was developing or a power bump trashed the RAM disk. After losing a few source code files while developing on the MIC80 I decided there must be a better way and developed the RDISK protocol.
RDISK uses UDP to transfer disk sectors between a server hosted disk image and the MIC80. The RDISK protocol requires that a physical disk sector fit into a single UDP packet. If the disk physical sector size is greater then the size of an Ethernet packet the UDP packet is sent as a fragmented IP packet. The current version of the RDISK protocol uses a one megabyte disk image with 2048 byte physical sectors and 2 physical sectors per track. This gives us 32, 128 byte CPM logical sectors per track and 512 tracks with the first 2 tracks reserved. This is the same disk format used by the MIC80 CPM "A" disk ROM disk and the Z80Emu disk file images.
MIC80 RDISK API:
The RDISK client code is included in the MIC80 EBIOS and can be accessed the same way as the EBIOS IP stack functions via the EBIOS jump table. The RDISK API allows a user program to mount and un mount a server disk image to the CPM disk device "C". A user program must not un mount a remote disk while the disk device is the currently selected CPM disk device. Instead a CPM program should call the CPM BDOS select disk function to select a disk device other the the "C" disk. When a disk is mounted a user program should call the CPM BDOS disk reset function to allow CPM to pickup changes to the disk in the drive. The ROM "A" disk image included with the MIC80 project files contains two CPM programs; MOUNT.COM to mount a disk and UNMOUNT.com to un-mount a CPM remote disk. The pascal source code is also included on the "A" disk image. The IP stack must be active for the RDISK API to function.
The following is a list of the EBIOS function to support the RDISK client protocol;
RDSK_GetError (01085Fh)
This function returns the last 16 bit RDISK error code.
On Exit:
HL (16 bits) = last remote RDISK error code.
RDSK_GetErrStr (010864h)
This function returns a copy of the last RDISK error string.
On Entry:
HL (24 bit) = address of length prefixed string to return error message. First byte is the string length, remaining bytes are string data up to 63 bytes.
RDSK_IsMount (010869h)
This function checks if a remote disk is mounted.
On Exit:
A = 0 if not mounted,
A = disk id mounted (low byte of disk id). CPM only allows for 15 disk drives so no need to return upper byte.
RDSK_Mount (01086Eh)
This function requests a disk be mounted.
On Entry:
A = 0 disk is mounted RW, A != 0 disk is mounted RO.
B = UDP socket handle for mount.
C = disk id to mount (1 - A, 2 - B...)
DE = address of 6 byte UDP address (2 byte port, 4 byte ip address).
HL = address of a remote disk name, 1 byte length and up to 63 byte name.
On Exit:
A = 0 command completed, A != 0 error occurred .
RDSK_Unmount (010873h)
This function requests a disk be unmounted.
On Exit:
A = 0 command completed, A != 0 error occurred .
The RDISK Protocol:
The RDISK protocol is client side driven. The client sends a request to the server on port 999 and the server will either respond with a response packet relative to the last client request or an error response packet. All response packets from the server start with a two byte response code followed by a two byte request identifier. If the two byte response code is zero the response packet from the server is valid. If the response code is not zero the server returned an error packet. All error response from the server are returned using the same packet format. When a client sends a request packet to an RDISK server the request packet contains a two byte request identifier. When the server sends a response packet to the client the server will copy the request identifier from the client request and return it in the response packet. A client should increment the request identifier when a new request is made. When a client retries a request because it has not received a response from the server in the timeout interval the same request identifier must be used by the client. The client ignores any response packets sent by the server that do not have the request identifier of the last request sent.
RDISK Error Responses:
The RDISK protocol use a string structure to return error messages as well as for the name for a disk image to mounted. A string in the RDISK protocol is 64 bytes in size. The first byte is the number of characters in the string followed by up to 63 one byte characters.
In pascal the error response is;
TDataString = string[63]; TErrorRespPack = packed recordRespCode: word;
RequestId: word;
ErrorMsg: TDataString;
end;
In assembler the error response is;
- RespCode: DW
RequestId DW
ErrorMsgLen: DB
ErrorMsg: DS 63
The RDISK protocol has three distinct states; 1 session allocation, 2 data transfer, 3 session termination.
1 Session Allocation:
An RDISK session is initiated by the MIC80 client requesting use of a disk image by sending a mount request to the RDISK server on port 999. The server checks to see if the disk image is already in use by another client. If the disk image is already in use the server will send an error response indicating the disk is already in use. If the requested disk is in use as a read only disk and the second request to use the disk image is also read only the server will allow the request. If the server allows the request to start a session the server will return a response containing information about the disk image layout and the session information. The response is sent to the same client UDP port number as the client used to send the request to the server.
In pascal the client mount request packet is;
TMountReqPack = packed recordCmd:word;
RequestId:word;
Flags:word;
DiskId:word;
DiskName:TDataString;
end;
The two byte "Cmd" field is set to 1 indicating the client is requesting is a mount or session start request.
The two byte "RequestId" is set to the next sequential request identifier value to indicate a new command request.
The two byte "Flag" field is uses bit 0 to indicate is the session should be read only. A one in bit zero indicates read only access and a zero in bit zero indicates read/write access.
The two byte "DiskId" field contains the CPM disk index (A = 1, B = 2,....) that will be used to access the disk under CPM on the MIC80.
The 64 byte "DiskName" field contains the name of the disk image without file path or fie extension the client wishes to use.
If the RDISK server is unable to complete the client request an error packet is returned (see above). If the server allows the client request to start a new session the server will return a response containing t he following structure;
TMountRespPack = packed recordRespCode:word;
RequestId:word;
SessionId:longword;
BlockSize:word;
DiskTracks:word;
TrackLogSectors:word;
end;
The two byte "RespCode" field must be zero if the server allowed the request, it will be non-zero if the request failed.
The two byte "RequestId" is a copy of the request identifier sent to the server.
The four byte "SessionId" field is a server generated value that must be used in subsequent client requests to the server.
The two byte "BlockSize" field contains the size in bytes of a physical sector for the disk image. (currently always 2048)
The two byte " DiskTracks" field contains the number of tracks used by the disk image. (currently alway 512)
The two byte " TrackLogSectors" field contains the number of CPM logical sectors per track used by the disk image. (currently alway 32)
If the server allowed the request the client and server have started a session. At this point the client can begin to transfer physical sectors with the server. The current MIC80 EBIOS only supports one remote disk mounted at a time. The MIC80 remote disk will always be mapped to the CPM disk "C". All further comunications from the client to the server should use the same client UDP and IP address.
2 Data Transfer:
Once a session has been established between the MIC80 client and the RDISK server the MIC80 client can read and write the disk image one physical sector at a time. The MIC80 BIOS will block and de-block the physical sectors in to the CPM 128 byte logical sectors . The sector buffering from physical disk sectors to logical sectors is handled the same as any CPM disk device would be. To read a physical sector from the RDISK server the client sends the following request packet to the server;
In pascal the read request packet has the following structure;
TReadReqPack = packed recordCmd:word;
RequestId:word;
SessionId:longword;
DiskId:word;
Track:word;
LogicalSector:word;
end;
The two byte "Cmd" field is set to 3 indicating the client is requesting a sector read request.
The two byte "RequestId" is set to the next sequential request identifier value to indicate a new command request.
The four byte "SessionId" field is the server generated value returned in the last mount response from the server.
The two byte "DiskId" field contains the CPM disk index (A = 1, B = 2,....) that will is used to access the disk under CPM.
The two byte " Track" field contains the track number of the physical sector to be read.
The two byte " LogicalSector" field contains any logical sector number in the desired physical secor.
If the RDISK server is unable to complete the client request an error packet is returned (see above). If the server allows the client request to read a physical sector the server will return a response containing t he following structure;
In pascal the read response packet has the following structure;
TReadRespPack = packed recordRespCode:word;
RequestId:word;
Data:TDataBlock
end;
The two byte "RespCode" field must be zero if the server allowed the request, it will be non-zero if the request failed.
The two byte "RequestId" is a copy of the request identifier sent to the server.
The remaning data in the response packet contains the data for the specified physical sector and will alway be the size in bytes returned in the server mount response "BlockSize" field.
If the disk image session was opened in read write mode the client can write a physical sector to the disk image.To write a physical sector to the RDISK server disk image the client sends the following request packet to the server;
In pascal the read request packet has the following structure;
TWriteReqPack = packed recordCmd: word;
RequestId: word;
SessionId: longword;
DiskId: word;
Track: word;
LogicalSector: word;
Data: TDataBlock
end;
The two byte "Cmd" field is set to 4 indicating the client is requesting a sector write request.
The two byte "RequestId" is set to the next sequential request identifier value to indicate a new command request.
The four byte "SessionId" field is the server generated value returned in the last mount response from the server.
The two byte "DiskId" field contains the CPM disk index (A = 1, B = 2,....) that will is used to access the disk under CPM.
The two byte " Track" field contains the track number of the physical sector to be write.
The two byte " LogicalSector" field contains any track logical sector number contained in the physical secor being written.
The remaning data in the request packet contains the data for the specified physical sector and will alway be the size in bytes returned in the server mount response "BlockSize" field.
If the RDISK server is unable to complete the client request an error packet is returned (see above). If the server allows the client request to write a physical sector the server will return a response containing t he following structure;
In pascal the write sector response packet has the following structure;
TWriteRespPack = packed recordRespCode:word;
RequestId:word;
end;
The two byte "RespCode" field must be zero if the server allowed the request, it will be non-zero if the request failed.
The two byte "RequestId" is a copy of the request identifier sent to the server.
3 Session Termination:
When the client has finished its use of the disk image or wants to mount a different disk image to a CPM disk device already mounted the client must send an unmount request. As an option the server may allow a client to mount a different image to a mounted CPM disk device by automatically unmounting the current disk image when a request is made to mount a disk image to a mounted CPM disk device. To request a server close a session the client sends the following unmount request packet to the server;
In pascal the unmount request packet has the following structure;
TUnmountReqPack = packed record
Cmd:word;
RequestId:word;
SessionId:longword;
DiskId:word;
end;
The two byte "Cmd" field is set to 2 indicating the client is requesting is a close session request.
The two byte "RequestId" is set to the next sequential request identifier value to indicate a new command request.
The four byte "SessionId" field is the server generated value returned in the last mount response from the server.
The two byte "DiskId" field contains the CPM disk index (A = 1, B = 2,....) that will is used to access the disk under CPM.
If the RDISK server is unable to complete the client request an error packet is returned (see above). If the server allows the client request to close the session the server will return a response containing t he following structure;
TUnmountRespPack = packed record
RespCode:word;
RequestId:word;
end;
The two byte "RespCode" field must be zero if the server allowed the request, it will be non-zero if the request failed.
The two byte "RequestId" is a copy of the request identifier sent to the server.