In the new firmware, I will use the COP signature to select the API so I can avoid using the stack to pass parameters. There are some functions, such as reading from or writing to NVRAM, in which all three registers are needed to pass parameters. For example, reading from NVRAM will require that the caller pass a 32-bit pointer to the buffer into which the NVRAM contents, along with a byte count (0-255, which becomes 1-256). So the code to call the read-from-NVRAM API would look something like the following:
Code: Select all
sep #%00100000 ;8-bit accumulator
rep #%00010000 ;16-bit index
lda #<count> ;bytes to fetch
ldx !#<buf> & $FFFF ;buffer address LSW
ldy !#<buf> >> 16 ;buffer address MSW
cop #knvrget ;call NVRAM fetch APIThat sort of thing can be buried in a macro:
Code: Select all
getnvram .macro .count,.buf
sep #%00100000
rep #%00010000
lda #.count
ldx !#.buf & $FFFF
ldy !#.buf> >> 16
cop #knvrget
.endm...which reduces the call to:
Code: Select all
getnvram 127,buffer...to get 128 bytes and store them at buffer. Obviously, I can't do that if one of the registers is expected to pass the API index.
When I write a loadable (from disk) kernel I will use the UNIX-style convention of calling APIs. I don't want to do that in the firmware because of the space that would be taken up with the stack manipulation code. A loadable kernel will have more elbow room for code and since it would be running from RAM, the execution time penalty of dealing with a stack frame will be partially offset by the avoidance of any wait-states.
My plan is to write a set of macros for calling kernel APIs, instead of trying to keep all that assembly language mumbo-jumbo straight. A necessary capability of such macros is that of being able to generate a stack frame from macro parameters, which the follow macro does:
Code: Select all
; generate stack frame from 1 or more words...
;
pushparm .macro ... ;arbitrary number of parameters allowed
;
.if %0 ;if non-zero count...
.i .set 0 ;initialize parameter index
.rept %0 ;repeat following code for number of parameters passed
.i .= .i+1
pea #%.i ;push a parameter
.endr ;end of repeated code
pea #.i ;push number of words in stack frame
.else
.error "error: macro syntax: "+%0$+" parm1[,parm2[,parm3]] ..."
.endif
.endmThe leftmost parameter will be at the top of the stack frame, and at the bottom of the stack frame will be a word indicating how many parameters are in the frame. That information can be used by APIs that accept a fixed number of parameters to detect if there is a mismatch and abort with an error, rather than do something silly. In the case of an API that accepts a variable number of parameters, the count word will tell the API what to expect.
The above is meant for use by API macros to build the appropriate stack frame prior to calling the API function. pushparm could also be used in a user application to generate a stack frame.
As pushparm pushes words, the higher-level macro has to handle the case in which an address, which will be a 32-bit value, is being processed. For example, the following macro would read N bytes from an open file descriptor FD and deposit said bytes into buffer BUF:
Code: Select all
read FD,BUF,NThe macro's code would be:
Code: Select all
; read data from open file...
;
read .macro .fd,.buf,.n ;file descriptor, buffer pointer, bytes to read
pushparm .fd,.buf >> 16,.buf & $FFFF,.n
;
; ———————————————————————————————————————————————————————————————
; above generates a DWORD pointer from the address passed in .buf
; ———————————————————————————————————————————————————————————————
;
cop #kread ;call kernel "read" API
.endmThis basic pattern would apply to all macros that work with addresses.