OpenCores
URL https://opencores.org/ocsvn/rtf65002/rtf65002/trunk

Subversion Repositories rtf65002

[/] [rtf65002/] [trunk/] [software/] [asm/] [bootrom.asm] - Rev 31

Go to most recent revision | Compare with Previous | Blame | View Log

; ============================================================================
;        __
;   \\__/ o\    (C) 2013  Robert Finch, Stratford
;    \  __ /    All rights reserved.
;     \/_//     robfinch<remove>@opencores.org
;       ||
;  
;
; This source file is free software: you can redistribute it and/or modify 
; it under the terms of the GNU Lesser General Public License as published 
; by the Free Software Foundation, either version 3 of the License, or     
; (at your option) any later version.                                      
;                                                                          
; This source file is distributed in the hope that it will be useful,      
; but WITHOUT ANY WARRANTY; without even the implied warranty of           
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            
; GNU General Public License for more details.                             
;                                                                          
; You should have received a copy of the GNU General Public License        
; along with this program.  If not, see <http://www.gnu.org/licenses/>.    
;                                                                          
; ============================================================================
;
CR      EQU     0x0D            ;ASCII equates
LF      EQU     0x0A
TAB     EQU     0x09
CTRLC   EQU     0x03
CTRLH   EQU     0x08
CTRLI   EQU     0x09
CTRLJ   EQU     0x0A
CTRLK   EQU     0x0B
CTRLM   EQU 0x0D
CTRLS   EQU     0x13
CTRLX   EQU     0x18
XON             EQU     0x11
XOFF    EQU     0x13

; error codes
E_Ok            =               0x00
E_Arg           =               0x01
E_BadMbx        =               0x04
E_QueFull       =               0x05
E_NoThread      =               0x06
E_NotAlloc      =               0x09
E_NoMsg         =               0x0b
E_Timeout       =               0x10
E_BadAlarm      =               0x11
E_NotOwner      =               0x12
; resource errors
E_NoMoreMbx     =               0x40
E_NoMoreMsgBlks =       0x41
E_NoMoreAlarmBlks       =0x44
E_NoMoreTCBs    =       0x45

; task status
TS_NONE     =0
TS_TIMEOUT      =1
TS_WAITMSG      =2
TS_PREEMPT      =4
TS_RUNNING      =8
TS_READY        =16
TS_SLEEP        =32

MAX_TASKNO      = 255

DIRENT_NAME             =0x00   ; file name
DIRENT_EXT              =0x1C   ; file name extension
DIRENT_ATTR             =0x20   ; attributes
DIRENT_DATETIME =0x28
DIRENT_CLUSTER  =0x30   ; starting cluster of file
DIRENT_SIZE             =0x34   ; file size (6 bytes)

; One FCB is allocated and filled out for each file that is open.
;
nFCBs   = 128
FCB_DE_NAME             =0x00
FCB_DE_EXT              =0x1C
FCB_DE_ATTR             =0x20
FCB_DE_DATETIME =0x28
FCB_DE_CLUSTER  =0x30   ; starting cluster of file
FCB_DE_SIZE             =0x34   ; 6 byte file size
FCB_DIR_SECTOR  =0x40   ; LBA directory sector this is from
FCB_DIR_ENT             =0x44   ; offset in sector for dir entry
FCB_LDRV                =0x48   ; logical drive this is on
FCB_MODE                =0x49   ; 0 read, 1=modify
FCB_NUSERS              =0x4A   ; number of users of this file
FCB_FMOD                =0x4B   ; flag: this file was modified
FCB_RESV                =0x4C   ; padding out to 80 bytes
FCB_SIZE                =0x50

FUB_JOB         =0x00   ; User's job umber
FUB_iFCB        =0x02   ; FCB number for this file
FUB_CrntLFA     =0x04   ; six byte current logical file address
FUB_pBuf        =0x0C   ; pointer to buffer if in stream mode
FUB_sBuf        =0x10   ; size of buffer for stream file
FUB_LFABuf      =0x14   ; S-First LFA in Clstr Buffer
FUB_LFACluster  =0x18   ; LFA of cluster
FUB_Clstr       = 0x20          ; The last cluster read
FUB_fModified   = 0x24  ; data in buffer was modified
FUB_fStream             = 0x25  ; non-zero for stream mode
FUB_PAD         =0x26   
FUB_SIZE        =0x30

; Boot sector info (62 byte structure) */
BSI_JMP         = 0x00
BSI_OEMName     = 0x03
BSI_bps         = 0x0B
BSI_SecPerCluster       = 0x0D
BSI_ResSectors  = 0x0E
BSI_FATS        = 0x10
BSI_RootDirEnts = 0x11
BSI_Sectors     = 0x13
BSI_Media       = 0x15
BSI_SecPerFAT   = 0x16
BSI_SecPerTrack = 0x18
BSI_Heads       = 0x1A
BSI_HiddenSecs  = 0x1C
BSI_HugeSecs    = 0x1E

BSI_DriveNum    = 0x24
BSI_Rsvd1               = 0x25
BSI_BootSig             = 0x26
BSI_VolID               = 0x27
BSI_VolLabel    = 0x2B
BSI_FileSysType = 0x36

         
MEM_CHK         =0
MEM_FLAG        =1
MEM_PREV        =2
MEM_NEXT        =3

; message queuing strategy
MQS_UNLIMITED   =0      ; unlimited queue size
MQS_NEWEST              =1      ; buffer queue size newest messages
MQS_OLDEST              =2      ; buffer queue size oldest messages

LEDS            EQU             0xFFDC0600
TEXTSCR         EQU             0xFFD00000
COLORSCR        EQU             0xFFD10000
TEXTREG         EQU             0xFFDA0000
TEXT_COLS       EQU             0x0
TEXT_ROWS       EQU             0x1
TEXT_CURPOS     EQU             11
KEYBD           EQU             0xFFDC0000
KEYBDCLR        EQU             0xFFDC0001
PIC                     EQU             0xFFDC0FF0
PIC_IE          EQU             0xFFDC0FF1
PIC_ES          EQU             0xFFDC0FF4
PIC_RSTE        EQU             0xFFDC0FF5
TASK_SELECT     EQU             0xFFDD0008
RQ_SEMA         EQU             0xFFDB0000
TO_SEMA         EQU             0xFFDB0010
SERIAL_SEMA     EQU             0xFFDB0020
KEYBD_SEMA      EQU             0xFFDB0030
IOF_LIST_SEMA   EQU     0xFFDB0040
MBX_SEMA        EQU             0xFFDB0050
MEM_SEMA        EQU             0xFFDB0060

SPIMASTER       EQU             0xFFDC0500
SPI_MASTER_VERSION_REG  EQU     0x00
SPI_MASTER_CONTROL_REG  EQU     0x01
SPI_TRANS_TYPE_REG      EQU             0x02
SPI_TRANS_CTRL_REG      EQU             0x03
SPI_TRANS_STATUS_REG    EQU     0x04
SPI_TRANS_ERROR_REG             EQU     0x05
SPI_DIRECT_ACCESS_DATA_REG              EQU     0x06
SPI_SD_SECT_7_0_REG             EQU     0x07
SPI_SD_SECT_15_8_REG    EQU     0x08
SPI_SD_SECT_23_16_REG   EQU     0x09
SPI_SD_SECT_31_24_REG   EQU     0x0a
SPI_RX_FIFO_DATA_REG    EQU     0x10
SPI_RX_FIFO_DATA_COUNT_MSB      EQU     0x12
SPI_RX_FIFO_DATA_COUNT_LSB  EQU 0x13
SPI_RX_FIFO_CTRL_REG            EQU     0x14
SPI_TX_FIFO_DATA_REG    EQU     0x20
SPI_TX_FIFO_CTRL_REG    EQU     0x24
SPI_RESP_BYTE1                  EQU     0x30
SPI_RESP_BYTE2                  EQU     0x31
SPI_RESP_BYTE3                  EQU     0x32
SPI_RESP_BYTE4                  EQU     0x33
SPI_INIT_SD                     EQU             0x01
SPI_TRANS_START         EQU             0x01
SPI_TRANS_BUSY          EQU             0x01
SPI_INIT_NO_ERROR       EQU             0x00
SPI_READ_NO_ERROR       EQU             0x00
SPI_WRITE_NO_ERROR      EQU             0x00
RW_READ_SD_BLOCK        EQU             0x02
RW_WRITE_SD_BLOCK       EQU             0x03

UART            EQU             0xFFDC0A00
UART_LS         EQU             0xFFDC0A01
UART_MS         EQU             0xFFDC0A02
UART_IS         EQU             0xFFDC0A03
UART_IE         EQU             0xFFDC0A04
UART_MC         EQU             0xFFDC0A06
UART_CM1        EQU             0xFFDC0A09
UART_CM2        EQU             0xFFDC0A0A
UART_CM3        EQU             0xFFDC0A0B
txempty         EQU             0x40
rxfull          EQU             0x01

CONFIGREC       EQU             0xFFDCFFF0
CR_CLOCK        EQU             0xFFDCFFF4
GACCEL          EQU             0xFFDAE000
AC97            EQU             0xFFDC1000
PSG                     EQU             0xFFD50000
PSGFREQ0        EQU             0xFFD50000
PSGPW0          EQU             0xFFD50001
PSGCTRL0        EQU             0xFFD50002
PSGADSR0        EQU             0xFFD50003

ETHMAC          EQU             0xFFDC2000
ETH_MODER               EQU             0x00
ETH_INT_SOURCE  EQU             0x01
ETH_INT_MASK    EQU             0x02
ETH_IPGT                EQU             0x03
ETH_IPGR1               EQU             0x04
ETH_IPGR2               EQU             0x05
ETH_PACKETLEN   EQU             0x06
ETH_COLLCONF    EQU             0x07
ETH_TX_BD_NUM   EQU             0x08
ETH_CTRLMODER   EQU             0x09
ETH_MIIMODER    EQU             0x0A
ETH_MIICOMMAND  EQU             0x0B
ETH_MIIADDRESS  EQU             0x0C
ETH_MIITX_DATA  EQU             0x0D
ETH_MIIRX_DATA  EQU             0x0E
ETH_MIISTATUS   EQU             0x0F
ETH_MAC_ADDR0   EQU             0x10
ETH_MAC_ADDR1   EQU             0x11
ETH_HASH0_ADDR  EQU             0x12
ETH_HASH1_ADDR  EQU             0x13
ETH_TXCTRL              EQU             0x14

SPRITEREGS      EQU             0xFFDAD000
SPRRAM          EQU             0xFFD80000

THRD_AREA       EQU             0x04000000      ; threading area 0x04000000-0x40FFFFF
BITMAPSCR       EQU             0x04100000
SECTOR_BUF      EQU             0x05FBEC00
BIOS_STACKS     EQU             0x05FC0000      ; room for 256 1kW stacks
BIOS_SCREENS    EQU     0x05C00000      ; 0x05C00000 to 0x05DFFFFF

BYTE_SECTOR_BUF EQU     SECTOR_BUF<<2
PROG_LOAD_AREA  EQU             0x4180000<<2

FCBs                    EQU             0x5F40000       ; room for 128 FCB's

FATOFFS                 EQU             0x5F50000       ; offset into FAT on card
FATBUF                  EQU             0x5F60000
DIRBUF                  EQU             0x5F70000
eth_rx_buffer   EQU             0x5F80000
eth_tx_buffer   EQU             0x5F84000

; Mailboxes, room for 2048
MBX_LINK        EQU             0x05F90000
MBX_TQ_HEAD     EQU             0x05F90800
MBX_TQ_TAIL     EQU             0x05F91000
MBX_MQ_HEAD     EQU             0x05F91800
MBX_MQ_TAIL     EQU             0x05F92000
MBX_TQ_COUNT    EQU     0x05F92800
MBX_MQ_SIZE     EQU             0x05F93000
MBX_MQ_COUNT    EQU     0x05F93800
MBX_MQ_MISSED   EQU     0x05F94000
MBX_OWNER               EQU     0x05F94800
MBX_MQ_STRATEGY EQU     0x05F95000
MBX_RESV                EQU     0x05F95800

; Messages, room for 8kW (8,192) messages
MSG_LINK        EQU             0x05FA0000
MSG_D1          EQU             0x05FA2000
MSG_D2          EQU             0x05FA4000
MSG_TYPE        EQU             0x05FA6000

; Task control blocks, room for 256 tasks
TCB_NxtRdy              EQU             0x05FBE100      ; next task on ready / timeout list
TCB_PrvRdy              EQU             0x05FBE200      ; previous task on ready / timeout list
TCB_NxtTCB              EQU             0x05FBE300
TCB_Timeout             EQU             0x05FBE400
TCB_Priority    EQU             0x05FBE500
TCB_MSGPTR_D1   EQU             0x05FBE600
TCB_MSGPTR_D2   EQU             0x05FBE700
TCB_hJCB                EQU             0x05FBE800
TCB_Status              EQU             0x05FBE900
TCB_CursorRow   EQU             0x05FBD100
TCB_CursorCol   EQU             0x05FBD200
TCB_hWaitMbx    EQU             0x05FBD300      ; handle of mailbox task is waiting at
TCB_mbq_next    EQU             0x05FBD400      ; mailbox queue next
TCB_mbq_prev    EQU             0x05FBD500      ; mailbox queue previous
TCB_iof_next    EQU             0x05FBD600
TCB_iof_prev    EQU             0x05FBD700
TCB_SP8Save             EQU             0x05FBD800      ; TCB_SP8Save area 
TCB_SPSave              EQU             0x05FBD900      ; TCB_SPSave area
TCB_ABS8Save    EQU             0x05FBDA00

KeybdHead       EQU             0x05FBEA00
KeybdTail       EQU             0x05FBEB00
KeybdEcho       EQU             0x05FBEC00
KeybdBad        EQU             0x05FBED00
KeybdAck        EQU             0x05FBEE00
KeybdLocks      EQU             0x05FBEF00
KeybdBuffer     EQU             0x05FBF000      ; buffer is 16 chars

HeapStart       EQU             0x04200000
HeapEnd         EQU             0x05AFFFFF

; Bitmap of tasks requesting the I/O focus
;
IOFocusTbl      EQU             0x05FBD000

; EhBASIC vars:
;
NmiBase         EQU             0xDC
IrqBase         EQU             0xDF

; BIOS vars at the top of the 8kB scratch memory
;
; TinyBasic AREA = 0xF00 to 0xF7F

HeadRdy0        EQU             0xF80
HeadRdy1        EQU             HeadRdy0+1
HeadRdy2        EQU             HeadRdy1+1
HeadRdy3        EQU             HeadRdy2+1
HeadRdy4        EQU             HeadRdy3+1
TailRdy0        EQU             HeadRdy4+1
TailRdy1        EQU             TailRdy0+1
TailRdy2        EQU             TailRdy1+1
TailRdy3        EQU             TailRdy2+1
TailRdy4        EQU             TailRdy3+1
FreeTCB         EQU             TailRdy4+1
TimeoutList     EQU             FreeTCB+1
RunningTCB              EQU             TimeoutList+1
FreeMbx         EQU             RunningTCB + 1
nMailbox        EQU             FreeMbx + 1
FreeMsg         EQU             nMailbox + 1
nMsgBlk         EQU             FreeMsg + 1

; The IO focus list is a doubly linked list formed into a ring.
;
IOFocusNdx      EQU             nMsgBlk + 1

IrqSource       EQU             0xF98

JMPTMP          EQU             0xFA0
SP8Save         EQU             0xFAE
SRSave          EQU             0xFAF
R1Save          EQU             0xFB0
R2Save          EQU             0xFB1
R3Save          EQU             0xFB2
R4Save          EQU             0xFB3
R5Save          EQU             0xFB4
R6Save          EQU             0xFB5
R7Save          EQU             0xFB6
R8Save          EQU             0xFB7
R9Save          EQU             0xFB8
R10Save         EQU             0xFB9
R11Save         EQU             0xFBA
R12Save         EQU             0xFBB
R13Save         EQU             0xFBC
R14Save         EQU             0xFBD
R15Save         EQU             0xFBE
SPSave          EQU             0xFBF

CharColor       EQU             0xFC0
ScreenColor     EQU             0xFC1
CursorRow       EQU             0xFC2
CursorCol       EQU             0xFC3
CursorFlash     EQU             0xFC4
Milliseconds    EQU             0xFC5
IRQFlag         EQU             0xFC6
RdyQueTick      EQU             0xFC7
eth_unique_id   EQU             0xFC8
LineColor       EQU             0xFC9
QIndex          EQU             0xFCA

Uart_rxfifo             EQU             0x05FBC000
Uart_rxhead             EQU             0xFD0
Uart_rxtail             EQU             0xFD1
Uart_ms                 EQU             0xFD2
Uart_rxrts              EQU             0xFD3
Uart_rxdtr              EQU             0xFD4
Uart_rxxon              EQU             0xFD5
Uart_rxflow             EQU             0xFD6
Uart_fon                EQU             0xFD7
Uart_foff               EQU             0xFD8
Uart_txrts              EQU             0xFD9
Uart_txdtr              EQU             0xFDA
Uart_txxon              EQU             0xFDB
Uart_txxonoff   EQU             0xFDC

startSector     EQU             0xFF0


        cpu             rtf65002
        code

message "jump table"
        ; jump table of popular BIOS routines
        org             $FFFF8000
        dw      DisplayChar
        dw      KeybdCheckForKeyDirect
        dw      KeybdGetCharDirect
        dw      KeybdGetChar
        dw      KeybdCheckForKey
        dw      RequestIOFocus
        dw      ReleaseIOFocus
        dw      ClearScreen
        dw      HomeCursor
        dw      ExitTask
        dw      SetKeyboardEcho
        dw      Sleep
        dw      do_load
        dw      do_save

        org             $FFFF8400               ; leave room for 256 vectors
message "cold start point"
KeybdRST
start
        sei                                             ; disable interrupts
        cld                                             ; disable decimal mode
        lda             #1
        sta             LEDS
        ldx             #BIOS_STACKS+0x03FF     ; setup stack pointer top of memory
        txs
        trs             r0,abs8                 ; set 8 bit mode absolute address offset

        jsr             MemInit                 ; Initialize the heap

        lda             #2
        sta             LEDS

        ; setup interrupt vectors
        ldx             #$05FB0001              ; interrupt vector table from $5FB0000 to $5FB01FF
                                                        ; also sets nmoi policy (native mode on interrupt)
        trs             r2,vbr
        dex
        lda             #brk_rout
        sta             (x)
        lda             #slp_rout
        sta             1,x
        lda             #KeybdRST
        sta             448+1,x
        lda             #p1000Hz
        sta             448+2,x
        lda             #p100Hz
        sta             448+3,x
        lda             #KeybdIRQ
        sta             448+15,x
        lda             #SerialIRQ
        sta             448+8,x
        lda             #InvalidOpIRQ
        sta             495,x
        lda             #bus_err_rout
        sta             508,x
        sta             509,x

        lda             #3
        sta             LEDS

        ; stay in native mode in case emulation is not supported.
        ldx             #$1FF                   ; set 8 bit stack pointer
        trs             r2,sp8
        
        ldx             #0
        stz             IrqBase                 ; support for EhBASIC's interrupt mechanism
        stz             NmiBase

        lda             #-1
        sta             TimeoutList             ; no entries in timeout list
        sta             HeadRdy0                ; task zero (the BIOS task) is always present
        sta             HeadRdy1
        sta             HeadRdy2
        sta             HeadRdy3
        sta             HeadRdy4
        sta             TailRdy0
        sta             TailRdy1
        sta             TailRdy2
        sta             TailRdy3
        sta             TailRdy4


        ; Initialize IO Focus List
        ;
        ldx             #1
st5:
        stz             IOFocusTbl,x
        inx
        cpx             #8
        bne             st5

        ldx             #1
        lda             #-1
st9:
        sta             TCB_iof_next,x
        sta             TCB_iof_prev,x
        inx
        cpx             #256
        bne             st9

        ; Initialize free message list
        lda             #8192
        sta             nMsgBlk
        stz             FreeMsg
        ldx             #0
        lda             #1
st4:
        sta             MSG_LINK,x
        ina
        inx
        cpx             #8192
        bne             st4
        lda             #-1
        sta             MBX_LINK+8191
        
        ; Initialize free mailbox list
        lda             #2048
        sta             nMailbox
        
        stz             FreeMbx
        ldx             #0
        lda             #1
st3:
        sta             MBX_LINK,x
        ina
        inx
        cpx             #2048
        bne             st3
        lda             #-1
        sta             MBX_LINK+2047

        ; Initialize the FreeTCB list
        lda             #1                              ; the next available TCB
        sta             FreeTCB
        ldx             #1
        lda             #2
st2:
        sta             TCB_NxtTCB,x
        ina
        inx
        cpx             #256
        bne             st2
        lda             #-1
        sta             TCB_NxtTCB+255
        lda             #4
        sta             LEDS

        ; Manually setup the BIOS task
        lda             #-1
        stz             RunningTCB              ; BIOS is task #0
        sta             TCB_NxtRdy              ; manually build the ready list
        sta             TCB_PrvRdy
        stz             HeadRdy0                ; insert at priority 4
        stz             TailRdy0
        stz             TCB_iof_next    ; manually build the IO focus list
        stz             TCB_iof_prev
        stz             IOFocusNdx              ; task #0 has the focus
        lda             #1
        sta             IOFocusTbl              ; set the task#0 request bit
        lda             #0
        sta             TCB_Priority
        lda             #TS_RUNNING|TS_READY
        sta             TCB_Status
        stz             TCB_CursorRow
        stz             TCB_CursorCol
        
        lda             #1
        sta             MBX_SEMA
        sta             IOF_LIST_SEMA
        sta             RQ_SEMA                 ; set ready queue semaphore
        sta             TO_SEMA                 ; set timeout list semaphore

        lda             #$CE                    ; CE =blue on blue FB = grey on grey
        sta             ScreenColor
        sta             CharColor
        sta             CursorFlash
        jsr             ClearScreen
        jsr             ClearBmpScreen
        lda             #4
        ldx             #0
        ldy             #IdleTask
        jsr             StartTask
        jsr             PICInit
        lda             CONFIGREC               ; do we have a serial port ?
        bit             #32
        beq             st7
        ; 19200 * 16
        ;-------------
        ; 25MHz / 2^32
        lda             #$03254E6E              ; constant for 19,200 baud at 25MHz
        jsr             SerialInit
st7:
        lda             #5
        sta             LEDS
        lda             CONFIGREC               ; do we have sprites ?
        bit             #1
        beq             st8
        lda             #$3FFF                  ; turn on sprites
        sta             SPRITEREGS+120
        jsr             RandomizeSprram
st8:
        ; Enable interrupts.
        ; Keyboard initialization must take place after interrupts are
        ; enabled.
        cli                                             
        lda             #14
        sta             LEDS
        jsr             KeybdInit
        lda             #1
        sta             KeybdEcho
        lda             #6
        sta             LEDS

        ; The following must be after interrupts are enabled.
        lda             #9
        sta             LEDS
        jsr             HomeCursor
        lda             #msgStart
        jsr             DisplayStringB
        jsr             ReportMemFree
        lda             #10
        sta             LEDS

        ; The AC97 setup uses the millisecond counter and the
        ; keyboard.
        lda             CONFIGREC               ; do we have a sound generator ?
        bit             #4
        beq             st6
        jsr             SetupAC97
        lda             #2
        ldx             #0
        ldy             #Beep
        jsr             StartTask
st6:
        lda             #11
        sta             LEDS
        jmp             Monitor
st1
        jsr             KeybdGetCharDirect
        bra             st1
        stp
        bra             start
        
msgStart
        db              "RTF65002 system starting.",$0d,$0a,00

;----------------------------------------------------------
; Initialize programmable interrupt controller (PIC)
;  0 = nmi (parity error)
;  1 = keyboard reset
;  2 = 1000Hz pulse
;  3 = 100Hz pulse (cursor flash)
;  4 = ethmac
;  8 = uart
; 13 = raster interrupt
; 15 = keyboard char
;----------------------------------------------------------
message "PICInit"
PICInit:
        ;
        lda             #$000C                  ; clock pulses are edge sensitive
        sta             PIC_ES
        lda             #$000F                  ; enable nmi,kbd_rst
        ; A10F enable serial IRQ
        sta             PIC_IE
PICret:
        rts

;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
message "DumpTaskList"
DumpTaskList:
        pha
        phx
        phy
        push    r4
        lda             #msgTaskList
        jsr             DisplayStringB
        ldy             #0
        php
        sei
dtl2:
        lda             HeadRdy0,y
        ld              r4,r1
        bmi             dtl1
dtl3:
        ldx             #3
        tya
        jsr             PRTNUM
        lda             #' '
        jsr             DisplayChar
        ld              r1,r4
        ldx             #3
        jsr             PRTNUM
        lda             #' '
        jsr             DisplayChar
        jsr             DisplayChar
        jsr             DisplayChar
        ld              r1,r4
        lda             TCB_Status,r1
        jsr             DisplayByte
        lda             #' '
        jsr             DisplayChar
        ldx             #3
        lda             TCB_PrvRdy,r4
        jsr             PRTNUM
        lda             #' '
        jsr             DisplayChar
        ldx             #3
        lda             TCB_NxtRdy,r4
        jsr             PRTNUM
        lda             #' '
        jsr             DisplayChar
        lda             TCB_Timeout,r4
        jsr             DisplayWord
        jsr             CRLF
        ld              r4,TCB_NxtRdy,r4
        bpl             dtl3
dtl1:
        iny
        cpy             #5
        bne             dtl2
        plp
        pop             r4
        ply
        plx
        pla
        rts

msgTaskList:
        db      CR,LF,"Pri Task Stat Prv Nxt Timeout",CR,LF,0

;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
message "DumpIOFocusList"
DumpIOFocusList:
        pha
        phx
        phy
        php
        sei
        lda             #msgIOFocusList
        jsr             DisplayStringB
        lda             IOFocusNdx
diofl2:
        bmi             diofl1
        tay
        ldx             #3
        jsr             PRTNUM
        lda             #' '
        jsr             DisplayChar
        lda             TCB_iof_prev,y
        ldx             #3
        jsr             PRTNUM
        lda             #' '
        jsr             DisplayChar
        lda             TCB_iof_next,y
        ldx             #3
        jsr             PRTNUM
        jsr             CRLF
        lda             TCB_iof_next,y
        cmp             IOFocusNdx
        bne             diofl2
        
diofl1:
        plp
        ply
        plx
        pla
        rts
        
msgIOFocusList:
        db      CR,LF,"Task Prv Nxt",CR,LF,0

RunningTCBErr:
        lda             #$FF
        sta             LEDS
        lda             #msgRunningTCB
        jsr             DisplayStringB
rtcberr1:
        jsr             KeybdGetChar
        cmp             #-1
        beq             rtcberr1
        jmp             start

msgRunningTCB:
        db      CR,LF,"RunningTCB is bad.",CR,LF,0

;------------------------------------------------------------------------------
; IdleTask is a low priority task that is always running. It runs when there
; is nothing else to run.
;------------------------------------------------------------------------------
IdleTask:
        inc             TEXTSCR+167             ; increment IDLE active flag
        cli                                             ; enable interrupts
        wai                                             ; wait for one to happen
        bra             IdleTask

;------------------------------------------------------------------------------
; r1 = task priority
; r2 = start flags
; r3 = start address
;------------------------------------------------------------------------------
message "StartTask"
StartTask:
        pha
        phx
        phy
        push    r4
        push    r5
        push    r6
        push    r7
        push    r8
        tsr             sp,r4                           ; save off current stack pointer
        ld              r6,r1                           ; r6 = task priority
        ld              r8,r2
        
        ; get a free TCB
        ;
        php
        sei
        lda             FreeTCB                         ; get free tcb list pointer
        bmi             stask1
        tax
        lda             TCB_NxtTCB,x
        sta             FreeTCB                         ; update the FreeTCB list pointer
        plp
        txa
        
        ; setup the stack for the task
        ld              r7,r2
        asl             r2,r2,#10                       ; 1kW stack per task
        add             r2,r2,#BIOS_STACKS+0x3ff        ; add in stack base
        txs
        ldx             #$FF
        stx             TCB_SP8Save,r7
        st              r6,TCB_Priority,r7
        stz             TCB_Status,r7
        stz             TCB_Timeout,r7
        ; setup virtual video for the task
        stz             TCB_CursorRow,r7
        stz             TCB_CursorCol,r7

        ; setup the initial stack image for the task
        ; Cause a return to the ExitTask routine when the task does a 
        ; final rts.
        ; fake an IRQ call by stacking the return address and processor
        ; flags on the stack
        ldx             #ExitTask                       ; save the address of the task exit routine
        phx
        phy                                                     ; save start address on stack
        push    r8                                      ; save processor status reg on stack
        
        ; now fake pushing the register set onto the stack. Registers start up
        ; in an undefined state.
        sub             sp,#15                          ; 15 registers
        tsx
        stx             TCB_SPSave,r7

        ; now restore the current stack pointer
        trs             r4,sp

        ; Insert the task into the ready list
        jsr             AddTaskToReadyList
stask2:
        pop             r8
        pop             r7
        pop             r6
        pop             r5
        pop             r4
        ply
        plx
        pla
        rts
stask1:
        plp
        lda             #msgNoTCBs
        jsr             DisplayStringB
        bra             stask2

msgNoTCBs:
        db              "No more task control blocks available.",CR,LF,0

;------------------------------------------------------------------------------
; This routine is called when the task exits with an rts instruction. OR
; it may be invoked with a JMP ExitTask. In either case the task must be
; running so it can't be on the timeout list.
;------------------------------------------------------------------------------
message "ExitTask"
ExitTask:
        sei
        ; release any aquired resources
        ; - mailboxes
        ; - messages
        lda             RunningTCB
        bmi             RunningTCBErr
        jsr             RemoveTaskFromReadyList
        stz             TCB_Status,r1                           ; set task status to TS_NONE
        jsr             ReleaseIOFocus
        ldx             FreeTCB                                         ; add the task control block to the free list
        stx             TCB_NxtTCB,r1
        sta             FreeTCB
        jmp             SelectTaskToRun

;------------------------------------------------------------------------------
; AddTaskToReadyList
; This subroutine is called from the timer ISR so it must be relatively
; fast.
; Parameters:
;       r1 = task number
; Returns:
;       none
;------------------------------------------------------------------------------
message "AddTaskToReadyList"
AddTaskToReadyList:
        php
        pha
        phx
        phy
        sei
        ldy             TCB_Priority,r1
        ldx             TCB_Status,r1   ; set the task status to ready
        or              r2,r2,#TS_READY
        stx             TCB_Status,r1
        ldx     TailRdy0,y              ; insert the task at the list tail
        bmi             arl1                    ; if no tail, then either the list is screwed up, or no tasks are on it.
        sta             TCB_NxtRdy,x    ; add as next ready on tail
arl4:
        stx             TCB_PrvRdy,r1   ; the old tail is the previous task on the list now.
        sta             TailRdy0,y              ; update with new tail
        ldx             #-1
        stx             TCB_NxtRdy,r1   ; There is no next ready task to the tail.
        ldx             HeadRdy0,y              ; check if the head of the ready list needs to be updated
        bpl             arl3
        sta             HeadRdy0,y              ; Update the head of the ready list.
        ldx             #-1                             ; There is no previous task at the head.
        stx             TCB_PrvRdy,r1   
arl3:   
        ply
        plx
        pla
        plp
        rts
        ; Here the tail of the ready list needed to be updated. Flag no prior task.
arl1:
        ldx             #-1
        bra             arl4

;------------------------------------------------------------------------------
; RemoveTaskFromReadyList
; This subroutine removes a task from the ready list.
; This can be called from within an ISR.
;
; r1 = task number
;------------------------------------------------------------------------------
message "RemoveTaskFromReadyList"
RemoveTaskFromReadyList:
        php                                             ; save off interrupt mask state
        pha
        phx
        phy
        push    r4
        push    r5

        sei
        ld              r4,TCB_NxtRdy,r1        ; Get previous and next fields.
        ld              r5,TCB_PrvRdy,r1        ; if there is no previous task, then this is
        bmi             rfr1                    ; the head of the list. Update.
        st              r4,TCB_NxtRdy,r5
        cmp             r4,#0                   ; is there a next task to update ?
        bmi             rfr4
        st              r5,TCB_PrvRdy,r4
        bra             rfr4

        ; Update the head of the list
rfr1:
        ldy             TCB_Priority,r1
        st              r4,HeadRdy0,y
        cmp             r4,#0                   ; did we empty the list ?
        bmi             rfr3
        ld              r5,#-1                  ; flag no previous task for head of list
        st              r5,TCB_PrvRdy,r4
rfr4:
        stz             TCB_NxtRdy,r1   ; Task is removed from ready list so the
        stz             TCB_PrvRdy,r1   ; contents of these fields shouldn't matter.
                                                        ; They are set to zero (the BIOS task) in case
                                                        ; there is a list problem.

        ldx             TCB_Status,r1   ; set the task status to no longer ready.
        and             r2,r2,#~TS_READY
        stx             TCB_Status,r1
rfr2:
        pop             r5
        pop             r4
        ply
        plx
        pla
        plp
rfr9:
        rts
rfr3:
        st              r4,TailRdy0,y   ;
        bra             rfr4

;------------------------------------------------------------------------------
; AddToTimeoutList
; AddToTimeoutList adds a task to the timeout list. The task is placed in the
; list depending on it's timeout value.
;
; Parameters:
;       r1 = task
;       r2 = timeout value
;------------------------------------------------------------------------------
message "AddToTimeoutList"
AddToTimeoutList:
        cmp             #0                                              ; quickly validate the task number
        bmi             RunningTCBErr                   ; must be between 0 and 255
        cmp             #MAX_TASKNO
        bpl             RunningTCBErr

        phx
        push    r4
        push    r5
        php
        sei

        ld              r5,#-1
        ld              r4,TimeoutList  ; are there any tasks on the timeout list ?
        bmi             attl1
attl_check_next:
        sub             r2,r2,TCB_Timeout,r4    ; is this timeout > next
        bmi             attl_insert_before
        ld              r5,r4
        ld              r4,TCB_NxtRdy,r4
        bpl             attl_check_next

        ; Here we scanned until the end of the timeout list and didn't find a 
        ; timeout of a greater value. So we add the task to the end of the list.
attl_add_at_end:
        st              r4,TCB_NxtRdy,r1        ; r4 was = -1
        st              r1,TCB_NxtRdy,r5
        st              r5,TCB_PrvRdy,r1
        st              r2,TCB_Timeout,r1
        bra             attl_exit

attl_insert_before:
        cmp             r5,#-1
        beq             attl2
        st              r4,TCB_NxtRdy,r1        ; next on list goes after this task
        st              r5,TCB_PrvRdy,r1        ; set previous link
        st              r1,TCB_NxtRdy,r5
        st              r1,TCB_PrvRdy,r4
        bra             attl3

        ; Here there is no previous entry in the timeout list
        ; Add at start
attl2:
        sta             TCB_PrvRdy,r4
        st              r5,TCB_PrvRdy,r1        ; r5 = -1
        st              r4,TCB_NxtRdy,r1
        sta             TimeoutList             ; update the head pointer
attl3:
        add             r2,r2,TCB_Timeout,r4    ; get back timeout
        stx             TCB_Timeout,r1
        ld              r5,TCB_Timeout,r4       ; adjust the timeout of the next task
        sub             r5,r5,r2
        st              r5,TCB_Timeout,r4
        bra             attl_exit

        ; Here there were no tasks on the timeout list, so we add at the
        ; head of the list.
attl1:
        sta             TimeoutList             ; set the head of the timeout list
        stx             TCB_Timeout,r1
        ldx             #-1                             ; flag no more entries in timeout list
        stx             TCB_NxtRdy,r1           ; no next entries
        stx             TCB_PrvRdy,r1           ; and no prev entries
attl_exit:
        ldx             TCB_Status,r1           ; set the task's status as timing out
        or              r2,r2,#TS_TIMEOUT
        stx             TCB_Status,r1
        plp
        pop             r5
        pop             r4
        plx
attl4:
        rts

;------------------------------------------------------------------------------
; This subroutine is called from within the timer ISR when the task's 
; timeout expires.
;
; r1 = task number
;------------------------------------------------------------------------------
message "RemoveFromTimeoutList"
RemoveFromTimeoutList:
        cmp             #0                                              ; quickly validate the task number
        bmi             RunningTCBErr                   ; must be between 0 and 255
        cmp             #MAX_TASKNO
        bpl             RunningTCBErr

        pha
        phx
        push    r4
        push    r5
        php
        sei

        ld              r4,TCB_PrvRdy,r1                ; adjust the links of the next and previous
        bmi             rftl2
        ld              r5,TCB_NxtRdy,r1                ; tasks on the list to point around the task
        st              r5,TCB_NxtRdy,r4
        bmi             rftl1
        st              r4,TCB_PrvRdy,r5
        ldx             TCB_Timeout,r1                  ; update the timeout of the next on list
        add             r2,r2,TCB_Timeout,r5    ; with any remaining timeout in the task
        stx             TCB_Timeout,r5                  ; removed from the list
        
        ; Here there is no next item on the list
rftl1:
        bra             rftl3

        ; Here there is no previous item on the list, so update the head of
        ; the list.
rftl2:
        ld              r5,TCB_NxtRdy,r1
        st              r5,TimeoutList          ; store next field into list head
        bmi             rftl3
        ld              r4,TCB_Timeout,r1               ; add any remaining timeout to the timeout
        add             r4,r4,TCB_Timeout,r5    ; of the next task on the list.
        st              r4,TCB_Timeout,r5
        ld              r4,#-1                                  ; there is no previous item to the head
        sta             TCB_PrvRdy,r5
        
        ; Here there is no previous or next items in the list, so the list
        ; will be empty once this task is removed from it.
rftl3:
        ldx             TCB_Status,r1           ; set the task status to not timing out
        and             r2,r2,#~TS_TIMEOUT
        stx             TCB_Status,r1
        plp
        pop             r5
        pop             r4
        plx
        pla
rft4:
        rts

;------------------------------------------------------------------------------
; Parameters:
; r1 = time duration in centi-seconds (1/100 second).
;------------------------------------------------------------------------------
Sleep:
        tax
        sei
        lda             RunningTCB
        jsr             RemoveTaskFromReadyList
        jsr             AddToTimeoutList
        ldx             #SleepRet                       ; The scheduler will be returning to this
        jmp             PushRegsAndRunTask      ; task eventually, once the timeout expires,
                                                                ; so fake an interrupt call
SleepRet:
        cli
        rts

;------------------------------------------------------------------------------
; Allocate a mailbox
; r1 = pointer to place to store handle
;------------------------------------------------------------------------------
message "AllocMbx"
AllocMbx:
        cmp             #0
        beq             ambx1
        phx
        phy
        push    r4
        ld              r4,r1
        php
        sei
        lda             FreeMbx                 ; Get mailbox off of free mailbox list
        sta             (r4)                    ; store off the mailbox number
        bmi             ambx2
        ldx             MBX_LINK,r1             ; and update the head of the list
        stx             FreeMbx
        dec             nMailbox                ; decrement number of available mailboxes
        tax
        ldy             RunningTCB                      ; set the mailbox owner
        bmi             RunningTCBErr
        lda             TCB_hJCB,y
        sta             MBX_OWNER,x
        lda             #-1                             ; initialize the head and tail of the queues
        sta             MBX_TQ_HEAD,x
        sta             MBX_TQ_TAIL,x
        sta             MBX_MQ_HEAD,x
        sta             MBX_MQ_TAIL,x
        stz             MBX_TQ_COUNT,x  ; initialize counts to zero
        stz             MBX_MQ_COUNT,x
        stz             MBX_MQ_MISSED,x
        lda             #8                              ; set the max queue size
        sta             MBX_MQ_SIZE,x   ; and
        lda             #MQS_NEWEST             ; queueing strategy
        sta             MBX_MQ_STRATEGY,x
ambx3:
        plp
        pop             r4
        ply
        plx
        lda             #E_Ok
        rts
ambx1:
        lda             #E_Arg
        rts
ambx2:
        plp
        pop             r4
        ply
        plx
        lda             #E_NoMoreMbx
        rts

;------------------------------------------------------------------------------
; r1 = message
; r2 = mailbox
;------------------------------------------------------------------------------
message "QueueMsgAtMbx"
QueueMsgAtMbx:
        pha
        phx
        phy
        php
        sei
        ldy             MBX_MQ_TAIL,x
        bmi             qmam1
        sta             MBX_LINK,y
        bra             qmam2
qmam1:
        sta             MBX_MQ_HEAD,x
qmam2:
        sta             MBX_MQ_TAIL,x
        inc             MBX_MQ_COUNT,x          ; increase the queued message count
        ldx             #-1
        stx             MSG_LINK,r1
        plp
        ply
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Returns
; r1 = message number
;------------------------------------------------------------------------------
message "DequeueMsgFromMbx"
DequeueMsgFromMbx:
        phx
        phy
        php
        sei
        tax                                             ; x = mailbox index
        lda             MBX_MQ_COUNT,x          ; are there any messages available ?
        beq             dmfm1
        dea
        sta             MBX_MQ_COUNT,x          ; update the message count
        lda             MBX_MQ_HEAD,x           ; Get the head of the list, this should not be -1
        bmi             dmfm1                   ; since the message count > 0
        ldy             MSG_LINK,r1             ; get the link to the next message
        sty             MBX_MQ_HEAD,x           ; update the head of the list
        bpl             dmfm2                   ; if there was no more messages then update the
        sty             MBX_MQ_TAIL,x           ; tail of the list as well.
dmfm2:
        sta             MSG_LINK,r1             ; point the link to the messahe itself to indicate it's dequeued
dmfm1:
        plp
        ply
        plx
        rts
        
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
DequeueThreadFromMbx:
        cpx             #0
        beq             dtfm1
        php
        sei
        push    r4
        ld              r4,MBX_TQ_HEAD,r1
        bpl             dtfm2
                pop             r4
                stz             (x)
                plp
                lda             #E_NoThread
                rts
dtfm2:
        push    r5
        dec             MBX_TQ_COUNT,r1
        st              r4,(x)
        ld              r4,TCB_mbq_next,r4
        st              r4,MBX_TQ_HEAD,r1
        bmi             dtfm3
                ld              r5,#-1
                st              r5,TCB_mbq_prev,r4
                bra             dtfm4
dtfm3:
                ld              r5,#-1
                st              r5,MBX_TQ_TAIL,r1
dtfm4:
        stz             MBX_SEMA+1
        ld              r5,(x)
        lda             TCB_Status,r5
        bit             #TS_TIMEOUT
        beq             dtfm5
                ld              r1,r5
                jsr             RemoveFromTimeoutList
dtfm5:
        ld              r4,#-1
        st              r4,TCB_mbq_next,r5
        st              r4,TCB_mbq_prev,r5
        stz             TCB_hWaitMbx,r5
        lda             TCB_Status,r5
        and             #~TS_WAITMSG
        sta             TCB_Status,r5
        pop             r5
        pop             r4
        plp
        lda             #E_Ok
        rts
dtfm1:
        lda             #E_Arg
        rts

;------------------------------------------------------------------------------
; r1 = handle to mailbox
; r2 = message D1
; r3 = message D2
;------------------------------------------------------------------------------
message "SendMsg"
SendMsg:
        cmp             #0                                              ; check the mailbox number to make sure
        bmi             smsg1                                   ; that it's sensible
        cmp             #2047
        bpl             smsg1
        push    r4
        push    r5
        push    r6
        php
        sei
        ld              r4,MBX_OWNER,r1
        bmi             smsg2                                   ; error: no owner
        pha
        phx
        jsr             DequeueThreadFromMbx    ; r1=mbx, r2=thread (returned)
        ld              r6,r2                                   ; r6 = thread
        plx
        pla
        cmp             r6,#0
        bpl             smsg3
                ; Here there was no thread waiting at the mailbox, so a message needs to
                ; be allocated
                ld              r4,FreeMsg
                bmi             smsg4           ; no more messages available
                ld              r5,MSG_LINK,r4
                st              r5,FreeMsg
                dec             nMsgBlk         ; decrement the number of available messages
                stx             MSG_D1,r4
                sty             MSG_D2,r4
                pha
                phx
                tax                                             ; x = mailbox
                ld              r1,r4                   ; acc = message
                jsr             QueueMsgAtMbx
                plx
                pla
                cmp             r6,#0                   ; check if there is a thread waiting for a message
                bmi             smsg5
smsg3:
        ld              r5,TCB_MSGPTR_D1,r6
        beq             smsg6
        stx             (r5)
smsg6:
        ld              r5,TCB_MSGPTR_D2,r6
        beq             smsg7
        sty             (r5)
smsg7:
        ld              r5,TCB_Status,r6
        and             r5,r5,#TS_TIMEOUT
        beq             smsg8
        ld              r1,r6
        jsr             RemoveFromTimeoutList
smsg8:
        ld              r1,r6
        jsr             AddTaskToReadyList
smsg5:
        plp
        pop             r6
        pop             r5
        pop             r4
        lda             #E_Ok
        rts
smsg1:
        lda             #E_BadMbx
        rts
smsg2:
        plp
        pop             r6
        pop             r5
        pop             r4
        lda             #E_NotAlloc
        rts
smsg4:
        plp
        pop             r6
        pop             r5
        pop             r4
        lda             #E_NoMsg
        rts

;------------------------------------------------------------------------------
; WaitMsg
; Wait at a mailbox for a message to arrive. This subroutine will block the
; task until a message is available or the task times out on the timeout
; list.
;
; Parameters
;       r1=mailbox
;       r2=pointer to D1
;       r3=pointer to D2
;       r4=timeout
; Returns:
;       r1=E_Ok                 if everything is ok
;       r1=E_BadMbx             for a bad mailbox number
;       r1=E_NotAlloc   for a mailbox that isn't allocated
;------------------------------------------------------------------------------
WaitMsg:
        cmp             #0                                              ; check the mailbox number to make sure
        bmi             wmsg1                                   ; that it's sensible
        cmp             #2047
        bpl             wmsg1
        phx
        phy
        push    r4
        push    r5
        push    r6
        push    r7
        ld              r6,r1
        php
        sei
        ld              r5,MBX_OWNER,r1
        bmi             wmsg2                                   ; error: no owner
        jsr             DequeueMsgFromMbx
        cmp             #0
        bpl             wmsg3

        ; Here there was no message available, remove the task from
        ; the ready list, and optionally add it to the timeout list.
        ; Queue the task at the mailbox.
        lda             RunningTCB                              ; remove the task from the ready list
        bmi             wmsg8
        jsr             RemoveTaskFromReadyList
        ld              r7,TCB_Status,r1                        ; set task status to waiting
        or              r7,r7,#TS_WAITMSG
        st              r7,TCB_Status,r1
        st              r6,TCB_hWaitMbx,r1                      ; set which mailbox is waited for
        ld              r7,#-1
        st              r7,TCB_mbq_next,r1                      ; adding at tail, so there is no next
        stx             TCB_MSGPTR_D1,r1                        ; save off the message pointers
        sty             TCB_MSGPTR_D2,r1
        ld              r7,MBX_TQ_HEAD,r1                       ; is there a task que setup at the mailbox ?
        bmi             wmsg6
        ld              r7,MBX_TQ_TAIL,r6
        st              r7,TCB_mbq_prev,r1
        sta             TCB_mbq_next,r7
        sta             MBX_TQ_TAIL,r6
        inc             MBX_TQ_COUNT,r6                         ; increment number of tasks queued
wmsg7:
        cmp             r4,#0                                           ; check for a timeout
        beq             wmsg10
        ld              r2,r4
        jsr             AddToTimeoutList
wmsg10:
        ld              r2,#wmsg8                                       ; save the return address
PushRegsAndRunTask:
        phx
        php                                                                     ; save status register
        pha                                                                     ; and save the register set
        phx
        phy
        push    r4
        push    r5
        push    r6
        push    r7
        push    r8
        push    r9
        push    r10
        push    r11
        push    r12
        push    r13
        push    r14
        push    r15
        jmp             SelectTaskToRun
        
        ; Here there were no prior tasks queued at the mailbox
wmsg6:
        ld              r7,#-1
        st              r7,TCB_mbq_prev,r1              ; no previous tasks
        st              r7,TCB_mbq_next,r1
        sta             MBX_TQ_HEAD,r6                  ; set both head and tail indexes
        sta             MBX_TQ_TAIL,r6
        ld              r7,#1
        st              r7,MBX_TQ_COUNT,r6              ; one task queued
        bra             wmsg7                                   ; check for a timeout value
        
        ; Store message D1 to pointer
wmsg3:
        cpx             #0
        beq             wmsg4
        ld              r7,MSG_D1,r1
        st              r7,(x)
        ; Store message D2 to pointer
wmsg4:
        cpy             #0
        beq             wmsg5
        ld              r7,MSG_D2,r1
        st              r7,(y)
        ; Add the newly dequeued message to the free messsage list
wmsg5:
        ld              r7,FreeMsg
        st              r7,MSG_LINK,r1
        sta             FreeMsg
        inc             nMsgBlk
wmsg8:
        plp
        pop             r7
        pop             r6
        pop             r5
        pop             r4
        ply
        plx
        lda             #E_Ok
        rts
wmsg1:
        lda             #E_BadMbx
        rts
wmsg2:
        plp
        pop             r7
        pop             r6
        pop             r5
        pop             r4
        ply
        plx
        lda             #E_NotAlloc
        rts

;------------------------------------------------------------------------------
; CheckMsg
; Check for a message at a mailbox. Does not block.
;
; Parameters
;       r1=mailbox
;       r2=pointer to D1
;       r3=pointer to D2
;       r4=remove from queue if present
; Returns:
;       r1=E_Ok                 if everything is ok
;       r1=E_NoMsg              if no message is available
;       r1=E_BadMbx             for a bad mailbox number
;       r1=E_NotAlloc   for a mailbox that isn't allocated
;------------------------------------------------------------------------------
CheckMsg:
        cmp             #0                                              ; check the mailbox number to make sure
        bmi             cmsg1                                   ; that it's sensible
        cmp             #2047
        bpl             cmsg1
        phx
        phy
        push    r4
        push    r5
        php
        sei
        ld              r5,MBX_OWNER,r1
        bmi             cmsg2                                   ; error: no owner
        cmp             r4,#0                                   ; are we to dequeue the message ?
        beq             cmsg3
        jsr             DequeueMsgFromMbx
        bra             cmsg4
cmsg3:
        lda             MBX_MQ_HEAD,r1                  ; peek the message at the head of the messages queue
cmsg4:
        cmp             #0
        bmi             cmsg5
        cpx             #0
        beq             cmsg6
        ld              r5,MSG_D1,r1
        st              r5,(x)
cmsg6:
        cpy             #0
        beq             cmsg7
        ld              r5,MSG_D2,r1
        st              r5,(y)
cmsg7:
        cmp             r4,#0
        beq             cmsg8
        ld              r5,FreeMsg
        st              r5,MSG_LINK,r1
        sta             FreeMsg
        inc             nMsgBlk
cmsg8:
        plp
        pop             r5
        pop             r4
        ply
        plx
        lda             #E_Ok
        rts
cmsg1:
        lda             #E_BadMbx
        rts
cmsg2:
        plp
        pop             r5
        pop             r4
        ply
        plx
        lda             #E_NotAlloc
        rts
cmsg5:
        plp
        pop             r5
        pop             r4
        ply
        plx
        lda             #E_NoMsg
        rts

;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
SetIOFocusBit:
        and             r2,r2,#$FF
        and             r1,r2,#$1F              ; get bit index 0 to 31
        ldy             #1
        asl             r3,r3,r1                ; shift bit to proper place
        lsr             r2,r2,#5                ; get word index /32 bits per word
        lda             IOFocusTbl,x
        or              r1,r1,r3
        sta             IOFocusTbl,x
        rts

;------------------------------------------------------------------------------
; The I/O focus list is an array indicating which tasks are requesting the
; I/O focus. The I/O focus is user controlled by pressing ALT-TAB on the
; keyboard.
;------------------------------------------------------------------------------
message "RequestIOFocus"
RequestIOFocus:
        pha
        phx
        phy
        php
        sei
        ldx             RunningTCB      
        ldy             IOFocusNdx              ; Is the focus list empty ?
        bmi             riof2
riof4:
        lda             TCB_iof_next,x  ; is the task already in the IO focus list ?
        bpl             riof3
        lda             IOFocusNdx              ; Expand the list
        ldy             TCB_iof_prev,r1
        stx             TCB_iof_prev,r1
        sta             TCB_iof_next,x
        sty             TCB_iof_prev,x
        stx             TCB_iof_next,y
riof3:
        jsr             SetIOFocusBit
        plp
        ply
        plx
        pla
        rts

        ; Here, the IO focus list was empty. So expand it.
        ; Update pointers to loop back to self.
riof2:
        stx             IOFocusNdx
        stx             TCB_iof_next,x
        stx             TCB_iof_prev,x
        bra             riof3

;------------------------------------------------------------------------------
; Releasing the I/O focus causes the focus to switch if the running task
; had the I/O focus.
;------------------------------------------------------------------------------
message "ReleaseIOFocus"        
ReleaseIOFocus:
        pha
        phx
        phy
        php
        sei
        ldx             RunningTCB      
        phx     
        ldy             #1
        and             r1,r2,#$1F              ; get bit index 0 to 31
        asl             r3,r3,r1                ; shift bit to proper place
        eor             r3,r3,#-1               ; invert bit mask
        lsr             r2,r2,#5                ; get word index /32 bits per word
        lda             IOFocusTbl,x
        and             r1,r1,r3
        sta             IOFocusTbl,x
        plx
        cpx             IOFocusNdx              ; Does the running task have the I/O focus ?
        bne             rliof1
        jsr             SwitchIOFocus   ; If so, then switch the focus.
rliof1:
        lda             TCB_iof_next,x  ; get next and previous fields.
        bmi             rliof2                  ; Is the task on the list ?
        ldy             TCB_iof_prev,x
        sta             TCB_iof_next,y  ; prev->next = current->next
        sty             TCB_iof_prev,r1 ; next->prev = current->prev
        cmp             r1,r3                   ; Check if the IO focus list is collapsing.
        bne             rliof2                  ; If the list just points back to the task
        cmp             r1,r2                   ; being removed, then it's the last task
        bne             rliof2                  ; removed from the list, so the list is being
        lda             #-1                             ; emptied.
        sta             IOFocusNdx
rliof2:
        lda             #-1                             ; Update the next and prev fields to indicate
        sta             TCB_iof_next,x  ; the task is no longer on the list.
        sta             TCB_iof_prev,x
rliof3:
        plp
        ply
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Get the location of the screen and screen attribute memory. The location
; depends on whether or not the task has the output focus.
;------------------------------------------------------------------------------
GetScreenLocation:
        lda             RunningTCB
        cmp             IOFocusNdx
        beq             gsl1
        and             r1,r1,#$FF
        asl             r1,r1,#13                       ; 8192 words per screen
        add             r1,r1,#BIOS_SCREENS
        rts
gsl1:
        lda             #TEXTSCR
        rts

GetColorCodeLocation:
        lda             RunningTCB
        cmp             IOFocusNdx
        beq             gccl1
        and             r1,r1,#$FF
        asl             r1,r1,#13                       ; 8192 words per screen
        add             r1,r1,#BIOS_SCREENS+4096
        rts
gccl1:
        lda             #TEXTSCR+$10000
        rts

;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
message "CopyVirtualScreenToScreen"
CopyVirtualScreenToScreen
        pha
        phx
        phy
        push    r4
        lda             #4095                           ; number of words to copy-1
        ldx             IOFocusNdx                      ; compute virtual screen location
        bmi             cvss3
        asl             r2,r2,#13                       ; 8192 words per screen
        add             r2,r2,#BIOS_SCREENS     ; add in screens array base address
        ldy             #TEXTSCR
        mvn
;cvss1:
;       ld              r4,(x)
;       st              r4,(y)
;       inx
;       iny
;       dea
;       bne             cvss1
        ; now copy the color codes
        lda             #4095
        ldx             IOFocusNdx
        asl             r2,r2,#13
        add             r2,r2,#BIOS_SCREENS+4096        ; virtual char color array
        ldy             #TEXTSCR+$10000
        mvn
;cvss2:
;       ld              r4,(x)
;       st              r4,(y)
;       inx
;       iny
;       dea
;       bne             cvss2
cvss3:
        ; reset the cursor position in the text controller
        ldy             IOFocusNdx
        ldx             TCB_CursorRow,y
        lda             TEXTREG+TEXT_COLS
        mul             r2,r2,r1
        add             r2,r2,TCB_CursorCol,y
        stx             TEXTREG+TEXT_CURPOS
        pop             r4
        ply
        plx
        pla
        rts
message "CopyScreenToVirtualScreen"
CopyScreenToVirtualScreen
        pha
        phx
        phy
        push    r4
        lda             #4095
        ldx             #TEXTSCR
        ldy             IOFocusNdx
        bmi             csvs3
        asl             r3,r3,#13
        add             r3,r3,#BIOS_SCREENS
        mvn
;csvs1:
;       ld              r4,(x)
;       st              r4,(y)
;       inx
;       iny
;       dea
;       bne             csvs1
        lda             #4095
        ldx             #TEXTSCR+$10000
        ldy             IOFocusNdx
        asl             r3,r3,#13
        add             r3,r3,#BIOS_SCREENS+4096
        mvn
;csvs2:
;       ld              r4,(x)
;       st              r4,(y)
;       inx
;       iny
;       dea
;       bne             csvs2
csvs3:
        pop             r4
        ply
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Clear the screen and the screen color memory
; We clear the screen to give a visual indication that the system
; is working at all.
;------------------------------------------------------------------------------
;
message "ClearScreen"
ClearScreen:
        pha                                                     ; holds a space character
        phx                                                     ; loop counter
        phy                                                     ; memory addressing
        push    r4                                      ; holds the screen color
        push    r5
        lda             TEXTREG+TEXT_COLS       ; calc number to clear
        ldx             TEXTREG+TEXT_ROWS
        mul             r2,r1,r2                        ; r2 = # chars to clear
        jsr             GetScreenLocation
        tay
        jsr             GetColorCodeLocation
        ld              r5,r1
        lda             #' '                            ; space char
        ld              r4,ScreenColor
        jsr             AsciiToScreen
csj4:
        sta             (y)
        iny
        st              r4,(r5)
        inc             r5
        dex
        bne             csj4
        pop             r5
        pop             r4
        ply
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Scroll text on the screen upwards
;------------------------------------------------------------------------------
;
message "ScrollUp"
ScrollUp:
        pha
        phx
        phy
        push    r4
        push    r5
        push    r6
        lda             TEXTREG+TEXT_COLS       ; acc = # text columns
        ldx             TEXTREG+TEXT_ROWS
        mul             r2,r1,r2                        ; calc number of chars to scroll
        sub             r2,r2,r1                        ; one less row
        pha
        jsr             GetScreenLocation
        tay
        jsr             GetColorCodeLocation
        ld              r6,r1
        pla
scrup1:
        add             r5,r3,r1
        ld              r4,(r5)                         ; move character
        st              r4,(y)
        add             r5,r6,r1
        ld              r4,(r5)                         ; and move color code
        st              r4,(r6)
        iny
        inc             r6
        dex
        bne             scrup1
        lda             TEXTREG+TEXT_ROWS
        dea
        jsr             BlankLine
        pop             r6
        pop             r5
        pop             r4
        ply
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Blank out a line on the display
; line number to blank is in acc
;------------------------------------------------------------------------------
;
BlankLine:
        pha
        phx
        phy
        push    r4
        ldx             TEXTREG+TEXT_COLS       ; x = # chars to blank out from video controller
        mul             r3,r2,r1                        ; y = screen index (row# * #cols)
        pha
        jsr             GetScreenLocation
        ld              r4,r1
        pla
        add             r3,r3,r4                ; y = screen address
        lda             #' '
        jsr             AsciiToScreen
blnkln1:
        sta             (y)
        iny
        dex
        bne             blnkln1
        pop             r4
        ply
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Convert ASCII character to screen display character.
;------------------------------------------------------------------------------
;
AsciiToScreen:
        and             #$FF
        cmp             #'A'
        bcc             atoscr1         ; blt
        cmp             #'Z'
        bcc             atoscr1
        beq             atoscr1
        cmp             #'z'+1
        bcs             atoscr1
        cmp             #'a'
        bcc             atoscr1
        sub             #$60
atoscr1:
        or              #$100
        rts

;------------------------------------------------------------------------------
; Convert screen character to ascii character
;------------------------------------------------------------------------------
;
ScreenToAscii:
        and             #$FF
        cmp             #26+1
        bcs             stasc1
        add             #$60
stasc1:
        rts

;------------------------------------------------------------------------------
; HomeCursor
; Set the cursor location to the top left of the screen.
;------------------------------------------------------------------------------
HomeCursor:
        phx
        ldx             RunningTCB
        and             r2,r2,#$FF
        stz             TCB_CursorRow,x
        stz             TCB_CursorCol,x
        cpx             IOFocusNdx
        bne             hc1
        stz             TEXTREG+TEXT_CURPOS
hc1:
        plx
        rts

;------------------------------------------------------------------------------
; Update the cursor position in the text controller based on the
;  CursorRow,CursorCol.
;------------------------------------------------------------------------------
;
UpdateCursorPos:
        pha
        phx
        push    r4
        ld              r4,RunningTCB
        and             r4,r4,#$FF
        cmp             r4,IOFocusNdx                   ; update cursor position in text controller
        bne             ucp1                                    ; only for the task with the output focus
        lda             TCB_CursorRow,r4
        and             #$3F                                    ; limit of 63 rows
        ldx             TEXTREG+TEXT_COLS
        mul             r2,r2,r1
        lda             TCB_CursorCol,r4
        and             #$7F                                    ; limit of 127 cols
        add             r2,r2,r1
        stx             TEXTREG+TEXT_CURPOS
ucp1:
        pop             r4
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Calculate screen memory location from CursorRow,CursorCol.
; Also refreshes the cursor location.
; Returns:
; r1 = screen location
;------------------------------------------------------------------------------
;
CalcScreenLoc:
        phx
        push    r4
        ld              r4,RunningTCB
        and             r4,r4,#$FF
        lda             TCB_CursorRow,r4
        and             #$3F                                    ; limit to 63 rows
        ldx             TEXTREG+TEXT_COLS
        mul             r2,r2,r1
        ld              r1,TCB_CursorCol,r4
        and             #$7F                                    ; limit to 127 cols
        add             r2,r2,r1
        cmp             r4,IOFocusNdx                   ; update cursor position in text controller
        bne             csl1                                    ; only for the task with the output focus
        stx             TEXTREG+TEXT_CURPOS
csl1:
        jsr             GetScreenLocation
        add             r1,r2,r1
        pop             r4
        plx
        rts
csl2:
        lda             #TEXTSCR
        pop             r4
        plx
        rts

;------------------------------------------------------------------------------
; Display a character on the screen.
; If the task doesn't have the I/O focus then the character is written to
; the virtual screen.
; r1 = char to display
;------------------------------------------------------------------------------
;
message "DisplayChar"
DisplayChar:
        push    r4
        ld              r4,RunningTCB
        and             r4,r4,#$FF
        and             #$FF                            ; mask off any higher order bits (called from eight bit mode).
        cmp             #'\r'                           ; carriage return ?
        bne             dccr
        stz             TCB_CursorCol,r4        ; just set cursor column to zero on a CR
        jsr             UpdateCursorPos
dcx14:
        pop             r4
        rts
dccr:
        cmp             #$91                            ; cursor right ?
        bne             dcx6
        pha
        lda             TCB_CursorCol,r4
        cmp             #83
        bcs             dcx7
        ina
        sta             TCB_CursorCol,r4
dcx7:
        jsr             UpdateCursorPos
        pla
        pop             r4
        rts
dcx6:
        cmp             #$90                            ; cursor up ?
        bne             dcx8            
        pha
        lda             TCB_CursorRow,r4
        beq             dcx7
        dea
        sta             TCB_CursorRow,r4
        bra             dcx7
dcx8:
        cmp             #$93                            ; cursor left ?
        bne             dcx9
        pha
        lda             TCB_CursorCol,r4
        beq             dcx7
        dea
        sta             TCB_CursorCol,r4
        bra             dcx7
dcx9:
        cmp             #$92                            ; cursor down ?
        bne             dcx10
        pha
        lda             TCB_CursorRow,r4
        cmp             #46
        beq             dcx7
        ina
        sta             TCB_CursorRow,r4
        bra             dcx7
dcx10:
        cmp             #$94                            ; cursor home ?
        bne             dcx11
        pha
        lda             TCB_CursorCol,r4
        beq             dcx12
        stz             TCB_CursorCol,r4
        bra             dcx7
dcx12:
        stz             TCB_CursorRow,r4
        bra             dcx7
dcx11:
        pha
        phx
        phy
        cmp             #$99                            ; delete ?
        bne             dcx13
        jsr             CalcScreenLoc
        tay                                                     ; y = screen location
        lda             TCB_CursorCol,r4        ; acc = cursor column
        bra             dcx5
dcx13   
        cmp             #CTRLH                          ; backspace ?
        bne             dcx3
        lda             TCB_CursorCol,r4
        beq             dcx4
        dea
        sta             TCB_CursorCol,r4
        jsr             CalcScreenLoc           ; acc = screen location
        tay                                                     ; y = screen location
        lda             TCB_CursorCol,r4
dcx5:
        ldx             $4,y
        stx             (y)
        iny
        ina
        cmp             TEXTREG+TEXT_COLS
        bcc             dcx5
        lda             #' '
        jsr             AsciiToScreen
        dey
        sta             (y)
        bra             dcx4
dcx3:
        cmp             #'\n'                   ; linefeed ?
        beq             dclf
        tax                                             ; save acc in x
        jsr     CalcScreenLoc   ; acc = screen location
        tay                                             ; y = screen location
        txa                                             ; restore r1
        jsr             AsciiToScreen   ; convert ascii char to screen char
        sta             (y)
        jsr             GetScreenLocation
        sub             r3,r3,r1                ; make y an index into the screen
        jsr             GetColorCodeLocation
        add             r3,r3,r1
        lda             CharColor
        sta             (y)
        jsr             IncCursorPos
        bra             dcx4
dclf:
        jsr             IncCursorRow
dcx4:
        ply
        plx
        pla
        pop             r4
        rts

;------------------------------------------------------------------------------
; Increment the cursor position, scroll the screen if needed.
;------------------------------------------------------------------------------
;
IncCursorPos:
        pha
        phx
        push    r4
        ld              r4,RunningTCB
        and             r4,r4,#$FF
        lda             TCB_CursorCol,r4
        ina
        sta             TCB_CursorCol,r4
        ldx             TEXTREG+TEXT_COLS
        cmp             r1,r2
        bcc             icc1
        stz             TCB_CursorCol,r4                ; column = 0
        bra             icr1
IncCursorRow:
        pha
        phx
        push    r4
        ld              r4,RunningTCB
        and             r4,r4,#$FF
icr1:
        lda             TCB_CursorRow,r4
        ina
        sta             TCB_CursorRow,r4
        ldx             TEXTREG+TEXT_ROWS
        cmp             r1,r2
        bcc             icc1
        beq             icc1
        dex                                                     ; backup the cursor row, we are scrolling up
        stx             TCB_CursorRow,r4
        jsr             ScrollUp
icc1:
        jsr             UpdateCursorPos
icc2:
        pop             r4
        plx
        pla
        rts

;------------------------------------------------------------------------------
; Display a string on the screen.
; The characters are packed 4 per word
;------------------------------------------------------------------------------
;
DisplayStringB:
        pha
        phx
        tax                                             ; r2 = pointer to string
dspj1B:
        lb              r1,0,x                  ; move string char into acc
        inx                                             ; increment pointer
        cmp             #0                              ; is it end of string ?
        beq             dsretB
        jsr             DisplayChar             ; display character
        bra             dspj1B
dsretB:
        plx
        pla
        rts

DisplayStringQ:
        pha
        phx
        tax                                             ; r2 = pointer to string
        lda             #TEXTSCR
        sta             QIndex
dspj1Q:
        lb              r1,0,x                  ; move string char into acc
        inx                                             ; increment pointer
        cmp             #0                              ; is it end of string ?
        beq             dsretQ
        jsr             DisplayCharQ    ; display character
        bra             dspj1Q
dsretQ:
        plx
        pla
        rts

DisplayCharQ:
        pha
        phx
        jsr             AsciiToScreen
        ldx             #0
        sta             (QIndex,x)
        lda             QIndex
        ina
        sta             QIndex
;       inc             QIndex
        plx
        pla
        rts

        
;------------------------------------------------------------------------------
; Display a string on the screen.
; The characters are packed 1 per word
;------------------------------------------------------------------------------
;
DisplayStringW:
        pha
        phx
        tax                                             ; r2 = pointer to string
dspj1W:
        lda             (x)                             ; move string char into acc
        inx                                             ; increment pointer
        cmp             #0                              ; is it end of string ?
        beq             dsretW
        jsr             DisplayChar             ; display character
        bra             dspj1W                  ; go back for next character
dsretW:
        plx
        pla
        rts

DisplayStringCRLFB:
        jsr             DisplayStringB
CRLF:
        pha
        lda             #'\r'
        jsr             DisplayChar
        lda             #'\n'
        jsr             DisplayChar
        pla
        rts

;------------------------------------------------------------------------------
; Initialize keyboard
;
; Issues a 'reset keyboard' command to the keyboard, then selects scan code
; set #2 (the most common one). Also sets up the keyboard buffer and
; initializes the keyboard semaphore.
;------------------------------------------------------------------------------
;
message "KeybdInit"
KeybdInit:
        lda             #1                      ; setup semaphore
        sta             KEYBD_SEMA
        lda             #32
        sta             LEDS
        ldx             #0
kbdi1:
        stz             KeybdHead,x             ; setup keyboard buffer
        stz             KeybdTail,x
        lda             #1                      ; turn on keyboard echo
        sta             KeybdEcho,x
        stz             KeybdBad,x
        inx
        cpx             #MAX_TASKNO+1
        bne             kbdi1

        lda             PIC_IE
        or              r1,r1,#$8000            ; enable kbd_irq
        sta             PIC_IE

        lda             #33
        sta             LEDS
        lda             #$ff            ; issue keyboard reset
        jsr             SendByteToKeybd
        lda             #38
        sta             LEDS
        lda             #1000000                ; delay a bit
kbdi5:
        dea
        sta             LEDS
        bne             kbdi5
        lda             #34
        sta             LEDS
        lda             #0xf0           ; send scan code select
        jsr             SendByteToKeybd
        lda             #35
        sta             LEDS
        ldx             #0xFA
        jsr             WaitForKeybdAck
        cmp             #$FA
        bne             kbdi2
        lda             #36
        sta             LEDS
        lda             #2                      ; select scan code set#2
        jsr             SendByteToKeybd
        lda             #39
        sta             LEDS
kbdi2:
        rts

msgBadKeybd:
        db              "Keyboard not responding.",0

SendByteToKeybd:
        phx
        ldx             RunningTCB
        sta             KEYBD
        lda             #40
        sta             LEDS
        tsr             TICK,r3
kbdi4:                                          ; wait for transmit complete
        tsr             TICK,r4
        sub             r4,r4,r3
        cmp             r4,#1000000
        bcs             kbdbad
        lda             #41
        sta             LEDS
        lda             KEYBD+3
        bit             #64
        beq             kbdi4
        bra             sbtk1
kbdbad:
        lda             #42
        sta             LEDS
        lda             KeybdBad,x
        bne             sbtk1
        lda             #1
        sta             KeybdBad,x
        lda             #43
        sta             LEDS
        lda             #msgBadKeybd
        jsr             DisplayStringCRLFB
sbtk1:
        lda             #44
        sta             LEDS
        plx
        rts
        
; Wait for keyboard to respond with an ACK (FA)
;
WaitForKeybdAck:
        lda             #64
        sta             LEDS
        tsr             TICK,r3
wkbdack1:
        tsr             TICK,r4
        sub             r4,r4,r3
        cmp             r4,#1000000
        bcs             wkbdbad
        lda             #65
        sta             LEDS
        lda             KEYBD
        bit             #$8000
        beq             wkbdack1
;       lda             KEYBD+8
        and             #$ff
wkbdbad:
        rts

; Wait for keyboard to respond with an ACK (FA)
; This routine picks up the ack status left by the
; keyboard IRQ routine.
; r2 = 0xFA (could also be 0xEE for echo command)
;
WaitForKeybdAck2:
        phx
        ldx             RunningTCB
WaitForKeybdAck2a:
        lda             KeybdAck,x
        cmp             r1,r2
        bne             WaitForKeybdAck2a
        stz             KeybdAck,x
        plx
        rts

;------------------------------------------------------------------------------
; Normal keyboard interrupt, the lowest priority interrupt in the system.
; Grab the character from the keyboard device and store it in a buffer.
; The buffer of the task with the input focus is updated.
;------------------------------------------------------------------------------
;
message "KeybdIRQ"
KeybdIRQ:
        cld
        pha
        phx
        phy
        push    r4

        ; support EhBASIC's IRQ functionality
        ; code derived from minimon.asm
        lda             #15                             ; Keyboard is IRQ #15
        sta             IrqSource       
        lb              r1,IrqBase              ; get the IRQ flag byte
        lsr             r2,r1
        or              r1,r1,r2
        and             #$E0
        sb              r1,IrqBase              ; save the new IRQ flag byte

        ld              r4,IOFocusNdx   ; get the task with the input focus

        ldx             KEYBD                           ; get keyboard character
        ld              r0,KEYBD+1                      ; clear keyboard strobe (turns off the IRQ)
        txy                                                     ; check for a keyboard ACK code
        and             r3,r3,#$ff
        cmp             r3,#$FA
        bne             KeybdIrq1
        sty             KeybdAck,r4
        bra             KeybdIRQc
KeybdIrq1:
        bit             r2,#$800                                ; test bit #11
        bne             KeybdIRQc                               ; ignore keyup messages for now
KeybdIrq2:
        lda             KeybdHead,r4                    
        ina                                                             ; increment head pointer
        and             #$f                                             ; limit
        ldy             KeybdTail,r4                    ; check for room in the keyboard buffer
        cmp             r1,r3
        beq             KeybdIRQc                               ; if no room, the newest char will be lost
        sta             KeybdHead,r4
        dea
        and             #$f
        stx             KeybdLocks,r4
        asl             r4,r4,#4                                        ; * 16
        add             r1,r1,r4
        stx             KeybdBuffer,r1                  ; store character in buffer
KeybdIRQc:
        pop             r4
        ply
        plx
        pla
        rti

KeybdRstIRQ:
        jmp             start

;------------------------------------------------------------------------------
; r1 0=echo off, non-zero = echo on
;------------------------------------------------------------------------------
SetKeyboardEcho:
        phx
        ldx             RunningTCB
        sta             KeybdEcho,x
        plx
        rts

;------------------------------------------------------------------------------
; Get a bit from the I/O focus table.
;------------------------------------------------------------------------------
GetIOFocusBit:
        phx
        phy
        tax
        and             r1,r1,#$1F              ; get bit index into word
        lsr             r2,r2,#5                ; get word index into table
        ldy             IOFocusTbl,x
        lsr             r3,r3,r1                ; extract bit
        and             r1,r3,#1
        ply
        plx
        rts
        
;------------------------------------------------------------------------------
; SwitchIOFocus
;
; Switches the IO focus to the next task requesting the I/O focus. This
; routine may be called when a task releases the I/O focus as well as when
; the user presses ALT-TAB on the keyboard.
;------------------------------------------------------------------------------
;
SwitchIOFocus:
        pha
        phy

        ; First check if it's even possible to switch the focus to another
        ; task. The I/O focus list could be empty or there may be only a
        ; single task in the list. In either case it's not possible to
        ; switch.
        ldy             IOFocusNdx              ; Get the task at the head of the list.
        bmi             siof3                   ; Is the list empty ?
        lda             TCB_iof_next,y  ; Get the next task on the list.
        cmp             r1,r3                   ; Will the list head change ?
        beq             siof3                   ; If not then no switch will occur
        
        ; Copy the current task's screen to it's virtual screen buffer.
        jsr             CopyScreenToVirtualScreen

        sta             IOFocusNdx              ; Make task the new head of list.

        ; Copy the virtual screen of the task recieving the I/O focus to the
        ; text screen.
        jsr             CopyVirtualScreenToScreen
siof3:
        ply
        pla
        rts
        
;------------------------------------------------------------------------------
; Get character from keyboard buffer
; return character in acc or -1 if no
; characters available.
; Also check for ALT-TAB and switch the I/O focus.
;------------------------------------------------------------------------------
message "KeybdGetChar"
KeybdGetChar:
        phx
        push    r4
        php
        sei
        ld              r4,RunningTCB
        bmi             RunningTCBErr
        cmp             #MAX_TASKNO
        bpl             RunningTCBErr
        ldx             KeybdTail,r4    ; if keybdTail==keybdHead then there are no 
        lda             KeybdHead,r4    ; characters in the keyboard buffer
        cmp             r1,r2
        beq             nochar
        asl             r4,r4,#4                        ; * 16
        phx
        add             r2,r2,r4
        lda             KeybdBuffer,x
        plx
        bit             #$200                                   ; check for ALT-tab
        beq             kgc4
        and             #$FF
        cmp             #TAB                                    ; if we find an ALT-tab
        bne             kgc4
        jsr             SwitchIOFocus
        ; Now eat up the ALT-TAB character
        ; Flush the keyboard buffer
        lsr             r4,r4,#4
        stz             KeybdTail,r4
        stz             KeybdHead,r4
        bra             nochar
kgc4:
        and             r1,r1,#$ff              ; mask off control bits
        inx                                             ; increment index
        and             r2,r2,#$0f
        lsr             r4,r4,#4                        ; / 16
        stx             KeybdTail,r4
        ldx             KeybdEcho,r4
        beq             kgc3
        cmp             #CR
        bne             kgc8
        jsr             CRLF                    ; convert CR keystroke into CRLF
        bra             kgc3
kgc8:
        jsr             DisplayChar
        bra             kgc3
nochar:
        lda             #-1
kgc3:
        plp
        pop             r4
        plx
        rts

;------------------------------------------------------------------------------
; Check if there is a keyboard character available in the keyboard buffer.
; Returns
; r1 = 1, Z=0 if there is a key available, otherwise
; r1 = 0, Z=1 if there is not a key available
;------------------------------------------------------------------------------
;
message "KeybdCheckForKey"
KeybdCheckForKey:
        phx
        push    r4
        php
        sei
        ld              r4,RunningTCB
        lda             KeybdTail,r4
        ldx             KeybdHead,r4
        sub             r1,r1,r2
        bne             kcfk1
        plp
        pop             r4
        plx
        lda             #0
        rts
kcfk1
        plp
        pop             r4
        plx
        lda             #1
        rts
;------------------------------------------------------------------------------
; Check if there is a keyboard character available. If so return true (1)
; otherwise return false (0) in r1.
;------------------------------------------------------------------------------
;
message "KeybdCheckForKeyDirect"
KeybdCheckForKeyDirect:
        lda             KEYBD
        and             #$8000
        beq             kcfkd1
        lda             #1
kcfkd1
        rts

;------------------------------------------------------------------------------
; Get character directly from keyboard. This routine blocks until a key is
; available.
;------------------------------------------------------------------------------
;
KeybdGetCharDirect:
        phx
kgc1:
        lda             KEYBD
        bit             #$8000
        beq             kgc1
        ld              r0,KEYBD+1              ; clear keyboard strobe
        bit             #$800                   ; is it a keydown event ?
        bne             kgc1
        bit             #$200                           ; check for ALT-tab
        bne             kgc2
        and             r2,r1,#$7f
        cmp             r2,#TAB                                 ; if we find an ALT-tab
        bne             kgc2
        jsr             SwitchIOFocus
        bra             kgc1
kgc2:
        and             #$ff                    ; remove strobe bit
        ldx             KeybdEcho               ; is keyboard echo on ?
        beq             gk1
        cmp             #CR
        bne             gk2                             ; convert CR keystroke into CRLF
        jsr             CRLF
        bra             gk1
gk2:
        jsr             DisplayChar
gk1:
        plx
        rts


;==============================================================================
; Serial port
;==============================================================================
;------------------------------------------------------------------------------
; Initialize the serial port
; r1 = low 28 bits = baud rate
; r2 = other settings
; The desired baud rate must fit in 28 bits or less.
;------------------------------------------------------------------------------
;
SerialInit:
;       asl             r1,r1,#4                        ; * 16
;       shlui   r1,r1,#32                       ; * 2^32
;       inhu    r2,CR_CLOCK                     ; get clock frequency from config record
;       divu    r1,r1,r2                        ; / clock frequency

        lsr             r1,r1,#8                        ; drop the lowest 8 bits
        sta             UART_CM1                        ; set LSB
        lsr             r1,r1,#8
        sta             UART_CM2                        ; set middle bits
        lsr             r1,r1,#8
        sta             UART_CM3                        ; set MSB
        stz             Uart_rxhead                     ; reset buffer indexes
        stz             Uart_rxtail
        lda             #0x1f0
        sta             Uart_foff                       ; set threshold for XOFF
        lda             #0x010
        sta             Uart_fon                        ; set threshold for XON
        lda             #1
        sta             UART_IE                         ; enable receive interrupt only
        stz             Uart_rxrts                      ; no RTS/CTS signals available
        stz             Uart_txrts                      ; no RTS/CTS signals available
        stz             Uart_txdtr                      ; no DTR signals available
        stz             Uart_rxdtr                      ; no DTR signals available
        lda             #1
        sta             Uart_txxon                      ; for now
        lda             #1
        sta             SERIAL_SEMA
        rts

;---------------------------------------------------------------------------------
; Get character directly from serial port. Blocks until a character is available.
;---------------------------------------------------------------------------------
;
SerialGetCharDirect:
sgc1:
        lda             UART_LS         ; uart status
        and             #rxfull         ; is there a char available ?
        beq             sgc1
        lda             UART
        rts

;------------------------------------------------
; Check for a character at the serial port
; returns r1 = 1 if char available, 0 otherwise
;------------------------------------------------
;
SerialCheckForCharDirect:
        lda             UART_LS                 ; uart status
        and             #rxfull                 ; is there a char available ?
        rts

;-----------------------------------------
; Put character to serial port
; r1 = char to put
;-----------------------------------------
;
SerialPutChar:
        phx
        phy
        push    r4
        push    r5

        ldx             UART_MC
        or              r2,r2,#3                ; assert DTR / RTS
        stx             UART_MC
        ldx             Uart_txrts
        beq             spcb1
        ld              r4,Milliseconds
        ldy             #1000                   ; delay count (1 s)
spcb3:
        ldx             UART_MS
        and             r2,r2,#$10              ; is CTS asserted ?
        bne             spcb1
        ld              r5,Milliseconds
        cmp             r4,r5
        beq             spcb3
        ld              r4,r5
        dey
        bne             spcb3
        bra             spcabort
spcb1:
        ldx             Uart_txdtr
        beq             spcb2
        ld              r4,Milliseconds
        ldy             #1000                   ; delay count
spcb4:
        ldx             UART_MS
        and             r2,r2,#$20              ; is DSR asserted ?
        bne             spcb2
        ld              r5,Milliseconds
        cmp             r4,r5
        beq             spcb4
        ld              r4,r5
        dey
        bne             spcb4
        bra             spcabort
spcb2:  
        ldx             Uart_txxon
        beq             spcb5
spcb6:
        ldx             Uart_txxonoff
        beq             spcb5
        ld              r4,UART_MS
        and             r4,r4,#0x80                     ; DCD ?
        bne             spcb6
spcb5:
        ld              r4,Milliseconds
        ldy             #1000                           ; wait up to 1s
spcb8:
        ldx             UART_LS
        and             r2,r2,#0x20                     ; tx not full ?
        bne             spcb7
        ld              r5,Milliseconds
        cmp             r4,r5
        beq             spcb8
        ld              r4,r5
        dey
        bne             spcb8
        bra             spcabort
spcb7:
        sta             UART
spcabort:
        pop             r5
        pop             r4
        ply
        plx
        rts

;-------------------------------------------------
; Compute number of characters in recieve buffer.
; r4 = number of chars
;-------------------------------------------------
CharsInRxBuf:
        ld              r4,Uart_rxhead
        ldx             Uart_rxtail
        sub             r4,r4,r2
        bpl             cirxb1
        ld              r4,#0x200
        add             r4,r4,r2
        ldx             Uart_rxhead
        sub             r4,r4,r2
cirxb1:
        rts

;----------------------------------------------
; Get character from rx fifo
; If the fifo is empty enough then send an XON
;----------------------------------------------
;
SerialGetChar:
        phx
        phy
        push    r4

        ldy             Uart_rxhead
        ldx             Uart_rxtail
        cmp             r2,r3
        beq             sgcfifo1                ; is there a char available ?
        lda             Uart_rxfifo,x   ; get the char from the fifo into r1
        inx                                             ; increment the fifo pointer
        and             r2,r2,#$1ff
        stx             Uart_rxtail
        ldx             Uart_rxflow             ; using flow control ?
        beq             sgcfifo2
        ldy             Uart_fon                ; enough space in Rx buffer ?
        jsr             CharsInRxBuf
        cmp             r4,r3
        bpl             sgcfifo2
        stz             Uart_rxflow             ; flow off
        ld              r4,Uart_rxrts
        beq             sgcfifo3
        ld              r4,UART_MC              ; set rts bit in MC
        or              r4,r4,#2
        st              r4,UART_MC
sgcfifo3:
        ld              r4,Uart_rxdtr
        beq             sgcfifo4
        ld              r4,UART_MC              ; set DTR
        or              r4,r4,#1
        st              r4,UART_MC
sgcfifo4:
        ld              r4,Uart_rxxon
        beq             sgcfifo5
        ld              r4,#XON
        st              r4,UART
sgcfifo5:
sgcfifo2:                                       ; return with char in r1
        pop             r4
        ply
        plx
        rts
sgcfifo1:
        lda             #-1                             ; no char available
        pop             r4
        ply
        plx
        rts


;-----------------------------------------
; Serial port IRQ
;-----------------------------------------
;
SerialIRQ:
        pha
        phx
        phy
        push    r4

        lda             UART_IS                 ; get interrupt status
        bpl             sirq1                   ; no interrupt
        and             #0x7f                   ; switch on interrupt type
        cmp             #4
        beq             srxirq
        cmp             #$0C
        beq             stxirq
        cmp             #$10
        beq             smsirq
        ; unknown IRQ type
sirq1:
        pop             r4
        ply
        plx
        pla
        rti


; Get the modem status and record it
smsirq:
        lda             UART_MS
        sta             Uart_ms
        bra             sirq1

stxirq:
        bra             sirq1

; Get a character from the uart and store it in the rx fifo
srxirq:
srxirq1:
        lda             UART                            ; get the char (clears interrupt)
        ldx             Uart_txxon
        beq             srxirq3
        cmp             #XOFF
        bne             srxirq2
        lda             #1
        sta             Uart_txxonoff
        bra             srxirq5
srxirq2:
        cmp             #XON
        bne             srxirq3
        stz             Uart_txxonoff
        bra             srxirq5
srxirq3:
        stz             Uart_txxonoff
        ldx             Uart_rxhead
        sta             Uart_rxfifo,x           ; store in buffer
        inx
        and             r2,r2,#$1ff
        stx             Uart_rxhead
srxirq5:
        lda             UART_LS                         ; check for another ready character
        and             #rxfull
        bne             srxirq1
        lda             Uart_rxflow                     ; are we using flow controls?
        bne             srxirq8
        jsr             CharsInRxBuf
        lda             Uart_foff
        cmp             r4,r1
        bmi             srxirq8
        lda             #1
        sta             Uart_rxflow
        lda             Uart_rxrts
        beq             srxirq6
        lda             UART_MC
        and             #$FD                    ; turn off RTS
        sta             UART_MC
srxirq6:
        lda             Uart_rxdtr
        beq             srxirq7
        lda             UART_MC
        and             #$FE                    ; turn off DTR
        sta             UART_MC
srxirq7:
        lda             Uart_rxxon
        beq             srxirq8
        lda             #XOFF
        sta             UART
srxirq8:
        bra             sirq1


;------------------------------------------------------------------------------
; Display nybble in r1
;------------------------------------------------------------------------------
;
DisplayNybble:
        pha
        and             #$0F
        add             #'0'
        cmp             #'9'+1
        bcc             dispnyb1
        add             #7
dispnyb1:
        jsr             DisplayChar
        pla
        rts

;------------------------------------------------------------------------------
; Display the byte in r1
;------------------------------------------------------------------------------
;
DisplayByte:
        pha
        lsr             r1,r1,#4
        jsr             DisplayNybble
        pla
        jmp             DisplayNybble   ; tail rts 
message "785"
;------------------------------------------------------------------------------
; Display the half-word in r1
;------------------------------------------------------------------------------
;
DisplayHalf:
        pha
        lsr             r1,r1,#8
        jsr             DisplayByte
        pla
        jsr             DisplayByte
        rts

message "797"
;------------------------------------------------------------------------------
; Display the half-word in r1
;------------------------------------------------------------------------------
;
DisplayWord:
        pha
        lsr             r1,r1,#16
        jsr             DisplayHalf
        pla
        jsr             DisplayHalf
        rts
message "810"
;------------------------------------------------------------------------------
; Display memory pointed to by r2.
; destroys r1,r3
;------------------------------------------------------------------------------
;
DisplayMemW:
        pha
        lda             #':'
        jsr             DisplayChar
        txa
        jsr             DisplayWord
        lda             #' '
        jsr             DisplayChar
        lda             (x)
        jsr             DisplayWord
        inx
        lda             #' '
        jsr             DisplayChar
        lda             (x)
        jsr             DisplayWord
        inx
        lda             #' '
        jsr             DisplayChar
        lda             (x)
        jsr             DisplayWord
        inx
        lda             #' '
        jsr             DisplayChar
        lda             (x)
        jsr             DisplayWord
        inx
        jsr             CRLF
        pla
        rts

message "Monitor"
;==============================================================================
; System Monitor Program
; The system monitor is task#0
;==============================================================================
;
Monitor:
        ldx             #BIOS_STACKS+0x03FF     ; setup stack pointer
        txs
        lda             #0                                      ; turn off keyboard echo
        jsr             SetKeyboardEcho
        jsr             RequestIOFocus
PromptLn:
        jsr             CRLF
        lda             #'$'
        jsr             DisplayChar

; Get characters until a CR is keyed
;
Prompt3:
        jsr             RequestIOFocus
;       lw              r1,#2                   ; get keyboard character
;       syscall #417
;       jsr             KeybdCheckForKeyDirect
;       cmp             #0
        jsr             KeybdGetChar
        cmp             #-1
        beq             Prompt3
;       jsr             KeybdGetCharDirect
        cmp             #CR
        beq             Prompt1
        jsr             DisplayChar
        bra             Prompt3

; Process the screen line that the CR was keyed on
;
Prompt1:
        lda             #80
        sta             LEDS
        ldx             RunningTCB
        bmi             RunningTCBErr
        cpx             #MAX_TASKNO
        bpl             RunningTCBErr
        lda             #81
        sta             LEDS
        stz             TCB_CursorCol,x ; go back to the start of the line
        jsr             CalcScreenLoc   ; r1 = screen memory location
        tay
        lda             #82
        sta             LEDS
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #'$'
        bne             Prompt2                 ; skip over '$' prompt character
        lda             #83
        sta             LEDS
        lda             (y)
        iny
        jsr             ScreenToAscii

; Dispatch based on command character
;
Prompt2:
        cmp             #':'
        beq             EditMem
        cmp             #'D'
        bne             Prompt8
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #'R'
        beq             DumpReg
        cmp             #'I'
        beq             DoDir
        dey
        bra             DumpMem
Prompt8:
        cmp             #'F'
        bne             Prompt7
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #'L'
        bne             Prompt8a
        jsr             DumpIOFocusList
        jmp             Monitor
Prompt8a:
        cmp             #'M'
        beq             DoFmt
        dey
        bra             FillMem
Prompt7:
        cmp             #'B'                    ; $B - start tiny basic
        bne             Prompt4
        lda             #3
        ldy             #CSTART
        ldx             #0
        jsr             StartTask
;       jsr             CSTART
        bra             Monitor
Prompt4:
        cmp             #'b'
        bne             Prompt5
        lda             #3                              ; priority level 3
        ldy             #$C000                  ; start address $C000
        ldx             #$20000000              ; flags: emmulation mode set
        jsr             StartTask
        bra             Monitor
        emm
        cpu             W65C02
        jml             $0C000
        cpu             rtf65002
Prompt5:
        cmp             #'J'                    ; $J - execute code
        beq             ExecuteCode
        cmp             #'L'                    ; $L - load dector
        beq             LoadSector
        cmp             #'W'
        beq             WriteSector
Prompt9:
        cmp             #'?'                    ; $? - display help
        bne             Prompt10
        lda             #HelpMsg
        jsr             DisplayStringB
        jmp             Monitor
Prompt10:
        cmp             #'C'                    ; $C - clear screen
        beq             TestCLS
        cmp             #'r'
        bne             Prompt12
        lda             #4                              ; priority level 4
        ldx             #0                              ; zero all flags at startup
        ldy             #RandomLines    ; task address
        jsr             (y)
;       jsr             StartTask
        jmp             Monitor
;       jmp             RandomLinesCall
Prompt12:
Prompt13:
        cmp             #'P'
        bne             Prompt14
        lda             #2
        ldx             #0
        ldy             #Piano
        jsr             StartTask
        jmp             Monitor
Prompt14:
        cmp             #'T'
        bne             Prompt15
        jsr             DumpTaskList
        jmp             Monitor
Prompt15:
        cmp             #'S'
        bne             Prompt16
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #'P'
        bne             Prompt18
        jsr             ignBlanks
        jsr             GetHexNumber
        sta             SPSave
        jmp             Monitor
Prompt18:
        dey
        jsr             spi_init
        cmp             #0
        bne             Monitor
        jsr             spi_read_part
        cmp             #0
        bne             Monitor
        jsr             spi_read_boot
        cmp             #0
        bne             Monitor
        jsr             loadBootFile
        jmp             Monitor
Prompt16:
        cmp             #'e'
        bne             Prompt17
        jsr             eth_main
Prompt17:
        cmp             #'R'
        bne             Monitor
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #'S'
        beq             LoadSector
        dey
        bra             SetRegValue
        jmp             Monitor
message "Prompt16"
RandomLinesCall:
;       jsr             RandomLines
        jmp             Monitor

DoDir:
        jsr             do_dir
        jmp             Monitor
DoFmt:
        jsr             do_fmt
        jmp             Monitor

TestCLS:
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #'L'
        bne             Monitor
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #'S'
        bne             Monitor
        jsr     ClearScreen
        ldx             RunningTCB
        stz             TCB_CursorCol,x
        stz             TCB_CursorRow,x
        jsr             CalcScreenLoc
        jmp             Monitor
message "HelpMsg"
HelpMsg:
        db      "? = Display help",CR,LF
        db      "CLS = clear screen",CR,LF
        db      "S = Boot from SD Card",CR,LF
        db      ": = Edit memory bytes",CR,LF
        db      "L = Load sector",CR,LF
        db      "W = Write sector",CR,LF
        db  "DR = Dump registers",CR,LF
        db      "D = Dump memory",CR,LF
        db      "F = Fill memory",CR,LF
        db  "FL = Dump I/O Focus List",CR,LF
        db      "B = start tiny basic",CR,LF
        db      "b = start EhBasic 6502",CR,LF
        db      "J = Jump to code",CR,LF
        db      "R[n] = Set register value",CR,LF
        db      "r = random lines - test bitmap",CR,LF
        db      "e = ethernet test",CR,LF
        db      "T = Dump task list",CR,LF
        db      "P = Piano",CR,LF,0

;------------------------------------------------------------------------------
; Ignore blanks in the input
; r3 = text pointer
; r1 destroyed
;------------------------------------------------------------------------------
;
ignBlanks:
ignBlanks1:
        lda             (y)
        iny
        jsr             ScreenToAscii
        cmp             #' '
        beq             ignBlanks1
        dey
        rts

;------------------------------------------------------------------------------
; Edit memory byte(s).
;------------------------------------------------------------------------------
;
EditMem:
        jsr             ignBlanks
        jsr             GetHexNumber
        or              r5,r1,r0
        ld              r4,#3
edtmem1:
        jsr             ignBlanks
        jsr             GetHexNumber
        sta             (r5)
        add             r5,r5,#1
        dec             r4
        bne             edtmem1
        jmp             Monitor

;------------------------------------------------------------------------------
; Execute code at the specified address.
;------------------------------------------------------------------------------
;
message "ExecuteCode"
ExecuteCode:
        jsr             ignBlanks
        jsr             GetHexNumber
        st              r1,JMPTMP
        lda             #xcret                  ; push return address so we can do an indirect jump
        pha
        ld              r1,R1Save
        ld              r2,R2Save
        ld              r3,R3Save
        ld              r4,R4Save
        ld              r5,R5Save
        ld              r6,R6Save
        ld              r7,R7Save
        ld              r8,R8Save
        ld              r9,R9Save
        ld              r10,R10Save
        ld              r11,R11Save
        ld              r12,R12Save
        ld              r13,R13Save
        ld              r14,R14Save
        ld              r15,R15Save
        jmp             (JMPTMP)
xcret:
        php
        st              r1,R1Save
        st              r2,R2Save
        st              r3,R3Save
        st              r4,R4Save
        st              r5,R5Save
        st              r6,R6Save
        st              r7,R7Save
        st              r8,R8Save
        st              r9,R9Save
        st              r10,R10Save
        st              r11,R11Save
        st              r12,R12Save
        st              r13,R13Save
        st              r14,R14Save
        st              r15,R15Save
        tsr             sp,r1
        st              r1,SPSave
        tsr             sp8,r1
        st              r1,SP8Save
        pla
        sta             SRSave
        jmp     Monitor

LoadSector:
        jsr             ignBlanks
        jsr             GetDecNumber
        pha
        jsr             ignBlanks
        jsr             GetHexNumber
        tax
        phx
;       ld              r2,#0x3800
        jsr             spi_init
        plx
        pla
        jsr             spi_read_sector
        jmp             Monitor

WriteSector:
        jsr             ignBlanks
        jsr             GetDecNumber
        pha
        jsr             ignBlanks
        jsr             GetHexNumber
        tax
        phx
        jsr             spi_init
        plx
        pla
        jsr             spi_write_sector
        jmp             Monitor

;------------------------------------------------------------------------------
; Dump the register set.
;------------------------------------------------------------------------------
message "DumpReg"
DumpReg:
        ldy             #0
DumpReg1:
        jsr             CRLF
        lda             #':'
        jsr             DisplayChar
        lda             #'R'
        jsr             DisplayChar
        ldx             #1
        tya
        ina
        jsr             PRTNUM
        lda             #' '
        jsr             DisplayChar
        lda             R1Save,y
        jsr             DisplayWord
        iny
        cpy             #15
        bne             DumpReg1
        jsr             CRLF
        lda             #':'
        jsr             DisplayChar
        lda             #'S'
        jsr             DisplayChar
        lda             #'P'
        jsr             DisplayChar
        lda             #' '
        jsr             DisplayChar
        lda             TCB_SPSave
        jsr             DisplayWord
        jsr             CRLF
        jmp             Monitor
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
SetRegValue:
        jsr             GetDecNumber
        cmp             #15
        bpl             Monitor
        pha
        jsr             ignBlanks
        jsr             GetHexNumber
        ply
        sta             R1Save,y
        jmp             Monitor
                
;------------------------------------------------------------------------------
; Do a memory dump of the requested location.
;------------------------------------------------------------------------------
;
DumpMem:
        jsr             ignBlanks
        jsr             GetHexNumber    ; get start address of dump
        tax
        jsr             ignBlanks
        jsr             GetHexNumber    ; get number of words to dump
        lsr                                             ; 1/4 as many dump rows
        lsr
        bne             Dumpmem2
        lda             #1                              ; dump at least one row
Dumpmem2:
        jsr             CRLF
        bra             DumpmemW
DumpmemW:
        jsr             DisplayMemW
        dea
        bne             DumpmemW
        jmp             Monitor


        bra             Monitor
message "FillMem"
FillMem:
        jsr             ignBlanks
        jsr             GetHexNumber    ; get start address of dump
        tax
        jsr             ignBlanks
        jsr             GetHexNumber    ; get number of bytes to fill
        ld              r5,r1
        jsr             ignBlanks
        jsr             GetHexNumber    ; get the fill byte
FillmemW:
        sta             (x)
        inx
        dec             r5
        bne             FillmemW
        jmp             Monitor

;------------------------------------------------------------------------------
; Get a hexidecimal number. Maximum of eight digits.
; R3 = text pointer (updated)
; R1 = hex number
;------------------------------------------------------------------------------
;
GetHexNumber:
        phx
        push    r4
        ldx             #0
        ld              r4,#8
gthxn2:
        lda             (y)
        iny
        jsr             ScreenToAscii
        jsr             AsciiToHexNybble
        cmp             #-1
        beq             gthxn1
        asl             r2,r2,#4
        and             #$0f
        or              r2,r2,r1
        dec             r4
        bne             gthxn2
gthxn1:
        txa
        pop             r4
        plx
        rts

GetDecNumber:
        phx
        push    r4
        push    r5
        ldx             #0
        ld              r4,#10
        ld              r5,#10
gtdcn2:
        lda             (y)
        iny
        jsr             ScreenToAscii
        jsr             AsciiToDecNybble
        cmp             #-1
        beq             gtdcn1
        mul             r2,r2,r5
        add             r2,r2,r1
        dec             r4
        bne             gtdcn2
gtdcn1:
        txa
        pop             r5
        pop             r4
        plx
        rts

;------------------------------------------------------------------------------
; Convert ASCII character in the range '0' to '9', 'a' to 'f' or 'A' to 'F'
; to a hex nybble.
;------------------------------------------------------------------------------
;
AsciiToHexNybble:
        cmp             #'0'
        bcc             gthx3
        cmp             #'9'+1
        bcs             gthx5
        sub             #'0'
        rts
gthx5:
        cmp             #'A'
        bcc             gthx3
        cmp             #'F'+1
        bcs             gthx6
        sub             #'A'
        add             #10
        rts
gthx6:
        cmp             #'a'
        bcc             gthx3
        cmp             #'z'+1
        bcs             gthx3
        sub             #'a'
        add             #10
        rts
gthx3:
        lda             #-1             ; not a hex number
        rts

AsciiToDecNybble:
        cmp             #'0'
        bcc             gtdc3
        cmp             #'9'+1
        bcs             gtdc3
        sub             #'0'
        rts
gtdc3:
        lda             #-1
        rts


;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
ClearBmpScreen:
        pha
        phx
        phy
        ldx             #(1364*768)>>2          ; x = # words to clear
        lda             #0x29292929                     ; acc = color for four pixels
        ldy             #BITMAPSCR                      ; y = screen address
cbsj4
        sta             (y)                                     ; store pixel data
        iny                                                     ; advance screen address
        dex                                                     ; decrement pixel count and loop back
        bne             cbsj4
        ply
        plx
        pla
        rts

;==============================================================================
;==============================================================================
;--------------------------------------------------------------------------
; Setup the AC97/LM4550 audio controller. Check keyboard for a CTRL-C
; interrupt which may be necessary if the audio controller isn't 
; responding.
;--------------------------------------------------------------------------
;
SetupAC97:
        pha
        phx
        phy
        push    r4
        ld              r4,Milliseconds
sac974:
        stz             AC97+0x26               ; trigger a read of register 26 (status reg)
sac971:                                         ; wait for status to register 0xF (all ready)
        ld              r3,Milliseconds
        sub             r3,r3,r4
        cmp             r3,#1000
        bpl             sac97Abort
        jsr             KeybdGetChar    ; see if we needed to CTRL-C
        cmp             #CTRLC
        beq             sac973
        lda             AC97+0x68               ; wait for dirty bit to clear
        bne             sac971
        lda             AC97+0x26               ; check status at reg h26, wait for
        and             #0x0F                   ; analogue to be ready
        cmp             #$0F
        bne             sac974
sac973:
        stz             AC97+2                  ; master volume, 0db attenuation, mute off
        stz             AC97+4                  ; headphone volume, 0db attenuation, mute off
        stz             AC97+0x18               ; PCM gain (mixer) mute off, no attenuation
        stz             AC97+0x0A               ; mute PC beep
        lda             #0x8000                 ; bypass 3D sound
        sta             AC97+0x20
        ld              r4,Milliseconds
sac972:
        ld              r3,Milliseconds
        sub             r3,r3,r4
        cmp             r3,#1000
        bpl             sac97Abort
        jsr             KeybdGetChar
        cmp             #CTRLC
        beq             sac975
        lda             AC97+0x68               ; wait for dirty bits to clear
        bne             sac972                  ; wait a while for the settings to take effect
sac975:
        pop             r4
        ply
        plx
        pla
        rts
sac97Abort:
        lda             #msgAC97bad
        jsr             DisplayStringCRLFB
        pop             r4
        ply
        plx
        pla
        rts

msgAC97bad:
        db      "The AC97 controller is not responding.",CR,LF,0

;--------------------------------------------------------------------------
; Sound a 800 Hz beep
;--------------------------------------------------------------------------
;
Beep:
        pha
        lda             #15                             ; master volume to max
        sta             PSG+128
        lda             #13422                  ; 800Hz
        sta             PSGFREQ0
        ; decay  (16.384 ms)2
        ; attack (8.192 ms)1
        ; release (1.024 s)A
        ; sustain level C
        lda             #0xCA12
        sta             PSGADSR0
        lda             #0x1104                 ; gate, output enable, triangle waveform
        sta             PSGCTRL0
        lda             #100                    ; delay about 1s
        jsr             Sleep
        lda             #0x0104                 ; gate off, output enable, triangle waveform
        sta             PSGCTRL0
        lda             #100                    ; delay about 1s
        jsr             Sleep
        lda             #0x0000                 ; gate off, output enable off, no waveform
        sta             PSGCTRL0
        pla
        rts

;--------------------------------------------------------------------------
;--------------------------------------------------------------------------
; 
Piano:
        jsr             RequestIOFocus
        lda             #15                             ; master volume to max
        sta             PSG+128
playnt:
        jsr             KeybdGetChar
        cmp             #CTRLC
        beq             PianoX
        cmp             #'a'
        beq             playnt1a
        cmp             #'b'
        beq             playnt1b
        cmp             #'c'
        beq             playnt1c
        cmp             #'d'
        beq             playnt1d
        cmp             #'e'
        beq             playnt1e
        cmp             #'f'
        beq             playnt1f
        cmp             #'g'
        beq             playnt1g
        bra             playnt
PianoX:
        jsr             ReleaseIOFocus
        rts

playnt1a:
        lda             #7217
        jsr             Tone
        bra             playnt
playnt1b:
        lda             #8101
        jsr             Tone
        bra             playnt
playnt1c:
        lda             #4291
        jsr             Tone
        bra             playnt
playnt1d:
        lda             #4817
        jsr             Tone
        bra             playnt
playnt1e:
        lda             #5407
        jsr             Tone
        bra             playnt
playnt1f:
        lda             #5728
        jsr             Tone
        bra             playnt
playnt1g:
        lda             #6430
        jsr             Tone
        bra             playnt

Tone:
        pha
        sta             PSGFREQ0
        ; decay  (16.384 ms)2
        ; attack (8.192 ms)1
        ; release (1.024 s)A
        ; sustain level C
        lda             #0xCA12
        sta             PSGADSR0
        lda             #0x1104                 ; gate, output enable, triangle waveform
        sta             PSGCTRL0
        lda             #1                              ; delay about 10ms
        jsr             Sleep
        lda             #0x0104                 ; gate off, output enable, triangle waveform
        sta             PSGCTRL0
        lda             #1                              ; delay about 10ms
        jsr             Sleep
        lda             #0x0000                 ; gate off, output enable off, no waveform
        sta             PSGCTRL0
        pla
        rts

;==============================================================================
;==============================================================================
;
; Initialize the SD card
; Returns
; acc = 0 if successful, 1 otherwise
; Z=1 if successful, otherwise Z=0
;
message "spi_init"
spi_init
        lda             #SPI_INIT_SD
        sta             SPIMASTER+SPI_TRANS_TYPE_REG
        lda             #SPI_TRANS_START
        sta             SPIMASTER+SPI_TRANS_CTRL_REG
        nop
spi_init1
        lda             SPIMASTER+SPI_TRANS_STATUS_REG
        nop
        nop
        cmp             #SPI_TRANS_BUSY
        beq             spi_init1
        lda             SPIMASTER+SPI_TRANS_ERROR_REG
        and             #3
        cmp             #SPI_INIT_NO_ERROR
        bne             spi_error
;       lda             #spi_init_ok_msg
;       jsr             DisplayStringB
        lda             #0
        rts
spi_error
        jsr             DisplayByte
        lda             #spi_init_error_msg
        jsr             DisplayStringB
        lda             SPIMASTER+SPI_RESP_BYTE1
        jsr             DisplayByte
        lda             SPIMASTER+SPI_RESP_BYTE2
        jsr             DisplayByte
        lda             SPIMASTER+SPI_RESP_BYTE3
        jsr             DisplayByte
        lda             SPIMASTER+SPI_RESP_BYTE4
        jsr             DisplayByte
        lda             #1
        rts

spi_delay:
        nop
        nop
        rts


; SPI read sector
;
; r1= sector number to read
; r2= address to place read data
; Returns:
; r1 = 0 if successful
;
spi_read_sector:
        phx
        phy
        push    r4
        
        sta             SPIMASTER+SPI_SD_SECT_7_0_REG
        lsr             r1,r1,#8
        sta             SPIMASTER+SPI_SD_SECT_15_8_REG
        lsr             r1,r1,#8
        sta             SPIMASTER+SPI_SD_SECT_23_16_REG
        lsr             r1,r1,#8
        sta             SPIMASTER+SPI_SD_SECT_31_24_REG

        ld              r4,#20  ; retry count

spi_read_retry:
        ; Force the reciever fifo to be empty, in case a prior error leaves it
        ; in an unknown state.
        lda             #1
        sta             SPIMASTER+SPI_RX_FIFO_CTRL_REG

        lda             #RW_READ_SD_BLOCK
        sta             SPIMASTER+SPI_TRANS_TYPE_REG
        lda             #SPI_TRANS_START
        sta             SPIMASTER+SPI_TRANS_CTRL_REG
        nop
spi_read_sect1:
        lda             SPIMASTER+SPI_TRANS_STATUS_REG
        jsr             spi_delay                       ; just a delay between consecutive status reg reads
        cmp             #SPI_TRANS_BUSY
        beq             spi_read_sect1
        lda             SPIMASTER+SPI_TRANS_ERROR_REG
        lsr
        lsr
        and             #3
        cmp             #SPI_READ_NO_ERROR
        bne             spi_read_error
        ldy             #512            ; read 512 bytes from fifo
spi_read_sect2:
        lda             SPIMASTER+SPI_RX_FIFO_DATA_REG
        sb              r1,0,x
        inx
        dey
        bne             spi_read_sect2
        lda             #0
        bra             spi_read_ret
spi_read_error:
        dec             r4
        bne             spi_read_retry
        jsr             DisplayByte
        lda             #spi_read_error_msg
        jsr             DisplayStringB
        lda             #1
spi_read_ret:
        pop             r4
        ply
        plx
        rts

; SPI write sector
;
; r1= sector number to write
; r2= address to get data from
; Returns:
; r1 = 0 if successful
;
spi_write_sector:
        phx
        phy
        pha
        ; Force the transmitter fifo to be empty, in case a prior error leaves it
        ; in an unknown state.
        lda             #1
        sta             SPIMASTER+SPI_TX_FIFO_CTRL_REG
        nop                     ; give I/O time to respond
        nop

        ; now fill up the transmitter fifo
        ldy             #512
spi_write_sect1:
        lb              r1,0,x
        sta             SPIMASTER+SPI_TX_FIFO_DATA_REG
        nop                     ; give the I/O time to respond
        nop
        inx
        dey
        bne             spi_write_sect1

        ; set the sector number in the spi master address registers
        pla
        sta             SPIMASTER+SPI_SD_SECT_7_0_REG
        lsr             r1,r1,#8
        sta             SPIMASTER+SPI_SD_SECT_15_8_REG
        lsr             r1,r1,#8
        sta             SPIMASTER+SPI_SD_SECT_23_16_REG
        lsr             r1,r1,#8
        sta             SPIMASTER+SPI_SD_SECT_31_24_REG

        ; issue the write command
        lda             #RW_WRITE_SD_BLOCK
        sta             SPIMASTER+SPI_TRANS_TYPE_REG
        lda             #SPI_TRANS_START
        sta             SPIMASTER+SPI_TRANS_CTRL_REG
        nop
spi_write_sect2:
        lda             SPIMASTER+SPI_TRANS_STATUS_REG
        nop                                                     ; just a delay between consecutive status reg reads
        nop
        cmp             #SPI_TRANS_BUSY
        beq             spi_write_sect2
        lda             SPIMASTER+SPI_TRANS_ERROR_REG
        lsr             r1,r1,#4
        and             #3
        cmp             #SPI_WRITE_NO_ERROR
        bne             spi_write_error
        lda             #0
        bra             spi_write_ret
spi_write_error:
        jsr             DisplayByte
        lda             #spi_write_error_msg
        jsr             DisplayStringB
        lda             #1

spi_write_ret:
        ply
        plx
        rts

; SPI read multiple sector
;
; r1= sector number to read
; r2= address to write data
; r3= number of sectors to read
;
; Returns:
; r1 = 0 if successful
;
spi_read_multiple:
        push    r4
        ld              r4,#0
spi_rm1:
        pha
        jsr             spi_read_sector
        add             r4,r4,r1
        add             r2,r2,#512
        pla
        ina
        dey
        bne             spi_rm1
        ld              r1,r4
        pop             r4
        rts

; SPI write multiple sector
;
; r1= sector number to write
; r2= address to get data from
; r3= number of sectors to write
;
; Returns:
; r1 = 0 if successful
;
spi_write_multiple:
        push    r4
        ld              r4,#0
spi_wm1:
        pha
        jsr             spi_write_sector
        add             r4,r4,r1                ; accumulate an error count
        add             r2,r2,#512              ; 512 bytes per sector
        pla
        ina
        dey
        bne             spi_wm1
        ld              r1,r4
        pop             r4
        rts
        
; read the partition table to find out where the boot sector is.
; Returns
; r1 = 0 everything okay, 1=read error
; also Z=1=everything okay, Z=0=read error
;
spi_read_part:
        phx
        stz             startSector                                             ; default starting sector
        lda             #0                                                              ; r1 = sector number (#0)
        ldx             #BYTE_SECTOR_BUF                                ; r2 = target address (word to byte address)
        jsr             spi_read_sector
        cmp             #0
        bne             spi_rp1
        lb              r1,BYTE_SECTOR_BUF+$1C9
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+$1C8
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+$1C7
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+$1C6
        sta             startSector                                             ; r1 = 0, for okay status
        plx
        lda             #0
        rts
spi_rp1:
        plx
        lda             #1
        rts

; Read the boot sector from the disk.
; Make sure it's the boot sector by looking for the signature bytes 'EB' and '55AA'.
; Returns:
; r1 = 0 means this card is bootable
; r1 = 1 means a read error occurred
; r1 = 2 means the card is not bootable
;
spi_read_boot:
        phx
        phy
        push    r5
        lda             startSector                                     ; r1 = sector number
        ldx             #BYTE_SECTOR_BUF                        ; r2 = target address
        jsr             spi_read_sector
        cmp             #0
        bne             spi_read_boot_err
        lb              r1,BYTE_SECTOR_BUF
        cmp             #$EB
        bne             spi_eb_err
spi_read_boot2:
        lda             #msgFoundEB
        jsr             DisplayStringB
        lb              r1,BYTE_SECTOR_BUF+$1FE         ; check for 0x55AA signature
        cmp             #$55
        bne             spi_eb_err
        lb              r1,BYTE_SECTOR_BUF+$1FF         ; check for 0x55AA signature
        cmp             #$AA
        bne             spi_eb_err
        pop             r5
        ply
        plx
        lda             #0                                              ; r1 = 0, for okay status
        rts
spi_read_boot_err:
        pop             r5
        ply
        plx
        lda             #1
        rts
spi_eb_err:
        lda             #msgNotFoundEB
        jsr             DisplayStringB
        pop             r5
        ply
        plx
        lda             #2
        rts

msgFoundEB:
        db      "Found EB code.",CR,LF,0
msgNotFoundEB:
        db      "EB/55AA Code missing.",CR,LF,0

; Load the root directory from disk
; r2 = where to place root directory in memory
;
loadBootFile:
        lb              r1,BYTE_SECTOR_BUF+BSI_SecPerFAT+1                      ; sectors per FAT
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+BSI_SecPerFAT
        bne             loadBootFile7
        lb              r1,BYTE_SECTOR_BUF+$27                  ; sectors per FAT, FAT32
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+$26
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+$25
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+$24
loadBootFile7:
        lb              r4,BYTE_SECTOR_BUF+$10                  ; number of FATs
        mul             r3,r1,r4                                                ; offset
        lb              r1,BYTE_SECTOR_BUF+$F                   ; r1 = # reserved sectors before FAT
        asl             r1,r1,#8
        orb             r1,r1,BYTE_SECTOR_BUF+$E
        add             r3,r3,r1                                                ; r3 = root directory sector number
        ld              r6,startSector
        add             r5,r3,r6                                                ; r5 = root directory sector number
        lb              r1,BYTE_SECTOR_BUF+$D                   ; sectors per cluster
        add             r3,r1,r5                                                ; r3 = first cluster after first cluster of directory
        bra             loadBootFile6

loadBootFile6:
        ; For now we cheat and just go directly to sector 512.
        bra             loadBootFileTmp

loadBootFileTmp:
        ; We load the number of sectors per cluster, then load a single cluster of the file.
        ; This is 16kib
        ld              r5,r3                                                   ; r5 = start sector of data area        
        ld              r2,#PROG_LOAD_AREA                              ; where to place file in memory
        lb              r3,BYTE_SECTOR_BUF+$D                   ; sectors per cluster
loadBootFile1:
        ld              r1,r5                                                   ; r1=sector to read
        jsr             spi_read_sector
        inc             r5                                              ; r5 = next sector
        add             r2,r2,#512
        dec             r3
        bne             loadBootFile1
        lda             PROG_LOAD_AREA>>2               ; make sure it's bootable
        cmp             #$544F4F42
        bne             loadBootFile2
        lda             #msgJumpingToBoot
        jsr             DisplayStringB
        lda             (PROG_LOAD_AREA>>2)+$1
        jsr             (r1)
        jmp             Monitor
loadBootFile2:
        lda             #msgNotBootable
        jsr             DisplayStringB
        ldx             #PROG_LOAD_AREA>>2
        jsr             DisplayMemW
        jsr             DisplayMemW
        jsr             DisplayMemW
        jsr             DisplayMemW
        jmp             Monitor

msgJumpingToBoot:
        db      "Jumping to boot",0     
msgNotBootable:
        db      "SD card not bootable.",0
spi_init_ok_msg:
        db "SD card initialized okay.",0
spi_init_error_msg:
        db      ": error occurred initializing the SD card.",0
spi_boot_error_msg:
        db      "SD card boot error",CR,LF,0
spi_read_error_msg:
        db      "SD card read error",CR,LF,0
spi_write_error_msg:
        db      "SD card write error",0

do_fmt:
        jsr             spi_init
        cmp             #0
        bne             fmt_abrt
        ldx             #DIRBUF
        ldy             #65536
        ; clear out the directory buffer
dfmt1:
        stz             (x)
        inx
        dey
        bne             dfmt1
        jsr             store_dir
fmt_abrt:
        rts

do_dir:
        jsr             CRLF
        jsr             spi_init
        cmp             #0
        bne             dirabrt
        jsr             load_dir
        ld              r4,#0                   ; r4 = entry counter
ddir3:
        asl             r3,r4,#6                ; y = start of entry, 64 bytes per entry
        ldx             #32                             ; 32 chars in filename
ddir4:
        lb              r1,DIRBUF<<2,y
        beq             ddir2                   ; move to next dir entry if null is found
        cmp             #$20                    ; don't display control chars
        bmi             ddir1
        jsr             DisplayChar
        bra             ddir5
ddir1:
        lda             #' '
        jsr             DisplayChar
ddir5:
        iny
        dex
        bne             ddir4
        lda             #' '
        jsr             DisplayChar
        asl             r3,r4,#4                ; y = start of entry, 16 words per entry
        lda             DIRBUF+$D,y
        ldx             #5
        jsr             PRTNUM
        jsr             CRLF
ddir2:
        jsr             KeybdGetChar
        cmp             #CTRLC
        beq             ddir6
        inc             r4
        cmp             r4,#512         ; max 512 dir entries
        bne             ddir3
ddir6:

dirabrt:
        rts

load_dir:
        pha
        phx
        phy
        lda             #4000
        ldx             #DIRBUF<<2
        ldy             #64
        jsr             spi_read_multiple
        ply
        plx
        pla
        rts
store_dir:
        pha
        phx
        phy
        lda             #4000
        ldx             #DIRBUF<<2
        ldy             #64
        jsr             spi_write_multiple
        ply
        plx
        pla
        rts

; r1 = pointer to file name
; r2 = pointer to buffer to save
; r3 = length of buffer
;
do_save:
        pha
        jsr             spi_init
        cmp             #0
        bne             dsavErr
        pla
        jsr             load_dir
        ld              r4,#0
dsav4:
        asl             r5,r4,#6
        ld              r7,#0
        ld              r10,r1
dsav2:
        lb              r6,DIRBUF<<2,r5
        lb              r8,0,r10
        cmp             r6,r8
        bne             dsav1
        inc             r5
        inc             r7
        inc             r10
        cmp             r7,#32
        bne             dsav2
        ; here the filename matched
dsav8:
        asl             r7,r4,#7        ; compute file address  64k * entry #
        add             r7,r7,#5000     ; start at sector 5,000
        ld              r1,r7           ; r1 = sector number
        lsr             r3,r3,#9        ; r3/512
        iny                                     ; +1
        jsr             spi_write_multiple
dsav3:
        rts
        ; Here the filename didn't match
dsav1:
        inc             r4
        cmp             r4,#512
        bne             dsav4
        ; Here none of the filenames in the directory matched
        ; Find an empty entry.
        ld              r4,#0
dsav6:
        asl             r5,r4,#6
        lb              r6,DIRBUF<<2,r5
        beq             dsav5
        inc             r4
        cmp             r4,#512
        bne             dsav6
        ; Here there were no empty entries
        lda             #msgDiskFull
        jsr             DisplayStringB
        rts
dsav5:
        ld              r7,#32
        ld              r10,r1
dsav7:
        lb              r6,0,r10        ; copy the filename into the directory entry
        sb              r6,DIRBUF<<2,r5
        inc             r5
        inc             r10
        dec             r7
        bne             dsav7
                                                ; copy the file size into the directory entry
        asl             r5,r4,#4        ; 16 words per dir entry
        sty             DIRBUF+$D,r5
        jsr             store_dir
        bra             dsav8
dsavErr:
        pla
        rts

msgDiskFull
        db      CR,LF,"The disk is full, unable to save file.",CR,LF,0

do_load:
        pha
        jsr             spi_init
        cmp             #0
        bne             dsavErr
        pla
        jsr             load_dir
        ld              r4,#0
dlod4:
        asl             r5,r4,#6
        ld              r7,#0
        ld              r10,r1
dlod2:
        lb              r6,DIRBUF<<2,r5
        lb              r8,0,r10
        cmp             r6,r8
        bne             dlod1
        inc             r5
        inc             r7
        inc             r10
        cmp             r7,#32
        bne             dlod2
        ; here the filename matched
dlod8:
        asl             r5,r4,#4                                ; 16 words
        ld              r3,DIRBUF+$d,r5                 ; get file size into y register
        asl             r7,r4,#7        ; compute file address  64k * entry #
        add             r7,r7,#5000     ; start at sector 5,000
        ld              r1,r7           ; r1 = sector number
        lsr             r3,r3,#9        ; r3/512
        iny                                     ; +1
        jsr             spi_read_multiple
dlod3:
        rts
        ; Here the filename didn't match
dlod1:
        inc             r4
        cmp             r4,#512
        bne             dlod4
        ; Here none of the filenames in the directory matched
        ; 
        lda             #msgFileNotFound
        jsr             DisplayStringB
        rts

msgFileNotFound:
        db      CR,LF,"File not found.",CR,LF
        
;==============================================================================
; Ethernet
;==============================================================================
my_MAC1 EQU     0x00
my_MAC2 EQU     0xFF
my_MAC3 EQU     0xEE
my_MAC4 EQU     0xF0
my_MAC5 EQU     0xDA
my_MAC6 EQU     0x42

; Initialize the ethmac controller.
; Supply a MAC address, set MD clock
;
message "eth_init"
eth_init:
        pha
        phy
        ldy             #ETHMAC
        lda             #0x64                           ; 100
        sta             ETH_MIIMODER,y
        lda             #7                                      ; PHY address
        sta             ETH_MIIADDRESS,y
        lda             #0xEEF0DA42
        sta             ETH_MAC_ADDR0,y         ; MAC0
        lda             #0x00FF
        sta             ETH_MAC_ADDR1,y         ; MAC1
        ply
        pla
        rts

; Request a packet and display on screen
; r1 = address where to put packet
;
message "eth_request_packet"
eth_request_packet:
        phx
        phy
        push    r4
        push    r5
        ldy             #ETHMAC
        ldx             #4                                      ; clear rx interrupt
        stx             ETH_INT_SOURCE,y
        sta             0x181,y                         ; storage address
        ldx             #0xe000                         ; enable interrupt
        stx             0x180,y
eth1:
        nop
        ldx             ETH_INT_SOURCE,y
        and             r2,r2,#4                        ; get bit #2
        beq             eth1
        ldx             0x180,y                         ; get from descriptor
        lsr             r2,r2,#16
        ldy             #0
        ld              r4,#TEXTSCR+3780        ; second last line of screen
eth20:
        add             r5,r1,r3
        ldx             (r5)                            ; get byte
        add             r5,r4,r3
        stx             (r5)                            ; store to screen
        iny
        cpy             #83
        bne             eth20
        pop             r5
        pop             r4
        ply
        plx
        rts

; r1 = packet address
;
message "eth_interpret_packet"
eth_interpret_packet:
        phx
        phy
        lb              r2,12,r1
        lb              r3,13,r1
        cpx             #8                                      ; 0x806 ?
        bne             eth2    
        cpy             #6              
        bne             eth2
        lda             #2                                      ; return r1 = 2 for ARP
eth5:
        ply
        plx
        rts
eth2:
        cpx             #8
        bne             eth3                            ; 0x800 ?
        cpy             #0
        bne             eth3
        lb              r2,23,r1
        cpx             #1
        bne             eth4
        lda             #1
        bra             eth5                            ; return 1 ICMP
eth4:
        cpx             #$11
        bne             eth6
        lda             #3                                      ; return 3 for UDP
        bra             eth5
eth6:
        cpx             #6
        bne             eth7
        lda             #4                                      ; return 4 for TCP
        bra             eth5
eth7:
eth3:
        eor             r1,r1,r1                        ; return zero for unknown
        ply
        plx
        rts

; r1 = address of packet to send
; r2 = packet length
;
message "eth_send_packet"
eth_send_packet:
        phx
        phy
        push    r4
        ldy             #ETHMAC
        ; wait for tx buffer to be clear
eth8:
        ld              r4,0x100,y
        and             r4,r4,#$8000
        bne             eth8
        ld              r4,#1                   ; clear tx interrupt
        st              r4,ETH_INT_SOURCE,y
        ; set address
        sta             0x101,y
        ; set the packet length field and enable interrupts
        asl             r2,r2,#16
        or              r2,r2,#0xF000
        stx             0x100,y
        pop             r4
        ply
        plx
        rts

; Only for IP type packets (not ARP)
; r1 = rx buffer address
; r2 = swap flag
; Returns:
; r1 = data start index
;
message "eth_build_packet"
eth_build_packet:
        phy
        push    r4
        push    r5
        push    r6
        push    r7
        push    r8
        push    r9
        push    r10

        lb              r3,6,r1
        lb              r4,7,r1
        lb              r5,8,r1
        lb              r6,9,r1
        lb              r7,10,r1
        lb              r8,11,r1
        ; write to destination header
        sb              r3,0,r1
        sb              r4,1,r1
        sb              r5,2,r1
        sb              r6,3,r1
        sb              r7,4,r1
        sb              r8,5,r1
        ; write to source header
        ld              r3,#my_MAC1
        sb              r3,6,r1
        ld              r3,#my_MAC2
        sb              r3,7,r1
        ld              r3,#my_MAC3
        sb              r3,8,r1
        ld              r3,#my_MAC4
        sb              r3,9,r1
        ld              r3,#my_MAC5
        sb              r3,10,r1
        ld              r3,#my_MAC6
        sb              r3,11,r1
        cmp             r2,#1
        bne             eth16                   ; if (swap)
        lb              r3,26,r1
        lb              r4,27,r1
        lb              r5,28,r1
        lb              r6,29,r1
        ; read destination
        lb              r7,30,r1
        lb              r8,31,r1
        lb              r9,32,r1
        lb              r10,33,r1
        ; write to sender
        sb              r7,26,r1
        sb              r8,27,r1
        sb              r9,28,r1
        sb              r10,29,r1
        ; write destination
        sb              r3,30,r1
        sb              r4,31,r1
        sb              r5,32,r1
        sb              r6,33,r1
eth16:
        ldy             eth_unique_id
        iny
        sty             eth_unique_id
        sb              r3,19,r1
        lsr             r3,r3,#8
        sb              r3,18,r1
        lb              r3,14,r1
        and             r3,r3,#0xF
        asl             r3,r3,#2                ; *4
        add             r1,r3,#14               ; return datastart in r1
        pop             r10
        pop             r9
        pop             r8
        pop             r7
        pop             r6
        pop             r5
        pop             r4
        ply
        rts

; Compute IPv4 checksum of header
; r1 = packet address
; r2 = data start
;
message "eth_checksum"
eth_checksum:
        phy
        push    r4
        push    r5
        push    r6
        ; set checksum to zero
        stz             24,r1
        stz             25,r1
        eor             r3,r3,r3                ; r3 = sum = zero
        ld              r4,#14
eth15:
        ld              r5,r2
        dec             r5                              ; r5 = datastart - 1
        cmp             r4,r5
        bpl             eth14
        add             r6,r1,r4
        lb              r5,0,r6                 ; shi = [rx_addr+i]
        lb              r6,1,r6             ; slo = [rx_addr+i+1]
        asl     r5,r5,#8
        or              r5,r5,r6                ; shilo
        add             r3,r3,r5                ; sum = sum + shilo
        add             r4,r4,#2                ; i = i + 2
        bra             eth15
eth14:
        ld              r5,r3                   ; r5 = sum
        and             r3,r3,#0xffff
        lsr             r5,r5,#16
        add             r3,r3,r5
        eor             r3,r3,#-1
        sb              r3,25,r1                ; low byte
        lsr             r3,r3,#8
        sb              r3,24,r1                ; high byte
        pop             r6
        pop             r5
        pop             r4
        ply
        rts

; r1 = packet address
; returns r1 = 1 if this IP
;       
message "eth_verifyIP"
eth_verifyIP:
        phx
        phy
        push    r4
        push    r5
        lb              r2,30,r1
        lb              r3,31,r1
        lb              r4,32,r1
        lb              r5,33,r1
        ; Check for general broadcast
        cmp             r2,#$FF
        bne             eth11
        cmp             r3,#$FF
        bne             eth11
        cmp             r4,#$FF
        bne             eth11
        cmp             r5,#$FF
        bne             eth11
eth12:
        lda             #1
eth13:
        pop             r5
        pop             r4
        ply
        plx
        rts
eth11:
        ld              r1,r2
        asl             r1,r1,#8
        or              r1,r1,r3
        asl             r1,r1,#8
        or              r1,r1,r4
        asl             r1,r1,#8
        or              r1,r1,r5
        cmp             #$C0A8012A              ; 192.168.1.42
        beq             eth12
        eor             r1,r1,r1
        bra             eth13

message "eth_main"
eth_main:
        jsr             eth_init
eth_loop:
        jsr             KeybdGetChar
        cmp             #-1
        beq             eth17
        cmp             #CTRLC
        bne             eth17
        rts
eth17
        lda             #eth_rx_buffer<<2               ; memory address zero
        jsr             eth_request_packet
        jsr             eth_interpret_packet    ; r1 = packet type

        cmp             #1
        bne             eth10
        ld              r2,r1                                   ; save off r1, r2 = packet type
        lda             #eth_rx_buffer<<2               ; memory address zero
        jsr             eth_verifyIP
        tay
        txa                                                             ; r1 = packet type again
        cpy             #1
        bne             eth10

        lda             #eth_rx_buffer<<2               ; memory address zero
        ldx             #1
        jsr             eth_build_packet
        tay                                                             ; y = icmpstart
        lda             #eth_rx_buffer<<2               ; memory address zero
        add             r4,r1,r3
        sb              r0,0,r4                                 ; [rx_addr+icmpstart] = 0
        lb              r2,17,r1
        add             r2,r2,#14                               ; r2 = len
        ld              r6,r2                                   ; r6 = len
        add             r15,r1,r3
        lb              r4,2,r15                                ; shi
        lb              r5,3,r15                                ; slo
        asl             r4,r4,#8
        or              r4,r4,r5                                ; sum = {shi,slo};
        eor             r4,r4,#-1                               ; sum = ~sum
        sub             r4,r4,#0x800                    ; sum = sum - 0x800
        eor             r4,r4,#-1                               ; sum = ~sum
        add             r15,r1,r3
        sb              r4,3,r15
        lsr             r4,r4,#8
        sb              r4,2,r15
        tyx
        jsr             eth_checksum
        lda             #eth_rx_buffer<<2               ; memory address zero
        ld              r2,r6
        jsr             eth_send_packet
        jmp             eth_loop
eth10:
        ; r2 = rx_addr
        cmp             #2
        bne             eth_loop                ; Do we have ARP ?
;       xor             r2,r2,r2                        ; memory address zero
        ldx             #eth_rx_buffer<<2
        ; get the opcode
        lb              r13,21,x
        cmp             r13,#1
        bne             eth_loop                ; ARP request
        ; get destination IP address
        lb              r9,38,x
        lb              r10,39,x
        lb              r11,40,x
        lb              r12,41,x
        ; set r15 = destination IP
        ld              r15,r9
        asl             r15,r15,#8
        or              r15,r15,r10
        asl             r15,r15,#8
        or              r15,r15,r11
        asl             r15,r15,#8
        or              r15,r15,r12
        ; Is it our IP ?
        cmp             r15,#$C0A8012A  ; //192.168.1.42
        bne             eth_loop
        ; get source IP address
        lb              r5,28,x
        lb              r6,29,x
        lb              r7,30,x
        lb              r8,31,x
        ; set r14 = source IP
        ld              r14,r5
        asl             r14,r14,#8
        or              r14,r14,r6
        asl             r14,r14,#8
        or              r14,r14,r7
        asl             r14,r14,#8
        or              r14,r14,r8
        ; Get the source MAC address
        push    r6
        push    r7
        push    r8
        push    r9
        push    r10
        push    r11
        lb              r6,22,x
        lb              r7,23,x
        lb              r8,24,x
        lb              r9,25,x
        lb              r10,26,x
        lb              r11,27,x
        ; write to destination header
        sb              r6,0,x
        sb              r7,1,x
        sb              r8,2,x
        sb              r9,3,x
        sb              r10,4,x
        sb              r11,5,x
        ; and write to ARP destination
        sb              r6,32,x
        sb              r7,33,x
        sb              r8,34,x
        sb              r9,35,x
        sb              r10,36,x
        sb              r11,37,x
        pop             r11
        pop             r10
        pop             r9
        pop             r8
        pop             r7
        pop             r6
        ; write to source header
;       stbc    #0x00,6[r2]
;       stbc    #0xFF,7[r2]
;       stbc    #0xEE,8[r2]
;       stbc    #0xF0,9[r2]
;       stbc    #0xDA,10[r2]
;       stbc    #0x42,11[r2]
        sb              r0,6,x
        lda             #0xFF
        sb              r1,7,x
        lda             #0xEE
        sb              r1,8,x
        lda             #0xF0
        sb              r1,9,x
        lda             #0xDA
        sb              r1,10,x
        lda             #0x42
        sb              r1,11,x
        ; write to ARP source
;       stbc    #0x00,22[r2]
;       stbc    #0xFF,23[r2]
;       stbc    #0xEE,24[r2]
;       stbc    #0xF0,25[r2]
;       stbc    #0xDA,26[r2]
;       stbc    #0x42,27[r2]
        sb              r0,22,x
        lda             #0xFF
        sb              r1,23,x
        lda             #0xEE
        sb              r1,24,x
        lda             #0xF0
        sb              r1,25,x
        lda             #0xDA
        sb              r1,26,x
        lda             #0x42
        sb              r1,27,x
        ; swap sender / destination IP
        ; write sender
        sb              r9,28,x
        sb              r10,29,x
        sb              r11,30,x
        sb              r12,31,x
        ; write destination
        sb              r5,38,x
        sb              r6,39,x
        sb              r7,40,x
        sb              r8,41,x
        ; change request to reply
;       stbc    #2,21[r2]
        lda             #2
        sb              r1,21,x
        txa                                             ; r1 = packet address
        ldx             #0x2A                   ; r2 = packet length
        jsr             eth_send_packet
        jmp             eth_loop

;--------------------------------------------------------------------------
; Initialize sprite image caches with random data.
;--------------------------------------------------------------------------
RandomizeSprram:
        ldx             #SPRRAM
        ld              r4,#14336               ; number of chars to initialize
rsr1:
        tsr             LFSR,r1
        sta             (x)
        inx
        dec             r4
        bne             rsr1
        rts

;--------------------------------------------------------------------------
; Draw random lines on the bitmap screen.
;--------------------------------------------------------------------------
;
RandomLines:
        pha
        phx
        phy
        push    r4
        push    r5
        jsr             RequestIOFocus
rl5:
        tsr             LFSR,r1
        tsr             LFSR,r2
        jsr             DrawPixel
        tsr             LFSR,r1
        sta             LineColor               ; select a random color
rl1:                                            ; random X0
        tsr             LFSR,r1
        ld              r5,#1364
        mod             r1,r1,r5
rl2:                                            ; random X1
        tsr             LFSR,r3
        mod             r3,r3,r5
rl3:                                            ; random Y0
        tsr             LFSR,r2
        ld              r5,#768
        mod             r2,r2,r5
rl4:                                            ; random Y1
        tsr             LFSR,r4
        ld              r5,#768
        mod             r4,r4,r5
rl8:
        jsr             DrawLine
        jsr             KeybdGetChar
        cmp             #CTRLC
        beq             rl7
        bra             rl5
rl7:
        jsr             ReleaseIOFocus
        pop             r5
        pop             r4
        ply
        plx
        pla
        rts

;--------------------------------------------------------------------------
; Draw a pixel on the bitmap screen.
; r1 = x coordinate
; r2 = y coordinate
; r3 = color
;--------------------------------------------------------------------------
DrawPixel:
        pha
        phx
        push    r4
        ld              r4,#768
        mod             r2,r2,r4
        ld              r4,#1364
        mod             r1,r1,r4
        mul             r2,r2,r4        ; y * 1364
        add             r1,r1,r2        ; + x
        sb              r3,BITMAPSCR<<2,r1
        pop             r4
        plx
        pla
        rts

;--------------------------------------------------------------------------
; Draw a line on the bitmap screen.
;--------------------------------------------------------------------------
;50 REM DRAWLINE
;100 dx = ABS(xb-xa)
;110 dy = ABS(yb-ya)
;120 sx = SGN(xb-xa)
;130 sy = SGN(yb-ya)
;140 er = dx-dy
;150 PLOT xa,ya
;160 if xa<>xb goto 200
;170 if ya=yb goto 300
;200 ee = er * 2
;210 if ee <= -dy goto 240
;220 er = er - dy
;230 xa = xa + sx
;240 if ee >= dx goto 270
;250 er = er + dx
;260 ya = ya + sy
;270 GOTO 150
;300 RETURN

DrawLine:
        pha
        phx
        phy
        push    r4
        push    r5
        push    r6
        push    r7
        push    r8
        push    r9
        push    r10
        push    r11

        sub             r5,r3,r1        ; dx = abs(x2-x1)
        bpl             dln1
        sub             r5,r0,r5
dln1:
        sub             r6,r4,r2        ; dy = abs(y2-y1)
        bpl             dln2
        sub             r6,r0,r6
dln2:

        sub             r7,r3,r1        ; sx = sgn(x2-x1)
        beq             dln5
        bpl             dln4
        ld              r7,#-1
        bra             dln5
dln4:
        ld              r7,#1
dln5:

        sub             r8,r4,r2        ; sy = sgn(y2-y1)
        beq             dln8
        bpl             dln7
        ld              r8,#-1
        bra             dln8
dln7:
        ld              r8,#1

dln8:
        sub             r9,r5,r6        ; er = dx-dy
dln150:
        phy
        ldy             LineColor
        jsr             DrawPixel
        ply
        cmp             r1,r3           ; if (xa <> xb)
        bne             dln200          ;    goto 200
        cmp             r2,r4           ; if (ya==yb)
        beq             dln300          ;    goto 300
dln200:
        asl             r10,r9          ; ee = er * 2
        sub             r11,r0,r6       ; r11 = -dy
        cmp             r10,r11         ; if (ee <= -dy)
        bmi             dln240          ;     goto 240
        beq             dln240
        sub             r9,r9,r6        ; er = er - dy
        add             r1,r1,r7        ; xa = xa + sx
dln240:
        cmp             r10,r5          ; if (ee >= dx)
        bpl             dln150          ;    goto 150
        add             r9,r9,r5        ; er = er + dx
        add             r2,r2,r8        ; ya = ya + sy
        bra             dln150          ; goto 150

dln300:
        pop             r11
        pop             r10
        pop             r9
        pop             r8
        pop             r7
        pop             r6
        pop             r5
        pop             r4
        ply
        plx
        pla
        rts

;include "float.asm"


;==============================================================================
; Memory Management routines follow.
;==============================================================================
MemInit:
        lda             #1                                      ; initialize memory semaphore
        sta             MEM_SEMA
        lda             #$4D454D20
        sta             HeapStart+MEM_CHK
        sta             HeapStart+MEM_FLAG
        sta             HeapEnd-2
        sta             HeapEnd-3
        lda             #0
        sta             HeapStart+MEM_PREV      ; prev of first MEMHDR
        sta             HeapEnd                 ; next of last MEMHDR
        lda             #HeapEnd
        ina
        sub             #$4
        sta             HeapStart+MEM_NEXT      ; next of first MEMHDR
        lda             #HeapStart
        sta             HeapEnd-1               ; prev of last MEMHDR
        rts

ReportMemFree:
        jsr             CRLF
        lda             #HeapEnd
        ina
        sub             #HeapStart
        ldx             #5
        jsr             PRTNUM
        lda             #msgMemFree
        jsr             DisplayStringB
        rts

msgMemFree:
        db      " words free",CR,LF,0
        
;------------------------------------------------------------------------------
; Allocate memory from the heap.
;------------------------------------------------------------------------------
MemAlloc:
        phx
        phy
        push    r4
memaSpin:
        ldx             MEM_SEMA+1
        beq             memaSpin
        ldx             #HeapStart
mema4:
        ldy             MEM_FLAG,x              ; Check the flag word to see if this block is available
        cpy             #$4D454D20
        bne             mema1                   ; block not available, go to next block
        ld              r4,MEM_NEXT,x   ; compute the size of this block
        sub             r4,r4,r2
        sub             r4,r4,#4                ; minus size of block header
        cmp             r1,r4                   ; is the block large enough ?
        bmi             mema2                   ; if yes, go allocate
mema1:
        ldx             MEM_NEXT,x              ; go to the next block
        beq             mema3                   ; if no more blocks, out of memory error
        bra             mema4
mema2:
        ldy             #$6D656D20
        sty             MEM_FLAG,x
        sub             r4,r4,r1
        cmp             r4,#4                   ; is the block large enough to split
        bpl             memaSplit
        stz             MEM_SEMA+1
        txa
        pop             r4
        ply
        plx
        rts
mema3:                                          ; insufficient memory
        stz             MEM_SEMA+1
        pop             r4
        ply
        plx
        lda             #0
        rts
memaSplit:
        add             r4,r1,r2
        add             r4,r4,#4
        ldy             #$4D454D20
        sty             (r4)
        sty             MEM_FLAG,r4
        stx             MEM_PREV,r4
        ldy             MEM_NEXT,x
        sty             MEM_NEXT,r4
        st              r4,MEM_PREV,y
        ld              r1,r4
        stz             MEM_SEMA+1
        pop             r4
        ply
        plx
        rts

;------------------------------------------------------------------------------
; Free previously allocated memory. Recombine with next and previous blocks
; if they are free as well.
;------------------------------------------------------------------------------
MemFree:
        cmp             #0                      ; null pointer ?
        beq             memf2
        phx
        phy
memfSpin:
        ldx             MEM_SEMA+1
        beq             memfSpin
        ldx             MEM_FLAG,r1
        cpx             #$6D656D20      ; is the block allocated ?
        bne             memf1
        ldx             #$4D454D20
        stx             MEM_FLAG,r1     ; mark block as free
        ldx             MEM_PREV,r1     ; is the previous block free ?
        beq             memf3           ; no previous block
        ldy             MEM_FLAG,x
        cpy             #$4D454D20
        bne             memf3           ; the previous block is not free
        ldy             MEM_NEXT,r1
        sty             MEM_NEXT,x
        beq             memf1           ; no next block
        stx             MEM_PREV,y
memf3:
        ldy             MEM_NEXT,r1
        ldx             MEM_FLAG,y
        cpx             #$4D454D20
        bne             memf1           ; next block not free
        ldx             MEM_PREV,r1
        stx             MEM_PREV,y
        beq             memf1           ; no previous block
        sty             MEM_NEXT,x
memf1:
        stz             MEM_SEMA+1
        ply
        plx
memf2:
        rts

;------------------------------------------------------------------------------
; Bus Error Routine
; This routine display a message then restarts the BIOS.
;------------------------------------------------------------------------------
;
message "bus_err_rout"
bus_err_rout:
        cld
        pla                                                     ; get rid of the stacked flags
        ply                                                     ; get the error PC
        ldx             #$05FFFFF8                      ; setup stack pointer top of memory
        txs
        jsr             CRLF
        lda             #msgBusErr
        jsr             DisplayStringB
        tya
        jsr             DisplayWord                     ; display the originating PC address
        lda             #msgDataAddr
        jsr             DisplayStringB
        tsr             #9,r1
        jsr             DisplayWord
        cli                                                     ; enable interrupts so we can get a char
ber1:
        jsr             KeybdGetChar
        cmp             #-1
        beq             ber1
        jmp             ber1
        
msgBusErr:
        db              "Bus error at: ",0
msgDataAddr:
        db              " data address: ",0

strStartQue:
        db              1,0,0,0,2,0,0,0,3,1,0,0,4,0,0,0
;       db              0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

;------------------------------------------------------------------------------
; 100 Hz interrupt
; - takes care of "flashing" the cursor
; - switching tasks
;------------------------------------------------------------------------------
;
p100Hz:
        ; Handle every other interrupt because 100Hz interrupts may be too fast.
        pha
        lda             #3                              ; reset the edge sense circuit
        sta             PIC_RSTE
;       inc             TEXTSCR+83              ; update IRQ live indicator on screen
        lda             IRQFlag
        ina
        sta             IRQFlag
        ror
        bcc             p100Hz11        
        pla
        rti

p100Hz11:

        cld             ; clear extended precision mode

        ; save off regs on the stack
        phx
        phy
        push    r4
        push    r5
        push    r6
        push    r7
        push    r8
        push    r9
        push    r10
        push    r11
        push    r12
        push    r13
        push    r14
        push    r15
        ldx             RunningTCB
        tsa                                             ; save off the stack pointer
        sta             TCB_SPSave,x
        tsr             sp8,r1                  ; and the eight bit mode stack pointer
        sta             TCB_SP8Save,x
        tsr             abs8,r1
        sta             TCB_ABS8Save,x  ; 8 bit emulation base register
        lda             TCB_Status,x    ; set the task status to PREEMPT
        and             r1,r1,#~TS_RUNNING
        or              r1,r1,#TS_PREEMPT
        sta             TCB_Status,x
p100Hz4:

        ; support EhBASIC's IRQ functionality
        ; code derived from minimon.asm
        lda             #3                              ; Timer is IRQ #3
        sta             IrqSource               ; stuff a byte indicating the IRQ source for PEEK()
        lb              r1,IrqBase              ; get the IRQ flag byte
        lsr             r4,r1
        or              r1,r1,r4
        and             #$E0
        sb              r1,IrqBase

        inc             TEXTSCR+83              ; update IRQ live indicator on screen
        
        ; flash the cursor
        cpx             IOFocusNdx              ; only flash the cursor for the task with the IO focus.
        bne             p100Hz1a
        lda             CursorFlash             ; test if we want a flashing cursor
        beq             p100Hz1a
        jsr             CalcScreenLoc   ; compute cursor location in memory
        tay
        lda             $10000,y                ; get color code $10000 higher in memory
        ld              r4,IRQFlag              ; get counter
        lsr             r4,r4
        and             r4,r4,#$0F              ; limit to low order nybble
        and             #$F0                    ; prepare to or in new value, mask off foreground color
        or              r1,r1,r4                ; set new foreground color for cursor
        sta             $10000,y                ; store the color code back to memory
p100Hz1a

        ; Check the timeout list to see if there are items ready to be removed from
        ; the list. Also decrement the timeout of the item at the head of the list.

p100Hz15:       
        ldx             TimeoutList
        bmi             p100Hz12                                ; are there any entries in the timeout list ?
        lda             TCB_Timeout,x
        bne             p100Hz14                                ; has this entry timed out ?
        txa
        jsr             RemoveFromTimeoutList
        jsr             AddTaskToReadyList
        bra             p100Hz15                                ; go back and see if there's another task to be removed

p100Hz14:
        dea                                                             ; decrement the entry's timeout
        sta             TCB_Timeout,x
        
p100Hz12:
SelectTaskToRun:
        ; Search the ready queues for a ready task.
        ; The search is occasionally started at a lower priority queue in order
        ; to prevent starvation of lower priority tasks. This is managed by 
        ; using a tick count as an index to a string containing the start que.

        ld              r6,#5                   ; number of queues to search
        ldy             IRQFlag                 ; use the IRQFlag as a buffer index
        lsr             r3,r3,#1                ; the LSB is always the same
        and             r3,r3,#$0F              ; counts from 0 to 15
        lb              r3,strStartQue,y        ; get the queue to start search at
p100Hz2:
        lda             HeadRdy0,y
        bmi             p100Hz1
        ; Move the head of the ready queue to the tail
        jsr             RemoveTaskFromReadyList ; remove task
        jsr             AddTaskToReadyList              ; add it back (automatically goes to tail spot)
        ldx             TCB_Status,r1           ; the task is no longer running
        and             r2,r2,#~TS_RUNNING
        stx             TCB_Status,r1
        lda             HeadRdy0,y
        sta             RunningTCB
        ldx             TCB_Status,r1           ; flag the task as the running task
        or              r2,r2,#TS_RUNNING       ; task is now running and not preempt
        and             r2,r2,#~TS_PREEMPT
        stx             TCB_Status,r1
        bra             p100Hz3
p100Hz1:
        iny
        cpy             #5
        bne             p100Hz5
        ldy             #0
p100Hz5
        dec             r6
        bne             p100Hz2

        ; here there were no tasks ready
        ; this might happen if the interrupt is called before tasks
        ; are setup. Otherwise the IDLE task should at least be running.
        bra             p100Hz6
p100Hz3:
p100Hz10
        ldx             RunningTCB
        lda             TCB_ABS8Save,x          ; 8 bit emulation base register
        trs             r1,abs8
        lda             TCB_SP8Save,x           ; get back eight bit stack pointer
        trs             r1,sp8
        ldx             TCB_SPSave,x            ; get back stack pointer
        txs
        ; restore registers
p100Hz6:
        pop             r15
        pop             r14
        pop             r13
        pop             r12
        pop             r11
        pop             r10
        pop             r9
        pop             r8
        pop             r7
        pop             r6
        pop             r5
        pop             r4
        ply
        plx
        pla
        rti


;------------------------------------------------------------------------------
; 1000 Hz interrupt
; This IRQ must be fast.
; Increments the millisecond counter
;------------------------------------------------------------------------------
;
p1000Hz:
        pha
        lda             #2                                              ; reset edge sense circuit
        sta             PIC_RSTE
        inc             Milliseconds                    ; increment milliseconds count
        inc             TEXTSCR+82                              ; update IRQ live indicator on screen
        pla
        rti

;------------------------------------------------------------------------------
; Sleep interrupt
; This interrupt just selects another task to run. The current task is
; stuck in an infinite loop.
;------------------------------------------------------------------------------
slp_rout:
        cld             ; clear extended precision mode
        pha
        phx
        phy
        push    r4
        push    r5
        push    r6
        push    r7
        push    r8
        push    r9
        push    r10
        push    r11
        push    r12
        push    r13
        push    r14
        push    r15
        ldx             RunningTCB
        bmi             RunningTCBErr
        and             r2,r2,#$FF
        tsa                                             ; save off the stack pointer
        sta             TCB_SPSave,x
        tsr             sp8,r1                  ; and the eight bit mode stack pointer
        sta             TCB_SP8Save,x
        lda             TCB_Status,x    ; set the task status to SLEEP
        and             r1,r1,#~TS_RUNNING
        or              r1,r1,#TS_SLEEP
        sta             TCB_Status,x
slp1:
        jmp             SelectTaskToRun

;------------------------------------------------------------------------------
; Check for and emulate unsupoorted instructions.
;------------------------------------------------------------------------------
InvalidOpIRQ:
        pha
        phx
        phy
        tsx
        lda             4,x             ; get the address of the invalid op off the stack
        lb              r3,0,r1 ; get the opcode byte
        cpy             #$44    ; is it MVP ?
        beq             EmuMVP
        cpy             #$54    ; is it MVN ?
        beq             EmuMVN
        ; We don't know what the op is. Treat it like a NOP
        ; Increment the address and return.
        pha
        lda             #msgUnimp
        jsr             DisplayStringB
        pla
        jsr             DisplayWord
        jsr             CRLF
        ina
        sta             4,x             ; save incremented return address back to stack
        ldx             #64
ioi1:
        tsr             abs8,r1
        jsr             DisplayWord
        lda             #' '
        jsr             DisplayChar
        dex
        bne             ioi1
        jsr             CRLF
        ply
        plx
        pla
        rti

EmuMVP:
        push    r4
        push    r5
        tsr             sp,r4
        lda             4,r4
        ldx             3,r4
        ldy             2,r4
EmuMVP1:
        ld              r5,(x)
        st              r5,(y)
        dex
        dey
        dea
        cmp             #$FFFFFFFF
        bne             EmuMVP1
        sta             4,r4
        stx             3,r4
        sty             2,r4
        inc             6,r4            ; increment the return address by one.
        pop             r5
        pop             r4
        ply
        plx
        pla
        rti

EmuMVN:
        push    r4
        push    r5
        tsr             sp,r4
        lda             4,r4
        ldx             3,r4
        ldy             2,r4
EmuMVN1:
        ld              r5,(x)
        st              r5,(y)
        inx
        iny
        dea
        cmp             #$FFFFFFFF
        bne             EmuMVN1
        sta             4,r4
        stx             3,r4
        sty             2,r4
        inc             6,r4            ; increment the return address by one.
        pop             r5
        pop             r4
        ply
        plx
        pla
        rti

msgUnimp:
        db      "Unimplemented at: ",0

brk_rout:
        rti
nmirout:
        pha
        phx
        lda             #msgPerr
        jsr             DisplayStringB
        tsx
        lda             4,x
        jsr             DisplayWord
        jsr             CRLF
        plx
        pla
        rti

msgPerr:
        db      "Parity error at: ",0

message "1298"
include "TinyBasic65002.asm"
message "1640"
        org $0FFFFFFF4          ; NMI vector
        dw      nmirout

        org     $0FFFFFFF8              ; reset vector, native mode
        dw      start
        
        end
        

Go to most recent revision | Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.