granati wrote:
BigDumbDinosaur
A little more explanation about the fact that i decided to catch the signature byte in COP handler.
- separation of bios calls and o.s. calls
- interface to floating point routines by the cop handler.
- more flexibility and efficiency changing the handler to a specific function (by changing the address on a table)
Even if one can reach any goal just with COP $XX and passing an index (ignoring $XX), i think is more clearly a logic separation of functions, assigning a signature to a specific field.
Marco
I actually considered using software interrupts (COP) to call API functions in POC's BIOS, but scrapped the idea after looking at the amount of front- and backend code that was required to do it properly. Since the BIOS is in ROM, its location is fixed, which eliminates one of the principal reasons for using a software interrupt API. The API entry jump table can be made static, allowing the treatment of BIOS functions as subroutines instead of software interrupt service routines. Also, calling BIOS API functions via JSR is a lot less complicated for both the caller and the BIOS. As a bonus, execution is substantially faster, as the overhead involved in processing a software interrupt (especially stack activity) is completely avoided.
Execution speed becomes important when you consider that some API functions may be called hundreds or thousands of times in rapid succession. For example, the
putcha function (put a character on TIA-232 channel A) would be called some 2000 times in a small fraction of a second to repaint the console screen. The call as I have it arranged is:
Code:
lda #char ;character to be printed
jsr putcha ;buffer it for transmission
Inside
putcha, the registers are preserved and the usual operations required to buffer a byte into a FIFO are carried out, followed by register restoration and an RTS. It's simple, clean and fast.
If I were to do it with software interrupts as you describe, I would need front end code to get the COP signature from the bank in which the calling program is located (which involves copying the value of
PB to
DB after
DB has been preserved), restart IRQs, check the signature for range, use it to index a table that tells the called function how many parameters to expect, and then use that index to look up the address of the function and go to it. All of that gets executes
before the actual work of transmitting a character gets done. When the function was completed, it would have to go to a back end that would update the registers as required, and then execute return-from-interrupt steps. Just my opinion, but it's way too much work just to call a BIOS API, given that the BIOS is almost always running in ROM, which means it's static.
Now, in the case of an operating system kernel running in RAM, calling APIs via software interrupts makes sense, especially if attempting to implement memory protection—you wouldn't want to expose a jump table to user-mode applications. That is the model I will be using when I develop my 816NIX kernel. However, I still won't be using the COP signature. Every API in the kernel will take pointers to data, which pointers will be pushed to the stack prior to the API call. It's modeled on the method used in System V UNIX.
Consider a C program to create a new file:
Code:
/* create & open a new file in ANSI C */
char fname[] = "/usr/bdd/newfile"; /* pathname */
int main() {
int fd; /* file descriptor */
fd = creat(fname,0664); /* create & open file */ }
return(fd); /* return file descriptor to caller */
}
If the above were compiled on an MC68000 system running the System V UNIX kernel you might get:
Code:
* machine code generated in main()...
*
move #$01b4,(sp) * push mode to stack
move #$41d7,-(sp) * push pathname pointer to stack
jsr creat * call creat API library code
*
*
* creat() kernel API call library machine code...
*
creat moveq #$08,d0 * load register D0 with creat API index ($08)
trap #$00 * invoke kernel API
bcs _error_ * if error, branch w/error code in D0
*
rts * file created & opened, file descriptor in D0
*
_error_ ...handle error processing...
The pathname pointer ($41D7) is a made-up value.
The equivalent on a 65C816 system using a System V UNIX API model would be:
Code:
;machine code generated in main()...
;
pea #$01b4 ;push file mode to stack
per $41d7 ;push pathname pointer
jsr creat ;call creat() library function
;
;
;creat() kernel API call library machine code...
;
creat sep #%00100000 ;select 8 bit accumulator
lda #$08 ;create() API index
cop $00 ;transfer execution to kernel
bcs _error_ ;kernel API returned an error
;
rts ;file created & opened, file descriptor in .A
;
_error_ ...handle error processing...
Note that
TRAP #$00 in the MC68000 code is the analog of
COP $00 in 65C816 assembly language. Like the '816, the 68K supports multiple trap numbers, but they are not used. Instead, the folks who wrote the kernel API library for use on the 68K decided to stick with what had been earlier implemented on the DEC PDP-11, which was to use a general purpose register to pass the API number to the kernel. It turned out to be more succinct that using the 68K's
TRAP #$00 vectoring capability.
In the case of the 65C816 model, passing the API index number in a register completely eliminates the work required to load the signature, excising many clock cycles out of the API front end. Unlike the MC68000, whose
TRAP instruction is vectored in hardware according to the trap number (which feature is not used in System V UNIX and Linux implementations),
COP is not (it sees only the vector at $00FFE4 in native mode), which means your code has to synthesize what the 68K does internally. Makes sense to me.