|8: DOS Plus Interrupts|
Many users will find that an intimate knowledge of DOS interrupts is not needed if use is confined to ready written applications or many programming languages. For users who write their own programs in one of the many interpreted or compiled high-level languages, such common facilities as will be required for most program operations will be provided either in the form of single instructions, or library subroutine calls provided with the language package.
For example, the PRINT statement in BASIC is a single high level command, capable of processing several different types of data output in a convenient pre-packed instruction. In languages like C, the compiler authors will usually have provided similar facilities in ready-made libraries (in C such as stdio, short for 'standard input/output').
Clearly, however, no matter how capable a language implementation is, there will always be a large number of system facilities which cannot be catered for in such a way. Many languages provide a means of adding to the routines held in libraries, or permit the embedding or calling of sections of machine code within programs to solve this type of need. Whatever the means of providing such facilities, the prime requisite for direct access to DOS interrupts involves the use of machine code at some point if legal coding techniques are to be employed.
The remainder of this chapter broadly outlines the philosophy of DOS system interrupts, which share many similarities with 6502 host MOS calls, but also include some considerable differences.
To those familiar with 6502 machine code, the concept of direct or indirect calls to Operating System or filing system functions will be well known. However, in the host there is a wide range of different call categories, the type and implementation of which is often governed by the operation required. In addition the filing system is a separate entity in the host, which also has numerous separate calls, again with their own rules.
For example, certain call types, like OSBYTE provide a large number of different general purpose facilities, but these are called in a different way to OSWORD calls, which are also, to a degree, general purpose. In addition there are numerous single function calls such as OSNEWL, OSRDCH and so on, each of which has a specific purpose and call, all of which adds to the general complexity of 6502 assembler coding. Filing system operations involve yet more call types, again with several different calls and new sets of rules. The range of differences also extends to the method of interfacing to such routines by user code, since some calls are vectored and may be intercepted for modification, while others are not and may not.
In DOS and DOS Plus, in large part because so much more memory is available, the situation is much more straightforward and better organised. Although the information used or returned by individual Operating System calls naturally differs from one to another, there is only one type of call in the system. Happily in this case the Operating System also includes the filing system and the same type of call, therefore, also caters for all filing system requirements too.
As a final bonus, all the calls are vectored and the system provides ready-made facilities to intercept or redirect these vectors. Naturally, these operations themselves are also carried out by means of the same type of system call, the interrupt, each of which is simply identified by a number.
Confusion may arise for the 6502 programmer because the meaning of the term interrupt differs slightly in DOS compared with its meaning in the host. Whereas in the BBC host an interrupt is normally concerned with the MOS servicing of hardware devices, actions being triggered by a timer or hardware activity, in DOS the term is used for all types of direct or indirect requested OS activity, however caused, including explicit calls from within user code.
One further major difference from the host is that in 86 series systems several simultaneous interrupts can be permitted and several types of NMI also exist, as opposed to the host's single NMI. However precise details of how these are handled is not required by the user, since the 'nesting' and prioritising of interrupts is performed transparently within DOS.
When an interrupt occurs the preparatory actions are much like those in the BBC host. Processor registers are saved and the return address is pushed on the stack. As each interrupt is allocated a priority category, all interrupts of an equal or lower category are masked out. However, higher priority interrupts are still permitted; therefore, if one of these should occur, the higher priority interrupt will interrupt an earlier one of lower priority.
As mentioned above, the very much larger memory available to DOS systems permits the luxury of, compared to 6502 systems, effectively unlimited stack space. This in turn permits a hierarchical interrupt philosophy. Suffice it to say that, if one interrupt should need to cut across the processing of another, there is sufficient space to permit all processor registers and data to be saved from the first interrupt in the usual way, so that its processing can resume normally after that of the higher priority interrupt is complete.
Essentially interrupts fall into one of three categories. Though the action taken within DOS follows a similar path regardless of the source and type of interrupt.
These are generated by certain unexpected events encountered by the processor during program execution, such as attempting to divide by zero. Internal hardware interrupts are, perhaps, most easily related to the BRK handling routines of the host, as both represent an irrecoverable error.
The assignment of such hardware events to these associated interrupt numbers is effectively 'hard-wired' into the processor chip and this association is not normally modifiable by any technique. So these interrupts cannot be prevented or avoided in a PC, and should not be suppressed or interfered with in the 512 by intercept code.
These are interrupts generated by peripheral hardware activity, most easily thought of like the actions performed by the host processor during IRQ1V processing, for example, because of a keypress. In a true PC these interrupts are triggered by a physical device controller signalling the need for service, the availability of returned data or the completion of an action. In machines so equipped, the 8087 maths co-processor is another source of external interrupts.
External hardware interrupts can be tied to either the processor's non-maskable interrupt pin, or to the maskable interrupt pin, depending on the type and cause. The NMI line is usually reserved for catastrophic events that must be processed instantly, such as a RAM parity error. Maskable interrupts are, of course, similar in nature to those in the BBC host and are, by definition, of a lower priority. The assignment of the interrupt priority level is defined by the system designer and the processor chip manufacturer and cannot be altered subsequently.
In the 512 system all peripheral hardware activity takes place in the host, therefore all such external device interrupts are simulated in the XIOS as a result of Tube transfer activity. Whereas these interrupt connections are normally physically 'hard-wired' in PCs, in the 512 this is not so and certain facilities therefore cannot be implemented.
Perhaps the most frustrating example is that, in a PC, pressing CTRL-BREAK causes the keyboard controller to perform a hard wired jump to the hardware reset routine (an NMI). This often permits escape from a hung program while, in the 512, the only recourse is to perform a complete cold start reboot of the system from scratch, a much more long winded and annoying process.
These interrupts are generated by user code (or DOS itself) by issuing a direct interrupt call to request that an Operating System function be carried out. Direct user-called interrupts are generally of the lowest priority, though these interrupts may cause higher priority interrupts as a result of the action of the hardware function called.
Software interrupts are provided by a range of interrupt numbers, but it should be noted that these numbers are not significant and imply no priority. The interrupt numbers are simply a convention, which are in no way hard-wired into the processor. All software interrupts are, therefore, always maskable.
The bottom 1024 bytes of memory (0000:0000 to 0000:03FF) are reserved in DOS systems for the interrupt vector table. Each vector entry consists of four bytes, the first two for the segment address, the second two for the offset, each of which is stored in the familiar low-byte-high-byte format. The offset address of the vector entry for any particular interrupt can, therefore, easily be found by multiplying the interrupt number by four.
As 1024 bytes are available and each entry takes four bytes, there is provision for up to 256 vectors and each can, though does not always, represent an interrupt. In all versions of DOS to date, however, less than a quarter of these locations have been used.
In the early days of DOS design, groups or blocks of vectors were pre-allocated, for various purposes, to permit individual developments in chip design, micro hardware and DOS facilities by the various parties while theoretically avoiding conflict between them. In practice the situation is not quite so clear since, as mentioned earlier, IBM, for example, have chosen to ignore these standards in several of their PCs, leading to some of the incompatibilities which appear between different types of hardware.
Officially the blocks of interrupt numbers are grouped as follows.
These are the internal hardware interrupt numbers, effectively allocated to the processor chip. All of these interrupts are generated by hardware events and, although the vector entry can be physically modified to intercept the event, such hardware events should not be prevented from jumping to the correct address of their associated vector. For users who write general applications programs, therefore, these vectors should be left alone.
These are the external hardware interrupts that are available for the computer hardware manufacturer. They direct execution to the BIOS (or XIOS) where the system supplier should implement the code, which varies according to the hardware devices and their controllers supplied with each different system.
20h upwards are the software interrupts. 20h to 3Fh and E1h to FDh are officially reserved for current MS-DOS or future developments by Microsoft Corporation. 60h to 67h are nominally reserved as user vectors and E0h is for Digital Research systems.
In summary interrupt numbers are officially allocated as shown in the table below.
|Internal hardware interrupts:|
|00h‑04h||Processor interrupts - all versions|
|05h‑0Dh||Processor interrupts - 80286 only|
|0Eh‑0Fh||Reserved by Intel for processor expansion
|External hardware interrupts:|
|10h‑1Fh||Peripheral and firmware functions
|20h‑3Fh||Allocated to MS-DOS|
|60h‑67h||Allocated as user vectors|
|E0h||Allocated to Digital Research (CP/M)|
|E1h‑FDh||Reserved for Microsoft expansion.|
|Table 8.1 Allocation of Interrupt Numbers.|
Officially all other interrupts should be unused though, as already mentioned, this cannot necessarily be relied upon, especially in IBM PC software. In some cases the contents of interrupt vectors do not point to executable code, but rather to data tables, often for graphics or keyboard character definitions and the like. This particularly applies to software written for the PC Junior, IBM's (relatively) unsuccessful home computer, giving yet more compatibility problems.
Remember that in the 512, interrupts 40h to 4Ch are allocated to the host MOS calls, while FEh and FFh have also been pressed into service. The actions of these interrupts are naturally peculiar to the 512's DOS Plus implementation and no DOS software from any source (except programs specific to the 512) will be aware of them.
Note also that, although INTs 60h to 67h are allocated as user interrupts, in fact INT 67h is used in later PCs as the Extended Memory management System (EMS) interrupt, though this is not relevant to the 512.
Although officially DOS Plus supports only interrupts between 21h and 27h inclusive to the level of PC-DOS 2.11, in fact numerous others have been implemented to varying degrees of completeness.
Some of these, as mentioned above, emulate in software the hardware interrupts of a PC, particularly those relating to internal interrupts and relevant external hardware devices. In most cases this technique is fairly successful but, where hardware interrupt emulation is less than complete, problems occur in applications software. Most frequently this relates to keyboard or disc functions Yet others are implemented in such a way that they simply jump to an immediate return with no action. This is done so that the interrupt may be called by an application without causing a program crash, which would be the result of a zero vector entry.
DOS interrupts are called by the assembly code instruction INT followed by the interrupt number, which causes the processor to prepare for an interrupt service request. This involves saving processor registers on the stack, setting up the return address and transferring execution to the code pointed to by the appropriate vector. For example, to call INT 21h, the most frequently used interrupt, the assembly code source statement would be literally:
Clearly, as it stands this does not convey any data. In fact, it merely directs execution to the requisite section of DOS code via the vector table, which initially merely allocates the interrupt priority and masks other interrupts of equal or lower priority. It is therefore necessary for the caller to supply additional parameters to many interrupt calls, usually in the form of the contents of certain of the 80186's registers, but sometimes with additional data held in memory.
Much like host OSBYTE calls, some interrupt calls require only parameter values in the processor's registers and these are then used immediately. For other calls the registers are also used as pointers to memory blocks, which the programmer must set aside to hold call parameters, data or results, more like a host OSWORD call. In a few cases interrupts are highly specific, requiring no entry parameters, with some also returning no values. These highly specific interrupts are more like such host MOS functions as OSNEWL. As can be seen, DOS interrupts naturally share many conceptual similarities with host MOS calls, but unlike host MOS calls, for all interrupts there is only one call mechanism.
The MS/PC-DOS 2.11 compatible interrupt functions and codes available in the 512's DOS Plus 2.11 are listed in Appendix A. They are shown first in numeric sequence, the order usually required when disassembling or debugging code, followed by a list grouped by function type, the sequence generally required when writing new code. The two lists are followed by the detailed call list, showing the definition of each call's entry requirements and any returned values or conditions.
Interrupt 21h (also often referred to as the general function dispatcher) provides most of the facilities likely to be used in general applications code, and has a wide range of functions. To identify the particular operation required, therefore, in every call to INT 21h a function code must be supplied. This is always placed in the general-purpose byte register AH.
The function code is directly analogous to the call number placed in the 6502 accumulator prior to a host OSBYTE call. Also, like OSBYTE calls, certain function types require one or more additional processor registers to be set up to contain parameters to the call. However, unlike host OSBYTE calls, in addition a number of INT 21h functions, particularly those relating to disc and memory operations, require parameter or data blocks to be set up in RAM, most commonly with DS:DX holding the segment:offset pointers to the address of the block.
Unfortunately, as DOS has been enhanced in successive versions, new functions have been added in a manner that results in the INT 21h function codes appearing to have been allocated at random. The result of this is that functions are not logically grouped into operational categories and there is no easy way to deduce, from the function number, what the operation type might be – whole disc, directory, file and record handling calls are intermixed with memory/process management and the other general input/output functions in a more or less chaotic sequence.
INT 22h has a single function, but this interrupt should never be called directly by user applications code. The vector entry at 0000:0088h to 0000:008Bh contains the address of the routine entered when a program terminates by means of a call to INT 21h (function 00h, 31h or 4Ch) or INT 27h.
The address of this vector is automatically copied into locations 0Ah to 0Dh in the PSP when a program is loaded, but before execution commences. The contents of this vector are, therefore, immediately available to each program, which may then modify the original vector for the purpose of spawning.
The original terminate address is already stored in the PSP, and a new address can be set up in the vector to point back to the calling program in question. The program can then call another program itself. On termination of the second or subsequent program the vector address is restored from its own PSP, itself taken from the modified vector entry. This technique ensures that any spawned programs always return to their original caller, which on final termination will have the original default termination handler vector replaced from its own PSP.
Note that, if program spawning is employed the technique of altering the terminate handler vector must be used, it is not optional.
If the terminate handler vector is not correctly modified to return control to the caller any (number of) calling programs would permanently remain memory resident, and unable to be removed. Since they would never be re-entered, they would never terminate, and control would return to COMMAND.COM with large portions of memory subsequently unavailable.
INT 23h is the CTRL-C handler address, vectored through 0000:008Ch to 0000:008Fh. This routine should never be called directly in user programs, but is called when CTRL-C is detected during any character input/output function and most (but not all) other interrupt function calls. In PCs, other functions than character I/O can be instructed to ignore CTRL-C aborts by setting the BREAK flag to off. This flag is non-functional in the 512, although the command itself is implemented for applications compatibility.
The address of the vector for INT 23h is also available in the PSP of each program, at locations 0Eh to 11h. As for the INT 22h terminate handler routine, the original vector address may be modified using the same philosophy when program spawning is to be employed. This ensures that unexpected user termination of a called routine also returns control to the calling program correctly. As before, the vector contents are copied back from the PSP on final termination of each called routine, including the last.
Note that, if the technique of spawning is employed the caution given for INT 22h equally applies, the INT 23h vector must be altered accordingly.
The interrupt vector for INT 24h is located at 0000:0090h to 0000:0093h. It contains the address of the routine called when a critical error (usually a hardware error condition) is encountered. It should never be explicitly called by user programs. The vector is copied into the PSP of a loaded program at locations 12h to 15h. On termination the vector is restored from the PSP as for INT 22h and 23h.
The philosophy of substituting a calling program's re-entry address for the original vector contents is exactly the same for INT 24h as for the previous two interrupts. Like them, if spawning is employed this vector must be altered to allow for return from unexpected system or hardware events which might terminate the called routine.
This call is vectored through the address held at 0000:0094h to 0000.0097h. It provides a direct call to the XIOS to allow direct reading of one or more physical disc sectors from a nominated disc drive. The calling program must allocate a memory buffer of suitable size to hold the incoming data and set registers DS:BX to point to this. The disc drive to be used is indicated by 0 for drive A:, 1 for drive B: and so on. It should be noted that absolute sector numbers start from one on DOS discs, unlike native BBC formats which begin at zero.
It is important to remember that this call leaves the CPU status register contents on the stack after the call returns, which the calling program must explicitly clear itself before any other operations involving the stack are called for.
This call is vectored through 0000:0098h to 0000:009Bh. Its function is precisely the reverse of INT 25h. A block of memory pointed to by DS:BX is written to the specified drive starting at a specified absolute sector number and continuing for a specified number of sectors.
Note that since this call accesses discs at the hardware level no updating of directories or FATs takes place, therefore it should be used with extreme caution. As for INT 25h, CPU status flags remain on the stack after the call and must be cleared by the program itself.
Note that information on this interrupt is supplied only for completeness. INT 27h is a relic from MS-DOS version 1 and is only required for applications which must run under that version. This interrupt, vectored through 0000:009Ch to 0000.009Fh, terminates direct (ie console connected) execution of the calling program, while leaving a (variable) area of memory allocated so it may not be used by subsequently loaded code.
The amount of retained memory is indicated by a segment offset held in register DX, therefore the maximum amount of memory which can be retained by this call is 64kb. As machine's memory sizes and program complexity have grown this amount of memory is now considered insufficient for many purposes, so the call has been replaced with INT 21h function 31h (terminate and keep) which has no such memory limitation. Apart from this point the following description of the general concepts applies to both calls.
The normal termination routine is entered as usual, so as to flush any buffers, close any open files and correctly restore the vectors for interrupts 22h to 24h inclusive, but the memory allocation is not freed. After termination any program code or data in the reserved memory area remains unchanged.
Programs using this technique are usually re-entered subsequently on an interrupt, the vector of which has been directed to point into the program. For example, this technique is commonly employed to use a keyboard interrupt for activating TSR type 'pop-up' programs, although any interrupt may be employed as required and communication can be from another program, rather than direct from the user.
On a call to the chosen interrupt, the intercepted vector directs execution to the resident program(s) which can investigate the call parameters to decide if action is required by them. If it is, then almost any normal DOS activity can then be carried out. If not, then the call is passed to the original vector address for other processing.
There is no theoretical limit to the number of programs that can be chained together on any single vector, provided that the code is written legally. TSR type programs should not be confused with either spawned programs, which are directly interrelated and generally terminate normally via INT 21h function 4Ch, or with CP/M background and RSX programs, neither of which exist in MS/PC-DOS.
Because the DOS emulator sits between applications code and the BDOS a special interrupt, E0h (224) has been reserved for Digital Research CP/M based systems by Intel, the 86 series chip manufacturer. Use of this call is valid for all DOS Plus programs except those which have entered the background, and BDOS calls may be mixed with DOS interrupt calls within a .COM, .CMD or .EXE program as required and as is most convenient.
Interrupt 224 is vectored through 0000:0380h to 0000:0383h, which in DOS Plus 2.1 calls 6000:4401h and is a direct entry into the BDOS kernel, giving direct program access to CP/M functions. Call philosophy is generally much as described for DOS interrupts, except that the BDOS call number must always be placed in general purpose register CL, and the usage of the other registers, while consistent within the range of direct BDOS calls, is different to those used in DOS interrupts.
A few calls which are available in pure CP/M or CCP/M systems are not permitted in DOS Plus systems. In addition, BDOS calls must be used exclusively in that part of a program which is to enter the background, and in programs which are destined to be converted to RSXs.
In a few cases the function of a particular BDOS call may be virtually identical to that of a DOS interrupt, and in such cases the programmer can simply choose the call which is the most convenient in the circumstances. However, in general, BDOS calls operate at a lower level than DOS interrupts, allowing access to program functions and system capabilities which are impossible in DOS, hence many DOS Plus utilities use BDOS calls to provide facilities which MS-DOS and PC-DOS cannot offer.
It should be remembered, when using BDOS calls, that facilities which relate to CP/M disc formats may very well not be appropriate for use with DOS-formatted discs. If such calls are likely to be used in programs which may be run with DOS formatted discs as well as CP/M media, it is the programmer's responsibility to test the media type and issue only suitable BDOS calls.
In the case of the 512, peripheral error conditions are detected by the host processor. These are passed across the Tube to the 512 generating an interrupt request. By default, the error number and string are placed in the error buffer of the 512 and a pointer to the error number is initialised. The 512 Tube code then jumps to the error handler, displaying the error details before returning control to the 80186 monitor.
As mentioned in Chapter 3, this facility is not usable by 512 stand-alone applications, because control does not return to the caller. If a program is to make use of the 512's error handling facilities it must intercept the error handler vector and direct control to its own error handling routine. This should be capable of identifying the error and dealing with it in an appropriate manner, either returning control to a suitable point within itself, or terminating tidily, in either case exiting by means of an interrupt return.
The locations of the 512 error handler vector and error pointer are given in the table below.
|0000:05F4||Error pointer offset|
|0000:05F6||Error pointer segment|
|0000:05F8||Error handler vector offset|
|0000:05FA||Error handler vector segment|
|Table 8.2 Locations of Error Pointers and Vectors.|
Part of an assembler code example is now given, Listing 8.1, to illustrate a typical error handler. This assumes that the program is at 0000:8000.
|Listing 8.1 Assembler Code, Example of Error Handler.|
Application errors can also be generated within the 512, by means of interrupt 04Fh, supplying both an error number and the error string terminated with a null byte (00h). The error pointer will be initialised as for host detected errors and the error handler used will be as indicated by the contents of the error handler vector (see above).
An outline example is shown below, to illustrate a call to the 80186 error handler. The code tests for the presence of a file before attempting to load it and assumes that the file name is in the current data segment. Note that, if the error handler is called and no error handler has been implemented, the call does not return.
; fixed parameter equates
|Listing 8.2 Assembler Code, Call to Error Handler.|
When an escape condition is detected by the 6502, the top bit of the escape flag at 0000:05F2h in the 512 is set and a Tube interrupt is generated. If programs are intended to process escapes, the condition should be tested for by checking this flag.
If an escape condition exists, the escape must be acknowledged in the host by issuing an OSBYTE with AL = 07Eh (Acknowledge detection of an ESCAPE condition). An application enor message may also be generated if required.
Note that, the escape flag should not be set or unset directly in the 512, as the change will not be reflected on the host side of the Tube, which will record an outstanding unprocessed escape. OSBYTE calls with AL = 07Ch (clear ESCAPE condition) or 07Dh (set ESCAPE condition) should be used to set or reset the escape condition.