!
! Filename:     kernel.asm
! Module name:  Coke
! Description:  CokeOS v3 kernel (to be included in coke.asm)
!
! Commands:
!   string QueryVersion () - retrieve version string
!   special Reset (byte[6] resetcode) - reset the board
!   ScriptResource (byte rezID) - execute a command script 
!   SetScriptTimer (byte rezID, byte timerID, word timeout) - set timed script
!   RemoveScriptTimer (byte timerID) - remove a script timer
!   special InitCodeResource (byte rezID, word numRelocs, word[] relocTable)
;        - relocate a code resource
!   ExecCodeResource (byte rezID) - execute a code resource
!
! Functions:
!   Startup - start the kernel
!   GetChar - retrieve next parameter and perform idle functions
!   SendMessage - send an asynchronous message to the host
!   InstallCommand - install external command
!   RemoveCommand - uninstall external command
!   InstallCommandTable - install a list of commands
!   RemoveCommandTable - remove a list of commands
!   SetTimer - install or modify a timer function
!   RemoveTimer - remove a timer function
!   InstallHook - install an interrupt hook function
!   RemoveHook - remove an interrupt hook function
!   RegisterIPC - register an IPC function
!   UnregisterIPC - unregister an IPC function
!   CallIPC - call an IPC function or broadcast a message
!   InvokeCommand - execute an external command, params on stack
!   InvokeCommandMem - execute a command, params in memory
!
! Comments:
!   The external EEPROM contains primarily the kernel and the
!   resource loader.  This is the kernel.  It contains the
!   main command loop, calls installed command and timer functions,
!   and provides functions and commands for installing and removing
!   hooks, timers, and commands. 
!
! History:
!   bv 5/24/99	- initial revision
!   bv 6/21/99	- added message queue and linked list hook functions
!   bv 7/16/99	- got rid of exports because they were stupid,
!		  replaced with IPC
!   bv 7/28/99	- got RAM and message queue integrity checks working
!		- fixed a big bug in the timer routines
	
; constants

SUSPICIOUSMESSAGECOUNT	equ 63
RAMCHECKVALUE		equ $f0

; code

Startup subroutine
	ldaa	#2
	jsr 	sys_status

	clra
	clrb
					; initialize devices
	staa	PORT0
	staa	PORT1
	staa	PORT2
	staa	PORT3
	staa	PORT4
	staa	PORT5
	staa	PORT6
	staa	PORT7
	staa	PORT8
	staa	PORT9
	staa	PORT10
	staa	PORT11
	staa	PORT12
	staa	PORT13
	staa	PORT14
	staa	PORT15
	
	std	coke_numtimers16 	; clear num timers
	std	coke_getcharptr		; get chars from serial port
	staa	coke_messagelength	; no messages yet

	ldaa	#RAMCHECKVALUE		; write the ram check value
	staa	coke_externalramcheck
	staa	coke_chipramcheck

	ldd	#coke_messagebuffer	; set the pointers to the buffer
	std	coke_messagegetptr
	std	coke_messageputptr
	
	ldd	#RESETVECTOR
	std	$1			; make JMP $0 do a clean reset
	ldaa	#OPCODE_JMPEXT
	staa	$0

	ldab	#JUMPTABLESIZE		; copy the jump table into RAM
	ldx	#JUMPTABLE_START
	ldy	#JumpTable
.jumploop
	ldaa	0,y
	staa	0,x
	ldaa	1,y
	staa	1,x
	ldaa	2,y
	staa	2,x
	inx	
	inx
	inx
	iny
	iny
	iny
	decb
	bne	.jumploop
	
	ldab	#HOOKTABLESIZE		; clear the hook pointers
	ldx	#coke_hooktable
.hookloop
	clr	0,x
	clr	1,x
	inx
	inx
	decb
	bne	.hookloop

	ldy	#IPCTABLESIZE		; clear all IPC pointers
	ldx	#coke_ipctable
	clra
	clrb
.ipcloop
	std	0,x
	inx				; next table pointer
	inx
	dey				; dec counter
	bne	.ipcloop		; loop it

	ldy	#COMMANDTABLESIZE	; set all $100 commands to DummyCommand
	ldx	#coke_commandtable
	ldd	#DummyCommand
.cmdloop
	std	0,x			; set the command
	inx				; next command
	inx
	dey				; dec counter
	bne	.cmdloop		; loop it

	ldx	#Coke_CommandTable	; install the kernel commands
	jsr	InstallCommandTable

	jsr	InitResource		; init the resource manager

	ldaa	#COKE_RESET		; send the reset notification
	jsr	sys_putc


MainLoop subroutine
.loop
	jsr	GetChar		; get a character
	cmpa	#STARTCMD	; is it the start cmd character?
	bne	.nak		; if not, nak it

	ldaa	#6		; turn on pretty lights
	jsr 	sys_status

	jsr	GetChar		; get the command character
	tab			; put it in b
	ldx	#coke_commandtable	; get pointer to start of table
	abx			; add command number times two
	abx
	ldx	0,x		; get the command ptr
	jsr	0,x		; call the command

	cmpa	#COKE_ACK	; is it an acknowledge?
	bne	.notack
	jsr	sys_putc	; if so, send it
	bra	.loop		; and continue
.notack
	cmpa	#COKE_FAIL	; is it a fail?
	bne	.notfail
	jsr	sys_putc	; if so, send the fail
	tba
	jsr	sys_putc	; and send the error code
	bra	.loop
.notfail
	cmpa	#COKE_QUERY	; is it a query?
	bne	.notquery
	jsr	sys_putc	; send query
	tba
	jsr	sys_putc	; send length byte
	tstb
.queryloop
	beq	.loop		; if length is zero, we done
	ldaa	0,x		; get character from query string
	jsr	sys_putc	; send it out
	inx			; next character
	decb			; decrement counter
	bra	.queryloop	; keep at it
.notquery
				; if not a valid response, send a nak
.nak
	ldaa	#COKE_NAK	; get NAK char
	jsr	sys_putc	; send it out
	bra	.loop		; go to loop
	

DummyCommand subroutine
	ldaa	#COKE_NAK
	rts


DummyExportTable subroutine
	ds	64		; 32 zero words -- reset vector



; Coke_GetChar returns in a the next parameter byte for a command. It returns
; a value from the serial port or memory, depending on whether coke_getcharptr
; is zero.  It will increment coke_getcharptr if it is non-zero.  If 
; coke_getcharptr is zero, it sends out any messages, and scans the timer
; table and excecutes timer and idle functions.  Remember that timer
; functions can add and remove timers themselves!  That's why I use an
; incrementing counter (iny instead of dey).

GetChar subroutine
	pshb
	pshx
	pshy

	ldx	coke_getcharptr ; get the pointer to a parameter list in memory
	beq	.idleloop	; if ptr is 0, then we read from serial port
	ldaa	0,x		; otherwise, get the byte from memory
	inx			; inc the pointer
	stx	coke_getcharptr ; and store it back
	jmp	.exit

.idleloop
					; first, check for RAM integrity
	ldaa	#RAMCHECKVALUE
	cmpa	coke_externalramcheck	; check the external RAM
	beq	.extok
	staa	coke_externalramcheck	; uh-oh..
	ldaa	#COKE_MESSAGE		; try to send a message
	jsr	sys_putc
	ldaa	#MESSAGE_Coke_ExternalRAMFailure
	jsr	sys_putc
	jsr	Delay10ms		; wait the stuff to be sent out
	bra	.checkdone
.extok
	cmpa	coke_chipramcheck	; check the internal HC11 RAM
	beq	.checkdone
	staa	coke_chipramcheck	; -very- uh-oh
	ldaa	#COKE_MESSAGE		; try to send a message
	jsr	sys_putc
	ldaa	#MESSAGE_Coke_InternalRAMFailure
	jsr	sys_putc
	jsr	Delay10ms		; wait the stuff to be sent out

.checkdone				; next, scan timer functions
	ldx	coke_numtimers16	; get number of installed timers
	beq	.notimers	; if none, get outa here
	ldy	#0		; clear counter
	ldx	#coke_timertable

.timerloop
	ldd	2,x		; get compare value
	bne	.incloop	; if timeout value != 0, check for timeout
	ldaa	1,x		; if it is zero, do it every time
	pshx
	ldx	6,x		; get the function pointer
	jsr	0,x		; and do the function
	pulx
	bra	.timerdone

.incloop
	ldd	timercounter	; get the current counter
	subd	4,x		; subtract the last value
	cpd	2,x		; is it bigger than the timeout time?
	blo	.timerdone  	; if not time, leave

	ldd	4,x        	; if it is time, get the last value
	addd	2,x   		; say that the time has passed
	std	4,x        	; store it

	ldaa	1,x		; put param in a
	pshx
	ldx	6,x		; get the function pointer
	jsr	0,x		; and do the function
	pulx			; don't touch the timer record after calling
				;   the function, because it might not be
.timerdone			;   the same function!
	ldab	#8
	abx			; go to next timer record
	iny			; inc timer count
	cpy	coke_numtimers16 ; have we done 'em all?
	blo	.timerloop	; if there are more timers to do, do 'em

.notimers
					; done with timer, check for messages
	ldaa	coke_messagelength	; any messages?
	beq	.nomessages		; if not, skip
	cmpa	#SUSPICIOUSMESSAGECOUNT	; too many messages?
	blo	.notsuspicious		; if not, carry on
	
	ldaa	#COKE_MESSAGE		; send out a warning message
	jsr	sys_putc
	ldaa	#MESSAGE_Coke_MessageQueueFailure
	jsr	sys_putc
	ldd	#coke_messagebuffer	; clear the message queue
	sei
	std	coke_messagegetptr
	std	coke_messageputptr
	clr	coke_messagelength
	cli
	bra	.nomessages

.notsuspicious
	ldx	coke_messagegetptr	; get the get pointer

.messageloop
	ldaa	#COKE_MESSAGE		; get the response character
	jsr	sys_putc		; send it out.
	ldaa	0,x			; get the message code
	jsr	sys_putc		; send it out too.
	inx				; increment the get ptr
	cpx	#coke_messagebufferend	; gone too far?
	blo	.notwrap		; if not, keep going
	ldx	#coke_messagebuffer	; if so, wrap to beginning
.notwrap
	stx	coke_messagegetptr	; store the new get pointer
	dec	coke_messagelength	; one less char in buffer (atomic dec)
	bra	.notimers		; repeat until buffer empty
					;  (could cause lockup!)
.nomessages
				; done with messages, check for char
	jsr	sys_isachar	; is a serial character pending?
	bne	.getchar
	jmp	.idleloop	; if not, keep looping
.getchar
	jsr	sys_getc	; get the char in a
	
.exit
	puly
	pulx
	pulb
	rts



; Coke_InvokeCommand allows a native routine to call a command handler by
; pushing parameters on the stack.  The parameters required by the command
; must be pushed in REVERSE ORDER.  (This also means that words must be
; pushed explicitly as hibyte, then lobyte.)  InvokeCommand cleans up the
; stack itself, so the caller doesn't need to pull off what it pushed. 
; However, REGISTERS ARE NOT PRESERVED!  The command's response is returned 
; in a -- all other registers are indeterminate.
; command num is in a, parameters are on stack.

InvokeCommand subroutine
	ldx	coke_getcharptr	; stash the old getcharptr on the stack,
				; for nested InvokeCommands
	pshx
	tsx			; put the stack pointer into x
	ldab	#5		; go past the other stuff to the first param
	abx
	stx	coke_getcharptr	; that's the new getcharptr
	
	tab			; put command num in b
	ldx	#coke_commandtable	; get pointer to start of table
	abx			; add command number times two
	abx
	ldx	0,x		; get the command ptr
	jsr	0,x		; call the command

	ldy	coke_getcharptr	; get the new getcharptr
	iny			; soon to be the new stack pointer
	pulx			; restore the old getcharptr
	stx	coke_getcharptr
	pulx			; get the return address
	tys			; put in the new stack pointer
	jmp	0,x		; jump to the return address


; Coke_InvokeCommandMem allows a native routine to call a command handler by
; providing a pointer to a command list in memory.  Note that words must be
; stored in memory with lobyte first.  Registers are preserved, except for a,
; which contains the command's response.
; command num is in a, parameter list pointer is in x

InvokeCommandMem subroutine
	pshb
	pshx
	pshy

	ldy	coke_getcharptr		; stash the old getcharptr
	pshy
	stx	coke_getcharptr		; use the new one

	tab				; put command num in b
	ldx	#coke_commandtable	; get pointer to start of table
	abx				; add command number times two
	abx
	ldx	0,x			; get the command ptr
	jsr	0,x			; call the command

	pulx				; restore the old getcharptr
	stx	coke_getcharptr

	puly
	pulx
	pulb
	rts


; timer table:
;
; 0: timer id (byte)
; 1: param (byte)
; 2: timeout value (word)
; 4: last count (word)
; 6: function pointer (word)

; Coke_SetTimer installs a timer function, or updates a timer function if the
; given timer ID is already intalled.  If the timeout value is zero, the
; function is continuously called in the idle loop.  Param is passed to the
; function in a when it is called, and can be used for whatever the function
; wants.
; timer id is in a, param is in b, function pointer in in x, timeout value
; is in y

SetTimer subroutine
	pshy
	pshx
	pshb
			; first, scan to see whether the timer exists already
	ldx	#coke_timertable
	ldy	coke_numtimers16	; get number of installed timers
	beq	.notimers		; if none, start a new one

	ldab	#8
.timerloop
	cmpa	0,x			; compare a to timer id
	beq	.settimer		; if match, go set it
	abx
	dey				; dec timer count
	bne	.timerloop		; if more timers to do, do 'em
.notimers
	inc	coke_numtimers		; if no match, create a new timer
.settimer
	pulb
	staa	0,x			; store timer id
	stab	1,x			; store param
	ldy	timercounter		; get current time
	sty	4,x			; store it as lastcount
	puly				; get function ptr
	sty	6,x			; store it
	puly				; get timeout
	sty	2,x			; store it
	ldx	6,x			; restore x register
	rts				; outa here


; Coke_RemoveTimer removes a timer function with the specified id number.
; timer id is in a

RemoveTimer subroutine
	pshy
	pshx

	ldy	coke_numtimers16	; get number of installed timers
	beq	.exit			; if none, get outa here
	ldx	#coke_timertable

	ldab	#8
.timerloop
	cmpa	0,x		; compare a to timer id
	beq	.killtimer	; if match, go kill it
	abx			; go to next timer record
	dey			; dec timer count
	bne	.timerloop	; if there are more timers to do, do 'em
	bra	.exit

.killtimer
	psha
	pshb
.killloop
	dey			; dec timer count
	beq	.killdone	; if last one, then get out of here
	ldd	8,x		; otherwise, copy next timer record into this
	std	0,x
	ldd	10,x
	std	2,x
	ldd	12,x
	std	4,x
	ldd	14,x
	std	6,x
	ldab	#8
	abx			; go to next timer record
	bra	.killloop	; loop

.killdone
	pulb
	pula
	dec	coke_numtimers	; one less timer

.exit
	pulx
	puly
	rts


; Coke_InstallCommand puts a command into the command pointer table.
; command number is in a, function pointer is in x.

InstallCommand
	pshb
	pshy

	tab				; put it in b
	ldy	#coke_commandtable	; get pointer to start of table
	aby				; add command number times two
	aby
	stx	0,y			; put the command ptr there
	
	puly
	pulb
	rts


; Coke_InstallCommandTable reads from a table of commands:
;    command number (byte)
;    function pointer (word)
; The table ends with a zero for command number.
; pointer to table is in x

InstallCommandTable subroutine
	psha
	pshx
	pshy

	pshx			; put table pointer into y
	puly
.loop
	ldaa	0,y		; get command number
	beq	.exit		; if zero, exit
	ldx	1,y		; get function pointer
	jsr	InstallCommand	; install the command
	iny			; next command
	iny
	iny
	bra	.loop
.exit
	puly
	pulx
	pula
	rts


; Coke_RemoveCommand replaces the command with the NAK function.
; command number is in a

RemoveCommand subroutine
	pshx
	ldx	#DummyCommand
	jsr	InstallCommand
	pulx
	rts


; Coke_RemoveCommandTable reads from a table of commands:
;    command number (byte)
;    function pointer (word)
; The table ends with a zero for command number.
; pointer to table is in x

RemoveCommandTable subroutine
	psha
	pshx
.loop
	ldaa	0,x		; get command number
	beq	.exit		; if zero, exit
	jsr	RemoveCommand	; install the command
	inx			; next command
	inx
	inx
	bra	.loop
.exit
	pulx
	pula
	rts


; Coke_InstallHook installs a hook function at the end of
; the linked list for that particular hook.  If the hook
; function is already installed, it does nothing.
; hook code in a, pointer to linked list block in x.

InstallHook subroutine
	pshb
	pshx
	pshy
	
	tab			; put hook code in b
	ldaa	#OPCODE_RTI	; put in RTI at end of hook
	staa	0,x
	clr	5,x		; set NextBlock to zero
	clr	6,x

	ldy	#coke_hooktable
	aby
	aby			; y = ptr to FirstBlock
	cpx	0,y		; is the block we're adding = FirstBlock?
	beq	.done		; if so, exit now
	pshy			; stash ptr on stack
	ldy	0,y		; y = FirstBlock
	bne	.blocknonzero	; is FirstBlock zero?

	puly			; if so, get back ptr to FirstBlock
	stx	0,y		; set FirstBlock to the block we're adding
	ldy	#hookbase	; get ptr to first hook vector
	aby			; add 3 times hook number
	aby
	aby			; y = ptr to hook vector
	ldx	3,x		; x = ptr to start of function
	stx	1,y		; put it in the vector
	ldab	#OPCODE_JMPEXT	; and put in the jmp opcode
	stab	0,y

	bra	.done
	
.blocknonzero
	ins			; pull old ptr off stack
	ins		
	cpx	5,y		; is block we're adding = NextBlock?
	beq	.done		; if we're already in the list, exit.
	pshy			; stash block ptr on stack
	ldy	5,y		; y = NextBlock
	bne	.blocknonzero	; if NextBlock is non-zero, loop.

	puly			; pull ptr to last block
	stx	5,y		; set NextBlock to the block we're adding
	ldx	3,x		; get ptr to the function we're adding
	stx	1,y		; store it at the end of previous function
	ldab	#OPCODE_JMPEXT	; and put in the jmp opcode
	stab	0,y

.done
	puly
	pulx
	pulb
	rts
	
		
; Coke_RemoveHook removes a hook function from the linked list.
; hook code in a, pointer to linked list block in x. 

RemoveHook subroutine
	pshb
	pshx
	pshy

	tab			; put hook code in b
	ldy	#coke_hooktable
	aby
	aby			; y = ptr to FirstBlock
	tst	0,y		; is hibyte of FirstBlock zero
	bne	.firstblockokay	; if not, use it
	tst	1,y		; if so, is lobyte of FirstBlock zero too?
	beq	.done		; if so, get out of here!

.firstblockokay
	cpx	0,y		; is FirstBlock == this block?
	bne	.notfirstblock

	ldx	#0		; if so, set FirstBlock = 0
	stx	0,y
	ldx	#hookbase	; get ptr to first hook vector
	abx			; add 3 times hook number
	abx
	abx
	ldab	#OPCODE_RTI	; set vector to RTI
	stab	0,x

	bra	.done

.notfirstblock
	ldy	0,y		; y = FirstBlock
.loop
	cpx	5,y		; is NextBlock == this block?
	beq	.foundit	; if so, let's do something
	ldy	5,y		; if not, y = NextBlock
	beq	.done		; if NextBlock is zero, we didn't find it.
	bra	.loop		; otherwise, keep looking
.foundit
	ldab	5,x		; get hibyte of this block's NextBlock
	stab	5,y		; store it in NextBlock of previous block
	ldab	6,x		; do the same with the lobyte
	stab	6,y
	ldab	0,x		; get opcode of the end of this function
	stab	0,y		; store it
	ldx	1,x
	stx	1,y

.done
	puly
	pulx
	pulb
	rts	


; Coke_RegisterIPC registers an IPC function.
; module ID in a, function pointer in x

RegisterIPC subroutine
	pshb
	pshy
	
	tab			; find the spot in the table
	ldy	#coke_ipctable
	aby
	aby
	stx	0,y		; put the table pointer there

	puly
	pulb
	rts


; Coke_UnregisterIPC removes an IPC function.
; module ID in a

UnregisterIPC subroutine
	pshb
	pshx

	tab			; find the spot in the table
	ldx	#coke_ipctable
	abx
	abx
	clr	0,x		; write a zero
	clr	1,x
	
	pulx
	pulb
	rts


; Coke_CallIPC calls an IPC function or functions.  Returns
; carry clear if function handled the message, carry set if not.
; IPC message in a, flags or module ID in b,
; pointer to parameter table in x.

CallIPC subroutine
	pshy

	bitb	#IPCFLAG_Broadcast	; is it a broadcast?
	bne	.broadcast		; if so, go do it

	pshb
	andb	#IPCMASK
	ldy	#coke_ipctable		; get a pointer to the table entry
	aby
	aby
	pulb
	ldy 	0,y			; get the function pointer
	beq	.badquit		; if zero, it's not installed
	cmpa	#IPC_AreYouThere	; is it an AreYouThere message?
	beq	.done			; if so, we're done (carry is clear)
	jsr	0,y			; if not, call the function
	bra	.done			; that's all.

.broadcast
	ldy	#coke_ipctable		; get pointer to start of table
.loop
	pshy
	ldy	0,y			; get the function pointer
	beq	.nope			; if zero, no good
	jsr	0,y			; otherwise, call the function
	bcs	.nope			; if it didn't handle it, keep moving
	bitb	#IPCFLAG_StopAfterOne	; are we supposed to stop after one?
	beq	.nope			; if not, keep moving
	puly				; if so, clean up stack
	bra	.done			; and we're done
.nope
	puly				; get back table ptr
	iny
	iny				; go to next table entry
	cpy	#coke_ipctable+[IPCTABLESIZE*2]
	blo	.loop			; if we're not done, keep at it.
	clc				; if we are, that's nice
	bra	.done
.badquit
	sec
.done
	puly
	rts


; Coke_ScriptResource executes a command script that is stored as a resource
; file.  A zero for a command character ends the script.  If any command does
; not respond with COKE_ACK, the script is terminated.
; resource id is in b.  If bad rez id, carry is returned set.

ScriptResource subroutine
	psha
	pshb
	pshx
	pshy

	ldx	coke_getcharptr
	pshx
	jsr	GetResource	; get the resource pointer in x
	bcs	.bad		; if bad resource id, exit now
	stx	coke_getcharptr ; store it in getcharptr
.loop
	jsr	GetChar		; get the command character
	tab			; put it in b
	beq	.exit		; if it's zero, that's the end of the script
	ldx	#coke_commandtable	; get pointer to start of table
	abx			; add command number times two
	abx
	ldx	0,x		; get the command ptr
	jsr	0,x		; call the command, response in a

	cmpa	#COKE_ACK	; is it a good response?
	beq	.loop		; if so, keep doing it	
.exit
	clc
.bad
	pulx			; restore getcharptr
	stx	coke_getcharptr

	puly
	pulx
	pulb
	pula
	rts



; Coke_SendMessage will put a byte into the message queue.
; The next time through Coke_GetChar, the message will be
; sent out over the serial port.  It needs a "critical" wrapper 
; because it can be called by both an interrupt routine and 
; main code.
; message code in a.

SendMessage subroutine
	psha
	pshb
	pshx

	tab				; put message code in b
	_entercritical			; disable interrupts (destroys a)
	ldx	coke_messageputptr	; get the put pointer
	stab	0,x			; put the character in the buffer
	inx				; increment the put ptr
	cpx	#coke_messagebufferend	; gone too far?
	blo	.notwrap		; if not, keep going
	ldx	#coke_messagebuffer	; if so, wrap to beginning
.notwrap
	stx	coke_messageputptr	; store the new put pointer
	inc	coke_messagelength	; one more char in buffer
	_leavecritical			; enable interrupts if appropriate

	pulx
	pulb
	pula
	rts


; UninitializeResource is called before a resource is freed.
; It will call a code resource's entrypoint with
; a = IPC_Coke_Goodbye if the resource has been initialized.
; resource num is in b

UninitializeResource subroutine
	psha
	
	jsr	GetAttributes		; get the resource flags
	bcs	.exit			; if rez id no good, then quit
	bita	#RFLAG_EXECUTABLE	; is it executable?
	beq	.exit			; if not, fail
	bita	#RFLAG_INITIALIZED	; is it unrelocated code?
	beq	.exit			; if so, fail

	pshb
	pshx
	pshy

	jsr	GetResource		; get the resource pointer in x
	ldaa	#IPC_Coke_Goodbye	; put the value in a
	jsr	0,x			; let's go do it.

	puly
	pulx
	pulb
.exit
	pula
	rts



; Command_Reset (5 bytes of RESETCHAR) (zero byte) resets the system.
; No acknowledge is given.

Command_Reset subroutine
	ldab 	#5		; want five RESETCHARS
.loop
	jsr	GetChar		; get the char
	cmpa	#RESETCHAR	; is it RESETCHAR?
	bne	.badchar	; if not, nak it
	decb			; dec counter
	bne	.loop		; if not five yet, loop

	jsr	GetChar		; get last char
	tsta			; is it zero?
	bne	.badchar	; if not, nak it

	sei			; disable interrupts			
	jmp	RESETVECTOR	; jump to reset vector

.badchar
	ldaa	#COKE_NAK
	rts


; Command_ScriptResource (resource id) executes a command script that
; is stored as a resource file.  The script ends with a zero as a
; command character.

Command_ScriptResource subroutine
	jsr	GetChar		; get the resource id
	tab			; put in b
	jsr	ScriptResource	; execute the script
	bcs	.fail
	ldaa	#COKE_ACK	; acknowledge
	rts
.fail
	ldab	#ERROR_Coke_BadResourceID
	ldaa	#COKE_FAIL
	rts


; Command_SetScriptTimer (resource id) (timer id) (timeout lobyte) (timeout hibyte)
; sets up a script to automatically and continuously execute every timeout period.

Command_SetScriptTimer subroutine
	jsr	GetChar		; get rez id
	tab			; put it in b
	jsr	GetChar		; get timer id in a
	xgdy			; put stuff into y
	jsr	GetChar		; get timeout lobyte
	tab			; put in b
	jsr	GetChar		; get timeout hibyte
	xgdy			; put timeout in y, stuff in d
	
	adda	#TIMER_USER	; make the timer a public timer
	bcs	.badtimer	; if overflow, fail

	psha
	ldaa	coke_numtimers	; get number of timers
	cmpa	#MAXTIMERS_USER	; are we over the limit?
	bhs	.badnum		; if so, no timer for you!
	pula

	ldx	#Timer_Script	; get the function pointer
	jsr	SetTimer	; set the timer function
	ldaa	#COKE_ACK	; acknowledge
	rts
.badtimer
	ldab	#ERROR_Coke_BadTimerID
	ldaa	#COKE_FAIL
	rts
.badnum
	pula
	ldab	#ERROR_Coke_TooManyTimers
	ldaa	#COKE_FAIL
	rts


; Command_RemoveScriptTimer (timer id) stops the given timer.

Command_RemoveScriptTimer subroutine
	jsr	GetChar		; get the timer id
	adda	#TIMER_USER	; make it a public timer
	bcs	.bad		; if overflow, fail
	jsr	RemoveTimer	; remove the timer function
	ldaa	#COKE_ACK
	rts
.bad
	ldab	#ERROR_Coke_BadTimerID
	ldaa	#COKE_FAIL
	rts


; Command_InitCodeResource (resource id) (numhirelocs lo) (numhirelocs hi)
;     (hirelocs) 
; relocates a code resource so it works at whatever address it is put at.
; (hirelocs) consists of:
;        (offset lo) (offset hi)
; It then executes the code with IPC_Coke_Hello in a.

Command_InitCodeResource subroutine
	jsr	GetChar			; get the resource id
	tab				; put it in b
	jsr	GetAttributes 		; get attributes
	bcs	.badresid		; if not valid, get outa here
	bita	#RFLAG_EXECUTABLE	; is it executable code?
	beq	.badnotexec		; if not, fail
	bita	#RFLAG_INITIALIZED	; is it unrelocated code?
	bne	.badinit
	jsr	GetResource	; get resource pointer in x
	stx	coke_scratch	; store it in scratch space
	stab	coke_scratch+2	; store resource id in scratch space too

	jsr	GetChar		; get numhirelocslo
	tab			; put into lobyte
	jsr	GetChar		; get numhirelocshi
	xgdy			; put count into y
	cpy	#0		; are there any hi relocs?
	beq	.exit		; if not, skip this stuff

.hiloop
	jsr	GetChar		; get lobyte of offset
	tab
	jsr	GetChar		; get hibyte of offset
	addd	coke_scratch	; add to start of code
	xgdx			; put into x
	
	ldaa	0,x		; get the byte
	adda	coke_scratch	; add the code start hibyte
	staa	0,x		; store it
	dey			; decrement counter
	bne	.hiloop		; if not zero, keep at it.

.exit
	ldab	coke_scratch+2		; get resource id
	jsr	GetAttributes		; get attributes byte
	oraa	#RFLAG_INITIALIZED	; set the relocated flag
	jsr	SetAttributes		; set attributes byte

	jsr	GetResource		; get resource pointer
	ldaa	#IPC_Coke_Hello		; put value in a
	jsr	0,x			; execute the code

	ldaa	#COKE_ACK		; done with this command
	rts

.failloop
	pshb			; stash error code
	jsr	GetChar		; get numhirelocslo
	tab			; put into lobyte
	jsr	GetChar		; get numhirelocshi
	xgdx			; put count into x
	cpx	#0		; are there any relocs?
	beq	.fexit		; if not, skip this stuff
.floop
	jsr	GetChar		; just eat up the table
	jsr	GetChar
	dex
	bne	.floop	
.fexit
	pulb			; restore error code
	ldaa	#COKE_FAIL
	rts
.badresid
	ldab	#ERROR_Coke_BadResourceID
	bra	.failloop
.badnotexec
	ldab	#ERROR_Coke_ResourceNotExecutable
	bra	.failloop
.badinit
	ldab	#ERROR_Coke_ResourceAlreadyInitialized
	bra	.failloop


; Command_ExecCodeResource (resource id) executes (via jsr) a code resource,
; with IPC_Coke_Execute in a.

Command_ExecCodeResource subroutine
	jsr	GetChar			; get the rez id
	tab				; put it in b
	jsr	GetAttributes		; get the resource flags
	bcs	.badresid		; if rez id no good, then quit
	bita	#RFLAG_EXECUTABLE	; is it executable?
	beq	.badnotexec		; if not, fail
	bita	#RFLAG_INITIALIZED	; is it unrelocated code?
	beq	.badnotinit		; if so, fail

	jsr	GetResource		; get the resource pointer in x
	ldaa	#IPC_Coke_Execute	; put the value in a
	jsr	0,x			; let's go do it.

.exit
	ldaa	#COKE_ACK
	rts

.fail
	ldaa	#COKE_FAIL
	rts
.badresid
	ldab	#ERROR_Coke_BadResourceID
	bra	.fail
.badnotexec
	ldab	#ERROR_Coke_ResourceNotExecutable
	bra	.fail
.badnotinit
	ldab	#ERROR_Coke_ResourceNotInitialized
	bra	.fail


; Timer_Script is a timer function that executes a given command script.
; The script's resource id is the param value, and is passed in a.

Timer_Script subroutine
	pshb
	tab			; put resource id in b
	jsr	ScriptResource	; execute the script
	pulb
	rts


Delay10ms subroutine
	pshx
	ldx	#3500
.loop
	dex
	bne	.loop
	pulx
	rts


; version stuff

	_versionstuff


; Coke_CommandTable is the command table for kernel commands

Coke_CommandTable subroutine
	dc.b	CMD_Coke_QueryVersion
	dc.w	Command_QueryVersion
	dc.b	CMD_Coke_Reset
	dc.w	Command_Reset
	dc.b	CMD_Coke_ScriptResource
	dc.w	Command_ScriptResource
	dc.b	CMD_Coke_SetScriptTimer
	dc.w	Command_SetScriptTimer
	dc.b	CMD_Coke_RemoveScriptTimer
	dc.w	Command_RemoveScriptTimer
	dc.b	CMD_Coke_InitCodeResource
	dc.w	Command_InitCodeResource
	dc.b	CMD_Coke_ExecCodeResource
	dc.w	Command_ExecCodeResource

	dc.b	0

