Diddling
SR to indicate return status is really not much of a chore. If the API processing front end is properly structured accessing the stack copy of
SR is no different than accessing any other register's stack copy. In the code that I have tested on POC V1.1, the API processing front end is as follows:
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 ;preserve MPU state
phx
phy
cli ;restart IRQs
and #$00FF ;mask noiose 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
jmp (apidptab,x) ;run appropriate code
;
;
; invalid API index error processing...
;
icop01 ...handle invalid API index...
At the point where IRQs are restarted the stack "picture" will look like the following:
Code:
; register stack frame...
;
reg_y =1 ;16 bit .Y
reg_x =reg_y+2 ;16 bit .X
reg_c =reg_x+2 ;16 bit .A
reg_sr =reg_c+2 ;8 bit SR
reg_pc =reg_sr+1 ;16 bit PC
reg_pb =reg_pc+2 ;8 bit PB
s_regsf =reg_pb+1-reg_y ;register stack frame size in bytes
The above are positive offsets relative to the stack pointer. There will also likely be other elements higher in the stack, but they aren't relevant for now.
With the above established, the programmer can readily modify any register and thus pass a value back to the user-space program when the MPU state is restored. So, if I want to set carry to tell the caller there was an error I can:
Code:
lda reg_sr,s ;stack copy of SR
ora #%00000001 ;set carry bit &...
sta reg_sr,s ;rewrite
The above assumes the m bit in
SR is
1 (8-bit accumulator). No other shenanigans are needed. The same technique would make it possible to pass a value back in any register, or even reroute execution of the user-space program, the latter by overwriting the word at offset
reg_pc with a different address.
whartung wrote:
On the IIGS, they have different entry points for different functions. The Toolbox (memory, graphics, etc.) has one entry point, GS/OS (disk/file system) has another. You push the toolbox id of the routine you want on to the stack (along with the parameters), and then invoke the toolbox -- which then dispatches appropriately.
That seems to be unnecessarily cumbersome when use of a software interrupt would require only an API index, and the caller wouldn't even have to know where in RAM to kernel entry point is located.
Quote:
If your system is "always" in native mode, the saving of the SR may be mostly wasted time. Simply, the software would be responsible for ensuring that the accumulator and index modes are properly set (to whatever values they need to be) rather than having the kernel routines constantly do this.
Having studied the design of the UNIX kernel in depth, I'd have to say that the code bits that are executed in the API front- and back-ends are trivial compared to the low-level code that does the actual work. You yourself noted this, e.g., waiting on a disk sector to load.
As for saving machine state, any operating system must afford generality to the user-space so programs running in the latter aren't unnecessarily constrained by limitations and/or fallacious assumptions built into the kernel. In that vein, you cannot make assumptions about what was going on at the time of an API call. Since the API front-end won't know whether the registers are set to 8 or 16 bits you will need to save
SR so you can put things back the way they were at the time of the API call. If
COP is used to call an API the preservation of
SR is automatic.
Also, if the operating system is able to support preemptive multitasking, it is imperative that machine state be full preserved upon an entry to the kernel. The reason is it is the kernel that will preempt a process when it is time to do so. Preemption will occur during the final stages of interrupt processing, whether that interrupt is hardware or the software interrupt used to access an API (in simplistic terms, the task switch comes by copying machine state from the stack to user-space storage and then copy the saved machine state of the preempting process from user-space storage to the stack; when interrupt return occurs, the new task will be running). If the API front end hasn't saved machine state and preemption occurs, the interrupted process will not be able to restart when it is its turn to run again.
commodorejohn wrote:
I'm just waiting for one of the resident Forth junkies to ask what on earth you'd ever need an operating system for.
Not to rile up any Forth junkies, but that is very much a niche situation. The overwhelming majority of computers in use today have an operating system, and Forth can certainly avail itself of operating system services, such as file access, time-of-day facilities, etc., but still be Forth.
BigEd wrote:
So, I think we have three ideas for an OS ABI on an '816:
JSL to one or one of several nailed-down addresses
COP with signature byte to select one of several services
COP with an ignored signature byte and with a service selector in a register
That's a good summary.
Quote:
It's normal enough to return results in registers, in a parameter block assembled by the caller, to well-known addresses, on the stack, or in the status flags. Or some mixture. As Jeff notes, there's more than one way to return status in the flags even when using COP.
How to pass returns to the caller are an interesting topic in themselves. Using UNIX running on MC68000 hardware as an example, if I create/open a file and the operation is successful, the kernel will say so by clearing carry and loading the file descriptor (a positive integer) into register
D0. If the operation fails for any reason, the kernel will say so by setting carry and loading an error code into
D0. This is the basic mechanism by which much of the kernel reports back to the user-space caller. (However, note that the standard C library modifies this mechanism, reporting an error in the global variable
errno and
-1 in place of a file descriptor.)
In some cases, the API that has been called will produce something other than just a integer. For example, in Linux we have
mkstemp(), which generates a temporary filename using randomized data to populate a template, and then creates and opens the file. One would call
mkstemp() from C as follows:
Code:
fd = mkstemp(char *template);
template is a pointer to a filename of the form
nameXXXXXX. For example,
payablesXXXXXX. Assuming a successful call,
fd will return a positive integer—the file descriptor—and the
XXXXXX field in the filename will be populated with randomized characters.
In order for the filename template to be so modified, the kernel has to be able to write into user space, since that is where the filename will be ensconced. This would be accomplished by indirectly writing through the filename pointer that was push to the stack prior to the API call, easy enough to do with 68000 and x86 hardware, but a little more involved with the 65C816.