; gnoGsos.s — GS/OS calls from inside a GNO/ME process.
;
; GNO patches GS/OS entry $E100A8 to its interceptor (OurGSOS in
; kern/gno/gsos.asm), which reads the call number + parameter-block
; pointer from the 6 INLINE bytes after the JSL and bumps the return
; address +6 to skip them:
;
;     jsl  $E100A8
;     dc   i2 callNum        ; 2 bytes
;     dc   i4 pBlockPtr      ; 4 bytes (bank : offset)
;     <-- OurGSOS returns here
;
; The plain stack-based form (callNum in X, pBlock pushed) that bare
; GS/OS accepts does NOT work under GNO.  We self-modify the inline
; callNum + pBlock operands, then dispatch.
;
; __gnoGsosCall(void *pBlock, unsigned short callNum) -> u16 error
;   C ABI: arg0 (pBlock ptr32) in A:X (A=offset, X=bank); arg1
;   (callNum i16) at (4,s).  Returns the GS/OS error in A (0 = OK).

	.text
	.globl __gnoGsosCall
__gnoGsosCall:
	rep #0x30
	sta __gnoPBlock          ; inline pBlock offset (low 16)
	txa
	sta __gnoPBlock+2        ; inline pBlock bank+pad (X = bank : pad)
	lda 4, s                 ; callNum from the stack
	sta __gnoCallNum         ; inline callNum
	; DP is ALREADY our per-process page: crt0Gno honors GNO's DP and never
	; forces DP=0, so codegen's bank-0 DP scratch stays process-private and
	; two of our binaries running concurrently no longer alias a shared $B4
	; saved DP.  GNO's interceptor keys the caller off curProcInd (scheduler
	; state), NOT off DP: StackGSOS (kern/gno/gsos.asm) does `ldx curProcInd`,
	; brackets the call with its own phd/pld, and installs its own DP work
	; area, so the caller's DP is preserved by GNO regardless of its value.
	; The phd/pld here is a harmless-redundant belt-and-suspenders; the GS/OS
	; error is returned in A (0 = OK) and pld does not touch A.
	phd
	jsl 0xe100a8
__gnoCallNum:
	.word 0                  ; patched: GS/OS call number
__gnoPBlock:
	.long 0                  ; patched: param-block pointer
	; --- OurGSOS returns here (return addr bumped +6); error in A ---
	pld                      ; restore our per-process DP (A = error, untouched)
	rtl


; __gnoQuit(void *pBlock, int status) -> noreturn
;   GS/OS QUIT ($2029) that carries the REAL exit status.  Same inline-dispatch
;   trick as __gnoGsosCall, but with ONE critical difference: the call number is
;   a hardcoded inline CONSTANT (.word 0x2029), leaving A free to hold `status`
;   at the trap.  GNO's StackGSOS does `ldx curProcInd; sta exitCode,x` on entry
;   (kern/gno/gsos.asm), so whatever is in A at the JSL is recorded as this
;   process's exit code and later decoded by the parent's wait() (WEXITSTATUS).
;   __gnoGsosCall loads A = callNum right before its trap, so routing QUIT
;   through it recorded EVERY process's exit status as 0x2029 -> wait() saw
;   w_retcode = 0x2029 & 0xFF = 0x29 (41).  This helper fixes that.
;   C ABI: arg0 (pBlock ptr32) in A:X (A=offset, X=bank); arg1 (status i16) at
;   (4,s).  QUIT reaps the process and returns to the launcher -- never here.
	.globl __gnoQuit
__gnoQuit:
	rep #0x30
	sta __gnoQuitPB          ; inline pBlock offset (low 16) = A
	txa
	sta __gnoQuitPB+2        ; inline pBlock bank+pad (X = bank : pad)
	lda 4, s                 ; A = status (arg1) -- StackGSOS stashes it as exitCode
	jsl 0xe100a8
	.word 0x2029             ; inline callNum = GS/OS QUIT (CONSTANT, never via A)
__gnoQuitPB:
	.long 0                  ; patched: param-block pointer
	; QUIT never returns; if it somehow does, spin rather than run off the end.
.LgnoQuitSpin:
	bra .LgnoQuitSpin


; __sigTrampoline -- the GNO kernel's signal-handler callback ABI shim.
;
; signal() installs THIS as the kernel handler (via Ksignal) for every caught
; signal, with the real C handler stashed in userHandlers[sig].  The kernel
; delivers a signal by either (case 1, raise/self-signal) calling the handler
; directly from KERNkill, or (case 2, async, e.g. an expired alarm) building a
; frame on the process stack that RTIs to the handler.  BOTH paths present the
; SAME ORCA/C frame at handler entry (kern/gno/signal.c KERNkill + ctool.asm
; ctxtRestore):
;   entry S=S0, native, m=x=0:
;     [S0+1..S0+3] = 24-bit RTL return   (case2: ctxtRestore-1; case1: KERNkill)
;     [S0+4..S0+5] = param word #1 = the SIGNAL NUMBER
;     [S0+6..S0+7] = param word #2 (0)
; ORCA/C handlers are CALLEE-POPS: on return the handler removes its 2 param
; words (4 bytes) and RTLs, leaving S=S0+7.  Our handlers are caller-pops with
; arg0 in A, so this trampoline calls the C handler with the signal number in A,
; then does the ORCA callee-pop return the kernel expects.  It also swaps to our
; process DP (case 1 enters in the KERNEL's DP) and restores it on the way out.
	.globl __sigTrampoline
__sigTrampoline:
	rep #0x30                ; force 16-bit A/X/Y (kernel entry is native m=x=0)
	lda 4, s                 ; A = signal number (param word #1) -- before any push
	tay                      ; Y = signal number (survives the pushes below)
	phb                      ; save incoming DBR
	phd                      ; save incoming DP
	lda >__gnoProcDP         ; A = this process's direct page (abs-long)
	tcd                      ; D = our process DP
	phk                      ; \  DBR = our program bank (PBR of this trampoline)
	plb                      ; /
	tya                      ; A = signal number
	asl a                    ; \  X = sig * 4 (userHandlers[] entries are 4-byte)
	asl a                    ; /
	tax
	lda >userHandlers, x     ; low 16 of handler ptr (abs-long indexed)
	ora >userHandlers+2, x   ; | bank word
	beq .LsigSkip            ; no handler (raced to SIG_DFL/IGN) -> just return
	; --- push a return frame so the C handler RTLs back to .LsigTrampBack ---
	phk                      ; handler-return bank = our PBR
	per .LsigTrampBack-1     ; handler-return offset (PC-relative -> reloc-safe)
	; --- push the handler address-1 as a 24-bit RTL-jump target ---
	sep #0x20
	lda >userHandlers+2, x   ; handler bank byte
	pha
	rep #0x20
	lda >userHandlers, x     ; handler offset
	dec a                    ; -1 (RTL adds 1)
	pha
	tya                      ; A = signal number (handler arg0, our ABI)
	rtl                      ; -> C handler; it RTLs back to .LsigTrampBack
.LsigTrampBack:
.LsigSkip:
	pld                      ; restore incoming DP
	plb                      ; restore incoming DBR   (S now back to entry S0)
	; --- ORCA callee-pop return: move the 3-byte RTL return up over the 4 ---
	; --- param bytes, discard them, RTL to the kernel, leaving S=S0+7.    ---
	rep #0x30
	lda 1, s                 ; ret low 16  ([S0+1,S0+2])
	sta 5, s                 ; -> [S0+5,S0+6]
	sep #0x20
	lda 3, s                 ; ret bank    ([S0+3])
	sta 7, s                 ; -> [S0+7]
	rep #0x20
	tsc
	clc
	adc #4                   ; drop the 4 param bytes
	tcs
	rtl                      ; -> ctxtRestore (case2) / KERNkill (case1)


; __gnoCaptureDP -- record this process's direct page for __sigTrampoline.
; Called from signal() (normal process context, D = our per-process page).
	.globl __gnoCaptureDP
__gnoCaptureDP:
	rep #0x30
	tdc                      ; A = D (our process direct page)
	sta >__gnoProcDP         ; abs-long store (bank-safe)
	rtl


; __gnoForkThunk -- fork() child entry shim.  fork() passes THIS as the spawn
; target, not the user's function directly.  GNO's createProc starts the child
; here with DBR=PBR=program bank and a FRESH direct page whose scratch bytes are
; uninitialized.  We seed the one per-thread scratch byte codegen depends on --
; $BE, the bank half of &global 32-bit pointers (crt0 sets it for the main thread;
; a fresh child page holds garbage) -- then hand off to __gnoChildStart (libcGno.c),
; which reads the stashed child entry, signals the parent's fork() handshake, and
; runs the child.  ($B8/$BA indirect target + $E0-$FF libcall scratch are written-
; before-read per use, so a fresh page needs no init; the reentrant __jsl_indir
; reads THIS child's own DP.)  __gnoChildStart _exit()s the child; the tail _exit
; is belt-and-suspenders in case it ever returns.
	.globl __gnoForkThunk
__gnoForkThunk:
	sep #0x20
	phk
	pla                        ; A = PBR (program bank)
	sta 0xbe                   ; DP $BE = program bank (per-thread &global bank byte)
	stz 0xbf                   ; pad so 16-bit `lda $BE` reads $00:PBR
	rep #0x30
	jsl __gnoChildStart        ; C: read stash, signal parent handshake, run child
	lda #0
	jsl _exit


	.data
	.globl __gnoProcDP
__gnoProcDP:
	.word 0                  ; our process DP; set by __gnoCaptureDP before delivery
