; ; 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 #DvrB_Addr ; Install the Initialization vector for driver B Sta Init_Vec+3 Lda #DvrA_Close ; Install the Close vector for driver A Sta Close_Vec+1 Lda #DvrB_Close ; Install the Close vector for driver B Sta Close_Vec+3 Lda # 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 #NullTxCmplSgn Lda #UnKnown_Int ; For Remove we fill in values for the Lda # 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 # 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