agon-mos
agon-mos copied to clipboard
MOS should support an extension system
MOS currently offers no mechanism to add new API calls, and the only mechanism to add a star command is via executables stored on disc that will be automatically executed if an unrecognised command is typed in.
To add in a new API call, or a resident star command, requires changing the MOS source code and re-building it.
We should provide mechanisms to add in new API calls, and star commands.
Acorn's MOS in contrast allowed for extensions via ROM and sideways RAM. Unrecognised calls/commands/interrupts etc would be passed on to any resident ROMS in turn to see if they chose to handle the call. This is done via OSBYTE 143
Implementing a similar system in the Agon MOS will allow us to build OS extensions that can increase the capabilities of the system.
One example of an extension module would be for joystick interface support. There are already several different solutions to physically adding joysticks to the Agon (there are a few different hardware add-ons, and the Agon Console8 has interfaces built in). If we have a module system then we can define a single standard API to get joystick information - each different interface could provide a module that implements the standard API. Software written to use that standard API and work with all joystick interfaces.
Without such a module system a developer wishing to add joystick support to a game would need to add custom code for every joystick interface they wish to support. New joystick interfaces would be unlikely to be compatible with older games unless the interface happens to be designed to work identically to an existing interface.
This is made possible by the BBC Micro being able to page a ROM into a 16K block from address &8000; code can therefore be assembled to run at that address.
In ADL mode, the eZ80 code is compiled / assembled to the address where the code is to execute. Relocating Z80 code at runtime is not trivial.
In Z80 mode, the Z80 code runs within a 64K segment, and the segment can be relocated, but must fall on a 64K boundary. This is achieved by setting the eZ80 MB register, which effectively forms the most significant byte of the address space (bits 16-23), thus where that 64K sits in memory. So each module would occupy 64K & have to be written in Z80 mode. This precludes C, which requires ADL mode.
MOS gets around this by compiling any external MOS command at a fixed address, and this is a reserved block at the top of memory.
hey @breakintoprogram thanks for your insights. (sorry to keep you hanging - I'd intended to respond sooner - took me a while to get over a cold...)
you are of course entirely correct as to how this was practical on a Beeb. the differences and parallels are very interesting.
it's a shame that the eZ80 works in 64kb segments. the restriction of using only z80 code in a segment (and no C) feels like something that could be lived with, but the segment size is really too large to dedicate to a single module. if only there were smaller segments, like 16kb, then this approach would probably be practical, but it is not to be.
this leaves the idea of relocating (e)Z80 code. as you say, doing that at run-time is not trivial. I think though that there may be ways to make it practical.
scanning thru a binary and working out new addresses for absolute jumps, assuming you know the original execution address, and then changing them to be based off a new base address, is not really that hard. if only things were that simple. :grin:
as I see it, the main difficulty is jump tables. these are a much harder problem. it may be just about possible to work out code that's doing a jump table, but it's virtually impossible to work out where within a binary file a table begins and ends.
but I have an idea...
I did a great deal of my "learn z80" work by reading the Ms. Pac-Man code. in that code, RST 20 is a jump table handler. this means is that the pacman code only has one way of doing jump tables, so anywhere a jump table is needed there's an RST 20 instruction followed by a list of jump addresses.
what if we had a similar "jump table handler" RST in MOS?
the exact implementation used in pacman is fairly straightforward, but would not be exactly suitable for our needs - we'd probably want to add in a "length" at the beginning of the table so we know how long it is.
having such a RST call means that we could scan thru a binary to find such an instruction and therefore to find jump tables, and then be able to adjust the addresses in that table.
the remaining difficulty in relocating a module would be areas of non-code in the binary. when relocating a module there's essentially two ways to go about things - either non-code areas would need to be "marked" in some way so the relocation routine could skip over them, or the relocation routine could "walk the code" to find all the potential executable code paths. obviously there are issues around this in terms of routines that use addresses of data that need some more thought than I'm giving here in this comment. perhaps there's potential for another RST call for "get address of data block" (needs more thought).
another potential issue with relocatable ez80 code is self-modifying code, but, well, let's just ignore that. the rules for relocatable modules in MOS should include "no self-modifying code" - essentially the module is a ROM.
these ideas probably mean that relocatable MOS modules would be restricted to assembler (as who knows what a C compiler will produce) but that feels like it could be a reasonable compromise.
We could introduce a new style of binary that starts with a header telling that it's a relocatable module. Then incorporate a relative address table with relative addresses need updating in the binary after loading.
Example from MSXDOS in Z80 mode. Something similar could be made for ADL mode.
START:
LD HL, _RAT
LD BC, SOURCE
LD DE, 4000h
CALL RELOCATE
RET
_RAT: DEFW R000C-SOURCE
DEFW R001E+1-SOURCE
DEFW R002F-SOURCE
DEFW 0FFFFH
SOURCE: PUSH IX
PUSH IY
PUSH HL
PUSH DE
PUSH BC
PUSH AF
EXX
EX AF,AF'
PUSH AF
PUSH HL
R000C: LD HL,(D635E+1)
LD A,L
OR H
POP HL
LD IX,KEYINT
LD IY,(EXPTBL+0-1)
JR NZ,J6375
POP AF
R001E: LD (D635E+1),SP
R0021: LD SP,0
CALL CALSLT
DI
D635E: LD SP,0
PUSH HL
LD HL,0
R002F: LD (D635E+1),HL
POP HL
J6369: EX AF,AF'
EXX
POP AF
POP BC
POP DE
A636E: POP HL
POP IY
POP IX
EI
RET
RELOCATE: PUSH DE ; DE = destination, HL = reloctable, BC = source
EX DE,HL
AND A
SBC HL,BC ; delta dest - source
PUSH HL
POP IX ; delta in IX
EX DE,HL
POP DE
J6310: LD C,(HL) ; DE = destination, HL = ptr start reloctable, BC = source
INC HL
LD B,(HL) ; BC holds contents reloctable entry
INC HL
LD A,C
AND B
INC A
RET Z ; ffffh to signal end of relocation table
PUSH DE ; DE = destination, HL = ptr to next entry reloctable, BC reloctable entry
EX DE,HL
ADD HL,BC ; destination + reloctable entry
INC HL ; +1
LD C,(HL)
INC HL
LD B,(HL) ; BC = current contents of destination memory
PUSH HL
PUSH IX
POP HL ; HL = delta
ADD HL,BC ; calculate new value
LD C,L
LD B,H ; BC is new value
POP HL ; HL = ptr to next entry reloctable
LD (HL),B
DEC HL
LD (HL),C ; update contents of destination memory with new value
EX DE,HL
POP DE
JR J6310 ; next entry