GARTHWILSON wrote:
BRK with the signature byte following is probably the most compact way, but puts an extra burden on the ISR and dramatically cuts the performance when you have real interrupts. I don't like BRK myself, and have never used it except in school in 1982.
LDX #<routine number>, JSR to the routine that does the jump table would be five bytes which is not terrible, but there's still kind of a lot of cycles of overhead.
My 816NIX operating system that will soon be operating
will use COP for calling user-accessible APIs. The basic process will be for the calling function to push a stack frame with whatever parameters are needed by the API, load .C (C-accumulator) with the non-zero API number and then execute COP $00. The COP ISR front end will push the registers, check the API number for range and if in range, ASL it to generate a 16 bit index and give the index to .X (X-register). A JMP (<apitab>,X) instruction will route execution to the appropriate API handler. As individual APIs have varying numbers of parameters, each one will be responsible for processing the caller's stack frame.
When an API has finished it will jump back to one of two common exit points: one to handle a successful completion and the other to handle any error conditions. The successful completion handler will write any return register values to the appropriate stack locations and clear the carry bit in the stack copy of the status register (SR). The error condition handler will write an error number into the accumulator stack entry and set the carry bit in the stack copy of SR. The final steps before RTIing back to the caller will be to realign the stack to discard the parameters pushed by the caller and reload the registers from the stack. It sounds somewhat convoluted. However 65C816 makes it pretty easy to implement this scheme.
My reasons for going with a software interrupt API call instead of a jump table are several:
- Portability. The software interrupt method allows the kernel to be anywhere in RAM, as user mode processes don't have to know where to look for a jump table. I can load the kernel into any of the 256 possible RAM banks in an '816 system and not have to inform user mode processes about it. All that is necessary is to point the COP vector at $00FFE4-$00FFE5 to the COP ISR front end, which can then JML (JuMp Long) to wherever the kernel is located.
- API stability. If no API jump table is required then one doesn't have to be updated if a new kernel API is added. If a jump table were used, every program that makes a kernel call (which would be just about every program) would have to be reassembled, recompiled or relinked to link in the new jump table. On the other hand, using the software interrupt method means that only the next unused API number has to be added to the API number table. Programs that were written prior to the change don't have to know about the new API table unless they are going to be modified to use the new API.
- Context change. POC V2's hardware will have some form of memory protection and (eventually) a user/supervisor mode context feature. The hardware will be able to tell when a kernel call has been made because a software interrupt causes the '816's VPB (vector pull) output to negate during cycles 7 and 8 of interrupt acknowledgement. This will cause the switch to supervisor mode, allowing code to touch hardware registers, etc., things which user mode processes won't be allowed to do. I wouldn't be able to readily implement this feature if API calls were via a jump table.
I decided to use COP instead of BRK for API calls so BRK could remain a means of interrupting program execution and starting an M/L monitor. BRK on the '816 running in native mode doesn't use the IRQ vector, so the overhead of testing for the B-bit in the SR copy wouldn't be present, even if I did use BRK instead of COP. As Garth noted, having to test for the B-bit in a 65C02 ISR does slow down interrupt processing a bit and of course, clobbers both .A and .X.
Quote:
Another possibility is that instead of the list of addresses composing the table that you index into, you JSR (with no indexing) into a list of three-byte JMPs to the actual routine addresses which can change as often as you like. This list of JSRs can be as long as you want. So for example if you have a routine to output a character to the current display or print device and it's called "OUTCH" for "output character" and its JMP line is permanently fixed to address $E211, you might do JSR OUTCH (ie, JSR $E211) in your program in RAM, and that takes you to the line in ROM where it says JMP ____ to wherever the real routine is regardless of how many times you've revised the ROM.
That's the classic kernel jump table that was implemented in all 8-bit Commodore hardware.