;
; os.asm -- Coke HC11 operating system
;
; original:  5/30/98 Steve Ginzburg
; rewritten: 9/12/98 Bret Victor
;
; This is programmed into the 512 bytes of EEPROM in the HC11.
; ($FE00 to $FFFF).  Only $FE00 to $FFBF is available for code.
;
; This OS sets up stuff, and then loads user code into RAM over
; the serial port, using the protocol described below.
; The OS also provides some services, which are accessable via
; the system vectors:
;
; sys_status      set status LEDs
; sys_getc        get character from serial port
; sys_putc        send character to serial port
; sys_isachar     see if a character is pending on serial port
; sys_audio       turn audio interrupt on or off
;
; The OS provides five hook vectors, for hooking user code into
; interrupt procs.  A hook routine is just like a normal subroutine
; except it runs while interrupts are disabled, and must return
; with an RTI instruction, not RTS.  To intstall a hook, store a
; pointer to the subroutine at vector+1 and THEN store #OPCODE_JMPEXT
; at vector.   Note that the user interrupt has the highest priority 
; of the four.
;
; userhookvec   called every 8 ms.
; audiohookvec  called every audiodelay, if audio is turned on.
; gfxhookvec    called every gfxdelay.
; irqhookvec    called during an external interrupt (irq line)
; swihookvec    called during a software interrupt (SWI instruction)
;
; The system registers timercounter, audiodelay, and gfxdelay can be
; read and written by user code at any time, provided an atomic
; operation is used.  timercounter is incremented at the userhookvec
; rate (8 ms).

	processor 68HC11

	include hc11f1reg.inc
	include memmap.inc
	include os.inc
	include macros.inc

; constants

DEFAULTGFXDELAY	equ #100	; 25 Hz

; local variables

rx_getptr	equ $0010
rx_putptr	equ $0012
tx_getptr	equ $0014
tx_putptr	equ $0016
rx_length	equ $0018
tx_length	equ $0019

; $0020 through $00FF reserved for public system registers
; see os.inc

rx_buffer	equ $0100
tx_buffer	equ $0200

; $0300 through $03FF reserved for stack space


; Start of OS code.
; We only have *** 424 bytes *** to do our thang, so you'll
; see some tight coding here.

	org	CROMBASE	; start of on-chip EEPROM

resetvec
StartOS	subroutine
	sei		; Inhibit interrupts
	ldx	#REGBASE	; Base address of register block

	; Instructions that must occur in the first 64 cycles:
	ldd	#%0100001000010000 	; timer prescaler = 8, RTI enable
					; do not protect EEPROM 
	staa	TMSK2,X
	stab	BPROT,X
	; End critical instructions

	lds	#CRAMTOP	; Init stack ptr

	; Initialize port D for deprecated and removed stuff...
	bset	DDRD,X ,#%00001100	; (F1:6-5)


	; Initialize HC11 registers
	ldaa	#%00100011	; A/D disabled
			; EEPROM and A/D use E clock
			; IRQ is edge-triggered
			; No delay coming out of STOP
			; Clock monitor disabled
			; Clock monitor follows CME bit
			; COP resets every ~1 second
	staa	OPTION,X	; See HC11F1 ref. 4-14

	ldd	#%0010000000101100	; Port C, G normal, 4xout enabled
			; IO Chip Sel. 1 disabled
			; IO Chip Sel. 2 enabled, active low
			; CSGEN has priority over CSPROG
			; CSPROG enabled
			; CSPROG ranges from $0000 to $ffff
	staa	OPT2,X	; See HC11F1 ref. 4-15
	stab	CSCTL,X	; See HC11F1 ref. 4-24

	ldd	#%1000000011001001	; CSGEN starts at $8000
			; IO Chip Sel. 1 & 2 valid during A valid
			; CSGEN is active low
			; CSGEN is valid during A valid
			; CSGEN is 32K long
	staa	CSGADR,X	; See HC11F1 ref. 4-25
	stab	CSGSIZ,X	; See HC11F1 ref. 4-26

	clr	CSSTRH,X	; don't stretch the clock
	
	ldaa	#%11111111	; Set port G for output on all pins
	staa	DDRG,X

	ldd	#DEFAULTGFXDELAY
	std	gfxdelay

	; Initialize serial (SCI)
	ldd	#tx_buffer	; set put and get pointers to start of buffer
	std	tx_getptr
	std	tx_putptr
	ldd	#rx_buffer
	std	rx_getptr
	std	rx_putptr
	clra		; say that buffers are empty
	staa	tx_length
	staa	rx_length

	clr	SPCR,X	; clear DWOM, no wired or (F1:8-6)

	ldd	#%1011000010101100	; Set baud rate to 9600
			; Enable Tx ints, Rx ints, Tx, and Rx
	staa	BAUD,X	; (F1:7-11)
	stab	SCCR2,X	; (F1:7-9)

	; Initialize timers
	ldd	#$4001
	staa	TCTL1,X	; Set TOC2 to toggle on compare (F1:9-10)
	staa	TMSK1,X	; Enable TOC2 interrupts (F1:9-11)
	stab	PACTL,X	; Set real-time interrupts to 8 ms.
			; TCTRL2 already taken care of above

	clr	OC1M,X	; Make sure TOC1 can't affect us
	clr	OC1D,X

	ldaa	#$FF
	staa	TFLG1,X	; Clear pending match on output compares

	; Disable hook routines
	ldaa	#OPCODE_RTI	; get opcode for RTI
	staa	gfxhookvec	; put into the vectors
	staa	audiohookvec	
	staa	userhookvec
	staa	swihookvec
	staa	irqhookvec

	clc
	jsr	sys_audio	; stop audio output

	; Light up LEDs
	ldaa	#1
	jsr	status

	cli		; init done, clear interrupt inhibit

	jmp	ROMBASE	; go to EEPROM thingee


; System calls:
;  status      change pattern on status LEDs
;  audio       turn audio interrupt on or off
;  isachar     returns number of chars in serial receive buffer
;  getchar     retrieves a character from the serial receive buffer
;  putchar     puts a character into the serial transmit buffer

; status accepts a value and lights up the status light according
;  to the low nibble.
; value is in a.

status	subroutine
	psha
	anda	#$0f	; want low 4 bits only
	eora	#$0f	; invert (active low)
	staa	PORTG+REGBASE	; write to port G
	pula
	rts


; audio turns on the audio interrupt if carry is set, or disables
; it if carry is clear

audio 	subroutine
	pshx
	ldx	#REGBASE
	bcc	.turnoff	; do we want to turn on or off?
	psha
	pshb
	_ENTERCRITICAL	; begin critical section
	ldd	TCNT,X	; get current counter value
	addd	audiodelay	; add the delay time
	std	TOC4,X	; schedule the next interrupt
	_LEAVECRITICAL	; end critical section
	bset	TMSK1,X ,#$10	; Enable TOC4 interrupts
	pulb
	pula
	pulx
	rts
.turnoff
	bclr	TMSK1,X ,#$10	; Disable TOC4 interrupts
	pulx
	rts


; isachar returns the number of characters in the serial receive
;  buffer, setting the z-flag if the buffer is empty and clearing
;  the z-flag if getchar will not block.

isachar	subroutine
	ldaa	rx_length
	rts


; getchar blocks until a character is available, and returns the
;  character in a.
; note:  there is no interrupt protection (sei/cli wrapper).  I THINK
;        that, with the way I have this stuff set up, interrupt
;        protection is unneccessary.  

getchar subroutine
	pshx
.block		; block until a character is ready
	ldaa	rx_length	; get length
	beq	.block	; if zero, keep waiting

	ldx	rx_getptr	; get the get ptr
	ldaa	0,x	; get the data
	inc	rx_getptr+1	; increment low byte of ptr, possibly wrapping.
	dec	rx_length	; one less byte in buffer

	pulx
	rts


; putchar puts a character into the serial transmit buffer.  If the
;  buffer is full, the function will actually BLOCK until space is
;  available the character.
; char to send in a.

putchar	subroutine
	pshx
	pshb
.block
	ldab	tx_length	; get length byte
	incb		; increment
	beq	.block	; if it was $FF, then we're full

	ldx	tx_putptr	; get the put ptr
	staa	0,x	; put the data there
	inc	tx_putptr+1	; increment low byte of ptr, possibly wrapping.
	inc	tx_length	; one more byte in buffer

	ldx	#REGBASE	; turn on the serial tx ready interrupt,
	bset	SCCR2,X ,#$80	; possibly kicking it off immediately

	pulb
	pulx
	rts
	

; nothing is a dummy function for reserved system calls.

nothing	subroutine
	rts


; Interrupt handlers:
;   serialvec handles sending and receiving characters to/from the uart.
;   audiovec provides a hook for audio processing.
;   gfxvec provides a hook for video refreshing.
;   uservec provides a hook for a user function, and maintains two counters.
;   irqvec provides a hook that is called when the irq line is pulled low.
;   swivec provides a hook that is called during a software interrupt.


; serialvec is called when a character has been received or when a character
;   can be safely sent to the transmit register.  Either condition can trigger
;   the interrupt, so this interrupt handles both picking up received characters
;   and sending out characters.
; note:  rx buffer overruns are NOT handled (they will result in wiping out
; the entire buffer, because rx_length will be inc'd to zero)

serialvec	subroutine
	ldx	#REGBASE
	brclr	SCSR,x ,#$20 ,.rxdone	; if no char to receive, skip rx part
	ldy	rx_putptr	; otherwise, get the put ptr
	ldaa	SCDR,x	; get the data
	staa	0,y	; put it into the buffer
	inc	rx_putptr+1	; increment low byte of pointer
	inc	rx_length	; one more byte in buffer
.rxdone
	brclr	SCSR,X ,#$80 ,.done	; if tx is busy, skip tx part
	ldaa	tx_length	; is there data to be sent?
	bne	.doxmit	; if so, let's send something
	bclr	SCCR2,x ,#$80	; if not, clear the tx interrupt enable
	rti		; and leave
.doxmit
	ldy	tx_getptr	; get the get ptr
	ldaa	0,y	; get some data
	staa	SCDR,x	; send it to the uart
	inc	tx_getptr+1	; increment the low byte of ptr
	dec	tx_length	; one less char in the buffer
.done
	rti		; that's all!


; audiovec is called every audiodelay, if audio output has been turned
; on. The interrupt handler schedules the next interrupt, and then calls the
; hook function.

audiovec	subroutine
	ldx	#REGBASE
	ldd	audiodelay	; get audio "half-wavelength"
	addd	TOC4,X	; add to last compare value
	std	TOC4,X	; update TOC4 (schedule next edge)
	bset	TFLG1,X ,#$10	; clear OC4F (reset timer)
	jmp	audiohookvec	; go do the audio hook routine


; gfxvec is called every gfxdelay, and is intended for graphics refreshes.
; The interrupt handler schedules the next interrupt, and then calls the
; hook function.

gfxvec	subroutine
	ldx	#REGBASE
	ldd	gfxdelay	; delay time for next interrupt
	addd	TOC2,X	; add to last compare value	
	std	TOC2,X	; update TOC2
	bset	TFLG1,X ,#$40	; clear OC2F
	jmp	gfxhookvec	; go do the gfx hook routine


; uservec is called every 8 ms by the real-time interrupt.  The interrupt
; handler increments two public counter registers, and then calls the
; hook function.

uservec 	subroutine
	ldx	timercounter	; get 16-bit value in counter
	inx				; increment
	stx	timercounter	; write new 16-bit counter value
	ldx	#REGBASE
	bset	TFLG2,X ,#$40	; clear the interrupt
	jmp	userhookvec		; go do the user hook routine


; swivec is called when the software interrupt intstruction (SWI) is
; excecuted.  It simply calls the hook function.

swivec	subroutine
	jmp	swihookvec	; go to the hook routine


; irqvec is called when the irq line is pulled.  It calls the hook function.

irqvec	subroutine
	rti
	jmp	irqhookvec


; interrupts that are not handled call ignorevec, which just sends
; them home.

ignorevec	subroutine
	rti


; System call entrypoints
	org	sys_vectors	; currently $FFA7
	rts		; sys_rts
	jmp	status	; sys_status
	jmp	getchar	; sys_getc
	jmp	putchar	; sys_putc
	jmp	isachar	; sys_isachar
	jmp	audio	; sys_audio
	jmp	nothing	; 
	jmp	nothing	; 
	jmp	nothing	; 


; Interrupt vector table
	org	IVECBASE	; $FFC0

	dc.w	ignorevec	; start of reserved vectors
	dc.w	ignorevec
	dc.w	ignorevec
	dc.w	ignorevec
	dc.w	ignorevec
	dc.w	ignorevec
	dc.w	ignorevec
	dc.w	ignorevec

	dc.w	ignorevec
	dc.w	ignorevec
	dc.w	ignorevec	; send of reserved vectors
	dc.w	serialvec	; SCI (uart) interrupt
	dc.w	ignorevec	; SPI transfer complete
	dc.w	ignorevec	; Pulse accumulator input edge
	dc.w	ignorevec	; Pulse accumulator overflow
	dc.w	ignorevec	; Timer overflow

	dc.w	ignorevec	; TIC4/OC5
	dc.w	audiovec	; TOC4
	dc.w	ignorevec	; TOC3
	dc.w	gfxvec		; TOC2
	dc.w	ignorevec	; TOC1
	dc.w	ignorevec	; TIC3
	dc.w	ignorevec	; TIC2
	dc.w	ignorevec	; TIC1

	dc.w	uservec	; Real-time interrupt
	dc.w	irqvec		; External IRQ
	dc.w	ignorevec	; External XIRQ
	dc.w	swivec		; Software int
	dc.w	resetvec	; Illegal opcode
	dc.w	ignorevec	; COP failure
	dc.w	ignorevec	; Clock monitor failure
	dc.w	resetvec	; Reset









