; PDP11 Tube Client Code ; ====================== ; Copyright (C)1989,2008 J.G.Harston ; ; v0.10 1989 JGH: Initial version, untested proof of concept. ; v0.11 2005 JGH: Altered some labels to assemble from BASIC ; v0.12 2008 JGH: Checks executed code for ROM and Unix headers ; ; This code may be freely reused. ; ; This code assumes a single memory space and assumes that code can access ; I/O devices, low memory vectors and user memory with ordinary MOV ; instructions. ; PDP-11 hardware addresses ; ========================= EMTVEC: EQU &04 ; Vector called by EMT call ; Tube client system configuration tweekables ; =========================================== START: EQU &F800 ; Start of code TUBEIO: EQU &FFF0 ; Base of Tube I/O registers IRQVEC: EQU &80 ; Vector called by Tube IRQs NMIVEC: EQU &84 ; Vector called by Tube NMIs WORKSP: EQU START-512 ; 256 bytes for buffers, etc EMTTABLE: EQU START-256 ; EMT dispatch table MEMBOT: EQU &100 ; Lowest available memory address MEMTOP: EQU WORKSP ; Highest available memory address ; Internal buffers, etc ; --------------------- ERRBLK: EQU WORKSP ; Buffer to store host error block CLIBUF: EQU WORKSP ; Space to enter command line from CLI prompt HANDLERS: EQU WORKSP+&E0 ; Address of environment handlers EXITV: EQU HANDLERS+&00 ; Address of exit handler EXITADDR: EQU HANDLERS+&02 ; Unused ESCV: EQU HANDLERS+&04 ; Address of escape handler ESCADDR: EQU HANDLERS+&06 ; Address of escape flag ERRV: EQU HANDLERS+&08 ; Address of error handler ERRADDR: EQU HANDLERS+&0A ; Address of error buffer EVENTV: EQU HANDLERS+&0C ; Address of event handler EVENTADDR: EQU HANDLERS+&0E ; Unused USERIRQV: EQU HANDLERS+&10 ; Address of unknown IRQ handler USERIRQADDR: EQU HANDLERS+&12 ; Unused EMTV: EQU HANDLERS+&14 ; Old SP within EMT handler EMTADDR: EQU HANDLERS+&16 ; Address of EMT dispatch table ADDR: EQU WORKSP+&F8 ; Transfer address PROG: EQU WORKSP+&FC ; Current program PROG2: EQU WORKSP+&FE ; Current program extended address ESCFLG: EQU WORKSP+&FF ; Escape flag ; Tube I/O devices ; ================ TUBE1: EQU TUBEIO+2 ; Tube Register 1 TUBE2: EQU TUBEIO+6 ; Tube Register 2 TUBE3: EQU TUBEIO+10 ; Tube Register 3 TUBE4: EQU TUBEIO+14 ; Tube Register 4 TUBE1S: EQU TUBEIO+0 ; Tube Status 1 TUBE2S: EQU TUBEIO+4 ; Tube Status 2 TUBE3S: EQU TUBEIO+8 ; Tube Status 3 TUBE4S: EQU TUBEIO+12 ; Tube Status 4 ; Code Entry ; ========== ; Set up vectors, hardware, memory, etc. ; Must be entered in Kernel mode. ORG START JMP STARTUP ; Jump to start up Tube code ; OSNEWL - Send LF/CR sequence ; ============================ NEWL: MOV #10,R0 JSR PC,WRCH ; Output LF MOV #13,R0 ; Fall through into WRCH ; OSWRCH - Send character in R0 to Tube Register 1 ; ================================================ WRCH: MOV R0,-(SP) ; Save R0 SEND_R1LP: MOVB @#TUBE1S,R0 ; Read Tube R1 status BIC #&BF,R0 ; Check b6 of status BEQ SEND_R1LP ; Loop until b6 set MOV (SP)+,R0 ; Get R0 back MOVB R0,@#TUBE1 ; Send byte to Tube R1 RTS PC ; Send text string at R1 to Tube Register 1 ; ========================================= ; Also sends terminating zero byte so can be used in startup sequence ; SEND_TXT: MOVB (R1)+,R0 ; Get byte from R1, increment R1 JSR PC,WRCH ; Send to WRCH via Tube R1 TST R0 ; Test current character BNE SEND_TXT ; Loop until &00 sent RTS PC ; Send byte in R0 to Tube Register 2 ; ================================== SEND_R2: MOV R0,-(SP) ; Save R0 SEND_R2LP: MOVB @#TUBE2S,R0 ; Read Tube R2 status BIC #&BF,R0 ; Check b6 of status BEQ SEND_R2LP ; Loop until b6 set MOV (SP)+,R0 ; Get R0 back MOVB R0,@#TUBE2 ; Send byte to Tube R2 RTS PC ; Send -string at R1 to Tube Register 2 ; ========================================= SEND_STR: MOVB (R1)+,R0 ; Get byte from R1, increment R1 JSR PC,SEND_R2 ; Send byte via Tube R2 CMP R0,#13 ; Test current character BNE SEND_STR ; Loop until sent RTS PC ; Send block at R1 to Tube Register 2 ; =================================== SEND_BLK: ADD R2,R1 ; Add length of control block to R1 SEND_LP: MOVB -(R1),R0 ; Decrement R1, Get byte from R1 JSR PC,SEND_R2 ; Send byte via Tube R2 DEC R2 ; Decrement count BNE SEND_LP ; Loop until all sent RTS PC ; Wait for block at R1 from Tube Register 2 ; ========================================= Get_BLK: ADD R2,R1 ; Add length of control block to R1 Get_LP: JSR PC,Get_R2 ; Wait for byte via Tube R2 MOVB R0,-(R1) ; Decrement R1, store byte to R1 DEC R2 ; Decrement count BNE Get_LP ; Loop until all received RTS PC ; OSARGS - Read info on open file or filing system ; ================================================ ; Tube Data: &0C handle block function -- result block ; ; On entry: R0=function ; R1=handle ; R2=>control block ; On exit: R0=returned value ; R1 preserved ; R2 preserved ; ARGS: MOV R2,-(SP) ; Save control block pointer MOV R1,-(SP) ; Save handle MOV R0,-(SP) ; Save function MOV #&0C,R0 JSR PC,SEND_R2 ; Send command &0C - OSARGS MOV R1,R0 JSR PC,SEND_R2 ; Send handle MOV R2,R1 MOV #4,R2 JSR PC,SEND_BLK ; Send four-byte control block MOV (SP)+,R0 JSR PC,SEND_R2 ; Send function JSR PC,Get_R2 ; Wait for returned result MOV R0,-(SP) ; Save result MOV #4,R2 JSR PC,Get_BLK ; Wait for four-byte control block MOV (SP)+,R0 ; Get result back MOV (SP)+,R1 ; Get handle back MOV (SP)+,R2 ; Get control block pointer back RTS PC ; OSBGet - Get a byte from open file ; ================================== ; Tube data: &0E handle -- Carry byte ; ; On entry: R1=handle ; On exit: R0=byte Read ; R1=preserved ; Cy set if EOF ; BGet: TST R1 ; Check handle BEQ RDCH ; BGET#0 calls OSRDCH MOV #&0E,R0 JSR PC,SEND_R2 ; Send command &0E - OSBGet MOV R1,R0 JSR PC,SEND_R2 ; Send handle BR Get_CHAR ; Wait for Carry, Byte ; OSRDCH - Wait for character from input stream ; ============================================= ; Tube data: &00 -- Carry Char ; ; On exit: R0=char, Cy=carry ; RDCH: MOV #0,R0 JSR PC,SEND_R2 ; Send command &00 - OSRDCH Get_CHAR: JSR PC,Get_R2 ; Get returned byte ADD #&FF80,R0 ; Copy b7 into carry ; Continue to fetch byte from Tube R2 Get_R2: MOVB @#TUBE2S,R0 ; Read Tube R2 status BPL Get_R2 ; Loop until b7 set MOVB @#TUBE2,R0 ; Get byte from Tube R2 RTS PC ; OSBPut - Put a byte to an open file ; =================================== ; Tube data: &10 handle byte -- &7F ; ; On entry: R0=byte to write ; R1=handle ; On exit: R0=preserved ; R1=preserved ; BPut: TST R1 ; Check handle BEQ WRCH ; BPUT#0 calls OSWRCH MOV R0,-(SP) ; Save R0 MOV #&10,R0 JSR PC,SEND_R2 ; Send command &10 - OSBPut MOV R1,R0 JSR PC,SEND_R2 ; Send handle MOV (SP),R0 ; Get R0 back JSR PC,SEND_R2 ; Send byte to Tube JSR PC,Get_R2 ; Wait for acknowledgement MOV (SP)+,R0 ; Restore R0 RTS PC ; OSFIND - Open or Close a file ; ============================= ; Tube data: &12 function string &0D -- handle ; &12 &00 handle -- &7F ; ; On entry: R0=function ; R1=handle or =>filename ; On exit: R0=zero or handle ; FIND: MOV R0,-(SP) ; Save R0 MOV #&12,R0 JSR PC,SEND_R2 ; Send command &12 - OSFIND MOV (SP)+,R0 ; Get R0 back JSR PC,SEND_R2 ; Send function TST R0 ; Check function BEQ Close ; Jump to deal with Close JSR PC,SEND_STR ; Send string at R1 JSR PC,Get_R2 ; Wait for returned handle RTS PC Close: MOV R1,R0 JSR PC,SEND_R2 ; Send handle to Tube JSR PC,Get_R2 ; Wait for acknowledgement MOV #0,R0 ; Zero R0 RTS PC ; OSFILE - Operate on whole files ; =============================== ; Tube data: &14 block string function -- result block ; ; On entry: R0=function ; R1=>control block ; On exit: R0=result ; R1 preserved ; control block updated ; FILE: MOV R2,-(SP) ; Save R2 MOV R1,-(SP) ; Save R1 MOV R0,-(SP) ; Save function MOV #&14,R0 JSR PC,SEND_R2 ; Send command &14 - OSFILE ADD #2,R1 ; Point to control block contents MOV #16,R2 JSR PC,SEND_BLK ; Send 16-byte control block SUB #2,R1 ; Point to filename pointer MOV (R1),R1 ; Get filename pointer to R1 JSR PC,SEND_STR ; Send filename string MOV (SP)+,R0 JSR PC,SEND_R2 ; Send function JSR PC,Get_R2 ; Wait for returned result MOV (SP),R1 ; Get control block pointer back MOV R0,-(SP) ; Save result ADD #2,R1 ; Point to control block contents MOV #16,R2 JSR PC,Get_BLK ; Wait for 16-byte control block MOV (SP)+,R0 ; Get result back MOV (SP)+,R1 ; Get control block pointer back MOV (SP)+,R2 ; Get R2 back RTS PC ; OSGBPB - Multiple byte read and write ; ===================================== ; Tube data: &16 block function -- block Carry result ; ; On entry: R0=function ; R1=>control block ; On exit: R0=returned value ; control block updated ; GBPB: TST (R1) ; Check handle BNE GBPB1 ; Non-zero handle TST R0 ; Check function BEQ GBPB1 ; Pass OSGBPB 0 to Tube CMP R0,#5 BCS GBPB_RDWR ; Channel 0 via OSRDCH/OSWRCH GBPB1: MOV R2,-(SP) ; Save R2 MOV R0,-(SP) ; Save function MOV #&16,R0 JSR PC,SEND_R2 ; Send command &16 - OSGBPB MOV #13,R2 JSR PC,SEND_BLK ; Send 13-byte control block MOV (SP)+,R0 JSR PC,SEND_R2 ; Send function MOV #13,R2 JSR PC,Get_BLK ; Wait for 13-byte control block MOV (SP)+,R2 ; Get R2 back BR Get_CHAR ; Get Carry and result byte ; ; Read or write block of memory to/from OSWRCH/OSRDCH ; --------------------------------------------------- ; NB, only uses 16-bit address and count, b16-b31 ignored and not updated ; GBPB_RDWR: MOV R2,-(SP) ; Save R2 MOV R0,-(SP) ; Save function MOVB 1(R1),R0 ; Get address low byte, may not be word aligned BIC #&FF00,R0 ; Ensure 8-bit value MOVB 2(R1),R2 ; Get address high byte BIC #&FF00,R2 ; Ensure 8-bit value SWAB R2 ; Move to high byte BIS R0,R2 ; R2=Address GBPB_LP: CMP (SP),#3 BCS GBPB_RD ; Function 3/4, read characters ; Function 1/2, write characters MOVB (R2)+,R0 ; Get character from memory JSR PC,WRCH ; Write it BR GBPB_NEXT ; Jump to update and loop GBPB_RD: JSR PC,RDCH ; Read character BCS GBPB_EXIT ; Carry set, exit MOVB R0,(R2)+ ; Store character GBPB_NEXT: TSTB 5(R1) ; Test byte low byte BNE GBPB_LO ; Count<>&xx00 DECB 6(R1) ; Decrement count high byte GBPB_LO: DECB 5(R1) ; Decrement count low byte BNE GBPB_LP ; Loop until all done TSTB 6(R1) ; Test count high byte BNE GBPB_LP ; Loop until all done CLC ; Clear carry for OK GBPB_EXIT: MOVB R2,1(R1) SWAB R2 MOVB R2,2(R1) ; Update address MOV (SP)+,R0 ; Pop function MOV (SP)+,R2 ; Restore R2 CLR R0 ; R0=0, function supported RTS PC ; STARTUP ; ======= ; Tube data: via R1: string &00 -- via R2: &7F or &80 ; BANNER: EQUB 10 EQUS "Acorn TUBE PDP-11 64K 0.12" EQUB 10 EQUB 10 EQUB 13 EQUB 0 ALIGN STARTUP: MOV #MEMTOP,SP ; Put stack at top of memory JSR PC,INIT_ENV ; Set up default handlers MOV #START,@#PROG ; Set current program MOV #BANNER,R1 ; Point to startup banner JSR PC,SEND_TXT ; Print it via Tube WRCH protocol JSR PC,CLI_WAIT ; Wait for and check result byte ; Fall through to CLICOM if nothing executed ; Command line prompt ; =================== ; Allow user to enter *command ; CLICOM: EXITHAND: MOV #MEMTOP,SP ; Put stack at top of memory JSR PC,INIT_ENV ; Initialise default handlers MOV #PROMPT,R1 JSR PC,SEND_TXT ; Display prompt MOV #COM_BLK,R1 ; Point to control block MOV #0,R0 JSR PC,WORD ; Read a line of text BCS COM_ESC ; Escape pressed MOV #CLIBUF,R1 JSR PC,CLI ; Execute command BR CLICOM ; Loop back for another line PROMPT: EQUS "11*" ; Command prompt EQUB 0 ALIGN ; COM_BLK: EQUW CLIBUF ; Input buffer EQUB &E0 ; Buffer length EQUB 32 ; Lowest acceptable char EQUB 255 ; Highest acceptable char ALIGN ; COM_ESC: MOV #124,R0 JSR PC,BYTE ; Acknowledge Escape JSR PC,MKERR EQUB 17 EQUS "Escape" EQUB 0 ALIGN ; ; Default error handler ; --------------------- ; On entry, R0=>error block ERRHAND: MOV #MEMTOP,SP ; Reset stack INC R0 ; Step past error number JSR PC,SEND_TXT ; Print error message BR CLICOM ; Default escape handler ; ---------------------- ESCHAND: ADD R0,R0 ; Move b6 into b7 MOVB R0,@#ESCADDR ; Store Escape flag RTS PC ; OSCLI - Send command line to host ; ================================= ; Tube data: &02 string &0D -- &7F or &80 ; ; On entry: R0=>command string ; CLI: MOV R0,-(SP) ; Save pointer to command MOV #2,R0 JSR PC,SEND_R2 ; Send command &02 - OSCLI MOV (SP)+,R1 ; Get pointer to command back JSR PC,SEND_STR ; Send string at R1 CLI_WAIT: JSR PC,Get_R2 ; Wait for result via Tube R2 CMP R0,#&80 ; Check return code BEQ EXECUTE_R1 ; Code to be executed CLR R0 RTS PC EXECUTE_R1: MOV @#ADDR,R1 ; Get transfer address ; EXECUTE - Enter code at R1 ; ========================== ; Checks for ROM header and UNIX header ; ; On entry: R1=>code to enter ; Should preserve other registers ; EXECUTE: MOV R1,-(SP) ; Save entry address MOVB 7(R1),R0 ; Get copyright offset BIC #&FF00,R0 ; Ensure 8-bit value ADD R0,R1 ; R1=>copyright string TSTB (R1)+ ; Check for copyright string BNE EXEC_NOTROM CMPB (R1)+,#ASC"(" BNE EXEC_NOTROM CMPB (R1)+,#ASC"C" BNE EXEC_NOTROM CMPB (R1)+,#ASC")" BNE EXEC_NOTROM MOV (SP),R1 ; Get entry address back MOVB 6(R1),R0 ; Get ROM type TST #&40,R0 BEQ EXEC_NOTLANG BIC #&F0,R0 CMP R0,#&07 BNE EXEC_NOTPDP MOVB 6(R1),R0 ; Get ROM type again TST #&20,R0 ; Does Tube transfer address exist? BEQ EXEC_ROM ; No, use address in R1 MOVB 7(R1),R0 ; Get copyright offset BIC #&FF00,R0 ; Ensure 8-bit value ADD R0,R1 ; Point to copyright message INC R1 ; Step past first zero byte EXEC_SKIP: TST (R1)+ ; Find terminating zero byte BNE EXEC_SKIP ADD R1,#4 ; Step past transfer address MOVB (R1)+,R0 ; Get execution offset MOVB (R1),R1 SWAB R1 BIC #&FF00,R0 BIC #&00FF,R1 ADD R1,R0 ; R0=offset from start address MOV (SP)+,R1 ; Get start address back ADD R0,R1 ; R1=entry address MOV R1,-(SP) ; Push it for later EXEC_ROM: MOV R1,@#PROG ; Set as current program ; See if a Unix header also exists ; EXEC_NOTROM: MOV (SP)+,R1 ; Get entry address back MOV (R1),R0 ; Get magic number CMP R0,#&105 ; &o0405 - overlay BCC EXEC_RAW ; &o0407 - normal CMP R0,#&109 ; &o0410 - read-only text BCS EXEC_RAW ; &o0411 - seperated I&D MOV 2(R1),R2 ; Size of text MOV 4(R1),R3 ; Size of initialised data ADD R3,R2 ; Size of program MOV 6(R1),R3 ; Size of uninitialised data MOV #0,R0 ; Destination address ADD #16,R1 ; Source address ; R0=dest ; R1=source ; R2=size ; R3=size to be zeroed EXEC_COPY: MOV (R1)+,(R0)+ ; Copy program to address &0000 SUB R2,#2 ; Must be word aligned BNE EXEC_COPY TST R3 ; Check for zero-length zeroed data BEQ EXEC_ENTER EXEC_ZERO: CLR (R0)+ ; Zero uninitialised data SUB R3,#2 BNE EXEC_ZERO EXEC_ENTER: MOV #0,R1 MOV R1,@#PROG ; Set as current program ; Need to set up a stack frame .EXEC_RAW MOV R1,PC ; Enter code EXEC_NOTLANG: JSR PC,MKERR EQUB 0 EQUS "This is not a language" EQUB 0 ALIGN ; EXEC_NOTPDP: JSR PC,MKERR EQUB 0 EQUS "Not PDP-11 code" EQUB 0 ALIGN ; OSBYTE ; ====== ; Tube data: &04 X A -- X ; &06 X Y A -- Cy Y X ; ; On entry: R0,R1,R2=OSBYTE parameters ; On exit: R0 preserved ; If R0<&80, R1=returned value ; If R0>&7F, R1, R2, Carry=returned values ; BYTE: CMP R0,#&80 BCC BYTE_HI MOV R0,-(SP) ; Save R0 MOV #4,R0 JSR PC,SEND_R2 ; Send command &04 - Short BYTE MOV R1,R0 JSR PC,SEND_R2 ; Send second parameter MOV (SP),R0 ; Get first parameter from top of stack JSR PC,SEND_R2 ; Send first parameter JSR PC,Get_R2 ; Wait for response MOV R0,R1 ; Pass to R1 MOV (SP)+,R0 ; Restore R0 RTS PC ; OSBYTE >&7F ; ----------- BYTE_HI: CMP R0,#&82 BEQ MEM82 ; Fetch address high word CMP R0,#&83 BEQ MEM83 ; Fetch low memory limit CMP R0,#&84 BEQ MEM84 ; Fetch high memory limit MOV R0,-(SP) ; Save R0 MOV #6,R0 JSR PC,SEND_R2 ; Send command &06 - Long BYTE MOV R1,R0 JSR PC,SEND_R2 ; Send second parameter MOV R2,R0 JSR PC,SEND_R2 ; Send third parameter MOV (SP)+,R0 ; Get first parameter from stack JSR PC,SEND_R2 ; Send first parameter CMP R0,#&9D ; Was it Fast BPut? BEQ BYTE_DONE ; Don't wait for response CMP R0,#&8E ; Was it language startup? BEQ BYTE_WAIT ; Wait for program startup MOV R0,-(SP) ; Save R0 again JSR PC,Get_R2 ; Wait for response ADD R0,#&FF80 ; Copy b7 into Carry JSR PC,Get_R2 ; Wait for response MOV R0,R2 ; Pass to R2 JSR PC,Get_R2 ; Wait for response MOV R0,R1 ; Pass to R1 MOV (SP)+,R0 ; Restore R0 BYTE_DONE: RTS PC BYTE_WAIT: JMP CLI_WAIT MEM82: ; Memory address high word MOV #0,R1 MOV #0,R2 RTS PC MEM83: ; Lowest available memory address MOV #MEMBOT,R1 MOV #MEMBOT / 256,R2 RTS PC MEM84: ; Highest available memory address MOV #MEMTOP,R1 MOV #MEMTOP / 256,R2 RTS PC ; OSWORD ; ====== ; On entry: R0=OSWORD number ; R1=>control block ; WORD: TST R0 BEQ RDLINE ; OSWORD 0, jump to read line ; ; OSWORD <>&00 ; ------------ ; Tube data: &08 in_length block out_length -- block ; MOV R3,-(SP) ; Save R3 MOV R2,-(SP) ; Save R2 MOV R0,-(SP) ; Save R0 MOV #8,R0 JSR PC,SEND_R2 ; Send command &08 - OSWORD MOV (SP),R0 ; Get R0 back CMP R0,#&80 ; Check OSWORD number BCS WORDLO ; <&80, calculate control block sizes MOVB (R0)+,R2 ; Get transmit size from control block MOVB (R0),R3 ; Get receive size from control block DEC R0 ; Point back to start of control block BR WORDGO ; Jump to send OSWORD command WORDLO: CMP R0,#&15 ; OSWORD &01-&7F use fixed control block sizes BCS WORD_INDEX ; OSWORD &01-&14, get length from table MOV #&10,R2 ; OSWORD &15-&7F uses 16 bytes both ways MOV #&10,R3 BR WORDGO ; Jump to send OSWORD command WORD_INDEX: ADD R0,R0 ; Double R0 to index into table ADD WORD_TABLE-2,R0 ; Point to table entry MOVB (R0)+,R2 ; Fetch send length MOVB (R0)+,R3 ; Fetch receive length WORDGO: MOV R2,R0 ; Get transmit block length JSR PC,SEND_R2 ; Send transmit block length ADD R2,R1 ; Point to past end of control block DEC R2 ; Convert 0 to -1 CMP R2,#&80 ; Check length of transmit block BCC WORD_NOTX ; Transmit block=0 or >&80, send nothing WORD_TX: MOVB -(R1),R0 ; Get byte from control block JSR PC,SEND_R2 ; Send byte to Tube R2 DEC R2 BPL WORD_TX ; Loop to send control block WORD_NOTX: MOV R3,R0 ; Get recive block length JSR PC,SEND_R2 ; Send receive block length ADD R3,R1 ; Point past end of control block DEC R3 ; Convert 0 to -1 CMP R3,#&80 ; Check length of received block BCC WORD_NORX ; Receive block=0 or >&80, receive nothing WORD_RX: JSR PC,Get_R2 ; Get byte from Tube R2 MOVB R0,-(R1) ; Store byte in control block DEC R3 BPL WORD_RX ; Loop to receive control block WORD_NORX: ; MOV (SP)+,R0 ; Restore registers MOV (SP)+,R2 MOV (SP)+,R3 RTS PC ; ; Table of OSWORD control block lengths for &01-&14 ; ------------------------------------------------- ; low byte=send length, high byte=recive length WORD_TABLE: EQUW &0500 ; &01 =TIME EQUW &0005 ; &02 TIME= EQUW &0500 ; &03 =TIMER EQUW &0005 ; &04 TIMER= EQUW &0504 ; &05 =MEM EQUW &0005 ; &06 MEM= EQUW &0008 ; &07 SOUND EQUW &000E ; &08 ENVELOPE EQUW &0504 ; &09 =POINT EQUW &0901 ; &0A Read bitmap EQUW &0501 ; &0B Read palette EQUW &0005 ; &0C Write palette EQUW &0800 ; &0D Read graphics coords EQUW &1910 ; &0E =TIME$ EQUW &0019 ; &0F TIME$= EQUW &0110 ; &10 Net_Tx EQUW &0D0D ; &11 Net_Rx EQUW &8000 ; &12 Net_Params EQUW &0808 ; &13 Net_Info EQUW &8080 ; &14 NetFS_Op ; Read a line of text ; ------------------- ; Tube data: &0A block -- &FF or &7F string &0D ; RDLINE: MOV #10,R0 JSR PC,SEND_R2 ; Send command &0A - RDLINE ADD #2,R1 MOV R2,-(SP) ; Save R2 MOV #3,R2 JSR PC,SEND_BLK ; Send 3-byte control block MOV (SP)+,R2 ; Restore R2 MOV #7,R0 JSR PC,SEND_R2 ; Send &0700 MOV #0,R0 JSR PC,SEND_R2 JSR PC,Get_R2 ; Wait for response ADD R0,#&FF80 ; Copy b7 into Carry BCS RD_DONE SUB #2,R1 ; Point to start of control block MOV R1,(R1) ; Get address to store text Read_STR: MOV #0,R2 ; Set count of received bytes JSR PC,Get_R2 ; Wait for byte from Tube R2 MOVB R0,(R1)+ ; Store it INC R2 ; Increment number of bytes CMP R0,#13 ; Check current byte BNE Read_STR ; Loop until DEC R2 ; R2 is length of string CLC ; Clear Carry RD_DONE: RTS PC ; Host->Client communication via interupts ; ======================================== ; Get_R1: MOVB @#TUBE1S,R0 ; Read Tube R1 status BPL Get_R1 ; Loop until b7 set MOVB @#TUBE1,R0 ; Get byte from Tube R1 RTS PC Get_R4: MOVB @#TUBE4S,R0 ; Read Tube R4 status BPL Get_R4 ; Loop until b7 set MOVB @#TUBE4,R0 ; Get byte from Tube R4 RTS PC ; Interupt handler ; ================ ; When Host sends a byte to R1 or R4 it generates a Client IRQ ; Within the interupt handler PSW has been saved on the stack ; and further interupts are disabled ; IRQ: MOV R0,-(SP) ; Save R0 MOVB @#TUBE4S,R0 ; Read Tube R4 status BMI IRQ_R4 ; If b7 set, R4 generated the interupt MOVB @#TUBE1S,R0 ; Read Tube R1 status BMI IRQ_R1 ; If b7 set, R1 generated the interupt MOV (SP)+,R0 ; Get R0 back JMP @#USERIRQV ; Something else generated the interupt ; Data present in Tube R1 generated an interupt ; IRQ_R1: MOVB @#TUBE1,R0 ; Get byte from Tube R1 BMI IRQ_ESCAPE ; b7 set, change Escape state ; ; R1<&80 - Host event being passed to client ; Tube data: via R1: &00 Y X A ; MOV R1,-(SP) ; Save R1 MOV R2,-(SP) ; Save R2 JSR PC,Get_R1 ; Wait for byte via Tube R1 MOV R0,R2 ; Pass to R2 JSR PC,Get_R1 ; Wait for byte via Tube R1 MOV R0,R1 ; Pass to R1 JSR PC,Get_R1 ; Wait for byte via Tube R1 JSR PC,@#EVENTV ; Call event vector MOV (SP)+,R2 ; Restore registers MOV (SP)+,R1 MOV (SP)+,R0 USERIRQ: ; Default unknown IRQ handler RTI ; Return from interupt ; R1>&7F - Host changing Escape state ; Tube data: via R1: flag, b7=1, b6=state ; IRQ_ESCAPE: JSR PC,@#ESCV ; Call Escape handler MOV (SP)+,R0 ; Restore R0 RTI ; Return from interupt ; Data present in Tube R4 generated an interupt ; IRQ_R4: MOVB @#TUBE4,R0 ; Get byte from Tube R4 BPL IRQ_DATA ; b7=0, jump to do data transfer ; R4>&7F - Error occured ; Tube data: via R4: &FF, via R2: &00 err string &00 ; MOV R1,-(SP) ; Remove this when errjmp done JSR PC,Get_R2 ; Wait for an initial byte from R2 MOV @#ERRADDR,R1 ; Point to error buffer JSR PC,Get_R2 MOVB R0,(R1)+ ; Store error number IRQ_R4LP: JSR PC,Get_R2 ; Wait for byte of error string MOV R0,(R1)+ ; Store in error buffer BNE IRQ_R4LP ; Loop until terminating &00 received MOV (SP)+,R1 ; Restore R1 MOV (SP)+,R0 ; Balance stack MOV @#ERRADDR,R0 ; Point to error block MOV @#ERRV,(SP) ; Replace return address with error handler RTI ; Jump to error handler and restore PSW ; Generate an error without using EMT call ; ---------------------------------------- MKERR: MOV (SP)+,R0 ; Point to inline error block JMP @#ERRV ; Jump to error handler ; R4<&80 - Data transfer ; Tube data: via R4: action ID block sync, via R3: data ; IRQ_DATA: ; R0=transfer type ; MOV R0,-(SP) ; Save transfer type JSR PC,Get_R4 ; Wait for caller ID MOV (SP),R0 ; Get transfer type back CMP R0,#5 ; Is it 'release'? BEQ IRQ_DONE JSR PC,Get_R4 ; Get data address byte 4 MOV R0,@ADDR+3 JSR PC,Get_R4 ; Get data address byte 3 MOV R0,@ADDR+2 JSR PC,Get_R4 ; Get data address byte 2 MOV R0,@ADDR+1 JSR PC,Get_R4 ; Get data address byte 1 MOV R0,@ADDR+0 MOVB @#TUBE3,R0 ; Clear Tube3 FIFO MOVB @#TUBE3,R0 JSR PC,Get_R4 ; Get sync byte MOV (SP)+,R0 ; Get transfer type back again ADD R0,R0 ; Index into NMI dispatch table CMP R0,#12 ; Check transfer type MOV NMIADDRS(R0),@#NMIVEC ; Set up NMI vector BCC IRQ_DONE ; Jump if not 256-byte transfers BNE NMI7 ; Jump with 256-byte read ; Transfer 6 - Send 256 bytes to Host via R3 ; ------------------------------------------ MOV R1,-(SP) MOV #256,R1 ; Transfer 256 bytes MOV @#ADDR,R0 ; Get transfer address NMI6_LOOP: TSTB @TUBE3S BPL NMI6_LOOP ; Wait for Tube R3 ready MOVB (R0)+,@#TUBE3 ; Fetch byte and send to Tube R3 DEC R1 ; Decrement count BNE NMI6_LOOP ; Loop for 256 bytes NMI6_DONE: TSTB @#TUBE3S BPL NMI6_DONE ; Wait for Tube R3 ready again CLR @#TUBE3 ; Send final sync byte MOV (SP)+,R1 ; Restore R1 IRQ_DONE: MOV (SP)+,R0 ; Restore R0 and return RTI ; Transfer 7 - Read 256 bytes from Host via R3 ; -------------------------------------------- NMI7: MOV R1,-(SP) MOV #256,R1 ; Transfer 256 bytes MOV @#ADDR,R0 ; Get transfer address NMI7_LOOP: TSTB @#TUBE3S BPL NMI7_LOOP ; Wait for Tube R3 ready MOVB @#TUBE3,(R0)+ ; Fetch byte from Tube R3 and store DEC R1 ; Decrement count BNE NMI7_LOOP ; Loop for 256 bytes MOV (SP)+,R1 ; Restore and return MOV (SP)+,R0 RTI ; Transfer 0 - Send single byte to Host ; ------------------------------------- NMI0: MOV R0,-(SP) MOV @#ADDR,R0 ; Get current address MOVB (R0)+,@#TUBE3 ; Transfer byte to Tube MOV R0,@#ADDR ; Save updated address MOV (SP)+,R0 RTI ; Transfer 1 - Read single byte from host ; --------------------------------------- NMI1: MOV R0,-(SP) MOV @#ADDR,R0 MOVB @#TUBE3,(R0)+ ; Transfer byte from Tube MOV R0,@#ADDR MOV (SP)+,R0 RTI ; Transfer 2 - Send double bytes to host ; -------------------------------------- NMI2: MOV R0,-(SP) MOV @#ADDR,R0 MOVB (R0)+,@#TUBE3 ; Send two bytes MOVB (R0)+,@#TUBE3 MOV R0,@#ADDR MOV (SP)+,R0 RTI ; Transfer 3 - Read double bytes from host ; ---------------------------------------- NMI3: MOV R0,-(SP) MOV @#ADDR,R0 MOVB @#TUBE3,(R0)+ ; Read two bytes MOVB @#TUBE3,(R0)+ MOV R0,@#ADDR MOV (SP)+,R0 RTI ; Transfers 4,5,6,7 - Just acknowledge NMI ; ---------------------------------------- NMI_ACK: CLRB @#TUBE3 ; Store to Tube R3 to acknowledge NMI RTI ; NMI transfer dispatch table ; --------------------------- NMIADDRS: EQUW NMI0 ; Single byte to host EQUW NMI1 ; Single byte from host EQUW NMI2 ; Double byte to host EQUW NMI3 ; Double byte from host EQUW NMI_ACK ; Execute EQUW NMI_ACK ; Release EQUW NMI_ACK ; 256 bytes to host EQUW NMI_ACK ; 256 bytes from host ; Miscellaneous EMT routines ; ========================== ; EMT 0 - Exit current program ; ---------------------------- EMT0: JMP @#EXITV ; Jump via exit handler ; EMT 15 - Generate an error ; -------------------------- EMT15: TST (SP)+ ; Drop return to EMT handler MOV (SP)+,@#EMTV ; Restore old EMT SP MOV (SP)+,@#ERRV ; Restore mainline error handler MOV (SP),R0 ; Get address of inline error block MOV @#ERRV,(SP) ; Replace with address of error handler RTI ; Jump to handler, restoring PSW ; EMT 14 - Read/Write handlers, etc. ; ---------------------------------- ; On entry: R0=0..255 to claim EMTs 0..255 ; R1=new routine address or 0 to read ; R0=&FFxx to set environment handlers ; R1=new handler address or 0 to read ; R2=new handler data address or 0 to read ; On exit: R0=preserved ; R1=old address ; R2=old handler address or preserved ; EMT14: TST (SP)+ ; Drop return to EMT handler MOV (SP)+,@#EMTV ; Restore old EMT SP MOV (SP)+,@#ERRV ; Restore mainline error handler MOV R0,-(SP) ; Save R0 TST R0 BMI EMT14_HANDLER ; Negative, set up handler CMP R0,#256 BCC EMT14_QUIT ; Out of range ADD R0,R0 ; Double R0 to offset into table ADD @#EMTADDR,R0 ; Index into EMT dispatch table MOV (R0),-(SP) ; Get old address TST R1 BEQ EMT14_READ ; Zero, just read MOV R1,(R0) ; Store new address if non-zero EMT14_READ: MOV (SP)+,R1 ; Get old address EMT14_QUIT: MOV (SP)+,R0 ; Restore R0 RTI ; EMT14_HANDLER: COM R0 CMP R0,#6 BCC EMT14_QUIT ; Out of range ADD R0,R0 ADD R0,R0 ; Times four to offset into table ADD #HANDLERS,R0 ; Index into handlers MOV (R0),-(SP) ; Save old handler address TST R1 BEQ EMT14_HAND2 ; Just read old handler MOV R1,(R0)+ ; Store new handler address EMT14_HAND2: MOV (R0),-(SP) ; Save old data address TST R2 BEQ EMT14_HAND3 ; Just read old data address MOV R2,(R0) ; Store new data address EMT14_HAND3: MOV (SP)+,R2 ; Get old data MOV (SP)+,R1 ; Get old address MOV (SP)+,R0 ; Restore R0 RTI ; EMT 13 - Misc control functions ; ------------------------------- ; On entry: R0=0 - Load BBC BASIC ; 1 - Set up default environment ; 2 - Set up default handlers only EMT13: CMP R0,#1 BEQ INIT_ENV ; Set up hardware and software handlers CMP R0,#2 BEQ INIT_HANDLES ; Set up software handlers ; Fall through with others EMTXX: ; EMTs 16-255 EVENT: ; Null event handler RTS PC ; EMT handler ; =========== ; On extry, R0-R5 contain any parameters ; PSW ignored ; On exit, R0-R5 contain any returned values ; C returns any returned value ; V set if error, R0=>error block ; EMT_HANDLER: BIC #&FFF0,2(SP) ; Clear stacked flags MOV @#ERRV,-(SP) ; Save old ERR handler MOV @#EMTV,-(SP) ; Save old EMT SP MOV SP,@#EMTV ; Save current EMT SP MOV #EMT_ERROR,@#ERRV ; Catch EMT errors MOV R0,-(SP) ; Make space on stack MOV R0,-(SP) ; Save R0 MOV 8(SP),R0 ; Get return address MOV -2(R0),R0 ; Get EMT instruction BIC #&FF00,R0 ; Get EMT number ADD R0,R0 ; Index into dispatch table ADD @EMTADDR,R0 ; Index into dispatch table MOV (R0),2(SP) ; Copy address to stack MOV (SP)+,R0 ; Restore R0 JSR PC,(SP)+ ; Jump to routine BVS EMT_ERROR ; V set, set stacked V flag BVC EMT_EXIT ; C clear, jump to exit BIS #1,6(SP) ; Set stacked C flag EMT_EXIT: MOV (SP)+,@#EMTV ; Restore old EMT SP MOV (SP)+,@#ERRV ; Restore old error handler RTI ; Return from EMT EMT_ERROR: MOV @#EMTV,SP ; Get EMT SP BIS #2,6(SP) ; Set stacked V flag BR EMT_EXIT ; Restore and return ; Set up default system environment ; ================================= INIT_ENV: MOV #EMT_HANDLER,@#EMTVEC ; Set up EMT vector MOV #0,@#EMTVEC+2 ; EMT processor status MOV #NMI_ACK,@#NMIVEC ; Set up NMI vector MOV #0,@#NMIVEC+2 ; NMI processor status MOV #IRQ,@#IRQVEC ; Set up IRQ vector MOV #0,@#IRQVEC+2 ; IRQ processor status ; INIT_HANDLES: MOV #Defaults,R0 ; Point to default handlers and EMTs MOV #EMTTABLE-32,R1 MOV #64,R2 INIT_LP: MOV (R0)+,(R1)+ ; Set up initial settings DEC R2 ; and EMT dispatch table BNE INIT_LP MOV #256-32,R2 INIT_CLR: MOV (R0),(R1)+ ; EMTs 16-255 do nothing DEC R2 BNE INIT_CLR RTS PC ; Default settings and EMT table ; ============================== Defaults: EQUW EXITHAND ; &E0 - Default exit handler EQUW 0 ; &E2 - Default exit address EQUW ESCHAND ; &E4 - Default escape handler EQUW ESCFLG ; &E6 - Default escape flag EQUW ERRHAND ; &E8 - Default error handler EQUW ERRBLK ; &EA - Default error buffer EQUW EVENT ; &EC - Default event handler EQUW 0 ; &EE - Unused EQUW USERIRQ ; &F0 - Default unknown IRQ handler EQUW 0 ; &F2 - Unused EQUW 0 ; &F4 - Holds old SP within EMT handler EQUW EMTTABLE ; &F6 - Default EMT dispatch table EQUD 0 ; &F8 - Transfer address EQUW STARTUP ; &FC - Default current program EQUB 0 ; &FE - Default program extended address EQUB 0 ; &FF - Escape flag EQUW EMT0 ; EMT 0 - QUIT EQUW CLI ; EMT 1 - OSCLI EQUW BYTE ; EMT 2 - OSBYTE EQUW WORD ; EMT 3 - OSWORD EQUW WRCH ; EMT 4 - OSWRCH EQUW NEWL ; EMT 5 - OSNEWL EQUW RDCH ; EMT 6 - OSRDCH EQUW FILE ; EMT 7 - OSFILE EQUW ARGS ; EMT 8 - OSARGS EQUW BGet ; EMT 9 - OSBGET EQUW BPut ; EMT 10 - OSBPUT EQUW GBPB ; EMT 11 - OSGBPB EQUW FIND ; EMT 12 - OSFIND EQUW EMT13 ; EMT 13 - System control EQUW EMT14 ; EMT 14 - Set handlers EQUW EMT15 ; EMT 15 - ERROR EQUW EMTXX ; EMTs 16-255 - unused ; Some naming conventions ; xxxxVEC - hardware vectors, eg EMTVEC, NMIVEC, etc ; xxxxV - software vectors, eg ESCV, ERRV, etc. ; xxxx or xxxHAND - handler for xxxx, eg ESCHAND, ERRHAND, etc. ; OSxxx routines handled by xxx, eg OSFILE handled by FILE