Tube Documentation Errors
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 BBC B+ 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 shouldn't also allow access to sideways ROMs with &FFFrxxxx and screen memory with &FFFExxxx, 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 15 to write the RTC, but OSWORD 14 reading the RTC omits the terminating <cr>.
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 sends and waits 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 out of sequence.
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.
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.
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, for example, the stack is at the top of user memory at &F000 and a language is copied over to the top of memory at &B000-&CFFF. The stack will be trampled on before the code can be entered. OSCLI and OSBYTE should use a temporary stack to prevent this, and the Z80 BASIC ROM patches the client code to do just 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 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.
Suggestion for reading memory limits with OSBYTE
OSBYTE &83 and &84 read the bottom and the top of available memory. OSBYTE &82 reads the high order word of the memory map. On the 6502 client, for example, OSBYTE &82 returns &0000 and OSBYTE &83 returns &0800, showing that the bottom of free memory is at &00000800.
A client with a memory map larger than 64K could allow programs to call OSBYTEs &82,&83,&84 in sequence to find the 32-bit memory size. A suggestion is for programs to do:
membot=OSBYTE(&83) :REM Read membot b0-b15 membot=membot OR OSBYTE(&82)<<16 :REM Read membot b16-b31 memtop=OSBYTE(&84) :REM Read memtop b0-b15 memtop=memtop OR OSBYTE(&82)<<16 :REM Read memtop b16-b31
To implement this, the client code should be set up so that OSBYTE &82 returns the top 16 bits of the lower memory limit. When OSBYTE &84 is called it should flag internally that the next OSBYTE &82 should return the top 16 bits of the upper memory limit. Any OSBYTE call other than &83 should reset the OSBYTE &82 behaviour to return the lower memory limit.
This is compatible with 16-bit memory maps as OSBYTE &82 will return the same value in all cases, and is compatible with current 32-bit clients as OSBYTE &82 returns &0000, indicating that the bottom of memory is in the bottom 64K.
This could be implemented in ARM with something like the following:
.ByteHi CMP R0,#&83 STRMNE (sp)!,R1 LDRBNE R1,[membot+2] STRBNE R1,[topword+0] LDRBNE R1,[membot+3] STRBNE R1,[topword+1] LDRMNE (sp)!,R1 BEQ Byte83 CMP R0,#&84 BEQ Byte84 CMP R0,#&82 BEQ Byte82 ... process OSBYTE call .Byte82 LDRB R1,[topword+0] LDRB R2,[topword+1] MOV pc,link .Byte83 LDRB R1,[membot+0] LDRB R2,[membot+1] MOV pc,link .Byte84 LDRB R1,[memtop+2] LDRB R2,[memtop+3] STRB R1,[topword+0] STRB R2,[topword+1] LDRB R1,[memtop+0] LDRB R2,[memtop+1] MOV pc,link .membot EQUD memorymin .memtop EQUD memorymax .topword EQUW memorymin << 16