Path: csiph.com!v102.xanadu-bbs.net!xanadu-bbs.net!news.glorb.com!xmission!news.bbs-scene.org!macgui.com!dog_cow From: D Finnigan Newsgroups: comp.sys.apple2.programmer Subject: Documentation for Multitasking Mouse Draw Demo Date: Fri, 21 Feb 2014 19:48:46 +0000 (UTC) Organization: Mac GUI Lines: 276 Message-ID: References: NNTP-Posting-Host: gunshin.wiredns.net Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Trace: technocratmedia-w1.news.bbs-scene.org 1393012126 29764 74.119.145.48 (21 Feb 2014 19:48:46 GMT) X-Complaints-To: abuse@bbs-scene.org NNTP-Posting-Date: Fri, 21 Feb 2014 19:48:46 +0000 (UTC) User-Agent: Mac GUI Usenet In-Reply-To: Xref: csiph.com comp.sys.apple2.programmer:1159 Documentation for Multitasking Mouse Draw Demo 20 Feb 14 For the benefit of all Apple II users, the source code is hereby surrendered into the public domain. I claim no copyright on the source code. === General Overview === This multitasking demo uses an AppleMouse card as a source of interrupts to power a small multitasking system. More properly, it might instead be called a multithreaded system. It has been tested to run on an Apple II Plus, a $FF ROM Apple IIc, and the Apple IIe standard and enhanced. There are three processes, or threads, depending on what you want to call them. They all share the same address space, and they all have a portion of the 6502 hardware stack devoted exclusively to them. Periodically, as the AppleMouse generates VBL interrupts, one process will be suspended, and another will be selected to run for some time. It is also possible for a process to voluntarily give up its CPU time by calling a yield subroutine. Together, these three processes cooperate to draw on the low-resolution graphics screen. There are two autonomous processes, controlled entirely by the Apple. One will plot a series of red blocks, the other, blue blocks. The third process is controlled by input from the operator. It will plot white blocks in the position of the mouse cursor when the mouse button is held down. When any key on the keyboard is struck, all white blocks will be changed to black. === System Initialization === The program loads at address $8000. Its first task is to prepare the screen by exiting from 80-columns firmware and then by setting the top of the text window in preparation for split graphics/text mode. The next objective is to locate an AppleMouse card. The search is performed "by the book," scanning all slots from 7 to 1 and looking for the ID bytes in the AppleMouse firmware. If found, BASEADDR will contain the low-byte and high-byte of the AppleMouse, $00C4, for example. MSLOT will contain the slot number times 16, $40, for example. These two locations are used for calls to the AppleMouse firmware. Finally, the slot number will be printed on the text screen. If no AppleMouse card could be located, the message "NO MOUSE" is printed on the screen, and the program will jump to the DOS warm start location. After the AppleMouse card is found, program execution jumps to the CREATEALL routine. This routine is responsible for creating the three processes. It starts by clearing the 6502 interrupt disable flag, then setting the flag. All changes to the interrupt flag are done "by the book," that is, by using the combination PHP SEI to disable, then PLP to enable once more. The first byte of STKTAB, the stack table, contains the total number of active processes. This number is incremented when each process is created, but it is never used thereafter. The rest of STKTAB holds the current stack pointer for each process while it is suspended. The first process starts with its stack pointer at $D0, the second at $B2, and the third, $94. These initial values are stored in STKTAB using the Y-register as a process ID offset. Next, the three processes are created. The Y-register holds the process ID, the A-register holds the low-byte of the starting address, and the X-register holds the high-byte of the starting address. The subroutine NEWPROC actually creates each process. NEWPROC first compares the intended process ID with MAXP, which holds the maximum process ID allowed. This comparison is, obviously, a mere formality in this case. Newly created processes look as if they have just been suspended by a VBL interrupt. Therefore, NEWPROC will push the program address in the proper stack location, indexed from STKTAB, and then will push the starting processor status and register values, all 0, following. Finally, the initial stack pointer for the process, which has been decremented with these stack additions, is stored back in its place within STKTAB. === Enabling interrupts === At this point so far, the three processes have been created, but interrupts remain off. Program execution now jumps to the VBLON subroutine to enable AppleMouse VBL interrupts which occur 60 times each second. VBLON first sends the mode byte $01 to the AppleMouse, which enables the mouse firmware, but not interrupts. SETMOUSE and INITMOUSE perform the mouse initialization. The address of the interrupt handler, IRQ, is stored at $3FE. Once the interrupt handler is prepared, the programs sends the mouse status byte of $09, which enables VBL interrupts, to the AppleMouse firmware. The final initialization task is to store $FF in ACT, which stores the active process ID, and $13 in CTR, which stores the interrupt tick counter. Now the AppleMouse is pulling the IRQ line to ground on the 6502, but these requests are being masked out by the interrupt disable flag. PLP allows these interrupts to get through, and the fun begins! === The interrupt handler === After interrupts are enabled for the first time, the system initialization program jumps into an idle loop, but this loop will probably never be executed again because the three processes start running. Execution should jump immediately to the IRQ handler because of the AppleMouse VBL interrupt. The problem of how to bootstrap the initial process is that we want it to run first and we do not want the registers from the system initialization stage to get mixed in. After system initialization, each process looks like it has just been suspended. The IRQ handler has no way to tell that the processes are in fact, newly created, and have never run. The solution was to make some special case code and set ACT, the active process ID, to $FF. Process scheduling is round-robin, and the process ID is merely incremented to get the next one. So $FF rolls over to $00, and the first process is selected to run. The IRQ handler starts by first pushing all the registers on the stack. Each process gets an exclusive portion on the stack, and the stack pointer is adjusted when selecting the next process to run, so there is no need to perform any special routine to save the registers. The very first time the interrupt handler runs, however, the registers pushed on the stack don't belong to any of the three processes. They are the registers from system initialization. This is not a problem, because as mentioned in the previous paragraph, the next process ID will roll over to $0, and the initial, zeroed-out registers, will be pulled from the appropriate location in the stack. After saving registers, SERVEMOUSE is called to ensure that the AppleMouse caused the interrupt. If so, READMOUSE is called to get the current cursor coordinates and button status. These values are saved in some zero page locations, MOUXPOS, MOUYPOS, and MOUSTAT, so they are accessible to running processes. The next step is to increment the tick counter, CTR. This counter increments for each mouse interrupt, and it gives each process a fair share of CPU time. A process can also choose to give up its CPU time by calling the YIELD subroutine, which will be described later. When the counter reaches $14 (decimal 20), the process is suspended and the next one is selected to run. Because the AppleMouse interrupts the CPU 60 times per second, this means that context switches occur approximately 3 times per second. The system initializes CTR to $13, so it will be incremented to $14, thus process 0 will be the very first process to run. If CTR did not reach $14 after being incremented, the interrupt handler is done, and it restores the registers. The stack pointer was not changed, so RTI leads back to the interrupted instruction of the same process. However, if CTR did reach $14, then the CPU time for the current process is expired, and it is time to select the next process to run. CTR is reset to 0, then ACT is loaded to the Y-register to examine its contents. If it contains $FF, then a special branch is made to bootstrap the initial process, as has already been explained. If not, the stack pointer is then stored in the STKTAB, indexed on Y, which contains the process ID of the process which was just interrupted. The Y-register is incremented to point to the next process ID. If it equals MAXP, which is 3, it rolls over to 0 again, since there are only three process IDs. ACT is updated with the new process ID from the Y-register. The context switch is simple, consisting of just 3 lines that load the stack pointer for the next process from the STKTAB. The speaker is clicked to give audible feedback. With the stack pointer adjusted, the register restore operations at IRQDONE pull the correct contents from the stack, and RTI sends the CPU operation back to where the next process had previously been suspended. === Giving up CPU time with YIELD === So far, the system described has been operating in a preemptive fashion: each process is allowed to run for a fraction of a second, then it is forcibly suspended so that the next process can run. It is also possible for a process to volunteer for preemption, that is, give up its remaining CPU time so that the next process may run. A subroutine called YIELD, which may be classified as a system call, performs this function. A program will perform a JSR to YIELD to give up its CPU time. YIELD needs to account for the difference between a JSR and a CPU interrupt. In the former, the processor status is not pushed on the stack, and the return address is pushed, but it is one less than the effective address. An interrupt, on the other hand, does push the processor status, and the return address is the effective address for resuming the interrupted program. YIELD begins by pushing the processor status to the stack, like an interrupt request does. It then disables interrupts, and proceeds to push the remaining registers to the stack. The next step is to fix the return address for the RTI. It needs to be incremented. The stack pointer is transferred to the X-register, which is then incremented by 5 to point to the low-byte of the return address. It is incremented, and if necessary, so is the high-byte. Program execution jumps to RESETCTR to reset the tick counter to 0, then the next program is selected to run, as usual. === The processes === As already mentioned, there are three processes. Two are completely controlled by the CPU, and the third is controlled by the user. Thus the two may be thought of as background daemons with the latter being the foreground process. Process 0 performs the setup work for a low-resolution drawing program using the mouse. It disables interrupts, then it sets the X and Y clamps to be 40x40. It then calls HOMEMOUSE to ensure that the cursor is within these boundaries. Interrupts are briefly enabled while the MIX and GR soft-switches are referenced to change the screen to low-resolution graphics with text. Most all of the Monitor subroutines are not reentrant because they store some values in zero-page locations. This means that only one process can call a Monitor routine at a time, and in order to enforce this policy, the process must disable interrupts for each Monitor call with PHP SEI, then enable with PLP afterward. After the screen is switched to graphics mode, CLRTOP is called to clear the top 40 rows to black, leaving just the text screen visible, which should still show the AppleMouse slot number. The main event loop of process 0 follows. First it will check the keyboard for any key press. If there is one, the program will jump to :CLRIT to clear all white blocks. If no key was pressed, then the mouse button status is examined to see if the button is pressed. If no button was pressed, then the loop goes back to examine the keyboard again. If the mouse button is down, a white block is plotted at the cursor coordinates which are stored in MOUXPOS and MOUYPOS. These are zero-page locations which were updated by the IRQ handler. If a key was pressed, the program will loop over the entire 40x40 screen in search of white blocks with the SCRN Monitor subroutine. If any are found, they are plotted over with black using the PLOT subroutine. Afterward, the message CLEAR is printed on the text screen. Interrupts are only disabled for the SCRN and PLOT subroutine calls. Processes 1 and 2 operate alike with only one meaningful difference in respect to multitasking. Both will plot a sequence of blocks on the screen, starting from the top or bottom row and working toward the opposite side. To distinguish the two, process 1 only plots red blocks, and process 2 only plots blue blocks. The meaningful difference is that process 1 only calls YIELD after it has plotted an entire row of blocks, whereas process 2 calls YIELD after each plot. Due to the use of YIELD in these two processes, the majority of CPU time is devoted to process 0, and thus the user operating his drawing program does not perceive any lag or delay. === Conclusion and improvements === This program demonstrates that it is possible to use interrupt-enabled, multitasking programs in a meaningful way on the 8-bit Apple II. Defensive programming practice, exercised by calling YIELD, means that process 0 is given highest priority. Therefore, the user experience does not suffer when there are two background processes also using CPU time. Some improvements of this system would be to allow variable process priorities by making a CTRTAB, similar to the STKTAB. This would also be indexed on the process ID in the Y-register, and it would contain a maximum tick count to be reached before the context switch occurs. This would be in substitute of the current system, which gives all processes up to $14 ticks on CTR before a context switch is forced. Another improvement would be to take advantage of the bank-switching scheme of the Apple IIe with a RamWorks-style AUX memory card. Each process would be granted its own stack, which would eliminate the need to subdivide the single 6502 stack as the program currently does.