; PDP11 Tube Parasite Code ; ======================== ; Copyright (C)1989 J.G.Harston ; ; v0.10 1989 JGH: Initial version, untested proof of concept. ; v0.11 2005 JGH: Altered some labels to assemble from BASIC ; ; This code may be freely reused. ; Memory ; ====== LOPAGE: EQU &04 ; Lowest available memory address DIV 256 HIPAGE: EQU &F8 ; Highest available memory address DIV 256 ESCFLG: EQU &1FF ; Escape flag ERRBLK: EQU &200 ; Buffer to store host error block CLIBUF: EQU &300 ; Space to enter command line from CLI prompt ; Set TUBEIO to address of Tube ULA ; ================================= TUBEIO: EQU 0 ; Address of base of Tube I/O registers TUBE1: EQU TUBEIO+1 ; Tube Register 1 TUBE2: EQU TUBEIO+3 ; Tube Register 2 TUBE3: EQU TUBEIO+5 ; Tube Register 3 TUBE4: EQU TUBEIO+7 ; Tube Register 4 TUBE1S: EQU TUBEIO+0 ; Tube Status 1 TUBE2S: EQU TUBEIO+2 ; Tube Status 2 TUBE3S: EQU TUBEIO+4 ; Tube Status 3 TUBE4S: EQU TUBEIO+6 ; Tube Status 4 ; Code Entry ; ========== ; Set up vectors, hardware, memory, etc. JMP STARTUP ; Jump to start up Tube code ; 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 MOV R0,@#TUBE1 ; Send byte to Tube R1 RTS PC ; Send text string at R1 to Tube Register 1 ; ========================================= SEND_TXT: MOV (R1)+,R0 ; Get byte from R1, increment R1 JSR PC,WRCH ; Send to WRCH via Tube R1 CMP R0,#0 ; 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 MOV R0,@#TUBE2 ; Send byte to Tube R2 RTS PC ; Send -string at R1 to Tube Register 2 ; ========================================= SEND_STR: MOV (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 R1,R2 ; Add length of control block to R1 SEND_LP MOV -(R1),R0 ; Decrement R1, Get byte from R1 JSR PC,SEND_R2 ; Send byte via Tube R2 SUB #1,R2 ; Decrement count of bytes to send BNE SEND_BLK ; Loop until all bytes sent RTS PC ; 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 BIC #&7F,R0 ; Check b7 of status BEQ Get_R2 ; Loop until b7 set MOVB @#TUBE2,R0 ; Get byte from Tube R2 RTS PC ; Wait for block at R1 from Tube Register 2 ; ========================================= Get_BLK: ADD R1,R2 ; Add length of control block to R1 Get_LP: JSR PC,Get_R2 ; Wait for byte via Tube R2 MOV R0,-(R1) ; Decrement R1, store byte to R1 SUB #1,R2 ; Decrement count of bytes to send BNE Get_LP ; Loop until all bytes received RTS PC ; OSARGS - Read info on open file ; =============================== ; Tube Data: &0C handle block function -- result block ; ; On entry: R0=function ; R1=>control block ; R2=handle ; On exit: R0=returned value ; R1 preserved ; R2 preserved ; ARGS: MOV R2,-(SP) ; Save handle MOV R0,-(SP) ; Save function MOV #&0C,R0 JSR PC,SEND_R2 ; Send command &0C - OSARGS MOV R2,R0 JSR PC,SEND_R2 ; Send handle 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)+,R2 ; Get original handle 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: 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 ; 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: 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 CMP R0,#0 ; 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: 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 ; STARTUP ; ======= ; Tube data: via R1: string &00 -- via R2: &7F or &80 ; BANNER: EQUB 10 EQUS "Acorn TUBE PDP-11 64K 0.10" EQUB 10 EQUB 10 EQUB 13 EQUB 0 ALIGN STARTUP: 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: JSR PC,INITERR ; Initialise error handler 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 255 EQUB 32 EQUB 255 ALIGN ; COM_ESC: MOV #124,R0 JSR PC,BYTE ; Acknowledge Escape ; Should generate a local error MOV @ESCAPE,R1 JSR PC,SEND_TXT ; Print "Escape" message BR CLICOM ESCAPE: EQUS "Escape" EQUB 10 EQUB 13 EQUB 0 ALIGN ; ; OSCLI - Send command line to host ; ================================= ; Tube data: &02 string &0D -- &7F or &80 ; ; On entry: R1=>command string ; CLI: MOV #2,R0 JSR PC,SEND_R2 ; Send command &02 - OSCLI 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 BNE CLI_DONE ; Nothing to execute, return ; call program CLI_DONE: RTS PC ; 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 respsonse CMP R0,#&8E ; Was it language startup? BEQ CLI_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 MEM82: ; Memory address high word MOV R1,#0 MOV R2,#0 RTS PC MEM83: ; Lowest available memory address MOV R1,#LOPAGE * 256 MOV R2,#LOPAGE RTS PC MEM84: ; Highest available memory address MOV R1,#HIPAGE * 256 MOV R2,#HIPAGE RTS PC ; OSWORD ; ====== ; On entry: R0=OSWORD number ; R1=>control block ; WORD: CMP R0,#0 BEQ RDLINE ; ; 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 R1,R2 ; 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: MOV -(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 R1,R3 ; 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 MOV 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 R1,#2 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 BIC #&7F,R0 ; Check b7 of status BEQ 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 BIC #&7F,R0 ; Check b7 of status BEQ 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 BIC #&7F,R0 ; Check b7 of status BNE IRQ_R4 ; R4 generated the interupt MOVB @#TUBE1S,R0 ; Read Tube R1 status BIC #&7F,R0 ; Check b7 of status BNE IRQ_R1 ; R1 generated the interupt MOV (SP)+,R0 ; Get R0 back JMP USERIRQ ; Something else generated the interupt ; Data present in Tube R1 generated an interupt ; IRQ_R1: MOVB @#TUBE1,R0 ; Get byte from Tube R1 CMP R0,#&80 ; Is b7 set? BCC IRQ_ESCAPE ; 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,IRQ_EVENT ; Call event vector MOV (SP)+,R2 ; Restore registers MOV (SP)+,R1 MOV (SP)+,R0 RTI ; Return from interupt IRQ_EVENT: RTS PC ; Need to jump to a vector ; R1>&7F - Host changing Escape state ; Tube data: via R1: flag, b7=1, b6=state ; IRQ_ESCAPE: ADD R0,R0 ; Move b6 into b7 MOVB @ESCFLG,R0 ; Store Escape flag 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 CMP R0,#&80 ; Is b7 set? BCS 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 @ERRBLK,R1 ; Point to error buffer JSR PC,Get_R2 MOV R0,(R1)+ ; Store error number IRQ_R4LP: JSR PC,Get_R2 ; Wait for byte of error string MOV R0,(R1)+ ; Store in error buffer CMP R0,#0 ; Check current character BNE IRQ_R4LP ; Loop until terminating &00 received MOV @ERRBLK,R0 ; Point to error block ; Need to enable interupts, then generate client error MOV (SP)+,R1 ; For the moment, restore all MOV (SP)+,R0 RTI ; and return ; 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 JSR PC,Get_R4 ; Get data address byte 3 JSR PC,Get_R4 ; Get data address byte 2 JSR PC,Get_R4 ; Get data address byte 1 JSR PC,Get_R4 ; Get sync byte ; Need to set up pointers for transfer via NMI IRQ_DONE: MOV (SP)+,R0 ; For the moment, restore all RTI ; and return ; Data transfer via NMIs ; ---------------------- ; Stubs ; ----- USERIRQ: RTS PC INITERR: RTS PC