Errors and bugs in Tube documentation and code ============================================== Acorn Application Note 004 "Tube Application Note" details the Acorn Tube interface and software protocols. In the process of converting the PDF version to text and documenting existing Tube host and client code I have noticed several documentary errors and code bugs. OSBYTE &8E, page 22 ~~~~~~~~~~~~~~~~~~~ When the client calls OSBYTE, after sending the parameters it should check for OSBYTE &8E and jump to check for a single acknowledge byte indicating if code is to be entered. OSBYTE &8E is 'Select language', and the only return is either an error (eg 'This is not a language') or a background data transfer and a call to enter code. The 6502 clients correctly checks this, but the Z80 client does not. Doing A%=142:X%=nn:CALL &FFF4 will cause the client to hang waiting for a full three-byte OSBYTE acknowledge when only a single data execution acknowldge is sent. Doing '*FX142,nn' works correctly as that is processed as an OSCLI call, and OSCLI waits for a code entry acknowldgement byte. Page 22 should read: IF osbyteno=&9D THEN RETURN from protocol (no reply) IF osbyteno=&8E THEN [ Wait for data in R2DATA and read it If this byte=&80 then code has been loaded as a result of the OSBYTE, and it should be entered at the address given by the last R4 protocol type 4 address. ] OSWORD control block lengths, p23 (also p4,5) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSWORD 5 is listed as sending a two-byte control block. OSWORD 5 is 'read byte in I/O memory', with XY%?0 to XY?3 containing the address to be read. Sending the bottom two bytes is sufficient for reading a location with 64K of memory, but really the whole 4-byte address should be sent. The 65C102 CoProcessor v1.10 client sends four bytes, and the Master is able to use the top two bytes to distinguish between IO memory at &FFFFxxxx and screen memory at &FFFExxxx. There is no reason why OSWORD 5 in the host doesn't also allow access to sideways ROMs with &FFFrxxxx, so really a Tube client should send a full four bytes. Mention is made of Tube clients sending and receiving 16 bytes for OSWORD 14 and OSWORD 15. This causes problems if the host implements a Real Time Clock as OSWORD 14 returns a 25-character string. In fact, the both the 6502 TUBE v1.10 client and the 65C102 CoProcessor v1.10 client: sends 1 and receives 24 bytes for OSWORD 14 and sends 32 and receives 0 bytes for OSWORD 15 This results in the full clock string being sent with OSWORD 14, but OSWORD 14 omits the terminating . When OSWORD calls with A>&7F are sent, the sent and received control block lengths are in the first two bytes of the control block. The application note omits to mention that lengths of &81 to &FF should send and receive *no* data, they should be treated the same as a length of zero. "Up to 128 bytes" is mentioned on page 5 and page 10, but it should be specified in the protocol description on page 23. All hosts treat &81 to &FF as being zero. The 6502 clients correctly send and wait for no data for &81 to &FF, but the Z80 v1.20 client does not. It sends up to 255 bytes and waits for up to 255 bytes, causing the Tube syncronisation to go off. OSFILE object type, p24 ~~~~~~~~~~~~~~~~~~~~~~~ The protocol states that R2DATA AND &7F is the object type. But why AND &7F? This loses bit 7. The 6502 clients do not do this, returning the full 8-bit object type, but the Z80 client does. Object type &FF is defined in other Acorn documents as being a Run-Only file, but doing AND &7F means that object type &FF is returned as &7F. Execute in Client Processor, p26 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While page 9 states that Transfer Type 4 is "execute in client processor", it actually only passes an address to the client; the client picks up that address in the foreground when the foreground action has completed, eg OSCLI, OSBYTE &8E or STARTUP. It does not cause the client to call code from the interupt code. Econet documentation implies that a RemoteJSR can cause a jump to an address in the client processor, but there is nothing in the client code to let this happen. A jump to code can only happen when OSCLI, OSBYTE &8E or STARTUP waits for an acknowledgement byte. Data transfer Sync Bytes, p26, 27 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The documentation for Transfer Types 4, 6 and 7 shouldn't really say "Wait for data in R4DATA, discard it", it should say "Wait for and remove synchronising byte from R4DATA", as with transfers 1 to 3. STARTUP procedure ~~~~~~~~~~~~~~~~~ In the startup sequence the client should do something sensible if the startup acknowledge is not &80, ie, there is no execute address to enter. This will usually be because a Soft Break occured, and the host has not recopied over the current language. The client should enter the current program at its entry address. If this is not possible, the convention is to set up an error handler and enter a '*' command prompt loop. The Z80 client deviates from this in that after the startup sequence it ignores the acknowledge and only enters the R4 transfer address if the startup was a Hard Break or PowerOn Reset, otherwise it enters a '*' command prompt loop. The Z80 BASIC ROM has to pretend a Soft Break is a Hard Break to trick the host into sending over the current language and to trick the client into expecting it. If a program entered on the client relocates itself, it should restart itself via the program entry mechanism so the client's current program address is updated. Otherwise, on a Soft Break the client will try to reenter the program at the address it was copied over to. This is usually done by constructing a *GO command and passing it to OSCLI, as VIEW does. This implies that the client should implement a '*GO' command to set a new current program address and enter it. Transfer Acknowldgements ~~~~~~~~~~~~~~~~~~~~~~~~ While the documentation, and the host code, transfers &7F/&80 acknowledges, the client code should just check bit 7 rather than an exact match for &7F/&80. Bugs in Client Code ~~~~~~~~~~~~~~~~~~~ The Z80 v1.20 client code is fairly untidy, has quite a bit of duplicated code, and has some bugs in it. There are only two that cause problems. A data transfer can overwrite the stack. This can occur if the stack is at the top of user memory at &F000 and a language is copied over to the top of memory at &B000-&EFFF. The stack will be trampled on before the code can be entered. OSCLI and OSBYTE should use a temporary stack to prevent this. When entering code, the stack is left unbalanced if the code does not have a ROM header. The naughty code is: LD A,(HL) CP ASC")" JR NZ,LF826 ; No ROM header, enter as raw code POP HL ; Get address of copyright message DEC HL ; Point to ROM type ; some other processing .LF826 LD HL,(PROG) ; Fetch program address JP (HL) ; And enter it If there is no ROM header the address that was examined for a copyright message is left on the stack, and the code cannot be RETurned from. A solution is for transient code to have a ROM header, but this would make the transient code the current program which may not be what is wanted. A better solution is for transient Z80 code to start with a POP to balance the stack. The Z80 BASIC ROM fixes this problem by balancing the stack before entering. In order that transient code can tell whether it has been called by the unbalanced code or the balanced code with the BASIC ROM the flags are set differently. If the stack is unbalanced, NZ is set If the stack is balanced, Z is set, so transient code can start with: JR Z,P%+3:POP AF The Z80 client EnterCode routine always sets the current program address to the entered address. It really should only do this for code with a ROM header. This is ineffectual though as STARTUP ignores the startup acknowledgement which would tell the client to re-enter the current program. The following is more of an oddity than a bug. On entering a language, the BBC sets the fault pointer (last error message) to the version string after the ROM title. The Master and all Tube clients set the fault pointer to the start of the copyright message. This ends up being the same thing if there is no version string: Header with version string: Header with no version string: EQUB OffsetToCopyright EQUB OffsetToCopyright EQUB version EQUB version EQUS "TITLE" EQUS "TITLE" EQUB 0 EQUS "0.12 (12 Jan 1900)" EQUB 0 EQUB 0 EQUS "(C) etc..." EQUS "(C) etc..." EQUB 0 EQUB 0