Interrupts In this chapter, we see how GS/OS and ProDOS 8 react to and handle interrupt signals generated by I/O devices. GS/OS and ProDOS 8 both let you install assembly- language subroutines to service sources of interrupts. They also define rules these subroutines must follow to ensure they will function smoothly together. In particular, the rules dictate the method an interrupt-handling subroutine must use to indicate whether it serviced the interrupt. Before we begin, we should review the concept of an interrupt. An interrupt is an electrical signal an I/O device sends to the microprocessor in an attempt to get its immediate and undivided attention. The signal is sent down a special line connected between a specific pin on the expansion slot connector used by the interrupting device and the IRQ (interrupt request) pin on the microprocessor. (On the Apple IIc and IIGS, equivalent connections are made between the microprocessor and each built-in I/O device capable of interrupting the system.) An I/O device typically generates an interrupt signal when it has new data to be read or when it is ready to receive more data. When the microprocessor detects an active IRQ signal, it completes the current instruction, stops executing the main program, and then passes control to an interrupt-handling subroutine. This subroutine (installed by the operating system or the application) is responsible for servicing the interrupt by clearing the condition that caused the interrupt and performing the necessary I/O operation. When it finishes, control returns to the main program at the point where it was interrupted, and execution of that program continues as if it had never been disturbed. The advantage of using an interrupt scheme like this to control I/O devices is that it is the most efficient one for handling asynchronous I/O operations (that is, opera- tions that can occur at any time). If interrupts were not available, a program would have to waste a lot of time frequently polling each I/O device in the system to ensure that incoming data was not lost or that outgoing data was being pushed out as quickly as possible. This is comparable to picking up a telephone without a ringer every few seconds to see if anyone is calling in. By adding the ringer (the interrupt signal), you can go about your normal duties until the phone rings (an active interrupt signal occurs), and then you can pick up the telephone (service the interrupt). 265 COMMON INTERRUPT SOURCES Many I/O devices available for the Apple II are capable of generating interrupts. look at the sources of interrupts usually available on three of the most common devices: the clock, the asynchronous serial interface, and the mouse. Clock - A clock device is able to keep track of the time and date without assistance of the microprocessor. (The logic is handled by a discrete ii circuit.) It typically contains a small battery that allows the clock to keep - of the time even when the computer is off. Most clock cards generate - at regular intervals: every second, minute, or hour. Asynchronous serial interface - An asynchronous serial interface is commonly used to link the computer to printers and modems. It can be told generate interrupts whenever it is ready to send out a character or whenever receives a character. Mouse - A mouse is an input device that is normally capable of interrupts when it is moved or its button is pressed. REACTING TO INTERRUPTS It is important to realize that the IRQ interrupt signal is mask able. In other words, it is possible for a program to instruct the microprocessor to ignore an active IRQ interrupt signal. It can do this by executing an SEI (set interrupt disable flag instruction. (The interrupt disable flag is a bit in the microprocessor status register.) If interrupts are disabled like this, the main program running in the system won't be disturbed. Time-critical operations, like disk reads and writes, cannot be interrupted without loss of data, so interrupts are always disabled first. The instruction that causes the microprocessor to respond to IRQ interrupts is CLI (clear interrupt disable flag). An application should clear the interrupt disable flag whenever possible so that it will perform smoothly in an environment in which interrupting devices may be active. When the microprocessor receives an IRQ signal, it immediately pushes the contents of the program counter register and the status register on the stack. If the processor is a 6502 (or, on the IIGS, a 65816 in 6502 emulation mode), it passes control to a low-level interrupt handler whose address is stored at $FFFE-$FFFF (low-order byte first). If the processor is in 65816 native mode, the handler's address is stored at $FFEE-$FFEF in bank $00. The low-level interrupt handler is in the firmware ROM on any Apple II. On models prior to the Apple IIgs, its main duty is to pass control to a high-level interrupt handler whose address is stored in the user-definable interrupt vector at $03FE and $03FF (low-order byte first). On the Apple IIgs, the low-level handler actually tries to process interrupts from built-in devices and passes control to the user-definable interrupt vector only if it is unable to do so. 266 Interrupts A properly designed high-level interrupt handler should perform the following chores in the following order: z Save the current values in the A, X, and Y registers and all information about the current machine state. z Clear the source of the interrupt. (It usually does this by reading the status registers of the I/O device.) z Service the interrupt by performing the I/O operation required. z Restore the A, X, and Y registers to their initial values, and restore the same machine state. z End with an RTI (return from interrupt) instruction. When ProDOS 8 is active, the user-definable interrupt vector points to a general- purpose interrupt handler within the main body of the operating system called the interrupt dispatcher. When GS/OS is active, the vector points to a similar dispatcher which manages ProDOS 16-style interrupt handlers. GS/OS-style interrupt handlers actually bind to the system at the low-level firmware level; control never passes to the user-definable interrupt vector unless the interrupt is unclaimed. GS/OS-style inter- rupt handlers are added to the system with the Bindlnt command. The ProDOS 8 interrupt dispatcher contains no specific code for identifying and servicing an interrupt. (This isn't too surprising since it could hardly be expected to support every possible source of interrupts.) To service an interrupt, it polls each member in a group of user-installed interrupt subroutines, the addresses of which are stored in an internal interrupt vector table. These subroutines are integrated into the system with the ProDOS 8 ALLOC - INTERRUPT command. Figure 6-1 shows the events that take place when an interrupt occurs under ProDOS 8. The interrupt dispatcher takes over and calls the first subroutine whose address it finds in the interrupt vector table. This subroutine will either recognize and claim the interrupt or not. If it does, the operating system restores all registers and returns to the interrupted program. If it doesn't, the operating system tries again by calling the next subroutine whose address is in the interrupt vector table. (The operating system examines the state of the carry flag to determine if the interrupt was claimed; if it was claimed, the carry flag comes back cleared.) This process repeats until the interrupt is claimed, at which point the interrupt dispatcher returns control to the interrupted application by executing an RTI instruction. If none of the installed subroutines claim the interrupt, a critical error occurs and the system hangs. The advantage of using a dispatching scheme like this to handle interrupts is that it allows for the development of interrupt-handling subroutines that are specific to only one device. That is, a subroutine need not concern itself with handling mouse, clock, serial, and "you-name-it" interrupts all at once. If the operating system rules are followed, you can easily install a mouse interrupt subroutine from one manufacturer Reacting to Interrupts 267 Figure 6-I How ProDOS 8 handles interrupts Eat here if interrupt not SEC SEC SEC Main ProDOS 8 User-installed error program interrupt-handling interrupt unclaimed subrouinne subroutlnes interrupt (system Eicit here if hangs) interrupt is serviced and a clock interrupt subroutine from another and they should work properly (See Eyes and Lichty's Programming the 65816 for detailed information on how 6502 and 65816 microprocessors react to interrupt signals.) INTERRUI'TS AND PRODOS 8 The ProDOS 8 general-purpose interrupt-handling subroutine (stored in the IRQ vector at $03FE-$03FF) did not work flawlessly in the first versions of 8; the one used in the newest versions of ProDOS 8 do. The moral is to always use ~L most current version of ProDOS 8 if you want the system to work smoothly with interrupts. You use ALLOC INTERRUPT to store the address of an interrupt-handling subroutine at the next available location in an 8-byte interrupt vector table in the ProDOS 8 global page beginning at $BF80. (A dummy $0000 address is stored in the table if a vector is unused.) Table 6-1 lists all the global page locations used by the ProDOS 8 interrupt-handling subroutine. 268 Interrupts Table 6-1 Global page data areas nsed by the ProDOS 8 interrupt-handling subroutine Address Symbolic label Description $BF80 1NTRUPT1 The address of the first user-installed interrupt subroutine $BF82 1NTRUPT2 The address of the second user-installed interrupt subroutine $BF84 1NTRUPT3 The address of the third user-installed interrupt subroutine $BF86 1NTRUPT4 The address of the fourth user-installed interrupt subroutine $BF88 INTAREG The A register is stored here when an interrupt occurs $BF89 INTXREG The X register is stored here when an interrupt occurs $BF8A INTYREG The Y register is stored here when an interrupt occurs $BF8B 1NTSREG The stack pointer is stored here when an interrupt occurs $BF8C INTPREG The processor status register is stored here when an interrupt occurs $BF8D 1NTBANKID The identification code for the active $Dx bank is stored here when an interrupt occurs $BF8E 1NTADDR The address of the instruction being executed when an interrupt occurred is stored here when an interrupt occurs The user-installed interrupt subroutine must adhere to the following rules: 1 Its first instruction must be CLD. z If the interrupt was not generated by its device, it must set the carry flag (with an SEC instruction) and exit. z If its device is the source of the interrupt, it must claim the interrupt by performing the necessary I/O operation, clear the interrupt condition (usually by reading the device status), clear the carry flag with CLC, and exit. z It must exit with all soft switches in the states they were in on entry. Most of these switches are used for memory bank switching or for controlling video display modes. (See Appendix 111 of Inside the Apple lIe.) z The subroutine must end with an RTS instruction (not an RT1 instruction). The ProDOS 8 interrupt handler executes the necessary RT1 instruction. Interrupts and ProDOS 8 269 There is no need for such a subroutine to save and restore the registers. The main ProDOS 8 interrupt-handling subroutine automatically for you. Two other nice features of the ProDOS 8 subroutine that significantly the writing of an interrupt subroutine are z The contents of locations $FA-$FF are saved before control passes to interrupt subroutine and are restored when you're through. This frees up convenient zero page locations for unrestricted use by your subroutine. z At least 16 bytes of stack space are freed up before your interrupt 5Lz gets control. This should be enough for even the most complex subroutines. The program in Table 6-2 (MOUSE.MOVE) shows how to properly interrupt-handling subroutine in a ProDOS 8 environment. To be able to run specific example, you must be using an Apple Ilc with the Apple Mouse - Apple lIe (or II Plus) with an Apple Mouse card installed in slot 4, or an Apple with its built-in mouse. The program assumes the mouse firmware is in slot 4; if not, change the SLOT EQU 4 directive to reflect the actual slot. (The mouse is in slot 7 of the Ilc Plus and the memory expandable version of the Ilc, but it slot 4 of earlier models.) MOUSE.M0VE directs the mouse to generate interrupts whenever it is across a tabletop. When the mouse is moved, the interrupt handler identifies mouse as the source of the interrupt and then prints the letter M on the screen. this happens more or less invisibly to the main program that is running; it just down by the time it takes to service the interrupt. The first thing M0USE.MOVE does is install the address of the interrupt (1RQHNDL) in the ProDOS 8 interrupt vector table using the ALLOC - I 1 RUPT command. If an error occurs, the program branches to ERROR and enters system Monitor. (An error occurs only if the interrupt vector table is full.) the next step is to initialize the mouse and enable mouse movement interrupts sending a mouse mode code of 3 to a subroutine called SETMOUSE. The address this subroutine, and all other standard mouse subroutines, begin somewhere in mouse interface's firmware in page $C4; the exact offset for each subroutine is in a table beginning at location $C412. The offset for SETM0USE is the zeroth in this table; the offsets for the other mouse subroutines used are indicated at beginning of the program. MOUSER is the standard subroutine the programs calls to execute a subroutine. It is responsible for setting up the correct subroutine address and plac the correct numbers in the microprocessor registers before passing control to - mouse firmware. When the mouse is moved, an interrupt occurs, and ProDOS 8 quickly 1RQHNDL. This subroutine first does what all good interrupt handlers should: determines whether the interrupt was caused by the expected source (that is, 270 Interrupts