mac-rom/Drivers/IOP/SCCIOP.aii
Elliot Nunn 0ba83392d4 Bring in CubeE sources
Resource forks are included only for .rsrc files. These are DeRezzed into their data fork. 'ckid' resources, from the Projector VCS, are not included.

The Tools directory, containing mostly junk, is also excluded.
2017-09-20 18:04:16 +08:00

2287 lines
58 KiB
Plaintext

;
; File: SCCIOP.aii
;
; Contains: xxx put contents here (or delete the whole line) xxx
;
; Written by: xxx put name of writer here (or delete the whole line) xxx
;
; Copyright: © 1988-1990 by Apple Computer, Inc., all rights reserved.
;
; Change History (most recent first):
;
; <3> 1/18/90 SWC WTO/Fix bugs in InstallTask and KillTask.
; <1.4> 12/8/89 WTO Fixed bug in version message to kernel. Range check on Driver ID
; was returning error. Needed to add compare to $00.
; <1.3> 7/8/89 CCH Added EASE comments to file.
; <1.2> 6/27/89 BTO Needed for AURORA padding
; <¥1.1> 2/8/89 SGS Initial release
; <1.0> 11/9/88 CCH Adding to EASE.
;
; To Do:
;
*
* File SCCIop.aii
*
* Copyright 1988 (c) Apple Computer Inc.
*
* Main routines to handle IOP. The Kernel is downloaded into the IOP
* during the IOPMgr initialization process. The IOPMgr then reads
* PRAM to set the ByPass mode to the appropriate state.
*
* Written by Bill O'Connor 2/19/88
* with ideas and code from Ron Hocksprung
* Gary Davidian
*
* Edit History
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Machine M65CX02
Longa off
Longi off
blanks on
print nopage, nomdir
include 'SCCIOPSysEqu.aii' ; IOP Sytem includes
include 'SCCIOPEqu.aii' ; IOP Kernel includes
Seg 'IOPOS'
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Vectored interrupt table
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Seg 'Vectors'
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Interrupt vector address table.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Vect Proc
Import NonMaskable, \
Reset, \
IRQ :Code
DC.W NonMaskable
DC.W Reset
DC.W IRQ
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Main entry into PIC kernel
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Seg 'Main'
Export NonMaskable, \
Reset, \
IRQ
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* NonMaskable interrupt handler.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
NonMaskable Proc
Lda #NonMask ; Let us know we are in NonMaskable Loop
Bra NonMaskable ; Should never get one
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* IRQ - save registers and jsr to ISR dispatcher.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
IRQ Proc
Import ISR_Dsptch : Code
Pha ; Save registers.
Phy
Phx
Jsr ISR_Dsptch ; Interrupt handler
Plx ; Restore registers
Ply
Pla
Rti
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Reset handler.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Reset Proc
Import Cmd_Table, \
VerInfo : Data
Import Kern_Task, \
Cmd_JmpTable, \
RxMsg_Signal, \
SCC_Dsptch, \
Timer_ISR, \
Init_Timer, \
Msg_Scan : Code
Sei ; Interrupts off
Stz Int_Host
Lda #SCC_Port|ByPass_Bit|PClk39
Sta SCC_Cntl ; Initialize SCC control reg
Lda #IO_Cntl_init ; Get initial value
Sta IO_Cntl ; Initialize I/O control reg
Lda #Host_Msk ; Enable host interrupts
Sta Int_Mask
Lda SCC_ACntl ; Sync SCC for writes
*
* Clear page zero
*
Ldx #$00 ; Index
@ClearLoop
Stz $00,x ; Zero out a byte
Dex ; Point to next byte
Bne @ClearLoop ; Clear the entire zero page
Stz DMA1_Cntl ; Init DMA1
Stz DMA2_Cntl ; Init DMA2
Stz TD_Cntl ; Init Timer DPLL control
Stz Int_Host ; Init host interrupt register
*
* Transfer the kernel command addresses to the zero page table
*
Ldx #KCmd_Table_size ; Begin transfer of command table
@Loop Lda Cmd_Table,x
Sta Krn_CmdBase,x
Dex
Bpl @Loop
*
* Initialize Zero Page variables
*
*
* IMPORTANT !!!!!
*
* Because of the manor in which the 6502 does a rts the address
* used must be the actual address minus 1
*
DvrA_Addr Set DvrA_Base-1
DvrB_Addr Set DvrB_Base-1
Lda #DvrA_ID ; Initialize the driver task ID table
Sta Dvr_TaskID
Lda #DvrB_ID
Sta Dvr_TaskID+1
Lda #>DvrA_Addr ; Install the Initialization vector for driver A
Sta Init_Vec+1
Lda #<DvrA_Addr
Sta Init_Vec
Lda #>DvrB_Addr ; Install the Initialization vector for driver B
Sta Init_Vec+3
Lda #<DvrB_Addr
Sta Init_Vec+2
Lda #>DvrA_Close ; Install the Close vector for driver A
Sta Close_Vec+1
Lda #<DvrA_Close
Sta Close_Vec
Lda #>DvrB_Close ; Install the Close vector for driver B
Sta Close_Vec+3
Lda #<DvrB_Close
Sta Close_Vec+2
Lda #$ff
Sta Bypass_Flag ; Set ByPass_Flag to on.
Jsr Init_Timer
_Reg_Ver Kern, VerInfo
_Inst_ISR HOST, Msg_Scan ; Install message scan for host interrupt
_Inst_ISR SCC, SCC_Dsptch ; Install the SCC interrupt dispatcher
_Inst_ISR TIMER, Timer_ISR
_Inst_RxMsgSgn 1, RxMsg_Signal ; Install the kernel receive message signaller
Lda #MaxRxMsg
Sta RxMsgCnt ; PIC Kernel uses 7 messages
Sta TxMsgCnt ; Both in and out
Ldx #$ff ; Stack base is $01FF
Stx Int_Reg ; Clear the interrupt register
Stx IOPAlive ; Tell IOPMgr we are Alive
Txs ; Initialize stack
Jmp Kern_Task ; Interrupts will be cleared when
; Event Wait is called.
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* ISR_Dsptch - determine interrupt source and dispatch to ISR.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ISR_Dsptch Proc
Import ISR_table, \
ISR_Decode :Data
Ldx Int_reg ; Get the interrupt latch
Lda ISR_Decode,x
Tax
Jmp (ISR_table,x) ; Dispatch the interrupt
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* SCC_Dsptch - determine source and dispatch SCC interrupts.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SCC_Dsptch Proc
Import SCC_table, \
UnKnown_Int :Code
Lda #Rd_Reg2 ; Read the SCC interrupt latch
Sta SCC_BCntl ; Interrupt latch is register 2 port B
Ldx SCC_BStat ; Read the status V1 V2 V3
Jmp (SCC_table,x) ; Dispatch the interrupt
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Unknown_SCCInt - unknown SCC interrupt handler.
*
* This routine handles interrupts that occur but do
* not have an SCCISR installed.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnKnown_SCCInt Proc
Brk
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Unknown_Int - unknown interrupt handler.
*
* This routine handles interrupts that occur but do
* not have an ISR installed.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Unknown_Int Proc
Tsx ; Get the stack pointer
Lda StackPage+6,x ; Get the psw from the stack
Bit #pswB ; Was it a break that caused the interrupt
Bne BRK_Handler ; Yes
Rts ; No, just return
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* BRK_Handler - handle a software interrupt
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
BRK_Handler Tsx ; Get the stack pointer
*
* Cycle through stack so that we can see it on the logic analyzer
*
Do_Again Inx
Beq BRK_Handler
Lda StackPage,x
Bra Do_Again
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Entry Points
*
* Rel_Task - release a task for one task cycle.
*
* Ev_Wait - wait for an event to happen.
*
* The return address should already be on the stack.
* Ev_Wait falls through into Ev_Dsptch.
*
* Register A - event mask.
*
* On exit register A contains the tEvent for the Task.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Rel_Task Proc
Export Ev_Wait
Import tSp, \
tNext, \
tEvent, \
tWait : Data
Lda #$80 ; This event is always set
; therefore the task will execute the
; next task cylce
Ev_Wait Ldy Cur_Task ; Get the Task ID
Sta tWait,y ; Store the event mask
*
* Ev_Dsptch - entry to task dispatcher.
*
Sei
Ldy Cur_Task ; Get the current task ID
Tsx
Txa
Sta tSp,y ; Save the task's stack pointer
Cli
Ldx #$FF
@Scan Lda tNext,y ; Get the next task ID
Tay
Lda tEvent,y ; Check the flags to see if this one is ready
And tWait,y
Stx IOPAlive ; This lets the Host know we're still alive
Beq @Scan
Sei
Sty Cur_Task ; Switch Tasks
Ldx tSp,y ; Get this task's stack pointer
Txs
Cli
Rts ; go to the task
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Ev_Signal - signal a task that an event has occurred.
*
* Events are flagged by setting bits in the tEvent byte of the
* TCB of the task to receive the signal.
*
* Register X - ID of task to signal.
* Register A - event flag.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ev_Signal Proc
Import tEvent : Data
Php
Sei
Ora tEvent,x ; Set the event bit
Sta tEvent,x ; Save it in the task's TCB
Plp
Rts
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Ev_Reset - reset an event flag.
*
* Register A - complement of event flag.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Ev_Reset Proc
Import tEvent : Data
Php
Sei
Ldx Cur_task ; We can only reset our own task
And tEvent,x ; Reset the event bit
Sta tEvent,x ; Save it in the task's TCB
Plp
Rts
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Msg_Scan - handle interrupts from host CPU. Must be installed
* when processor is reset.
*
* Scan messages for New Message Sent and Message Complete. Each
* task installs message handlers that signal the task when new messages
* are received or messages are completed. Each task must install a
* handler for each message box it intends to use.
*
* The only message the kernel will accept when in ByPassMode
* is ByPassMode(Off).
*
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Msg_Scan Proc
Import TxMsgSignal, \
RxMsgSignal : Code
Import MsgNumTimes2 : Data
Lda #Host_Msk
Sta Int_Reg ; Clear the host interrupt
Trb Int_Mask ; Disable host interrupts
Cli ; Renable interrupts of higher priority
Lda ByPass_Flag ; We don't do anything in bypass mode
Bne Do_ByPass ; except receive on message one
*
* Service Completed Transmit Messages first
*
Ldy TxMsgCnt ; Get highest Tx message number
Lda #MsgCmplt ; Get the desired message state
@TxMsgLoop Cpa TxMsgState,y ; See if the message completed
Bne @TxMsgNext ; If no service needed goto next msg
Phy ; Preserve loop counter
Ldx MsgNumTimes2,y ; Convert message number to table index
Jsr TxHandler ; Call the handler
Ply ; Restore the message number
Lda #Idle
Sta TxMsgState,y ; Change message state to Idle
Lda #MsgCmplt ; Restore desired message state
@TxMsgNext Dey ; Point to previous message
Bne @TxMsgLoop ; Loop through all messages
Bra RxMsgStart
*
* Service Receive New Message Sent last
*
Do_ByPass Ldy #1 ; In ByPass mode we only receive message 1
Bra ByPassSkip ; for the kernel
RxMsgStart Ldy RxMsgCnt ; Get highest Rx message number
ByPassSkip Lda #NewMsgSent ; Get the desired message state
@RxMsgLoop Cpa RxMsgState,y ; See if message just arrived
Bne @RxMsgNext ; If no service needed
Lda #MsgRcv
Sta RxMsgState,y ; Change message state to Message Received
Phy ; Preserve loop counter
Ldx MsgNumTimes2,y ; Convert message number to table index
Jsr RxHandler ; Call the handler
Ply ; Restore the message number
Lda #NewMsgSent ; Restore desired message state
@RxMsgNext Dey ; Point to previous message
Bne @RxMsgLoop ; Loop through all messages
Sei
Lda #Host_Msk ; Get the host interrupt bit
Tsb Int_Mask ; Re-enable host interrupts
Rts ; All done
TxHandler Jmp (TxMsgSignal-2,x) ; Go to handler - msg's start at 1
RxHandler Jmp (RxMsgSignal-2,x) ; Go to handler - msg's start at 1
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* RxMsg_Signal - signal kernel that a message has arrived.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RxMsg_Signal Proc
Import Cmd_JmpTable : Code
Ldx #Kern_ID ; ID of task we wish to signal
Lda #RxMsg ; Signal we want to send
Jmp Cmd_JmpTable+(SignalTask*3) ; Signal Task Command has an RTS
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Kern_Task - main kernel task routine and event dispatcher
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Kern_Task Proc
Import Dsptch_KRxMsg : Code
Import Cmd_JmpTable : Code
_Wait_Event RxMsg ; Wait until we get a receive message event
Asl A ; tEvent is already in register A
Tax
Jmp (Kern_Disp-2,x)
Kern_Disp DC.W Dsptch_KRxMsg ; Dispatch Kernel message is the only
; event we look for.
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Dsptch_KRxMsg - Decode and dispatch messages to Kernel
*
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Dsptch_KRxMsg Proc
Import MsgNumTimes32 : Data
Import Kern_Task, \
Alloc_Driver, \
DeAlloc_Driver, \
ByPass_Mode, \
Init_Driver, \
Version, \
SCC_Control, \
Cmd_JmpTable : Code
_Reset_Event RxMsg ; Reset the event
Ldy #$01 ; Kernel has only one message (msg1)
Ldx MsgNumTimes32,y ; Get the Receive message page offset
Lda ByPass_Flag ; Check for ByPass mode
Bne Do_ByPass
Procede Lda RxMsgPage,x ; Get the command byte
Beq UnKnown_Msg ; Zero is not a command
Cmp #Max_KMsg+1 ; If > the max msg #
Bge Unknown_Msg ; branch to unknown message routine
Stz RxMsgPage,x ; Clear the message byte
Asl A ; Make it a word offset
Phx
Tax
Jmp (Msg_Table-2,x) ; Dispatch the command
Do_ByPass Lda RxMsgPage,x
Cmp #ByPass ; Is it a ByPass Call
Beq Procede ; Yes, procede
Cmp #SCCCntl ; Allow the SCC Control register call.
Beq Procede ; Yes, procede with message
Stz RxMsgPage+2,x
Lda ByPass_Flag
Sta RxMsgPage+1,x ; Return ByPass client ID
Lda #InByPass
Bra Complete
UnKnown_Msg Stz RxMsgPage+2,x
Stz RxMsgPage+1,x
Lda #UnKnwnMsg ;
Complete Sta RxMsgPage,x ; Return the error
Lda #MsgCmplt ; Tell host we've completed the message
Sta RxMsgState,y ; Store it in the message state variable
Lda #Int0 ; Load A with host interrupt bit
Tsb Int_Host
Jmp Kern_Task ; Return to wait for next event
Msg_Table DC.W Alloc_Driver
DC.W DeAlloc_Driver
DC.W Init_Driver
DC.W ByPass_Mode
DC.W Version
DC.W SCC_Control
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Alloc_Driver
*
* Passed
*
* struct AllocMsgType {
* byte msgNum; /* Alloc_Channel msg = $01 */
* byte Driver; /* Dvr A = $00 Dvr B = $01 */
* byte ClientID; /* Port use ID > $00 */
* }
*
* Returns
*
* struct AllocErrType{
* byte errNum; /* NoErr = $00
* Error = -1
* DvrInUse = -4
* InBypass = -5
* */
* byte ClientID; /* ID > 0 if port in use */
* int Addr; /* Driver load address - 1 */
* }
*
* Stack Info
* -----------------
* | Msg # * 32 |
* -----------------
* SP ->
*
* Register Y - contains message number starting at 1
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Alloc_Driver Proc
Import Kern_Task : Code
Plx ; Restore X - msg number * 32
Phy
Ldy RxMsgPage+1,x ; Get the channel byte (DvrA or DvrB)
Cpy #MaxDvr+1 ; Are we trying to open a nonexistant driver
Bge ERR
Cpy #DvrA
Blt ERR
Stz RxMsgPage+1,x ; Clear the Driver byte
Lda Dvr_Use,y ; See if the driver is in use
Bne InUse
Lda RxMsgPage+2,x ; Get the ClientID byte
Sta Dvr_Use,y ; Store the ID if the driver is free
Stz RxMsgPage+2,x ; Clear the ClientID
Tya ; Make the driver ID a word offset
Asl A
Tay
Lda Init_Vec,y ; Return the driver starting address
Sta RxMsgPage+3,x
Lda Init_Vec+1,y
Sta RxMsgPage+2,x
Bra Complete ; NoErr is reported because we've been
; clearing bytes as we go!!!
InUse Stz RxMsgPage+2,x ; Clear the ClientID of the message
Sta RxMsgPage+1,x ; Return the ClientID of the client using the Driver
Lda #DvrInUse
Sta RxMsgPage,x ; Return the error Driver In Use
Bra Complete
ERR Stz RxMsgPage+2,x ; Clear ClinetID
Stz RxMsgPage+1,x ; Clear driver byte
Lda #Error ; We made a boo boo
Sta RxMsgPage,x ; Return the code
Complete Ply
Lda #MsgCmplt ; Tell host we've completed the message
Sta RxMsgState,y ; Store it in the message state variable
Lda #Int0 ; Load A with host interrupt bit
Tsb Int_Host
Jmp Kern_Task ; Return to wait for next event
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* DeAlloc_Driver
*
* Passed
*
* struct DeAllocMsgType {
* byte msgNum; /* DeAlloc_Channel msg = $02 */
* byte Driver; /* Dvr A = $00 Dvr B = $01 */
* }
*
* Returns
*
* struct DeAllocErrType{
* byte errNum; /* NoErr = $00
* Error = -1
* InByPass = -5
* */
* }
*
* Stack Info
* -----------------
* | Msg # * 32 |
* -----------------
* SP ->
*
* Register Y - contains message number starting at 1
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DeAlloc_Driver Proc
Import Kern_Task : Code
Plx ; Restore X - msg number * 32
Phy
Ldy RxMsgPage+1,x ; Get the driver byte (DvrA or DvrB)
Cpy #MaxDvr+1 ; Are we trying to close a nonexistant driver
Bge ERR
Cpy #DvrA
Blt ERR
Stz RxMsgPage+1,x ; clear the channel byte
Lda #00
Sta Dvr_Use,y ; Disable driver use byte
Tya
Asl A ; Make the channel number a word offset
Tax
Jsr FakeIndirX
Bra Complete
ERR Lda #Error ; Report that we made a boo boo
Sta RxMsgPage,x ; We were trying to close a nonexistant Driver
Complete Ply
Lda #MsgCmplt ; Tell host we've completed the message
Sta RxMsgState,y ; Store it in the message state variable
Lda #Int0 ; Load A with host interrupt bit
Tsb Int_Host
Jmp Kern_Task ; Return to wait for next event
FakeIndirX Jmp (Close_Vec,x) ; Jump to the channel's close routine
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Init_Driver
*
* Passed
*
* struct InitMsgType {
* byte msgNum; /* Init_Channel msg = $03 */
* byte Driver; /* Dvr A = $00 Dvr B = $01 */
* }
*
* Returns
*
* struct InitErrType {
* byte errNum; /* NoErr = $00
* Error = -1
* InByPass = -5
* NotAlloc = -6
* */
* }
*
*
* On Entry
*
* Stack Info
* -----------------
* | Msg # * 32 |
* -----------------
* SP ->
*
* Register Y - contains message number starting at 1
*
* On Exit
*
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Init_Driver Proc
Import Kern_Task, \
Cmd_JmpTable : Code
Plx ; Get the message offset
Phy ; Save the message number
Ldy RxMsgPage+1,x ; Get the driver byte (DvrA or DvrB)
Cpy #MaxDvr+1 ; Are we trying to init a nonexistant driver
Bge ERR
Cpy #DvrA
Blt ERR
Stz RxMsgPage+1,x ; Clear driver byte
Lda Dvr_Use,y ; Check to see if driver has been allocated
Beq Not_Open
Phx ; Save the message offset
Lda Dvr_TaskID,y ; Get the Task ID for this driver
Pha ; Save Task ID temp.
Tya ; Load A with the driver byte
Asl A ; Make the driver byte a word offset
Tax
Lda Init_Vec,x ; Get the address of the driver init routine
Ldy Init_Vec+1,x
Plx ; Get the task ID
Sec ; Set Carry for Install Task
Jsr Cmd_JmpTable+(InstTask*3) ; Install the task into the Q
_Wait_Event InitFin ; Wait for the task to signal it's completion
_Reset_Event InitFin ; Reset tWait so we don't get this event again
Plx ; Restore the message offset
Stz RxMsgPage,x ; Signal No Error
Bra Complete
Not_Open Lda #NotAlloc ; Not open, therefore cannot be initialized
Sta RxMsgPage,x
Bra Complete
ERR Lda #Error ; Report that we made a boo boo
Sta RxMsgPage,x ; We were trying to close a nonexistant driver
Complete Ply
Lda #MsgCmplt ; Tell host we've completed the message
Sta RxMsgState,y ; Store it in the message state variable
Lda #Int0 ; Load A with host interrupt bit
Tsb Int_Host
Jmp Kern_Task ; Return to wait for next event
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* ByPass_Mode
*
* Passed
*
* struct ByPassMsgType {
* byte msgNum; /* ByPass_Mode msg = $04 */
* byte On_Off; /* Off = 0 On = 1 */
* byte ClientID; /* ID > 0 */
* }
*
* Returns
*
* struct ByPassErrType{
* byte errNum; /* NoErr = $00
* ChanInUse = -4
* InByPass = -5
* BadID = -7
* */
* Union {
* byte ByPassID; /* ID > 0 if in ByPass */
* byte ClientAID; /* ID > 0 If currently allocated */
* } switch;
* byte ClientBID; /* ID > 0 IF currently allocated */
* }
*
* Stack Info
* -----------------
* | Msg # * 32 |
* -----------------
* SP ->
*
*
* Register Y - contains message number starting at 1
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ByPass_Mode Proc
Import Kern_Task, \
Cmd_JmpTable : Code
Plx
Phy
Ldy RxMsgPage+1,x ; Get the On/Off byte
Beq ByPass_Off ; = 0 means we want to turn ByPass Off
Stz RxMsgPage+1,x ; Clear On/Off byte
Lda Dvr_AUse ; Check to see if Driver A is closed
Bne In_Use
Lda Dvr_BUse ; Check to see if Driver B is closed
Bne In_Use
Lda ByPass_Flag ; Check to see if we are in ByPass Mode
Bne In_ByPass
Php
Sei
Phx
Jsr OnReset
Plx
Plp
Lda #ByPass_Bit|RTXCA_36|RTXCB_36 ; Put PIC into ByPass Mode, RTXC=3.6 MHZ
Tsb SCC_Cntl
Lda RxMsgPage+2,x ; Set ByPass_Flag to ID byte
Sta ByPass_Flag ;
Bra Complete
ByPass_Off Stz RxMsgPage+1,x ; Clear on off byte
Lda RxMsgPage+2,x ; Get the ID
Stz RxMsgPage+2,x ; Clear the ID
Cmp ByPass_Flag ; Make sure who ever put us in ByPass takes us out
Bne WrongID
Php
Sei
Lda #ByPass_Bit ; Take PIC out of ByPass Mode
Trb SCC_Cntl
Jsr OffReset
Plp
Stz ByPass_Flag ; Turn ByPass mode off
Bra Complete
WrongID Lda ByPass_Flag
Sta RxMsgPage+1,x ; Return the ID of the person who turned ByPass on
Lda #BadID ; Make sure who ever put us
Sta RxMsgPage,x ; in bypass mode takes us out!!!!
Bra Complete
In_ByPass Lda ByPass_Flag
Sta RxMsgPage+1,x ; Return the ID of the person who turned ByPass on
Stz RxMsgPage+2,x ; Clear ID
Lda #InByPass
Sta RxMsgPage,x ; Return the error In ByPass mode
Bra Complete
In_Use Lda Dvr_AUse
Sta RxMsgPage+1,x ; Return driver A ClientID
Lda Dvr_BUse
Sta RxMsgPage+2,x ; Return driver B ClientID
Lda #DvrInUse ; Report that a driver is in use
Sta RxMsgPage,x
Complete Ply
Lda #MsgCmplt ; Tell host we've completed the message
Sta RxMsgState,y ; Store it in the message state variable
Lda #Int0 ; Load A with host interrupt bit
Tsb Int_Host
Jmp Kern_Task ; Return to wait for next event
OffReset Lda #SCC_Msk
Tsb Int_Mask ; Enable SCC interrupts
Bra OffSkip
OnReset Lda #SCC_Msk
Trb Int_Mask
*
* Initialize the SCC
*
OffSkip Lda SCC_ACntl ; Synchronize Read/Writes
*
* Initialize the SCC
*
DoB Lda #Wr_reg9
Sta SCC_ACntl
Lda #ChanB_Reset
Sta SCC_ACntl
Ldx #0
@LoopB Lda Table,X ; Get the register number
Tay ; Transfer indicator bits
Iny ; Was it an $FF? (END OF TABLE?)
Beq DoA ; If so, exit
Sta SCC_BCntl ; Set register
Inx ; Move to next character
Lda Table,X ; Register contents
Sta SCC_BCntl
Inx
Bra @LoopB
DoA Lda #Wr_reg9
Sta SCC_ACntl
Lda #ChanA_Reset
Sta SCC_ACntl
Ldx #0
@LoopA Lda Table,X ; Get the register number
Tay ; Transfer indicator bits
Iny ; Was it an $FF? (END OF TABLE?)
Beq @Done ; If so, exit
Sta SCC_ACntl ; Set register
Inx ; Move to next character
Lda Table,X ; Register contents
Sta SCC_ACntl
Inx
Bra @LoopA
@Done Rts
Table
DC.B Wr_reg4, X16CLK|StopBit2
DC.B Wr_reg2, 00
DC.B Wr_reg3, Rx_8Bits
DC.B Wr_reg15, 00 ; Clear External status interrupts
DC.B Wr_reg0, Reset_ExtInt ; Reset external status interrupts twice ??
DC.B Wr_reg0, Reset_ExtInt
DC.B Wr_reg1, 00 ; Disable all interrupts
DC.B $ff ; End of table
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Version
*
* Passed
*
* struct VersionMsgType {
* byte msgNum; /* Version = $05 */
* byte DriverID; /* Driver A = $00 */
* /* Driver B = $01 */
* /* Kernel = $02 */
* }
*
* Returns
*
* struct VersionErrType{
* byte errNum; /* NoErr = $00 */
* ptr VerInfo; /* Pointer to version info */
* }
*
* Stack Info
* -----------------
* | Msg # * 32 |
* -----------------
* SP ->
*
*
* Register Y - contains message number starting at 1
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Version Proc
Import VerInfo : Data
Import Kern_Task : Code
Plx
Phy
Lda RxMsgPage+1,x
Cmp #00 ; Do a range check on Driver ID
Blt Err
Cmp #Kern+1
Bge Err
Asl A
Tay
Lda Ver_Ptr,y ; Return the address of the version info
Sta RxMsgPage+1,x
Lda Ver_Ptr+1,y
Sta RxMsgPage+2,x
Cmplt Ply
Lda #MsgCmplt ; Tell host we've completed the message
Sta RxMsgState,y ; Store it in the message state variable
Lda #Int0 ; Load A with host interrupt bit
Tsb Int_Host
Jmp Kern_Task ; Return to wait for next event
Err Lda #Error ; Report Error and complete
Sta RxMsgPage,x
Bra Cmplt
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* SCC_Control - turn the general purpose clock source on and
* off when in bypass.
*
* Passed
*
* struct SCCCntlMsgType {
* byte msgNum; /* SCC_Cntl msg = $06 */
* byte Driver; /* Dvr A = $00 Dvr B = $01 */
* byte GPI; /* GPI = 0 (Off) GPI = 1 (On) */
* }
*
* Returns
*
* struct SCCCntlErrType{
* byte errNum; /* NoErr = $00
* Error = -1
* */
* }
*
* Stack Info
* -----------------
* | Msg # * 32 |
* -----------------
* SP ->
*
* Register Y - contains message number starting at 1
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SCC_Control Proc
Import Kern_Task : Code
Plx ; Restore X - msg number * 32
Phy
Lda ByPass_Flag ; This command only works in bypass.
Beq ERR
Stz RxMsgPage,x ; Assume no error.
Lda RxMsgPage+1,x ; Get the channel byte (DvrA or DvrB)
Cpa #DvrA ; Is it for DvrA or DvrB
Bne @Chck_B
Lda RxMsgPage+2,x ; Get the GPI
Bne @GPIA_On ; <> 0 means turn GPIA on
Lda SCC_Cntl ; Get the current value of the register.
And #ÅRTXCA_GPIA ; Make the clock source for RTXCA 3.686 MHZ
Sta SCC_Cntl ; Store the new value
Bra Complete
@GPIA_On Lda #RTXCA_GPIA ; Get the bit mask.
Tsb SCC_Cntl ; Make the clock source for RTXCA GPIA
Bra Complete
@Chck_B Cpa #DvrB ; Was it Driver B
Bne ERR ; It is an error if it isn't.
Lda RxMsgPage+2,x ; Get the GPI
Bne @GPIB_On ; <> 0 means turn GPIB on
Lda SCC_Cntl ; Get the current value of the register
And #ÅRTXCB_GPIA ; Make the clock source for RTXCB 3.686 MHZ
Sta SCC_Cntl ; Store the new value.
Bra Complete
@GPIB_On Lda #RTXCB_GPIA ; Get the bit mask.
Tsb SCC_Cntl ; Make the clock source for RTXB GPIB
Bra Complete
ERR Lda #Error ; We made a boo boo
Sta RxMsgPage,x ; Return the code
Complete Ply
Lda #MsgCmplt ; Tell host we've completed the message
Sta RxMsgState,y ; Store it in the message state variable
Lda #Int0 ; Load A with host interrupt bit
Tsb Int_Host
Jmp Kern_Task ; Return to wait for next event
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Kernel Subroutines called by indirect jump through table
* in the Zero Page
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Remv_RxMsgSgn - Install receive message signaller
*
* Register X - message number * 2 (msg numbers start at 1)
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Remv_RxMsgSgn Proc
Import RxMsgSignal : Data
Export NullRxMsgSgn
Bcs Inst_RxMsgSgn
Ldy #>NullRxMsgSgn
Lda #<NullRxMsgSgn
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Inst_RxMsgSgn - Install receive message signaller
*
* Register X - message number * 2 (msg numbers start at 1)
* Register Y - High byte of message signaller address
* Register A - Low byte of message signaller address
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inst_RxMsgSgn Php ; Save the status byte
Sei
Sta RxMsgSignal-2,x ; Store the low byte of the msg handler
Inx
Tya
Sta RxMsgSignal-2,x ; Store the high byte of the msg handler
Plp ; Restore the status byte
Rts ; Return to caller
NullRxMsgSgn Rts
EndProc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Remv_TxCmplSgn - Removel transmit completion signaller
*
* Register X - message number * 2 (msg numbers start at one)
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Remv_TxCmplSgn Proc
Import TxMsgSignal : Data
Export NullTxCmplSgn
Bcs Inst_TxCmplSgn
Ldy #>NullTxCmplSgn
Lda #<NullTxCmplSgn
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Inst_TxCmplSgn - Install transmit completion signaller
*
* Register X - message number * 2 (msg numbers start at one)
* Register Y - High byte of completion handler
* Register A - Low byte of completion handler
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inst_TxCmplSgn Php
Sei
Sta TxMsgSignal-2,x ; Store the low byte of the msg handler
Tya
Sta TxMsgSignal-1,x ; Store the high byte of the msg handler
Plp
Rts ; Return to caller
NullTxCmplSgn Rts
EndProc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Inst_Task - Install task. Always installs task after Kernel.
*
* Register X - TaskID of task to install
* Register Y - High byte of task address
* Register A - Low byte of task address
*
* Carry Clear - Branch to Kill Task
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inst_Task Proc
Import tPrev, \
tNext, \
tSp, \
tEvent, \
tWait : Data
Bcc Kill_Task
Php
Sei
Cpx #DvrA_ID
Beq Do_DvrA
Sta DvrB_Sp-1 ; Push the task address on the stack
Sty DvrB_Sp
Lda #<(DvrB_Sp-2) ; Init the stack pointer
Bra SkipA
Do_DvrA Sta DvrA_Sp-1 ; Push the task address on the stack
Sty DvrA_Sp
Lda #<(DvrA_Sp-2) ; Init the stack pointer
SkipA Sta tSp,x
Ldy #Kern_ID ; Insert the Task after the Kernel task
Lda tNext,y ; Get the Kernel's tNext link.
Sta tNext,x ; Make the new Task link to what the kernel
; was linked to.
Tay ; Make y-reg point to what the tNext of the Kernel
Txa ; Make a-reg the value of the one we are inserting
Sta tPrev,y ; Update the previous pointer of the task we are inserting
; in front of.
Ldy #Kern_ID
Sta tNext,y ; Make the Kernel TCB point to the new TCB
Tya ; Make a-reg point to Kernel
Sta tPrev,x ; Make tPrev of the task to be inserted
; point to the Kernel.
Lda #$80
Sta tEvent,x ; Set the Always Event
Sta tWait,x ; Wait for the Always event
Plp
Rts
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Kill_Task - Kill task.
*
* Register X - TaskID of the task to be removed. (ChanA_ID ChanB_ID only !)
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Kill_Task
Php
Sei
Lda tPrev,x ; Get tPrev of task to be removed.
Tay ; y-reg points to task before the task to be removed.
Lda tNext,x ; Get tNext from task to be removed
Tax ; Make x-reg point to next task.
Sta tNext,y ; Make tNext of previous task point the task
; after the task to be removed.
Tya ; Make a-reg point to prev task
Sta tPrev,x ; Make tPrev of next task point to the tNext
; of the task that is removed.
Plp
Rts
EndProc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Remv_ISR - remove an Interrupt service routine
*
* Register X - interrupt type
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Remv_ISR Proc
Export Inst_ISR
Import UnKnown_Int : Code
Import ISR_Table :Data
Bcs Inst_ISR ; Is this a Remove or Install ISR call??
Ldy #>UnKnown_Int ; For Remove we fill in values for the
Lda #<Unknown_Int ; Unknown interrupt handler.
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Inst_ISR - make an entry into the interrupt table.
*
* Register X - interrupt type
* Register Y - High byte of interrupt address
* Register A - Low byte of interrupt address
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inst_ISR Php
Sei
Sta ISR_table,x ; Store the low byte of the address
Tya
Sta ISR_table+1,x ; Store the high byte of the address
Plp
Rts
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Remove_ISR - remove an Interrupt service routine
*
* Carry -> Set - install SCC ISR routine
* Clear - remove SCC ISR routine
*
* Register X - interrupt type
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Remv_SCC_ISR Proc
Import UnKnown_SCCInt : Code
Import SCC_table : Data
Bcs Inst_SCC_ISR
Ldy #>UnKnown_Int
Lda #<Unknown_Int
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Inst_SCC_ISR - make an entry into the SCC interrupt table.
*
* Register X - interrupt type
* Register Y - High byte of interrupt address
* Register A - Low byte of interrupt address
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inst_SCC_ISR Php
Sei
Sta SCC_table,x ; Store the low byte of the address
Tya
Sta SCC_table+1,x ; Store the high byte of the address
Plp
Rts
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Reset Channel - reset channel A or B of the SCC
*
* Carry -> Set - reset A
* Clear - reset B
*
* A Register is corrupted
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Reset_Chan Proc
Bcc ChanB ; Is this a ChanA or ChanB reset
Php ; Do ChanA reset
Sei
Lda #Wr_Reg9
Sta SCC_ACntl
Lda #ChanA_Reset|MIE|No_Vec
Sta SCC_ACntl
Plp
Rts
ChanB Php ; Do ChanB Reset
Sei
Lda #Wr_Reg9
Sta SCC_ACntl
Lda #ChanB_Reset|MIE|No_Vec
Sta SCC_ACntl
Plp
Rts
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Reg_Ver - register version info.
*
* Register A - Driver ID
* Register Y - High byte of info address
* Register X - Low byte of info address
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Reg_Ver Proc
Phx
Asl A
Tax
Pla
Sta Ver_Ptr+1, x ; Store the Low byte of the address
Sty Ver_Ptr, x ; Store the High byte of the address
Rts
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Inst_TmTask - install a timer task.
*
* Carry -> Set - Branch to cancel timer task
* Clear - execute install timer task
*
* Register A - Low byte of parameter address
* Register X - High byte of the parameter address
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inst_TmTask Proc
Export Get_TMPB, \
Init_Timer, \
Timer_ISR
Php
Sei
Bcs Can_TmTask ; Cancel timer task if carry is set
Sta AddressLo ; Store the parameter address
Stx AddressHi
Ldy #$00
Lda (AddressLo),y
Tax
Iny
Lda (AddressLo),y ; Get the low byte of the completion routine
Sta TMPBpcL,x
Iny
Lda (AddressLo),y ; Get the high byte of the completion routine
Sta TMPBpcH,x
Iny
Lda (AddressLo),y ; Get the low byte of the time value
Sta TMPBTimeL,x
Iny
Lda (AddressLo),y ; Get the high byte of the time value
Sta TMPBTimeH,x
Txa
Tay
Jsr StartTimer
Plp
Rts
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: Can_TmTask
* Inputs: Y - TMPB index
* Outputs: none
* Destroys: A, X, Y, n, v, z, c
* Size: 100 bytes
* Cycles: 15 - if it is already completed
* 50 - if it is the only running timer
* 98 + StopTimer + StartTimer - if head, with successors
* 65..94+ - if not head
* Stack Usage: 2 bytes
* Calls: Stop, Start
* Called by:
*
* Function: Cancels the timer in the indicated TMPB, and removes it
* from the timer queue.
*
* NOTE: This routine must be called with interrupts DISABLED
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Can_TmTask
Lda TMPBcmplt,y ; Exit if the task has completed
Beq @Exit
Cpy tmReqQhead ; See if it is running
Bne @SrchQueue ; If not head of queue
Lda TMPBnext,y ; Check for successor
Bpl @HasSucc ; If not the only entry
Sta tmReqQhead ; Set head to nil
Lda #$00
Sta TMPBpcH,y ; Indicate that the routine is complete
Lda #Timer_Msk ; Disable timer interrupts
Trb Int_Mask ; Update the interrupt mask
Plp
Rts ; All done
@HasSucc Ldx tmReqQhead ; Get queue head
Jsr Stop ; Stop the head timer
Ldy tmReqQhead ; Get index of entry to dequeue
Lda #$00
Sta TMPBpcH,y ; Indicate that the routine is complete
Ldx TMPBnext,y ; Get index of successor
Stx tmReqQhead ; Successor is new head
Jsr @AdjTime ; Adjust the delay time
Jsr Start ; Start new head timer
Plp
Rts
@SrchQueue Lda tmReqQhead ; Index tmReqQhead
Sty tmIndexTemp
@loop Tax ; Load index register
Bmi @exit ; Exit if end of list (nil)
Lda TMPBnext,x ; Get index to next TMPB
Cpa tmIndexTemp ; Check for a match
Bne @loop ; Loop if not a match
Lda TMPBnext,y ; Prepare to un-link entry
Sta TMPBnext,x ; Finish unlinking it
Bmi @exit ; If entry has no successor
Tax
@AdjTime Clc ; Clear carry for addition
lda TMPBtimeL,x ; TMPBtime,x := TMPBtime,x + TMPBtime,y
Adc TMPBtimeL,y
Sta TMPBtimeL,x
Lda TMPBtimeH,x
Adc TMPBtimeH,y
Sta TMPBtimeH,x
@exit Plp
Rts ; All done
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: StartTimer
* Inputs: Y - TMPB index
* Outputs: none
* Destroys: A, X, Y, n, v, z, c
* Size: 114 bytes
* Cycles: 39 + Start - if timer queue was empty
* 142 + Stop + StartTimer +..., otherwise
* Stack Usage: 2 bytes
* Calls: Stop, Start
* Called by:
*
* Function: Starts running the timer in the TMPB.
* The timeH and timeL fields of the TMPB must be filled in prior
* to this call.
*
* NOTE: This routine must be called with interrupts DISABLED
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
StartTimer
Lda #$ff
Sta TMPBcmplt,y ; Completion state is active
Ldx tmReqQhead ; Get index of head of list
Bpl @QNotEmpty ; If queue already has entries
Sty tmReqQhead ; New entry is queue head
Lda #nil ; Mark this entry as end of list
Sta TMPBnext,y ; Indicate no successor
Lda #Timer_Msk ; Enable timer interrupts
Tsb Int_Mask ; Update the interrupt mask
Tya ; Get TMPB index
Tax ; Setup TMPB index for StartTimer
Bra Start ; Start the timer and return
@QNotEmpty Jsr Stop ; Stop the currently running timer
Ldx tmReqQhead ; Index tmReqQhead
@loop Stx tmIndexTemp ; Remember index of previous TMPB
Lda TMPBnext,x ; Get index to next TMPB
Tax ; Load index register
Bmi @exit ; Exit if end of list (nil)
Sec ; Set carry for subtract
Lda TMPBtimeL,y ; TMPBtime,y := TMPBtime,y - TMPBtime,x
Sbc TMPBtimeL,x
Sta TMPBtimeL,y
Lda TMPBtimeH,y
Sbc TMPBtimeH,x
Sta TMPBtimeH,y
Bpl @loop ; Loop until TMPBtime,y is < 0
Clc ; Clear carry for addition
Lda TMPBtimeL,y ; TMPBtime,y := TMPBtime,y + TMPBtime,x
Adc TMPBtimeL,x
Sta TMPBtimeL,y
Lda TMPBtimeH,y
Adc TMPBtimeH,x
Sta TMPBtimeH,y
Sec ; Set carry for subtract
Lda TMPBtimeL,x ; TMPBtime,x := TMPBtime,x - TMPBtime,y
Sbc TMPBtimeL,y
Sta TMPBtimeL,x
Lda TMPBtimeH,x
Sbc TMPBtimeH,y
Sta TMPBtimeH,x
@exit Txa ; Prepare to store x
Sta TMPBnext,y ; Add rest of list after new entry
Ldx tmIndexTemp ; Get index of entry before new one
Tya ; Prepare to store y
Sta TMPBnext,x ; Add new entry after beginning of list
Ldx tmReqQhead ; Get index of head of list
*
* Falls into Start, which follows below
* bra Start ; Start the timer for the list head
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: Start
* Inputs: X - tmReqQhead TMPB index
* Outputs: none
* Destroys: A, n, v, z, c
* Size: 75 bytes
* Cycles: 26 - when timer in range $8000..$FFFF
* 33 - when timer in range $0000..$007F
* 40 - when timer in range $0080..$00FF
* 43/49 - when timer in range $0100..$7FFF
* Stack Usage: 2 bytes
* Calls: none
* Called by: StartTmr, Timer_Int, CancelTmr
*
* Function: Loads the hardware timer with the amount of time remaining
* for the timer at the head of the timer queue, or $0080 if
* the amount is less than 1, or $7F80 if the value is greater
* than $007F. Adjusts the value in the TMPB to reflect the
* value loaded into the hardware time. The timer is then
* started and will interrupt when it reaches zero.
*
* NOTE: This routine must be called with interrupts DISABLED
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Start
Lda TMPBtimeH,x ; Get high bits
Bne @OORangeH ; If way too big
Lda TMPBtimeL,x ; Get low bits
Bmi @OORangeL ; Not in range $0000..$007F
Stz TMPBtimeL,x ; Clear low bits
Sta TmCnt_Hi ; Start the counter
Lda #$FF ; Set latch to $FF to re-load timer
Sta TmLatch_Hi ; with $FF when timer runs out
Rts
@OORangeH Bmi @OORangeNeg ; If negative
Lda #$7F ; Load timer with $7F80
Sta TmCnt_Hi ; Start the counter
Lda #$FF ; Set latch to $FF to re-load timer
Sta TmLatch_Hi ; with $FF when timer runs out
Sec ; Set carry for subtract
Lda TMPBtimeL,x ; Get low bits
Sbc #$7F ; TMPBtime,x := TMPBtime,x - $007F
Sta TMPBtimeL,x
Bcs @exit ; If no borrow
Dec TMPBtimeH,x ; Adjust for borrow
@exit Rts
@OORangeNeg Stz TmCnt_Hi ; Use $0080 if negative
Lda #$FF ; Set latch to $FF to re-load timer
Sta TmLatch_Hi ; with $FF when timer runs out
Rts
@OORangeL Sec ; Set carry for subtract
Sbc #$7F ; TMPBtime,x := TMPBtime,x - $007F
Sta TMPBtimeL,x
Lda #$7F ; Load timer with $7F80
Sta TmCnt_Hi ; Start the counter
Lda #$FF ; Set latch to $FF to re-load timer
Sta TmLatch_Hi ; with $FF when timer runs out
Rts
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: Stop
* Inputs: X - tmReqQhead TMPB index
* Outputs: n - indicates sign of TMPBtimer,x
* Destroys: A, v, z, c
* Size: 41 bytes
* Cycles: 38..40 + 9 if close to rollover
* Stack Usage: 2 bytes
* Calls: none
* Called by: StartTimer, Timer_Int, CancelTmr
*
* Function: Reads the hardware timer, and updates the head of the
* timer queue to reflect the actual amount of time remaining.
* It also returns an indication of the sign of the timer
* remaining for the queue head.
*
* NOTE: This routine must be called with interrupts DISABLED
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Stop
Lda TmCnt_Lo ; Read low byte of counter
Cpa #$10 ; See if close to rollover
Blt Stop ; Wait if close to rollover
Lda TmCnt_Hi ; Read high byte of counter
Clc ; Clear carry for addition
Bpl @ZeroExt ; If high byte was positive
Adc TMPBtimeL,x ; Add to timer remaining for the
Sta TMPBtimeL,x ; Timer at head of the timer list
Bcs @TestSign ; If no borrow from high byte
Dec TMPBtimeH,x ; Adjust high byte
@TestSign Lda TMPBtimeH,x
Rts ; All done
@ZeroExt Adc TMPBtimeL,x ; Add to timer remaining for the
Sta TMPBtimeL,x ; Timer at head of the timer list
Bcc @TestSign ; If no carry into high byte
Inc TMPBtimeH,x ; Adjust high byte
Rts ; All done
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: Timer_ISR
* Inputs: none
* Outputs: none
* Destroys: A, X, Y, n, v, z, c
* Size: 60 bytes
* Cycles: 15 + Stop + Start, if timer not fully expired
* 60 + Stop, if expired, no more queued,
* no completion handler
* 84 + Stop, if expired, no more queued,
* has completion handler
* 62 + Stop + Start, if expired, and more queued
* no completion handler
* 86 + Stop + Start, if expired, and more queued
* has completion handler
* Stack Usage: 2 bytes
* Calls: Stop, Start, TMPB completion handler
* Called by: Interrupt
*
* Function: Handles timer interrupts, determines if the timer at the
* head of the timer queue has fully expired, and calls the
* completion handler for that timer if one exists. Start
* any remaining timers on the timer queue.
*
* NOTE: This routine must be called with interrupts DISABLED
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Timer_ISR
Ldx tmReqQhead ; Get index of head of list
Bmi @unknownint ; If timer queue is empty
Jsr Stop ; Stop timer and update queue head
Bpl Start ; If not expired, restart timer and exit
Phx ; Save pointer to dequeued entry
Stz TMPBcmplt,x ; Zero completion routine indicating done.
Lda TMPBnext,x ; Get next queue entry
Sta tmReqQhead ; Dequeue head
Bmi @DisTimer ; If no successor disable timer
Tax ; Get pointer to new queue head
Jsr Start ; Start next timer
Bra @RunCmplt ; Go run the completion routine
@DisTimer Lda #Timer_Msk ; Disable timer interrupts
Trb Int_Mask ; Update the interrupt mask
@RunCmplt Plx ; Restore pointer to dequeued entry
Lda TMPBpcL,x
Sta AddressLo
Lda TMPBpcH,x
Sta AddressHi
Jmp (AddressLo) ; Jump to the completion routine. The
; return address is on the stack
@unknownint Brk ; TMR interrupt with empty Q
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: Get_TMPB
* Inputs: none
* Outputs: Y - TMPB index
* n - 1 if no free TMPBs available
* Destroys: A, Y, n, z
* Size: 22 bytes
* Cycles: 34 if BCPB was available
* 13 if BCPB was not available
* Stack Usage: 2 bytes
* Calls: none
* Called by:
*
* Function: Returns the index of a free TiMer Parameter Block
* (TMPB), and removes it from the list of free TMPBs.
* A flag is returned to indicate if a free TMPB existed.
*
* NOTE: This routine must be called with interrupts DISABLED.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Get_TMPB
Php
Sei
Bcs Free_TMPB
Ldy availhead ; Get the available list head
Bmi @exit ; If list empty
Lda TMPBnext,y ; Get successor
Sta availhead ; Update list head
@exit Plp
Rts ; All done
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: Free_TMPB
* Inputs: Y - TMPB index
* Outputs: none
* Destroys: A, n, z, c
* Size: 19 bytes
* Cycles: 27
* Stack Usage: 2 bytes
* Calls: none
* Called by:
*
* Function: Adds the TMPB that was passed in, to the list of available
* TMPBs. If the TMPB is currently active, the system crashes
*
* NOTE: This routine must be called with interrupts DISABLED
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Free_TMPB
Lda availhead ; Get the available list head
Sta TMPBnext,y ; Link it to end of new entry
Sty availhead ; New entry is new list head
Lda TMPBstate,y ; Check the state
Plp
Rts ; All done
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Routine: Init_Timer
* Inputs: none
* Outputs: none
* Destroys: A, X, n, v, z, c
* Size: 40 bytes
* Cycles: 45 + 14 per TMPB
* Stack Usage: 2 bytes
* Calls: none
* Called by: Reset
*
* Function: Initializes Timer data structures.
*
* NOTE: This routine must be called with interrupts DISABLED.
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Init_Timer
last Equ TMPBsize*(MaxTMPBs-1)
Lda #0 ; Index of first TMPB
Sta availhead ; All TMPBs are now available
Clc ; Clear carry for additions
@loop Tax ; Setup index register
Adc #TMPBsize ; Point to next TMPB
Sta TMPBnext,x ; Have previous point to next
Stz TMPBcmplt,x ; Zero the completion state
Cpa #last ; Check for last TMPB
Blt @loop ; Process next TMPB
Lda #nil ; Mark empty lists
Sta last+TMPBnext ; Mark end of free list
Sta tmReqQhead ; Request queue head is empty
Lda #Timer_Msk ; Disable timer interrupts
Trb Int_Mask ; Update the interrupt mask
Lda #One_Shot ; Prepare to force one shot mode
Trb TD_Cntl ; Reset the bit
Lda #$80 ; Low byte latch is a constant
Sta TmLatch_Lo ; Initialize the low timer latch
Rts
*
* TiMer Parameter Block (TMPB)
*
TMPBnext DS.B 1 ; Next TMPB on list
TMPBstate DS.B 1 ; Request state
TMPBtimeH DS.B 1 ; High byte of 2 byte timer
TMPBtimeL DS.B 1 ; Low byte of 2 byte timer
TMPBpcH DS.B 1 ; Completion routine PC high
TMPBpcL DS.B 1 ; Completion routine PC low
TMPBcmplt DS.B 1 ; Completion state (0 = complete 0<> means running)
DS.B TMPBsize*(MaxTMPBs-1) ; Allocate remaining TMPBs
availhead DS.B 1 ; Head of TMPB free list
tmReqQhead DS.B 1 ; Head of TMPB request queue
tmIndexTemp DS.B 1 ; Temporary storage for index
endproc
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
*
* Global Data Area for kernel
*
*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Global_Kernel Proc
Export ISR_table, \
ISR_Decode, \
MsgNumTimes2, \
MsgNumTimes32, \
Cmd_Table, \
Cmd_JmpTable, \
SCC_Table, \
TxMsgSignal, \
RxMsgSignal, \
tPrev, \
tNext, \
tSp, \
tEvent, \
tWait, \
tTNxt, \
tTEvent, \
tTISR, \
VerInfo
Import Remv_RxMsgSgn, \
Remv_TxCmplSgn, \
Remv_ISR, \
Remv_SCC_ISR, \
Inst_Task, \
Rel_Task, \
Ev_Wait, \
Ev_Signal, \
Ev_Reset, \
TEv_Reset, \
Get_TMPB, \
Inst_TmTask, \
Reg_Ver, \
NullRxMsgSgn, \
NullTxCmplSgn, \
UnKnown_Int, \
UnKnown_SCCInt, \
Dsptch_KRxMsg
*
* TCB - Task Control Block
*
* Each active task has a TCB entry in the TCB linked list.
* The List starts out with only one active task which is the kernel task.
* The Kernel is always active.
*
tPrev DC.B Kern_ID ; Previous Task
tNext DC.B Kern_ID ; Next task ID (initially 0 for Kernel Task only)
tSp DC.B $FF ; Stack Pointer for this task
tEvent DC.B $80 ; Event flags (bit 7 event always active)
tWait DC.B $00 ; Event Mask (Wait for RxMsg1 event always)
TCB_Size Equ *-tPrev
DC.B $00 ; tPrev ChanA Task
DC.B $00 ; tNext ChanA Task
DC.B $a9 ; tSp ChanA Task
DC.B $80 ; tEvent ChanA Task (bit 7 event always active)
DC.B $00 ; tWait ChanA Task
DC.B $00 ; tPrev ChanB Task
DC.B $00 ; tNext ChanB Task
DC.B $54 ; tSp ChanB Task
DC.B $80 ; tEvent ChanB Task (bit 7 event always active)
DC.B $00 ; tWait ChanB Task
*
* 6502 and SCC interrupt service routine vector tables
*
ISR_table DC.W Unknown_Int ; 0 priority
DC.W UnKnown_Int ; DMA1
DC.W Unknown_Int ; DMA2
DC.W UnKnown_Int ; SCC
DC.W UnKnown_Int ; Timer
DC.W UnKnown_Int ; Host
ISR_Decode DC.B 0 ; %00000000
DC.B 0
DC.B DMA1 ; %00000010
DC.B 0
DC.B DMA2 ; %00000100
DC.B 0
DC.B DMA2 ; %00000110
DC.B 0
DC.B SCC ; %00001000
DC.B 0
DC.B SCC ; %00001010
DC.B 0
DC.B SCC ; %00001100
DC.B 0
DC.B SCC ; %00001110
DC.B 0
DC.B HOST ; %00010000
DC.B 0
DC.B DMA1 ; %00010010
DC.B 0
DC.B DMA2 ; %00010100
DC.B 0
DC.B DMA2 ; %00010110
DC.B 0
DC.B SCC ; %00011000
DC.B 0
DC.B SCC ; %00011010
DC.B 0
DC.B SCC ; %00011100
DC.B 0
DC.B SCC ; %00011110
DC.B 0
DC.B TIMER ; %00100000
DC.B 0
DC.B DMA1 ; %00100010
DC.B 0
DC.B DMA2 ; %00100100
DC.B 0
DC.B DMA2 ; %00100110
DC.B 0
DC.B SCC ; %00101000
DC.B 0
DC.B SCC ; %00101010
DC.B 0
DC.B SCC ; %00101100
DC.B 0
DC.B SCC ; %00101110
DC.B 0
DC.B TIMER ; %00110000
DC.B 0
DC.B DMA1 ; %00110010
DC.B 0
DC.B DMA2 ; %00110100
DC.B 0
DC.B DMA2 ; %00110110
DC.B 0
DC.B SCC ; %00111000
DC.B 0
DC.B SCC ; %00111010
DC.B 0
DC.B SCC ; %00111100
DC.B 0
DC.B SCC ; %00111110
SCC_table DC.W UnKnown_SCCInt
DC.W UnKnown_SCCInt
DC.W UnKnown_SCCInt
DC.W UnKnown_SCCInt
DC.W UnKnown_SCCInt
DC.W UnKnown_SCCInt
DC.W UnKnown_SCCInt
DC.W UnKnown_SCCInt
*
* Transmit message Request Completion signal routines
*
TxMsgSignal DC.W NullTxCmplSgn
DC.W NullTxCmplSgn
DC.W NullTxCmplSgn
DC.W NullTxCmplSgn
DC.W NullTxCmplSgn
DC.W NullTxCmplSgn
DC.W NullTxCmplSgn
*
* Receive new message signal routines
*
RxMsgSignal DC.W NullRxMsgSgn
DC.W NullRxMsgSgn
DC.W NullRxMsgSgn
DC.W NullRxMsgSgn
DC.W NullRxMsgSgn
DC.W NullRxMsgSgn
DC.W NullRxMsgSgn
*
* Kernel Command Table - this contains the addresses of the kernel commands
* which will later be tranfered to the zero page,
* Up to twenty commands are supported. They must be
* in order of their command numbers.
*
Cmd_Table DC.W Remv_RxMsgSgn
DC.W Remv_TxCmplSgn
DC.W Remv_ISR
DC.W Remv_SCC_ISR
DC.W Inst_Task
DC.W Rel_Task
DC.W Ev_Wait
DC.W Ev_Signal
DC.W Ev_Reset
DC.W Reset_Chan
DC.W Get_TMPB
DC.W Inst_TmTask
DC.W Reg_Ver
DS.W 7
Cmd_JmpTable Jmp (Krn_CmdBase+(RemvRxMsg*2))
Jmp (Krn_CmdBase+(RemvTxCmpl*2))
Jmp (Krn_CmdBase+(RemvISR*2))
Jmp (Krn_CmdBase+(RemvSCCISR*2))
Jmp (Krn_CmdBase+(InstTask*2))
Jmp (Krn_CmdBase+(RelTask*2))
Jmp (Krn_CmdBase+(WaitEvent*2))
Jmp (Krn_CmdBase+(SignalTask*2))
Jmp (Krn_CmdBase+(ResetEvent*2))
Jmp (Krn_CmdBase+(ResetChan*2))
Jmp (Krn_CmdBase+(GetTMPB*2))
Jmp (Krn_CmdBase+(InstTmTask*2))
Jmp (Krn_CmdBase+(RegVer*2))
MsgNumTimes2 DC.B 0,2,4,6,8,10,12,14
MsgNumTimes32 DC.B 0,32,64,96,128,160,192,224
*
* Version Information
*
String Pascal
VerInfo DC.B $02
DC.B 'Enhanced Communications Controller'
DC.B 'Version 1.0A0 © Apple Computer Inc. 1988'
DCB.W 512, $00FF ; Needed for AURORA ROM resource padding
endproc
End