IMPLEMENTING AN OPERATING SYSTEM API IN A 65C816 SYSTEM
Re: IMPLEMENTING AN OPERATING SYSTEM API IN A 65C816 SYSTEM
That does sound like a good idea. Acorn's OSWORD interface is similar, in that a parameter block is passed (the address is passed in X and Y) and that block of memory can be used both for inputs and outputs. Using the stack makes sense, on a processor which has good stack access (and lots of stack space).
Re: IMPLEMENTING AN OPERATING SYSTEM API IN A 65C816 SYSTEM
BitWise wrote:
BigEd wrote:
One of the simple and tempting things to do to improve the '816 memory map is to hide the vectors until they are needed (using the vector pull signals) - that works well with COP but not with jumps. With the OS in low memory you can then have contiguous memory starting part way through bank 0.
This approach can also be extended to handle COP and BRK vectors too. When VPA is asserted and the data bus reads $02 or $00 (respectively), this same hardware can load the JML instruction to the COP-/BRK-handler. You'll need the help of a CPLD (at least) to implement this in a space-efficient manner, but it ought to be well within the realm of feasibility.
Re: IMPLEMENTING AN OPERATING SYSTEM API IN A 65C816 SYSTEM
The other thing that I've not seen mentioned is the JMP ($xxxx,X) instruction. Load the function ID into X (ensuring bit 0 is clear), then JML, BRK, or COP. That leaves A,Y for passing an address to a parameter block, or using the stack.
The reason you want to avoid using a single COP and a single pool of function IDs as BDD feels more comfortable with is because there is **zero** performance advantage and **negative** usability advantages to monolithic kernels on the 65xx architecture. You absolutely want the ability to dynamically load and unload modules/libraries (exactly the same thing in 65xx land), and these modules can be (and almost certainly will be) loaded at a different address every time you load them.
So, if you use COP, you'll want one register to select which module you're addressing, and X to select a function within that module. A better solution is to use a well-known kernel function similar to Amiga's OpenLibrary() call which manages library sharing and lifecycle, and returns the address of the library just opened (thus, if three processes all use the same library, they will all get the same address back). This pointer can be saved into your program's data area like so:
For example, here's how I'd 65816-ize the start-up code found in Tripos-compatible applications, assuming someone were to port Tripos to the 65816:
As you can see, now you can call the library directly without incurring any kernel overhead by loading your function selector in X and executing JSL libA. Easy peasy, it's probably the 2nd or 3rd fastest method of invoking services on a 65xx CPU (you might remember an earlier set of reasoning I did here on these forums a decade or so ago about how to support OOP dispatch -- it's exactly the same problem), and it's infinitely (RAM permitting) open-ended. You're not limited to just what the kernel offers in functionality. (While I enjoy the everything-is-a-file metaphor, I don't believe it should be followed religiously. There are legitimate counter-cases where this is demonstrably not true, such as GUIs).
The reason you want to avoid using a single COP and a single pool of function IDs as BDD feels more comfortable with is because there is **zero** performance advantage and **negative** usability advantages to monolithic kernels on the 65xx architecture. You absolutely want the ability to dynamically load and unload modules/libraries (exactly the same thing in 65xx land), and these modules can be (and almost certainly will be) loaded at a different address every time you load them.
So, if you use COP, you'll want one register to select which module you're addressing, and X to select a function within that module. A better solution is to use a well-known kernel function similar to Amiga's OpenLibrary() call which manages library sharing and lifecycle, and returns the address of the library just opened (thus, if three processes all use the same library, they will all get the same address back). This pointer can be saved into your program's data area like so:
Code: Select all
; somehow get address of library in A,X
sta libA+1
stx libA+3
php
sep #$30
lda #OPCODE_JML
sta libA
plp
Code: Select all
_start:
; First, call the kernel to find our DOS entrypoint.
ldx #sc_FindDos
cop #0
sta DosLib+1
stx DosLib+3
; Now, open a file, then close it again, just because we can.
pea #MODE_OLD
pea #>filename
pea #<filename
ldx #LVO_Open
jsl DosLib
; A already has the reference to the SCB if the file existed.
; For brevity, I omit error handling.
pha
ldx #LVO_Close
jsl DosLib
; Clean up the stack. If we used more than 4 slots,
; then TSC;ADC;TCS would be smaller.
plx
plx
plx
plx
; Exit back to the shell.
ldx #LVO_Exit
pea #0
jsl DosLib ; never returns.
.data
DosLib:
jml $000000
filename:
.byte "RAM:Foo/Bar",0
Re: IMPLEMENTING AN OPERATING SYSTEM API IN A 65C816 SYSTEM
Just as contribution to this discussion, in my c16 computer i'm developing system calls through 'cop' instruction; i use 'signature byte' as 'function number'.
For example, printing a character to console, is accomplished with this code:
The code i use for cop handler can be see at http://65xx.unet.bz/c16sw/index.php.
Error condition is returned raising the carry flag (the error code in lower byte of y register).
System functions can reside anywhere in memory, but not in bank 0, and parameters can be
passed through registers or/and through stack.
The table that hold the system function address (3-bytes) use a 4-th byte for hold the count
of bytes passed on stack (for clean stack when cop handler exit).
Marco
For example, printing a character to console, is accomplished with this code:
Code: Select all
lda #'A'
cop $00
Error condition is returned raising the carry flag (the error code in lower byte of y register).
System functions can reside anywhere in memory, but not in bank 0, and parameters can be
passed through registers or/and through stack.
The table that hold the system function address (3-bytes) use a 4-th byte for hold the count
of bytes passed on stack (for clean stack when cop handler exit).
Marco
http://65xx.unet.bz/ - Hardware & Software 65XX family
- BigDumbDinosaur
- Posts: 9425
- Joined: 28 May 2009
- Location: Midwestern USA (JB Pritzker’s dystopia)
- Contact:
Re: IMPLEMENTING AN OPERATING SYSTEM API IN A 65C816 SYSTEM
A bump to this topic.
Starting with POC V1.3, the BIOS API is called using COP #N, where N is the desired service number. I had also tried out a BIOS version that used JSL to called APIs, but determined that using COP produced smaller code, both in the BIOS itself and in the applications that call for API services. The difference in performance of COP - RTI compared to JSL - RTL is only one cycle, not considering the front- and backend processing that is involved.
Starting with POC V1.3, the BIOS API is called using COP #N, where N is the desired service number. I had also tried out a BIOS version that used JSL to called APIs, but determined that using COP produced smaller code, both in the BIOS itself and in the applications that call for API services. The difference in performance of COP - RTI compared to JSL - RTL is only one cycle, not considering the front- and backend processing that is involved.
Last edited by BigDumbDinosaur on Thu Oct 16, 2025 7:39 am, edited 1 time in total.
x86? We ain't got no x86. We don't NEED no stinking x86!