A device driver consists of a set of routines, one to implement each of the fourteen entry points contained in the entry point table which was described in the previous chapter. This chapter describes the functions which must be provided by each of these routines, including details of register usage.
Of the fourteen device driver entry points, eleven of them match up directly with EXOS function codes 1 to 11. Whenever the user makes one of these EXOS calls, EXOS will find out which device is the correct one for this channel and call the appropriate entry point of that device driver. These calls are referred to as the device channel calls.
The three remaining device driver entry points are, for initialisation, interrupt service and channel buffer moving. Calls to these three routines are originated from within the EXOS kernel at appropriate times and each is discussed in detail below.
Whenever a device driver routine is entered, the segment containing the
entry point will be paged into Z80page 3. Page 2 will always
contain the system segment (segment 0FFh
), and page 0 will
of course contain the page zero segment. In the case of the device
channel calls, the segment containing the channel descriptor and channel
RAM (see later) will be in page 1, for other calls the contents of page 1
will be undefined. The stack pointer will be set to the system stack, in
Z80page 2, and there will be at least 100 bytes available on the
stack, in addition to that needed for interrupt servicing (only 50 bytes
for an interrupt routine).
When a device driver is called, register IY will always
contain the adress of the DD_TYPE
field of the device
descriptor, in Z80page 2. In the case of extension devices (linked
in from extension ROMs), this can be used to access the device RAM which
is allocated immediately below the device descriptor in the segment. A
user device may sometimes have to access RAM relative to its device
descriptor, which will not be in the system segment, so it will have to
page the correct segment in (remembering to disable interrupts
temporarily since the stack will be paged out). To enable a user device
to do this, the segment number of the segment containing its device
descriptor is passed in register B' whenever the device is
called.
Device driver routines can corrupt all registers, including the index registers and the alternate register set, since they will have been saved by EXOS. The device driver can also corrupt the contents of Z80page 1 with impunity, but should exercise caution with the other Z80pages. Generally registers A, BC and DE are used to pass parameters to and return results from the routines.
The device initialisation routine is passed no parameters (other than the segment and address of the channel descriptor in B' and IY), and returns no results. It is called when the device is first linked into the system, and again whenever a reset EXOS function call is made, which occurs at a warm reset or when a new applications program takes control.
Any channels which the device may have open will vanish when this routine is called, and so any variables or data areas which the device may keep must be reset. Note that any RAM segments allocated to the device will not be freed, so the device must remember that it still has these after subsequent initialisations.
Every channel which is open has an area of channel RAM allocated to it. It is the job of the open channel or create channel routines (described below) to make an allocate buffer EXOS call to obtain the required amount of RAM. The allocate buffer EXOS call itself is described in section 11.25. This function call must be made before the open or create channel routine returns to EXOS, even if zero bytes of channel RAM are required, since it also sets up a channel descriptor for the channel.
When the allocate buffer call is made, it will return the address of the channel RAM in register IX. This will be in Z80page 1 and the correct segment will be in page 1. Whenever the device driver is entered in future with a channel call to this channel, page 1 and register IX will be set up correctly. if n bytes of channel RAM are allocated then they can be accessed at addresses:
The 16 bytes of RAM immediately above the channel RAM (IX+0 ·· IX+15) contain a channel descriptor. This contains system information about the channel and should not be modified by the device.
In the case of nonvideo devices, the channel RAM will all be in one
segment. In the case of video devices however, only a certain amount of
the RAM, specified by the device and starting at IX-1, will
definitely be in one segment, the rest may carry on down into other
segments. If this is the case then each new segment will have a segment
number one less than the previous one and they will all be video segments
(0FCh
to 0FFh
). This allows a video device to
obtain sufficient RAM for a large video page. Normally only the
builtin video driver will be a video device, although any device
can make itself one simply by having a bit set in its device descriptor
(see section 6.2).
Once allocated the channel RAM can be moved by EXOS. This can only occur when another channel is opened or closed, or a user device linked in. Since devices are not allowed to make any of these EXOS calls, it is impossible for the channel RAM to be moved while the device driver is executing. Whenever the channel RAM is moved the buffer moved entry point of the device driver will be called. This entry point is described below.
The buffer moved entry point is called by EXOS immediately after it has moved a channel buffer of this device. This routine returns no result but is passed the following parameters:
B', IY = Device desriptor segment & address (as usual) IX = New address of channel descriptor, will be paged into Z80-page 1 A = Channel number of channel buffer moved BC = Amount that channel buffer has moved
The channel buffer may have been moved into a different segment. If the device needs to know this then it can read the new segment number from the page 1 register. The distance moved parameter in register BC is strictly speaking a signed 17bit number, with the sign bit missing. This means that if, for example, a value of 1 is passed in BC, then this could mean that the buffer has been moved either up by 1 byte, or down by 65535 bytes. In practice this difference does not matter since it only affects the new segment number and this can be determined seperately.
Whenever the buffer moved entry point is called, interrupts will be disabled and should not be reenabled by the device driver. This is to ensure that the device's interrupt routine cannot be called while it is in an intermediate state.
EXOS can handle interrupts from any of the four possible sources on the
computer (video, sound, 1Hz and external). When an interrupt
occurs, EXOS examines the Dave chip to determine which
source it came from. It then scans through the device chain calling the
interrupt entry point of any device which has requested servicing of this
type of interrupt (by setting a bit in DD_IRQFLAG
in its
device descriptor - see section 6.2). When all
devices have been called, the interrupt is cleared in the Dave chip, all
registers and paging restored and EXOS returns to the interrupted
program.
Interrupts are allowed at any time, including while executing device driver code, except while certain system variables are being updated or channel buffers are being moved. Also, interrupts are disabled while servicing an earlier interrupt, so there is no nesting of interrupts. If an interrupt from another source occurs while already servicing an interrupt then it will be held up until servicing of the first one is complete. Thus no interrupts should be missed but they may be serviced late.
The interrupt entry point of a device driver is optional, it is only
required if the DD_IRQFLAG
field of the device descriptor is
nonzero. When a device is linked in, EXOS will ensure that any
sources of interrupts which the device wants to service are enabled in
the Dave chip.
The device's interrupt routine will be entered just like any other entry point, with registers B' and IY set up to the device descriptor segment and address as usual. No results are returned from the interrupt routine and all registers can be corrupted. (AF, BC, DE, HL, IX, IY, AF', BC', DE', HL'). The entry point will be called with interrupts disabled and they should not be reenabled, neither should the device attempt to reset the interrupt flag in the Dave chip - EXOS does that.
There is an EXOS variable (see chapter 8) called
IRQ_ENABLE_STATE
which defines which of the four sources of
interrupts are currently enabled. Any of them can be enabled or disabled
by changing this EXOS variable and writing it out to the interrupt enable
register in the Dave chip. This should be done with care since the
keyboard will not be scanned if video interrupts are disabled so it can
be difficult to recover from this.
The device channel calls are the device entry points which correspond with EXOS function codes 1 to 11. Full details of these EXOS calls can be found in chapter 11. This section describes them only from the device's point of view.
All of these routines have certain parameters and results in common. These are:
B', IY = Device descriptor segment and address IX = Pointer to channel RAM in Z80-page 1 A = Channel number +1 (see next paragraph) BC & DE = General parameters to routine
A = Status code, returned to user BC & DE = General results from routine
The channel number parameter passed to the device routine is one greater than the channel number as specified by the user. This due to the way in which EXOS handles channel numbers internally, and means that a device can never be passed a channel number of zero.
The device driver does not need to return with the status register set depending on the value returned in A. The setting of flags is done by EXOS before returning to the user.
For most devices the open channel and create channel routines can be the same. The difference is only relevant for file handling devices, where "open" is intended to open an existing file and "create" is intended to create a new one.
The routine will be passed a pointer to a filename string in DE (length byte first). This will have been copied from the string passed by the user, into a buffer in the system segment, and will have been uppercased and checked for syntax and length (max 128 characters). If no filename was specified by the user this will be the null string.
The unit number specified by the user (or a default) will be passed in register C. Unit numbers are explained in section 11.1.
Assuming that the device decides that it will accept the open channel call, it must make an allocate buffer call to setup the channel descriptor and obtain any channel RAM which it may need for this channel. Details of this call can be found in section 11.25. This function call will return a pointer to the RAM in IX and page it into page 1. This is the only case of an EXOS call corrupting any unusual registers or the paging.
All devices must provide a block read and a block write routine, wich are capable of reading or writing up to 65535 bytes. Some devices (such as disk) will implement these intelligently, doing data transfers directly into the user's buffer. However most devices simply do repeated calls to their own character read or write routines, copying the bytes into or out of the buffer.
Special care must be taken with accessing the user's buffer area. The
buffer pointer is passed in DE straight from the user's call.
This may point to any address in any of the four Z80 pages, and refers
to the segment which was in that page when the user called EXOS, not when
EXOS called the device driver routine. The device driver will therefore
have to translate this address to one in Z80page 1, and page in the
correct segment in order to access the buffer, but must not forget the
segment with its channel RAM in. In order to determine the segment
number, four variables are provided in the system segment which define
the four segments which where paged in when the EXOS call was made.
These are called USR_P0
, USR_P1
,
USR_P2
and USR_P3
. Their addresses were given
in section 5.4. These variables are handled
reentrantly, so they will survive nested EXOS calls correctly.
Note also that the user's buffer can cross a segment boundary and so the segment may need to be changed, and the address adjusted several times. Also the device should cope correctly with a block size of zero bytes, simply returning a zero status code without doing anything.
If an error occurs part way through a block read or write the registers DE and BC should be returned with their values correctly adjusted to indicate how much has been read or written.