REM > 6502.Serial65/src REM Generated from HostFS source REM Source for Single Port Tube Client MOS for 6502 REM =============================================== REM This code may be freely reused, with acknowledgements : REM This implements a Tube client communicating with a host REM via a single port, such as a serial port, a parallel port, REM or even a single Tube register, all in less than 2K. : REM The link is a text channel that escapes out to send commands REM and receive data transfers. This version assumes a clean, REM error-free link channel : REM Can be built as a BBC filing system, with code to access REM BBC memory map. : REM v0.16 16-Nov-2010 J.G.Harston REM v0.17 09-Mar-2013 JGH: Added additional Tube entry points, REM EXECUTE enters code with A=0/1, SEC/CLC. REM v0.18 07-Apr-2013 JGH: Moved some code that overlapped I/O area. REM 09-Nov-2016 JGH: Edited slightly to allow conversion to as65 source. REM v0.19 11-Nov-2016 JGH: Added conditionals for sideways ROM code. REM v0.20 18-Mar-2017 JGH: Added option for local RDLINE, optimised OSCLI. REM v0.21 23-Feb-2018 JGH: *HOSTFS ON sets WRCHV/RDCHV. REM Serv3 responds to returned boot option. REM To do: REM TxRDY should also check for outgoing CTS/RTS : REM v0.22 tweek1%=FALSE:REM ReadData tweek2%=TRUE :REM Tube transfer tweek3%=TRUE :REM Load to I/O accesses hardware directly tweek4%=FALSE tweek%=tweek1% OR tweek2% OR tweek3% OR tweek4% : : REM Specify target REM ============== ver$="0.21":date$=" (23 Feb 2018)" IF tweek%:ver$="0.22":date$=" (13 Jan 2020)" SWROM% =FALSE:REM Build 6502 Client ROM RDLINE%=FALSE:REM Pass RDLINE to host RDLINE%=TRUE :REM Implement RDLINE locally : REM Boilerplate REM =========== IF HIMEM>&FFFF:SYS "OS_GetEnv"TOA$:IFLEFT$(A$,5)<>"B6502":OSCLI"B6502"+MID$(A$,INSTR(A$," ")) ON ERROR REPORT:PRINT" at line ";ERL:END DEFFNif(A%):IFA%:z%=-1:=opt% ELSE z%=P%:=opt% DEFFNendif:IFz%=-1:=opt% ELSE z%=P%-z%:P%=P%-z%:O%=O%-z%:=opt% DEFFNelse:IFz%=-1:z%=P%:=opt% ELSE z%=P%-z%:P%=P%-z%:O%=O%-z%:z%=-1:=opt% DIM mcode% &FFF, L% &7F : REM Declare code start and space REM ============================ IF SWROM%=0:tweek3%=0 IF SWROM%=0:load%=&F800 IF SWROM% :load%=&8000 : REM System vectors REM ============== USERV=&200:BRKV =&202:IRQ1V=&204:IRQ2V=&206:CLIV =&208:BYTEV=&20A WORDV=&20C:WRCHV=&20E:RDCHV=&210:FILEV=&212:ARGSV=&214:BGetV=&216 BPutV=&218:GBPBV=&21A:FINDV=&21C:FSCV =&21E:EVNTV=&220 : REM System addresses REM ================ ROMSTART%=&F800 :REM Start of ROM code to copy to RAM IOSPACE% =&FEF0 :REM Start of I/O space ERRBUF=&236 :REM Error buffer INPBUF=&236:INPEND=&300 :REM Supervisor string buffer : PROG =&EE:REM &EE/F = PROG - Current program NUM =&F0:REM &F0/1 = NUM - hex accumulator MEMTOP =&F2:REM &F2/3 = MEMTOP - top of memory TRANS =&F4:REM &F4/5 = ADDR - Data transfer address (assembler doesn't like 'INC ADDR') TRANShi=&F6:REM &F6/7 = ADDR+2 - Data transfer address high word LPTR =&F8:REM &F8/9 = String pointer, OSWORD control block CTRL =&FA:REM &FA/B = CTRL - OSFILE, OSGBPB control block CMD =&FA:REM CMD - Address of command line parameters TEXT =&FA:REM &FA/B = TEXT - PrText string pointer IRQA =&FC:REM &FC = IRQ A store FAULT =&FD:REM &FD/E => last error ESCFLG =&FF:REM &FF = Escape flag : REM I/O values, these should work for BenEater REM ========================================== TxStatus=&5001:TxRDY=0:TxData=&5000:TxInit=&00:TxCommand=&5002:TxCmd=&0A:RxCont=&0A RxStatus=&5001:RxRDY=8:RxData=&5000:RxInit=&00:TxControl=&5003:TxCtl=&1E:RxStop=&02 TxDelay=100:REM *NOTE* 6551 ACIA has bug, TxRDY never works. MAXRAM%=&4000:name$="Eater65" : REM I/O values, these are suitable for a 6850 REM ========================================= TxStatus=&FEF0:TxRDY=2:TxData=&FEF1:TxInit=&13:TxCommand=0:TxCmd=0:RxCont=&15 REM &13=Reset ACIA, &15=8N1, clock/16, RTS low RxStatus=&FEF0:RxRDY=1:RxData=&FEF1:RxInit=&55:TxControl=0:TxCtl=0:RxStop=&55 REM &55=8N1, clock/16, RTS high TxDelay=0 MAXRAM%=0:IF SWROM%:name$="HostFS" ELSE name$="Serial65" : REM Serial Tube system values REM ========================= esc=&9B : IF tweek%:name$=name$+"v"+MID$(ver$,3,2) FOR P=0 TO 1:opt%=P*3+4 P%=load%:O%=mcode% PROCasmROMHeader PROCasmTubeCode NEXT:IF O%>L%:PRINT "ERROR: Code overrun" PRINT"Saving ";LEFT$(name$,10); OSCLI"Save "+LEFT$(name$,10)+" "+STR$~mcode%+" "+STR$~O%+" "+STR$~load%+" "+STR$~load% PRINT:ON ERROR ON ERROR OFF:END IF SWROM%:OSCLI"SetType "+name$+" BBC" *Quit END : : DEFFNerror:IF SWROM%:[OPT P*3+4:JSR Error:]:=opt% ELSE [OPT P*3+4:BRK:]:=opt% : DEFPROCasmTubeCode [OPT opt% \ Start of Tube system code \ ========================= \ On hardware reset all of memory reads come from ROM, all writes go to RAM. \ Accessing any I/O location pages ROM out of memory map, thence all reads \ come from RAM. \ .LF800 JMP RESET .PrBanner JSR PrText .BANNER EQUB 13:EQUS "SERIAL TUBE 6502 64K ":EQUS LEFT$(ver$+" ",5) EQUB 13:EQUB 0 RTS \ Tube Client Startup Code \ ======================== .RESET SEI:LDX #&00 :\ Disable interupts .LF802 LDA &FF00,X:STA &FF00,X :\ Copy entry block to RAM DEX:BNE LF802 \ The following code copies the page with the I/O registers in \ it without accessing the I/O registers. Modify IOSPACE% according \ to where the I/O registers actually are. LDX #IOSPACE% AND 255 .LF819 LDA &FDFF,X:STA &FDFF,X :\ Copy &FE00-&FEEF to RAM, avoiding DEX:BNE LF819 :\ IO space at &FEFx : LDY #ROMSTART% AND 255:STY LPTR+0 :\ Point to start of ROM LDA #ROMSTART% DIV 256:STA LPTR+1 .LF82A :\ Copy rest of ROM to RAM LDA (LPTR),Y:STA (LPTR),Y :\ Copy a page to RAM INY:BNE LF82A :\ Loop for 256 bytes INC LPTR+1:LDA LPTR+1 :\ Inc. address high byte CMP #&FE:BNE LF82A :\ Loop up to &FDFF : .STARTUP SEI:LDX #&35 .LF80D LDA LFF00,X:STA USERV,X :\ Set up default vectors DEX:BPL LF80D:TXS :\ and clear stack OPT FNif(TxCommand=0) LDA #TxInit:STA TxStatus :\ Initialise port and page ROM out LDA #RxInit:STA RxStatus OPT FNelse LDA #TxInit:STA TxStatus :\ Initialise port and page ROM out LDA #TxCmd:STA TxCommand LDA #TxCtl:STA TxControl OPT FNendif :\ Accessing I/O registers will page ROM out if running :\ from RAM. Once ROM is paged out we can do subroutine :\ calls as we can now read from stack in RAM. LDA #&00:STA ESCFLG:STA MEMTOP+0 :\ Clear Escape flag OPT FNif(MAXRAM%=0) LDA #ROMSTART% DIV 256:STA MEMTOP+1 :\ Set memtop to start of ROM OPT FNelse LDA #MAXRAM% DIV 256:STA MEMTOP+1 :\ Set memtop to top of RAM OPT FNendif JSR InitError :\ Claim error handler LDA PROG+0:STA TRANS+0 :\ Copy PROG to TRANS address LDA PROG+1:STA TRANS+1 \ Tell the Host that we've restarted \ ---------------------------------- \ Tube data &18 &00 &FF &FF -- Cy Y X \ followed by string \ \ Note for Host authors, Host MUST NOT respond by echoing back a SoftReset \ as the Client will be trapped in an infinite STARTUP loop. If Host wants \ to read Client to determine CPU this will change TRANS so must only be \ done if a later transaction will set TRANS after a language transfer, eg \ on Hard Reset. \ OPT FNif(RDLINE%) LDA #0 OPT FNelse LDX #0:LDA #&FF:TAY :\ As we are using a serial link, send Soft Reset JSR osFSC:TXA :\ Sends A=&FF, X=0 Soft Reset, Y=&FF no boot OPT FNendif .STARTUP2 PHA :\ Save returned Ack byte, will be &00 if no response JSR PrBanner:JSR OSNEWL :\ Display startup banner LDA #CmdPrompt AND 255 :\ Next time RESET is soft entered, STA LF800+1 :\ banner not printed LDA #CmdPrompt DIV 256 STA LF800+2 PLA:CLC:JSR WaitCheckCode :\ Check Ack code, if &80 enter code, : :\ else enter command prompt loop \ Supervisor Command prompt \ ========================= .CmdPrompt LDX #&FF:TXS:JSR InitError :\ Reset stack, claim error handler LDA #CmdPrompt AND 255:STA PROG+0 :\ Make Command Prompt the current program LDA #CmdPrompt DIV 256:STA PROG+1 .CmdOSLoop LDA #ASC"*":JSR OSWRCH :\ Print '*' prompt LDX #LF95D AND 255 LDY #LF95D DIV 256 LDA #&00:JSR OSWORD :\ Read line to INPBUF BCS CmdOSEscape LDX #INPBUF AND 255 LDY #INPBUF DIV 256 :\ Execute command JSR OS_CLI:JMP CmdOSLoop :\ and loop back for another .CmdOSEscape LDA #&7E:JSR OSBYTE :\ Acknowledge Escape state OPT FNerror:EQUB 17:EQUS "Escape":BRK \ Control block for command prompt input \ -------------------------------------- .LF95D EQUW INPBUF :\ Input text to INPBUF EQUB INPEND-INPBUF :\ Maximum number of characters EQUB &20:EQUB &FF :\ Min=&20, Max=&FF \ Error handler \ ============= .InitError LDA #ErrorHandler AND 255:STA BRKV+0 :\ Claim error handler LDA #ErrorHandler DIV 256:STA BRKV+1 RTS .ErrorHandler LDX #&FF:TXS :\ Reset stack JSR OSNEWL LDX FAULT+0:LDY FAULT+1 INX:BNE P%+3:INY :\ XY=>error string JSR PRSTRNG :\ Print error string JSR OSNEWL:JMP CmdPrompt :\ Jump to command prompt \ Interrupt handlers \ ================== .IRQHandler STA IRQA:PLA:PHA :\ Save A, get flags from stack AND #&10:BNE BRKHandler :\ If BRK, jump to BRK handler JMP (IRQ1V) :\ Continue via IRQ1V handler .IRQ1Handler JMP (IRQ2V) :\ Pass on to IRQ2V .BRKHandler TXA:PHA :\ Save X TSX:LDA &0103,X :\ Get address from stack CLD:SEC:SBC #&01:STA FAULT+0 LDA &0104,X SBC #&00:STA FAULT+1 :\ &FD/E=>after BRK opcode PLA:TAX:LDA IRQA :\ Restore X, get saved A CLI:JMP (BRKV) :\ Restore IRQs, jump to Error Handler .IRQ2Handler LDA IRQA :\ Restore saved A .NMIHandler RTI \ Skip Spaces \ =========== .SkipSpaces1 INY .SkipSpaces LDA (LPTR),Y:CMP #&20:BEQ SkipSpaces1 .NullReturn RTS \ Scan hex \ ======== .ScanHex LDX #&00:STX NUM+0:STX NUM+1 :\ Clear hex accumulator .LF98C LDA (LPTR),Y :\ Get current character CMP #&30:BCC LF9B1 :\ <'0', exit CMP #&3A:BCC LF9A0 :\ '0'..'9', add to accumulator AND #&DF:SBC #&07:BCC LF9B1 :\ Convert letter, if <'A', exit CMP #&40:BCS LF9B1 :\ >'F', exit .LF9A0 ASL A:ASL A:ASL A:ASL A :\ *16 LDX #&03 :\ Prepare to move 3+1 bits .LF9A6 ASL A:ROL NUM+0:ROL NUM+1 :\ Move bits into accumulator DEX:BPL LF9A6 :\ Loop for four bits, no overflow check INY:BNE LF98C :\ Move to next character .LF9B1 RTS \ MOS INTERFACE \ ~~~~~~~~~~~~~ \ \ \ OSRDCH - Wait for character from input stream \ ============================================= \ On exit, A =char, Cy=Escape flag \ .osRDCH JSR WaitByte:PHA :\ Wait for character LDA ESCFLG:ASL A :\ Get Escape flag to carry PLA:RTS :\ Get character to A and return \ OSRDCH - Request character via Tube \ =================================== \ On exit, A =char, Cy=Escape flag \ \ Tube data &00 -- Carry Char \ .osRDCH_IO LDA #&00:JSR SendCommand :\ Send command &00 - OSRDCH .WaitCarryChar :\ Wait for Carry and A JSR WaitByte:ASL A :\ Wait for carry JMP WaitByte \ OSCLI - Execute command \ ======================= \ On entry, XY=>command string \ On exit, All registers corrupted \ Local *commands \ --------------- .CmdTable EQUS "GO":EQUB &80 EQUS "HELP":EQUB &81 EQUB 0 .osCLI STX LPTR+0:STY LPTR+1 :\ LPTR=>command string LDY #&FF .osCLIlp0 JSR SkipSpaces1 CMP #ASC"*":BEQ osCLIlp0 :\ Skip spaces and stars CMP #ASC"A":BCC osCLI_IO :\ Doesn't start with a letter LDX #&FF:DEY :\ Point to before start of strings .osCLIlp1 TYA:PHA :\ Save start of command line .osCLIlp2 INX:INY :\ Step to next characters LDA CmdTable,X:BMI osCLImatch :\ Check for end of command LDA (LPTR),Y CMP #ASC".":BEQ osCLIskip :\ EQ+CS = abbreviated command AND #&DF :\ Force upper case CMP CmdTable,X:BEQ osCLIlp2 :\ Check more characters CLC :\ CC=skip this command .osCLIskip INX LDA CmdTable,X:BPL osCLIskip :\ Step to end of entry BCS osCLIdot :\ CS=abbreviated command .osCLInomatch PLA:TAY :\ Restore line pointer LDA CmdTable+1,X:BNE osCLIlp1 :\ Not at end of table BEQ osCLI_IO :\ End of table, pass to Tube .osCLImatch LDA (LPTR),Y CMP #ASC"A":BCS osCLInomatch :\ More letters, check next entry DEY :\ Prepare to skip any spaces .osCLIdot PLA:JSR SkipSpaces1 :\ Drop saved pointer, step past dot, skip spaces LDA CmdTable,X :\ Get command byte CMP #&81:BEQ CmdHelp :\ &81, jump to *Help :\ Fall through to *Go \ *GO - call machine code \ ----------------------- .CmdGo JSR ScanHex:JSR SkipSpaces :\ Read hex value and move past spaces CMP #&0D:BNE osCLI_IO :\ More parameters, pass to Tube to deal with TXA:BEQ CmdGo2:LDX #NUM-PROG :\ If no parameters, jump to PROG, else jump to NUM .CmdGo2 LDA PROG+0,X:STA TRANS+0 :\ Set address to jump to LDA PROG+1,X:STA TRANS+1 BCS SaveEnterCode :\ CS set from CMP earlier \ *Help - Display help information \ -------------------------------- .CmdHelp JSR PrBanner :\ Print help message :\ Continue to pass '*Help' command to Tube \ OSCLI - Send command line to host \ ================================= \ On entry, &F8/9=>command string \ \ Tube data &02 string &0D -- &7F or &80 \ .osCLI_IO LDA #&02:JSR SendCommand :\ Send command &02 - OSCLI JSR SendStringF8 :\ Send command string at LPTR : :\ Drop through to wait for Ack and enter code .WaitEnterCode JSR WaitByte:SEC :\ Wait for Ack from Tube .WaitCheckCode ROL A:BCC SaveEnterDone :\ If <&80, exit ROR A :\ Restore Carry, CC=RESET, CS=OSCLI .SaveEnterCode LDA PROG+1:PHA:LDA PROG+0:PHA :\ Save current program JSR EnterCode:\TAX :\ Enter code, save return value PLA:STA PROG+0:STA MEMTOP+0 :\ Restore current program PLA:STA PROG+1:STA MEMTOP+1 :\ and set top of memory to it LDA #0:\TXA :\ A=return value .SaveEnterDone RTS \ FSC - FSC Functions \ =================== \ On entry, A, X, Y=FSC parameters \ On exit, A, X, Y=return values \ \ Tube data &18 X Y A -- &FF Y X or \ &7F then string -- respsonse \ .osFSC PHA:LDA #&18:SEC:BCS ByteHi2 \ OSBYTE - Byte MOS functions \ =========================== \ On entry, A, X, Y=OSBYTE parameters \ On exit, A preserved \ If A<&80, X=returned value \ If A>&7F, X, Y, Carry=returned values \ .osBYTE CMP #&80:BCS ByteHigh :\ Jump for long OSBYTEs \ \ BYTELO \ Tube data &04 X A -- X \ PHA:LDA #&04:JSR SendCommand :\ Send command &04 - BYTELO TXA:JSR SendByte :\ Send single parameter PLA:PHA:JSR SendByte :\ Send function JSR WaitByte:TAX :\ Get return value PLA:RTS :\ Restore A and return .ByteHigh CMP #&82:BEQ Byte82 :\ Read memory high word CMP #&83:BEQ Byte83 :\ Read bottom of memory CMP #&84:BEQ Byte84 :\ Read top of memory \ \ BYTEHI \ Tube data &06 X Y A -- Cy Y X \ PHA:LDA #&06:CLC .ByteHi2 PHP:JSR SendCommand:PLP :\ Send command &06 or &18 - BYTEHI or FSC TXA:JSR SendByte :\ Send first parameter TYA:JSR SendByte :\ Send second parameter PLA:JSR SendByte :\ Send function BCS ByteHi3 :\ Skip OSBYTE checks CMP #&8E:BEQ WaitEnterCode :\ If select language, check to enter code CMP #&9D:BEQ LFAEF:CLC :\ Fast return with Fast BPUT .ByteHi3 PHA:JSR WaitByte:BCC ByteHi4 :\ Get Carry or byte/string response ASL A:BCC FSCString :\ Jump to send FSC string .ByteHi4 ASL A:JSR WaitByte:TAY :\ Get return high byte JSR WaitByte:TAX:PLA :\ Get return low byte .LFAEF RTS OPT FNif(MAXRAM%=0) .Byte84:LDX MEMTOP+0:LDY MEMTOP+1:RTS :\ Read top of memory OPT FNelse .Byte84:LDX #MAXRAM% AND 255:LDY #MAXRAM% DIV 256:RTS :\ Read top of memory OPT FNendif .Byte83:LDX #&00:LDY #&08:RTS :\ Read bottom of memory .Byte82:LDX #&00:LDY #&00:RTS :\ Return &0000 as memory high word .FSCString PLA:JSR SendString :\ Send string LDY #&FF .FSCStrLp2 INY:LDA (LPTR),Y CMP #ASC"!":BCS FSCStrLp2 :\ Skip to ' ' or DEY \ Check - does this cope with *cmd *RUN */ *space/space ? .FSCStrLp3 INY:LDA (LPTR),Y CMP #ASC" ":BEQ FSCStrLp3 :\ Skip to non-' ' TYA:CLC:ADC LPTR+0:STA CMD+0 LDA LPTR+1:ADC #0:STA CMD+1:\ Point to command parameters JSR WaitEnterCode :\ Wait for Ack, enter code if needed BPL LFAEF :\ Response=<&40, all done, return response : :\ Response=&40 (&80 at this point), print text .FSCStrChr JSR WaitByte :\ Wait for a character CMP #&00:BEQ LFAEF :\ &00 terminates string JSR OSWRCH:JMP FSCStrChr :\ Print character \ OSWORD - Various functions \ ========================== \ On entry, A =function \ XY=>control block \ .osWORD STX LPTR+0:STY LPTR+1 :\ LPTR=>control block TAY:BEQ RDLINE :\ OSWORD 0, jump to read line \ \ Tube data &08 A in_length block out_length -- block \ PHA:PHA :\ Save function LDA #&08:JSR SendCommand :\ Send command &08 - OSWORD PLA:JSR SendByte :\ Send function TAX:BPL WordSendLow :\ Jump with functions<&80 LDY #&00:LDA (LPTR),Y :\ Get send block length from control block TAY:JMP WordSend :\ Jump to send control block .WordSendLow LDY WordLengthsTx-1,X :\ Get send block length from table CPX #&15:BCC WordSend :\ Use this length for OSWORD 1 to &14 LDY #&10 :\ Send 16 bytes for OSWORD &15 to &7F .WordSend TYA:JSR SendByte :\ Send send block length DEY:BMI LFB45 :\ Zero or &81..&FF length, nothing to send .LFB38 LDA (LPTR),Y:JSR SendByte :\ Send byte from control block DEY:BPL LFB38 :\ Loop for number to be sent .LFB45 TXA:BPL WordRecvLow :\ Jump with functions<&80 LDY #&01:LDA (LPTR),Y :\ Get receive block length from control block TAY:JMP WordRecv :\ Jump to receive control block .WordRecvLow LDY WordLengthsRx-1,X :\ Get receive length from table CPX #&15:BCC WordRecv :\ Use this length for OSWORD 1 to &14 LDY #&10 :\ Receive 16 bytes for OSWORD &15 to &7F .WordRecv TYA:JSR SendByte :\ Send receive block length DEY:BMI LFB71 :\ Zero of &81..&FF length, nothing to receive .LFB64 JSR WaitByte:STA (LPTR),Y :\ Get byte to control block DEY:BPL LFB64 :\ Loop for number to receive .LFB71 LDY LPTR+1:LDX LPTR+0:PLA :\ Restore registers RTS \ RDLINE - Read a line of text \ ============================ \ On entry, A =0 \ XY=>control block \ On exit, A =undefined \ Y =length of returned string \ Cy=0 ok, Cy=1 Escape \ \ Tube data &0A block -- &FF or &7F string &0D \ .RDLINE OPT FNif(RDLINE%) :\ Perform RDLINE locally LDY #2 LDA (LPTR),Y:STA LPTR+2 :\ Copy control block to w/s DEY:LDA (LPTR),Y:TAX DEY:LDA (LPTR),Y STA LPTR+0:STX LPTR+1 :\ (LPTR)=>string buffer, LPTR+2=max length .Word00Lp1 JSR OSRDCH:BCS Word00Esc :\ Wait for character CMP #&7F:BNE Word00Char :\ Not Delete .Word00Delete TYA:BEQ Word00Lp1 :\ Nothing to delete LDA #&7F:JSR OSWRCH :\ VDU 127 DEY:JMP Word00Lp1 :\ Dec. counter, loop back .Word00Char CMP #&08:BEQ Word00Delete:\ BS is also Delete CMP #&15:BNE Word00Ins :\ Not Ctrl-U TYA:BEQ Word00Lp1 :\ Nothing to delete LDA #&7F .Word00Lp2 JSR OSWRCH:DEY :\ Delete characters BNE Word00Lp2 BEQ Word00Lp1 :\ Jump back to start .Word00Ins STA (LPTR),Y :\ Store character CMP #&0D:CLC:BEQ Word00cr:\ Return - finish CPY LPTR+2:BCS Word00max :\ Maximum length CMP #&20:BCS Word00ctrl :\ Control character DEY :\ Cancel following INY .Word00ctrl INY:JSR OSWRCH :\ Inc. counter, print character .Word00max JMP Word00Lp1 :\ Loop for more .Word00cr JSR OSNEWL:CLC :\ Return with CC, Y=length .Word00Esc RTS OPT FNelse LDA #&0A:JSR SendCommand :\ Send command &0A - RDLINE LDY #&04 .LFB7E LDA (LPTR),Y:JSR SendByte:\ Send control block DEY:CPY #&01:BNE LFB7E :\ Loop for 4, 3, 2 LDA #&07:JSR SendByte :\ Send &07 as address high byte LDA (LPTR),Y:PHA :\ Get text buffer address high byte DEY:TYA:JSR SendByte :\ Send &00 as address low byte LDA (LPTR),Y:PHA :\ Get text buffer address low byte LDX #&FF:JSR WaitByte :\ Wait for response ASL A:BCS RdLineEscape :\ Jump if Escape returned PLA:STA LPTR+0:PLA STA LPTR+1:LDY #&00 :\ Set &F8/9=>text buffer .RdLineLp JSR WaitByte:STA (LPTR),Y:\ Store returned character INY:CMP #&0D:BNE RdLineLp:\ Loop until LDA #&00:DEY:CLC:INX :\ Return A=0, Y=len, X=00, Cy=0 RTS .RdLineEscape PLA:PLA:LDA #&00 :\ Return A=0, Y=len, X=FF, Cy=1 RTS OPT FNendif \ OSWORD control block lengths \ ---------------------------- .WordLengthsTx EQUB &00:EQUB &05:EQUB &00:EQUB &05 EQUB &04:EQUB &05:EQUB &08:EQUB &0E EQUB &04:EQUB &01:EQUB &01:EQUB &05 EQUB &00:EQUB &08:EQUB &20:EQUB &10 EQUB &0D:EQUB &00:EQUB &04:EQUB &80 .WordLengthsRx EQUB &05:EQUB &00:EQUB &05:EQUB &00 EQUB &05:EQUB &00:EQUB &00:EQUB &00 EQUB &05:EQUB &09:EQUB &05:EQUB &00 EQUB &08:EQUB &19:EQUB &00:EQUB &01 EQUB &0D:EQUB &80:EQUB &04:EQUB &80 \ OSARGS - Read info on open file \ =============================== \ On entry, A =function \ X =>data word in zero page \ Y =handle \ On exit, A =returned value \ X preserved \ Y preserved \ \ Tube data &0C handle block function -- result block \ .osARGS PHA:LDA #&0C:JSR SendCommand :\ Send command &0C - OSARGS TYA:JSR SendByte :\ Send handle LDA &03,X:JSR SendByte :\ Send data word LDA &02,X:JSR SendByte LDA &01,X:JSR SendByte LDA &00,X:JSR SendByte PLA:JSR SendByte :\ Send function JSR WaitByte:PHA :\ Get and save result JSR WaitByte:STA &03,X :\ Receive data word JSR WaitByte:STA &02,X JSR WaitByte:STA &01,X JSR WaitByte:STA &00,X PLA:RTS :\ Get result back and return \ OSBGET - Get a byte from open file \ ================================== \ On entry, Y =handle \ On exit, A =byte Read \ Y =preserved \ Cy set if EOF \ \ Tube data &0E handle -- Carry byte \ .osBGET LDA #&0E:JSR SendCommand :\ Send command &0E - OSBGET TYA:JSR SendByte :\ Send handle JMP WaitCarryChar :\ Jump to wait for Carry and byte \ OSBPUT - Put a byte to an open file \ =================================== \ On entry, A =byte to write \ Y =handle \ On exit, A =preserved \ Y =preserved \ \ Tube data &10 handle byte -- &7F \ .osBPUT PHA:LDA #&10:JSR SendCommand :\ Send command &10 - OSBPUT TYA:JSR SendByte :\ Send handle PLA:JSR SendByte :\ Send byte PHA:JSR WaitByte:PLA:RTS :\ Wait for acknowledge and return \ OSFIND - Open or Close a file \ ============================= \ On entry, A =function \ Y =handle or XY=>filename \ On exit, A =zero or handle \ \ Tube data &12 function string &0D -- handle \ &12 &00 handle -- &7F \ .osFIND PHA:LDA #&12:JSR SendCommand :\ Send command &12 - OSFIND PLA:JSR SendByte :\ Send function CMP #&00:BNE OPEN :\ If <>0, jump to do OPEN \ CLOSE PHA:TYA:JSR SendByte :\ Send handle JSR WaitByte:PLA:RTS :\ Wait for acknowledge, restore regs and return .OPEN JSR SendString :\ Send pathname JMP WaitByte :\ Wait for and return handle \ OSFILE - Operate on whole files \ =============================== \ On entry, A =function \ XY=>control block \ On exit, A =result \ control block updated \ \ Tube data &14 block string function -- result block \ .osFILE STX CTRL+0:STY CTRL+1 :\ CTRL=>control block PHA:LDA #&14:JSR SendCommand :\ Send command &14 - OSFILE LDY #&11 .LFC5F LDA (CTRL),Y:JSR SendByte :\ Send control block DEY:CPY #&01:BNE LFC5F :\ Loop for &11..&02 DEY:LDA (CTRL),Y:TAX INY:LDA (CTRL),Y:TAY :\ Get pathname address to XY JSR SendString :\ Send pathname PLA:JSR SendByte :\ Send function JSR WaitByte:PHA :\ Wait for result LDY #&11 .LFC7E JSR WaitByte:STA (CTRL),Y :\ Get control block back DEY:CPY #&01:BNE LFC7E :\ Loop for &11..&02 LDY CTRL+1:LDX CTRL+0 :\ Restore registers PLA:RTS :\ Get result and return \ OSGBPB - Multiple byte Read and write \ ===================================== \ On entry, A =function \ XY=>control block \ On exit, A =returned value \ control block updated \ \ Tube data &16 block function -- block Carry result \ .osGBPB STX CTRL+0:STY CTRL+1 :\ CTRL=>control block PHA:LDA #&16:JSR SendCommand :\ Send command &16 - OSGBPB LDY #&0C .LFC9A LDA (CTRL),Y:JSR SendByte :\ Send control block DEY:BPL LFC9A :\ Loop for &0C..&00 PLA:JSR SendByte :\ Send function LDY #&0C .LFCA8 JSR WaitByte:STA (CTRL),Y :\ Get control block back DEY:BPL LFCA8 :\ Loop for &0C..&00 LDY CTRL+1:LDX CTRL+0 :\ Restore registers JMP WaitCarryChar :\ Jump to get Carry and result \ Tube I/O routines \ ================= \ Send a string \ ------------- .SendString STX LPTR+0:STY LPTR+1 :\ Set LPTR=>string .SendStringF8 LDY #&00 .LF9B8 LDA (LPTR),Y:JSR SendByte :\ Send character to I/O INY:CMP #&0D:BNE LF9B8 :\ Loop until sent LDY LPTR+1:RTS :\ Restore Y from &F9 and return \ OSWRCH - Send character to output stream \ ======================================== \ On entry, A =character \ On exit, A =preserved \ \ Tube data character -- \ .osWRCH :\ WRCH is simply SendByte \ Tube Core I/O Routines \ ====================== \ Characters and commands are sent over the same single port \ Outward commands are escaped, and inward responses are escaped \ \ Outward \ x VDU x \ esc,esc VDU esc \ esc,n MOS function, control block follows \ \ Inward \ x char/byte x \ esc,esc char/byte esc \ esc,&00 BRK, error number+text+null follows \ esc,<&80 read returned control block set length \ esc,&8n Escape change, b0=new state \ esc,&9x,Y,X,A Event \ esc,&Ax reserved for networking \ esc,&Bx end transfer \ esc,&Cx,addr set address \ esc,&Dx,addr execute address \ esc,&Ex,addr start load from address \ esc,&Fx,addr start save from address \ All commands are data inward, except esc,&Fx which is data outward \ Send a byte, escaping it if needed \ ---------------------------------- \ On entry, A=byte to send \ On exit, A,P preserved \ .SendByte PHP:JSR SendData :\ Send byte CMP #esc:BNE SendByte2 :\ If not esc, done JSR SendData :\ If esc, send twice to prefix it .SendByte2 PLP:RTS \ Send an escaped command \ ----------------------- \ On entry, A=command \ On exit, A,X,Y preserved, P corrupted .SendCommand PHA .SendCmdLp JSR ReadByte:BCS SendCmdLp :\ Flush input LDA #esc:JSR SendData :\ Send esc prefix PLA:JMP SendData :\ Send command byte (always <&80) \ Check if a byte is waiting, and read it if there \ ------------------------------------------------ \ On exit, P=EQ CC - nothing waiting \ P=NE CS - byte waiting, returned in A \ .ReadByte JSR ReadData :\ See if data present PHP:BCS WaitByte2 :\ Data present, continue with it PLP:LDA #0:RTS :\ No data present \ Wait for a byte, decoding escaped data \ -------------------------------------- \ On exit, A =byte \ P =preserved \ .WaitByte PHP .WaitByteLp JSR WaitData :\ Wait for data present .WaitByte2 BNE WaitByteOk :\ Not esc, return it JSR WaitData:BEQ WaitByteOk:\ esc,esc, return as esc PHA:TYA:PHA:TXA:PHA:TSX :\ Save registers LDA &103,X:JSR WaitCommand :\ Decode escaped command PLA:TAX:PLA:TAY:PLA :\ Restore registers JMP WaitByteLp :\ Loop back to wait for a byte .WaitByteOk PLP :\ Restore flags .WaitByteOk2 RTS :\ Return byte \ Wait for raw byte of data \ ------------------------- \ On exit, A =byte \ P =EQ byte=esc, NE byte<>esc \ .WaitData JSR ReadData:BCC WaitData :\ Loop until data present RTS \ Decode escaped command \ ---------------------- \ On entry, A=command, P set accordingly \ All registers can be trashed \ .WaitCommand BEQ WaitError :\ esc,&00 - error BMI WaitTransfer :\ esc,>&7F - data transfer \ esc,1..127 - read a control block \ --------------------------------- TAY :\ Move count to Y .WaitLength JSR WaitByte :\ Wait for a byte DEY:STA (CTRL),Y :\ Store it BPL WaitLength:RTS \ esc,&00 - error \ --------------- .WaitError STA ERRBUF:TAY :\ Store error BRK JSR WaitByte:STA ERRBUF+1 :\ Store error number .WaitErrorLp JSR WaitByte:STA ERRBUF+2,Y:\ Store error character INY:CMP #0:BNE WaitErrorLp :\ Loop until final &00 : \\ This doesn't work \\ .WaitErr1 \\ LDA #&1E:\JSR SendCommand :\ Send a SYNC command \\ JSR ReadByte:\BCC WaitErr1 :\ Wait until Host responds \\ .WaitErr2 \\ JSR ReadByte:\BCS WaitErr2 :\ Flush everything from host : LDX #16:TAY .WaitErr1 DEY:BNE WaitErr1 :\ Pause a while to let Host DEX:BNE WaitErr1 :\ reconnect after an error : JSR WaitExitRelease :\ Release Tube, restore Screen LDA ERRBUF+1:ORA ERRBUF+2 :\ Check for error 0,"" BEQ P%+5:JMP ERRBUF :\ Jump to error, no return JMP STARTUP2 :\ Error 0,"" is RESET \ esc,&8n - Escape change \ ----------------------- .WaitTransfer CMP #&C0:BCS WaitStart CMP #&A0:BCS WaitEnd CMP #&90:BCS WaitEvent LSR A:ROR ESCFLG:RTS :\ Set error flag from b0 \ esc,&9x - Event \ --------------- .WaitEvent JSR WaitByte:TAY :\ Fetch event Y parameter JSR WaitByte:TAX :\ Fetch event X parameter JSR WaitByte :\ Fetch event A parameter JMP (EVNTV) :\ Dispatch to event vector \ esc,&Ax - Reserved \ ------------------ .WaitEnd CMP #&B0:BCC WaitExit :\ Return to WaitByte \ esc,&Bx - End transfer \ ---------------------- PLA:PLA:PLA:PLA:PLA:PLA :\ Pop JSR WaitCommand, A,Y,X,A PLA:PLA :\ Pop JSR Wait/ReadByte in Load/SaveLoop .WaitExitSave PLA:BPL WaitExitRelease :\ Pop transfer flag, b0=0 - Tube release ROR A:BCS WaitExitScreen :\ b0=1, Screen release .WaitExit RTS .WaitExitRelease .WaitExitScreen RTS \ esc,&C0+ - Start transfer \ ------------------------- .WaitStart PHA:LDY #4 :\ Save command, point to TRANS .WaitStartLp JSR WaitByte:STA TRANS-1,Y :\ Wait for 4-byte data address DEY:BNE WaitStartLp PLA:CMP #&D0:BCC WaitExit :\ esc,&Cx - set address for later entry \CMP #&E0:\BCC CallCode :\ esc,&Dx - enter code immediately CMP #&E0:BCS TransData :\ esc,&Ex - transfer data \ esc,&D0+ - Enter code \ --------------------- .CallCode LDA #&00:SEC:JMP (TRANS) :\ Enter code with A=0, SEC \ Decide what local memory to transfer data to/from \ ------------------------------------------------- \ A=&Ex/&Fx - Load/Save \ .TransData .WaitTransIO AND #&F0 :\ A=transfer flag with b7=1 for IO transfer PHA:LDY #0 :\ Stack IO/Screen flag, init Y=0 CMP #&F0:BCS WaitSaveIO :\ esc,&Fx - save data \ Load data from remote host \ -------------------------- .WaitLoadIO :\ esc,&Ex - load data OPT FNif(tweek3%=0) JSR WaitByte :\ Fetch byte OPT FNelse \ Within this loop, A and X are scratch registers and can be trashed JSR WaitLoadByte BNE WaitLoadIOput :\ Not , raw byte JSR WaitLoadByte BNE WaitLoadEscape :\ .WaitLoadIOput OPT FNendif STA (TRANS),Y :\ Store in I/O memory INY:BNE WaitLoadIO INC TRANS+1:JMP WaitLoadIO :\ Loop until terminated by esc,&Bx .WaitLoadEscape \ Save data to remote host \ ------------------------ .WaitSaveIO LDA (TRANS),Y:JSR SendByte :\ esc,&Fx - save data JSR ReadByte:BCS WaitSaveExit :\ Poll input for termination INY:BNE WaitSaveIO :\ Update checksum for 256 bytes INC TRANS+1:JMP WaitSaveIO :\ Loop until terminated by esc,&Bx .WaitSaveExit JMP WaitExitSave .WaitLoadByte \ Enter Code pointed to by TRANS transfer address \ =============================================== \ Checks to see if code has a ROM header, and verifies it \ if it has. CC=entered from RESET, CS=entered from OSCLI .EnterCode .EnterCodeIO PHP:LDY #&07:LDA (TRANS),Y :\ Get copyright offset CLD:CLC:ADC TRANS+0:STA FAULT+0 LDA #0:TAY:ADC TRANS+1:STA FAULT+1 :\ &FD/E=>copyright message \ \ Now check for &00,"(C)" LDY #&00:LDX #3 .EnterCheck LDA (FAULT),Y:CMP CheckCopy,X:BNE LF8FA INY:DEX:BPL EnterCheck :\ Check for &00,"(C)" \ \ &00,"(C)" exists, check ROM type byte \ ------------------------------------- LDY #&06:LDA (TRANS),Y :\ Get ROM type AND #&4F:CMP #&40:BCC NotLanguage :\ b6=0, not a language AND #&0D:BNE Not6502Code :\ type<>0 and <>2, not 6502 code .LF8FA TXA:ROL A:ROL A:AND #1 :\ A=0 - raw, A=1 - header LDX TRANS+0:LDY TRANS+1:BPL LF904 :\ Entered code<&8000, don't move memtop STX MEMTOP+0:STY MEMTOP+1 :\ Set memtop to current program STX PROG+0:STY PROG+1 :\ Set current program to address entered .LF904 PLP:JMP (TRANS) :\ Enter code with A=raw/code, Cy=RESET/OSCLI flag .CheckCopy EQUS ")C(":EQUB 0 \ \ Any existing error handler will probably have been overwritten \ So, set up new error handler before generating an error .NotLanguage JSR InitError OPT FNerror:EQUB 249:EQUS "This is not a language":EQUB 0 .Not6502Code JSR InitError OPT FNerror:EQUB 249:EQUS "This is not 6502 code":EQUB 0 \ Low level I/O routines \ ====================== \ This is where detailed playful frobbing is done to ensure a clean \ error-free reliable link channel. All calling code assumes these \ routines are 100% error-free and reliable. Any handshaking, \ retries, error correction, etc must be done at this level. \ \ Code can be made faster by migrating knowledge about the hardware \ up to the Tube I/O layer. \ \ Send a raw byte of data \ ----------------------- \ On entry, A=byte to send \ On exit, A,X,Y preserved, P corrupted \ .SendData PHA OPT FNif(TxRDY) .SendWait LDA TxStatus :\ Get Status AND #TxRDY:BEQ SendWait :\ Wait until data can be sent OPT FNendif OPT FNif(TxDelay) LDA #TxDelay:SEC :\ 6551-style TxRDY bugfix .SendWait SBC #1:BNE SendWait OPT FNendif PLA:STA TxData:RTS :\ Send data \ Read raw data \ ------------- \ On exit, P =CC, no data \ =CS, data present, EQ=esc, NE=not esc \ .ReadData OPT FNif(tweek1%) \ Check if anything squeezed in before we managed to raise RTS LDA RxStatus :\ Get RxStatus AND #RxRDY:BNE ReadDataRx :\ Data already present OPT FNendif PHP:SEI:TYA:PHA :\ Speed up by disabling IRQs OPT FNif(TxCommand=0) LDY #RxCont:STY RxStatus :\ Lower RTS to allow input OPT FNelse LDY #RxCont:STY TxCommand :\ Lower RTS to allow input OPT FNendif LDY #RxStop:LDA RxStatus :\ Get RxStatus AND #RxRDY:BNE ReadDataOk :\ Data present OPT FNif(TxCommand=0) PLA:STY RxStatus:TAY:PLP :\ Raise RTS, restore Y,P OPT FNelse PLA:STY TxCommand:TAY:PLP :\ Raise RTS, restore Y,P OPT FNendif CLC:RTS :\ CC=No data present .ReadDataOk OPT FNif(TxCommand=0) PLA:STY RxStatus:TAY:PLP :\ Raise RTS, restore Y,P OPT FNelse PLA:STY TxCommand:TAY:PLP :\ Raise RTS, restore Y,P OPT FNendif .ReadDataRx LDA RxData:CMP #esc:SEC:RTS :\ CS=Data present, EQ/NE=esc ]:IF(P%>IOSPACE%)AND(SWROM%=0):ERROR 1,"Code overrun" [OPT P*3+4 \ Spare space \ =========== EQUS STRING$((IOSPACE%-P%)AND255,CHR$255) \ I/O Space \ ========= EQUS STRING$(8,CHR$0) \ Tube I/O Registers \ ================== .TubeS1 :\ &FEF8 :EQUB 0 .TubeR1 :\ &FEF9 :EQUB 0 .TubeS2 :\ &FEFA :EQUB 0 .TubeR2 :\ &FEFB :EQUB 0 .TubeS3 :\ &FEFC :EQUB 0 .TubeR3 :\ &FEFD :EQUB 0 .TubeS4 :\ &FEFE :EQUB 0 .TubeR4 :\ &FEFF :EQUB 0 \ DEFAULT VECTOR TABLE \ ==================== .LFF00 EQUW NullReturn :\ &200 - USERV EQUW ErrorHandler :\ &202 - BRKV EQUW IRQ1Handler :\ &204 - IRQ1V EQUW IRQ2Handler :\ &206 - IRQ2V EQUW osCLI :\ &208 - CLIV EQUW osBYTE :\ &20A - BYTEV EQUW osWORD :\ &20C - WORDV EQUW osWRCH :\ &20E - WRCHV EQUW osRDCH :\ &210 - RDCHV EQUW osFILE :\ &212 - FILEV EQUW osARGS :\ &214 - ARGSV EQUW osBGET :\ &216 - BGetV EQUW osBPUT :\ &218 - BPutV EQUW osGBPB :\ &21A - GBPBV EQUW osFIND :\ &21C - FINDV EQUW NullReturn :\ &21E - FSCV EQUW NullReturn :\ &220 - EVNTV EQUW NullReturn :\ &222 - UPTV EQUW NullReturn :\ &224 - NETV EQUW NullReturn :\ &226 - VduV EQUW NullReturn :\ &228 - KEYV EQUW NullReturn :\ &22A - INSV EQUW NullReturn :\ &22C - RemV EQUW NullReturn :\ &22E - CNPV EQUW NullReturn :\ &230 - IND1V EQUW NullReturn :\ &232 - IND2V EQUW NullReturn :\ &234 - IND3V \ Print hex numbers \ ================= .PrHexXY TYA:JSR PrHex:TXA .PrHex PHA:LSR A:LSR A:LSR A:LSR A JSR PrNybble:PLA .PrNybble AND #15:CMP #10:BCC PrDigit:ADC #6 .PrDigit ADC #ASC"0":JMP OSWRCH \ Print embedded string \ ===================== \ Mustn't use LPTR so can be called from OSCLI .PrText PLA:STA TEXT+0:PLA:STA TEXT+1 :\ CTRL=>embedded string JSR PrString2:JMP (TEXT) :\ Print string and jump back to code : .PrString STX TEXT+0:STY TEXT+1 :\ CTRL=>string at YX .PrStringLp LDY #&00:LDA (TEXT),Y :\ Get character BEQ PrString2:JSR OSASCI :\ Print character if not &00 .PrString2 INC TEXT+0:BNE LFEA6:INC TEXT+1 :\ Increment address .LFEA6 TAY:BNE PrStringLp :\ Loop back if not &00 .NullReturn RTS \ Standard Tube entry points \ ========================== \ NB! All API entry points must be called in BINARY mode \ EQUS STRING$((&FF95-P%)AND255,CHR$255) .LFF95 :JMP NullReturn :\ &FF95 .OSCOLD :JMP NullReturn :\ &FF98 .PRSTRNG:JMP PrString :\ &FF9B Print zero-terminated text at YX, returns A=0, Y corrupted .LFF9E :JMP NullReturn :\ &FF9E .SCANHEX:JMP ScanHex :\ &FFA1 Scan hex string at (&F8), returned in &F0/1 .DISKACC:JMP NullReturn :\ &FFA4 .DISKCCP:JMP NullReturn :\ &FFA7 Quit current program .PRHEX :JMP PrHex :\ &FFAA Print A in hex, A corrupted .PR2HEX :JMP PrHexXY :\ &FFAD Print YX in hex, A corrupted .USERINT:JMP NullReturn :\ &FFB0 .PRTEXT :JMP PrText :\ &FFB3 Print zero-terminated inline text, returns A=0, Y corrupted .VECDEF :EQUB &36:EQUW LFF00 :\ &FFB6 .OSQUIT :JMP CmdPrompt :\ &FFB9 Enter supervisor *command prompt .OSERROR:JMP NullReturn :\ &FFBC .OSINIT :JMP InitError :\ &FFBF Initialise MOS error handler, A corrupted .DISKRST:JMP NullReturn :\ &FFC2 .LFFC5 :JMP NullReturn :\ &FFC5 .NVRDCH :JMP osRDCH :\ &FFC8 .NVWRCH :JMP osWRCH :\ &FFCB .OSFIND :JMP (FINDV) :\ &FFCE .OSGBPB :JMP (GBPBV) :\ &FFD1 .OSBPUT :JMP (BPutV) :\ &FFD4 .OSBGET :JMP (BGetV) :\ &FFD7 .OSARGS :JMP (ARGSV) :\ &FFDA .OSFILE :JMP (FILEV) :\ &FFDD .OSRDCH :JMP (RDCHV) :\ &FFE0 .OSASCI :CMP #&0D:BNE _OSWRCH :\ &FFE3 .OSNEWL :LDA #&0A:JSR OSWRCH :\ &FFE7 .OSWRCR :LDA #&0D :\ &FFEC ._OSWRCH .OSWRCH :JMP (WRCHV) :\ &FFEE .OSWORD :JMP (WORDV) :\ &FFF1 .OSBYTE :JMP (BYTEV) :\ &FFF4 .OS_CLI :JMP (CLIV) :\ &FFF7 .NMIV :EQUW NMIHandler :\ &FFFA NMI Vector .RESETV :EQUW RESET :\ &FFFC RESET Vector .IRQV :EQUW IRQHandler :\ &FFFE IRQ Vector ]:ENDPROC : : DEFPROCasmROMHeader ENDPROC