Disclaimer: Some of this information (in particular, any relating to the PCW) has been obtained by:
FID files (Field Installable Device Drivers) are a system used in Amstrad CP/M to load drivers for hardware without having to change the BIOS itself. There are four systems which use FID files:
When the system is booted, FIDs are loaded from the boot disc into memory bank 0. This happens when the BIOS and BDOS are present, but the CCP has not yet been started.
A FID file has to be in Digital Research Paged Relocatable format. The exact layout of this file format is described below, but since LINK.COM can generate it, details should not be necessary.
To create a PRL file using LINK.COM, add the [OP]
switch:
LINK FILENAME[OP]
REN FILENAME.FID=FILENAME.PRL
After the PRL header generated by LINK, there is a special FID header. This is 32 bytes long:
xx00: JP FID_EMS xx03: DB 'MACHINE ' ;Computer name, see below xx0B: DB 'FID' ;File type, see below xx0E: DW version ;FID version number, specific to FID xx10: DW checksum ;see below xx12: DB low bound xx13: DB high bound xx14: DS 12 ;ReservedThe meaning of these fields is as follows:
Locomotive recommend that anything which you want application programs to be able to access easily should be put immediately after the header - for example, service routines or internal variables.
FID files are not allowed to make calls to the BIOS or BDOS; they should only make the documented SuperVisor Calls.
The SuperVisor Calls are values set in the FID when it loads, by the host environment. They correspond to addresses of routines or variables within CP/M or LocoScript.
If you are writing in RMAC or M80, then the SVCs are defined by the following statements at the start of the source file (before any code).
On the PCW, the SVCs are:
SVC_D_HOOK EQU $+0FE00h ;SVC 0 SVC_C_HOOK EQU $+0FE01h ;SVC 1 SVC_D_CHANGED EQU $+0FE02h ;SVC 2 SVC_D_SETUP EQU $+0FE03h ;SVC 3
On the Spectrum +3, there are rather more of them:
SVC_BANK_05 EQU $+0FE00h ;SVC 0 SVC_BANK_68 EQU $+0FE01h ;SVC 1 SVC_CATCHUP EQU $+0FE02h ;etc. SVC_SCB EQU $+0FE03h SVC_C_HOOK EQU $+0FE04h SVC_D_HOOK EQU $+0FE05h SVC_D_CHANGED EQU $+0FE06h SVC_ALLOCATE EQU $+0FE07h SVC_MAX_ALLOCATE EQU $+0FE08h SVC_DEALLOCATE EQU $+0FE09h SVC_C_FIND EQU $+0FE0Ah
My PCW16 implementation currently supports:
SVC_MEMLIST EQU $+0FE00h ;SVC 0 SVC_CPM_ADDR EQU $+0FE01h ;SVC 1 SVC_SCB EQU $+0FE03h ;SVC 3 CP/M Plus only SVC_C_HOOK EQU $+0FE04h ;SVC 4 SVC_D_HOOK EQU $+0FE05h ;SVC 5 SVC_D_CHANGED EQU $+0FE06h ;SVC 6 SVC_C_FIND EQU $+0FE0Ah ;SVC 10The SVCs must only be used as 16-bit words.
Add a disc drive to the system.
This routine must only be called from the FID_EMS routine.
Entered with:
Returns:
Add a character device (printer, screen etc) to the system.
This routine must only be called from the FID_EMS routine.
Entered with:
Returns:
Signal that a disc may have been changed. It can be called during an interrupt.
Entered with B=drive; returns with all registers preserved.
Call to DD SETUP.
This call is used by FIB files to set up disc timing parameters. It is entered with:
Returns with AF, BC, DE and HL corrupt.
Address of list of memory banks used by CP/M.
The list is formed of bytes M0-M9. The memory bank arrangements are:
Although CP/M 2 does not support multiple banks, the PCW16 BIOS does in fact implement the SELMEM call, and these are the memory banks it uses. FID files are loaded into Bank 2; the TPA is Bank 1, and Bank 0 contains a copy of the BDOS and CCP.
Convert a CP/M address to a Rosanne bank and offset.
Entered with:
This routine should be called if you think it likely that some interrupts have been missed.
Entered with:
In the PCW16 implementation, this is 0 if the FID is being loaded into a CP/M 2 system. It is important to check that SVC_SCB is nonzero before using the SCB.
This routine must only be called from the FID_EMS routine.
Entered with:
This routine must only be called from the FID_EMS routine. Returns:
This routine must only be called from the FID_EMS routine. It can be used to deallocate memory allocated when the FID was loaded.
Entered with:
This routine must only be called from the FID_EMS routine.
Entered with:
So, what actually goes in the FID?
This is the one piece of code which the FID must provide. It is called immediately after the FID has loaded. It is entered with:
The FID will return with the carry flag set if it wants to stay, and reset if it does not. If it is returning with carry reset, it must deallocate any memory it had previously allocated with SVC_ALLOCATE or SVC_MAX_ALLOCATE. It must not return carry reset if it has used SVC_x_HOOK to add itself to the BIOS.
On return, HL should point at a message (terminated with CR, LF, 0FFh) which will be printed by the BIOS. It should fit on one line; under CP/M, it can contain escape sequences. LocoScript will try to centre the message on its startup screen.
The character device jumpblock is passed to SVC_C_HOOK when new character devices are added. It reads:
JP FID_C_INIT JP FID_C_I_STATUS JP FID_C_INPUT JP FID_C_O_STATUS JP FID_C_OUTPUT JP FID_C_M_STATUS JP FID_C_MESSAGE
This is called when the device is first added to the devices table, and also whenever CP/M changes the baud rate using DEVICE.
This is used to check if the device is ready to provide input to CP/M.
This is used to check if the device is ready to take output from CP/M.
Output a character, return when it is done.
This is used to check if the device is ready to take system message output from CP/M.
Output a system message character, return when it is done.
The disc device jumpblock is passed to SVC_D_HOOK when new disc devices are added. It reads:
JP FID_D_LOGON JP FID_D_READ JP FID_D_WRITE JP FID_D_FLUSH JP FID_D_MESS
Log in a disc. Initialise the DPB.
Read a sector (512 bytes).
Write a sector (512 bytes).
Write anything to disc that needs writing.
Expand an internal message number to an ASCII string. The string should be at most 50 characters long and end with 0FFh. The message "Retry, Ignore or Cancel?" will be appended to it when it is displayed.
If you have a COM file which wants to talk to a FID, then it should first check that the CP/M version under which it is running supports FIDs at all. If it does, then call FIND FID to see if your FID is loaded.
FIND FID returns the address of the FID header (the JP FID_EMS instruction). Calls or bytes which you want to access should be in the bytes just after the header - ie, starting at byte 20h. Since the FID is loaded on a page boundary, offsets can be calculated by loading the L register with the offset.
Assuming your FID is loaded, then you can make calls to it:
LD DE,fidname CALL FIND_FID LD L,20h LD (CALLAD),HL ; ;Set up any parameters ; CALL USERF CALLAD: DW 0
Or you can access data in the FID. Either put some transfer code above 0C000h and call it with USERF, or copy blocks in/out with XMOVE and MOVE.
If you're writing a device driver, then the routine interfaces above come in very handy. But since FIDs are loaded into the same memory bank as the XBIOS, the opportunities for mischief are quite extensive.
This test is used by Cirtech in CEN.FID:
TEST: LD HL,0FC00h LD DE,3 LD B,1Eh TEST1: LD A,(HL) CP 0C3h RET NZ ADD HL,DE DJNZ TEST1 XOR A RET- it checks that the BIOS jumpblock is present, and returns Z if it is or NZ if it isn't. This should be used if you're going to play around with the internals of CP/M or LocoScript.
This is only possible under CP/M, so use the test above. Then, you can use:
SVCSCB EQU 0FB9Chand it should work.
Any FID loaded in the PCW16 can call any Rosanne function except
os_app_exit
and os_release_allmem
. Future versions
may impose more restrictions on calls that can be used.
The three supplied FID files use precisely this technique for disc I/O, memory allocation and screen output.
Although a FID is emphatically not allowed to call the BIOS or BDOS, it seems to be possible to call at least some of the XBIOS routines. You will have to experiment to find out which. The XBIOS is probably not present under LocoScript (I haven't looked) so use the CP/M test again.
In PCW16 CP/M, you must not call USERF, and the XBIOS is not present in the same bank as the FID files; so you can't call it.
Suppose you wanted to change the beep sound. Since there is no SVC_SOUND_HOOK command, a little subtlety is called for.
On both the +3 and the PCW, the beeper is controlled by OUT commands. A quick search (using, for example, SID with SIDRSX and BANKRSX) will quickly reveal the piece of code responsible.
The thing to do is avoid absolute addresses. Things move around from version to version, and it isn't fun maintaining a vast table of the known addresses of things in different versions.
Instead, write a routine in FID_EMS to scan memory for a characteristic pattern of bytes matching this routine. Whenever the signature is matched (there may be more than one copy of the routine in memory) insert a jump to your replacement. While this technique isn't foolproof, it seemed to work for me in the two FIDs I wrote which use it.
A .FIB file is a file in the .FID format, containing disc timing parameters for a PCW add-on drive. .FIB files are not supported on the Spectrum +3.
Under CP/M, the two file types are treated interchangeably; under LocoScript, .FIB files are read in immediately the system is started, and .FID files only after LocoScript/Spell/File/Mail have all signed on.
When a .FIB file is loaded, it will install its disc timing parameters and depart.
These .FID files
allow the +3 floppy drives to be run at different timings.
PRL files are designed to be loadable on a page boundary - an address which is a multiple of 256. They are used by Digital Research for such things as RSXs or GSX graphics drivers.
This description does not cover all variations of the PRL file format. For that, see PRL File Format.
A PRL file starts with a 256-byte header:
DB 0 ;unused byte DW len ;Length of code + initialised data, bytes DB 0 ;unused byte DW bsslen ;Length of uninitialised data, bytes DS 249 ;Unused
In practice, a FID will nearly always set bsslen to 0, and keep all its variables in one place. This is partly because neither M80 nor RMAC properly supports uninitialised data areas.
A PRL file can be created by Digital Research's linker LINK.COM:
LINK FILENAME[OP]
REN FILENAME.FID=FILENAME.PRL
and uninitialised data can be allocated using the M
switch:
LINK FILENAME[OP, Mxxxx]
- where xxxx = no. of bytes to allocate, Hex.
After the 256 bytes of header, there are len bytes of FID code, and then ( len + 7 ) / 8 bytes of relocation map. Each byte in the map corresponds to 8 bytes in the PRL file, with bit 7 corresponding to the lowest byte and bit 0 corresponding to the highest. So bit 7 of the first map byte corresponds to the first byte after the header of the PRL file.
If a bit is set in the map, a byte in the file is relocatable. For a PRL file whose header was loaded at xx00 hex, relocatable bytes should have xx added to them. In practice, the header is discarded; so an alternative formula is that if the first byte of FID code is at xx00h, then xx-1 should be added to relocatable bytes.
In FID files, relocatable bytes with a value of 0FFh refer to the SuperVisor Calls. In this case, both the relocatable byte and the one before it will be replaced by the address of the SVC:
01 FF | | | +---This byte marked relocatable +------This byte not marked relocatablewill be replaced by the address of SVC 1.
FID files also contain a signature and checksum which are described elsewhere.