BigEd wrote:
My inclination though would always be to use the accumulator to select the api call, and use X and Y for the first two parameters...
When UNIX was ported to the MC68000, the system call API was arranged so the index was loaded into register
D0, which would mirror the idea of loading it into the 65C816's accumulator. Although the 68000 has more general purpose registers than the '816 they are not used for parameter-passing. It's all done on the stack.
In my "treatise" on
65C816 interrupt processing, I presented an API calling procedure in which the index is passed in the accumulator and parameters are passed via the stack. The call is invoked with
COP—the signature byte is ignored. The example is one of taking the ANSI C library code that would open a file and seeing how it would be handled in 65C816 assembly language.
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 C library were running on an '816, we might see a subroutine such as the following for the
creat() API call:
Code:
;create new file...
;
pea #$01b4 ;push file mode to stack
pea #$41d7 ;push pathname pointer
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
;
_error_ ...error processing...
Within the kernel, front-end code such as the following would be executed when the COP instruction is encountered:
Code:
;KERNEL API FRONT END — EXECUTED IN RESPONSE TO A COP INSTRUCTION
;
; ——————————————————————————————————————————————————————————————————
; .A must be loaded with the 8 bit API index prior to executing COP.
; ——————————————————————————————————————————————————————————————————
;
icop rep #%00110000 ;16 bit registers
pha ;save .A for return access
phx ;preserve .X &...
phy ;.Y if necessary
cli ;restart IRQs
and #$00ff ;mask noise in .B (16 bit mask)
beq icop01 ;API index cannot be zero
;
dec a ;zero-align API index
cmp #maxapi ;index in range (16 bit comparison)?
bcs icop01 ;no, error
;
asl a ;double API index for...
tax ;API dispatch table offset
sta apioff ;save offset &...
jmp (apidptab,x) ;run appropriate code
;
;
; invalid API index error processing...
;
icop01 ...handle invalid API index...
The theory for this procedure is there are API calls that require three or more parameters. In some cases, the parameter count could be variable. For example, opening a file in Linux can be qualified with some additional flags to tell the kernel to create the file if it doesn't exist. In fact, a call to
open() could involve anywhere from two to seven parameters.
Logically, such a requirement could be handled by pushing the file-handling parameters along with a parameter that tells the API code how many parameters are to be processed. In my opinion, doing this with a mix of register loads and stack pushes would quickly become unwieldy. None of the UNIX implementations about which I have internal knowledge take that approach; they all use the stack for parameters and a single register for the API index. Such a philosophy makes sense for the sake of uniformity. If all APIs use only the stack for parameter-passing a common front- and back-end mechanism can be used in all cases. As the '816 has instructions for conveniently managing the stack, it seems sensible to me to take advantage of them.
Quote:
A pity that the COP's operand isn't much use.
The signature byte can be gotten by preserving
DB, loading
DB with the stack copy of
PB, fetching the stack copy of
PC, copying it to
.X and decrementing it. An
<addr>,X load would then be made to get the signature byte, where
<addr> would be
$0000 (not
$00, which would be direct page and result in the load coming from bank
$00). It's major hoop-jumping just to get one byte. Better, I think, to supply the API index in a register or on the stack.