How the OS and ROM communicate - 359 bytes (2.1%)
- §1. ROM header
- §2. ROM service entry routine
- §3. Called on a hard reset
- §4. Claim private workspace memory
Every Sideways ROM requires a header (including a copyright string) in order to be identified as a ROM. The 'ROM type' byte indicates the type of ROM: we are a service ROM (not a language ROM) with a service entry point and it is written in 6502.
* = $8000 !text "RCM" normally a language entry point, ('JMP address') but this is not a language ROM so these bytes are unused. 'RCM' is probably the initials of Richard Manby who wrote most of this code. JMP .serviceEntryRoutine service entry point !byte $82 ROM type byte: $82 means it's not a language ROM, has a service entry point, and is 6502 code (not BASIC) !byte .copyrightString - 1 - $8000 offset to copyright string (-1) !byte $01 binary version number .titleString = $8009 !text "Graphics Extension ROM ",.fullVersion,.charLF,.charCR !byte 0 terminator (no version string) .copyrightString = $8027 !text "(C)1985 Acornsoft" copyright string !byte 0 terminator
§2. ROM service entry routine.
ROMs with a service entry point (as indicated by the ROM type byte in the ROM header) receive calls from the OS. The service call type is passed in the accumulator. If the ROM handles the call, it returns with A=0. If the service call is not handled, the accumulator must be returned unchanged. The GXR handles four of the service calls, as shown below.
.serviceEntryRoutine = $8039 Check for service call to claim private workspace CMP #2 relative private workspace claim? BEQ .serviceRelativePrivateWorkspaceClaim branch if so Check for service call indicating an unknown OSBYTE (*FX) call CMP #7 unrecognised OSBYTE call? BNE + if (not OSBYTE) then branch JMP .handleOSBYTECall + Check for service call indicating *HELP CMP #9 *HELP command? BNE + if (not *HELP) then branch JMP .handleStarHelp + Check for service call indicating an unrecognised *command CMP #4 unrecognised star command? BNE + if (not star command) then branch JMP .handleUnrecognisedStarCommand + RTS exit with A preserved, so call is passed on to the next ROM
Reset the ROM workspace byte to zero (this marks the ROM as inactive, no memory claimed yet), and check the ROM number. If odd, then we initialise the ROM, otherwise we remain inactive and pass on the service call to the next ROM. See the next section for more of an explanation. On Entry: X is the ROM number
.lastResetWasHardReset = $8053 LDA #0 store zero into ROM workspace byte STA .romWorkspaceBytes,X (high byte of private workspace) TXA !if MACHINE = BBC_B { AND #1 } else if MACHINE = BBC_B_PLUS { AND #2 } else if MACHINE = ELECTRON { NOTE: What's the best rule for deciding default on/off for an Electron? I suspect a 1980s cartridge release for use with the Plus 1 would have done AND #2 so one cartridge would enable it by default and the other wouldn't. AND #1 } else { +unknown_machine } BNE .claimWorkspace if (ROM number is odd) then branch .passOnToNextROM = $805d LDA #2 set A to 'relative private workspace RTS claim' for the next ROM to check
§4. Claim private workspace memory.
On a reset, the ROM receives a service call to claim the private workspace memory it needs. Private workspace is memory required by the GXR (a) to store the current state of the system, (b) for a flood fill buffer and (c) to store sprites. There is one case where the GXR is not enabled. On a power on or hard reset (CTRL-Break) if the GXR is in an even numbered ROM socket it does not claim any memory and is inactive until the *GXR command is given. The inactive GXR state is a useful default as it doesn't consume memory (doesn't raise PAGE), which generally gives better compatibility with games and other software. The ROM workspace byte: Each ROM can store a single byte of state at .romWorkspaceBytes,X (where X is the ROM number) * An inactive GXR has the byte set to zero. * If the GXR is becoming active, then it is one. * If the GXR is becoming inactive, then it is zero. * If the GXR is already active, then it stores the page number of its private workspace. An active GXR ROM can be made inactive using *NOGXR. Since this memory allocation only happens on a reset, the user is prompted to 'Press BREAK' after using any of the *commands that change the required memory use.
.serviceRelativePrivateWorkspaceClaim = $8060 Remember parameters STA .vduWorkspaceA store service call number (A=2) STY .vduWorkspaceC store Y=first available page JSR .getPrivateWorkspaceAddress LDA .privateWorkspaceHigh BEQ + if (no workspace set) then branch Check if soft character definitions have already been cached LDY #.workspaceOffsetHasCachedSoftCharacterDefinitions LDA (.privateWorkspaceLow),Y BPL + if (not cached already) then branch Restore the character definitions if they were already cached in the workspace area JSR .copyWorkspaceCacheBackIntoCharacterDefinitons + Store the first available page as our workspace address LDY .vduWorkspaceC private workspace page STY .privateWorkspaceHigh LDA .lastResetType what type of reset was last done? AND #3 0 Soft reset (BREAK) 1 Power on 2 Hard reset (CTRL-BREAK) [NOTE: Arguably should use OSBYTE $FD] BNE .lastResetWasHardReset if (not a soft reset) then branch Soft reset LDA .romWorkspaceBytes,X (high byte of private workspace) BEQ .passOnToNextROM if (GXR not active) then branch (pass on to next ROM) .claimWorkspace = $8087 TYA A = Y = first available page CMP .romWorkspaceBytes,X have we already got this space? BEQ .alreadyClaimed if (already claimed) then branch not already claimed, so claim it STA .romWorkspaceBytes,X store amount of claimed space No sprite chosen by default LDA #0 LDY #.workspaceOffsetChosenSpriteIndex STA (.privateWorkspaceLow),Y workspace[chosen sprite index] = 0 Zero sprites initially LDY #.workspaceOffsetNumberOfSprites STA (.privateWorkspaceLow),Y workspace[number of sprites] = 0 Zero sprite pages DEY Y=#.workspaceOffsetSpritePages STA (.privateWorkspaceLow),Y workspace[sprite pages] = 0 Flood fill enabled by default DEY Y=#.workspaceOffsetOptions LDA #$80 STA (.privateWorkspaceLow),Y workspace[options] = $80 (flood enabled) Record three pages used DEY Y=#.workspaceOffsetTotalPagesWithoutSprites LDA #3 STA (.privateWorkspaceLow),Y workspace[#pages without sprites] = 3 .alreadyClaimed = $80a7 Store sprite address LDY #.workspaceOffsetChosenSpriteAddressLow } LDA (.privateWorkspaceLow),Y } STA .vduTempStoreDC } INY } tempStoreDC/DD = sprite address LDA (.privateWorkspaceLow),Y } STA .vduTempStoreDD } Check for an existing chosen sprite INY Y=#.workspaceOffsetChosenSpriteIndex LDA (.privateWorkspaceLow),Y BEQ + if (no chosen sprite) then branch Set currently chosen sprite to zero TAX X = chosen sprite LDA #0 } STA (.privateWorkspaceLow),Y } workspace[currently chosen sprite] } = 0 JSR .addSpriteLoop ensure the sprite is back in sprite memory properly + Check if flood fill is pending, due to be activated LDY #.workspaceOffsetOptions } LDA (.privateWorkspaceLow),Y } AND #$C0 } Check top two bits of options. CMP #$40 } check top bit is clear (previously } non flood mode) and bit 6 is set } (flood mode is pending) BNE .notActivatingFlood } if (we are not activating flood) } then branch Activate flood mode ASL A=$80 (shifts bit 6 to bit 7) making flood enabled STA (.privateWorkspaceLow),Y store in options INY Y=#.workspaceOffsetSpritePages LDA (.privateWorkspaceLow),Y BEQ .notActivatingFlood if (no sprite pages) then branch Copy all the sprite data 2 pages further in memory to make room for the two pages of flood fill data STA .vduTempStoreDF tempStoreDF = number of sprite pages LDA #0 } STA .vduTempStoreDA } set low bytes to zero STA .vduTempStoreDC } STA .vduTempStoreDE } CLC LDY #.workspaceOffsetSpriteStartPage } LDA (.privateWorkspaceLow),Y } tempStoreDD = sprite start page STA .vduTempStoreDD } ADC #2 STA .vduTempStoreDB tempStoreDB = sprite data plus two pages JSR .blockCopyMemoryDecrementing copy tempStoreDE/DF bytes from tempStoreDC/DD to tempStoreDA/DB .notActivatingFlood = $80e9 Calculate how many pages we use (1 or 3) until the sprite data LDY #.workspaceOffsetOptions LDA (.privateWorkspaceLow),Y A = workspace[options] ASL carry = flood enabled LDA #$80 A = $80 ROL A = carry, carry = 1 ROL A = 2*carry + 1 = number of pages required (1 or 3), carry = 0 ADC .privateWorkspaceHigh add current workspace page Store sprite start page LDY #.workspaceOffsetSpriteStartPage STA (.privateWorkspaceLow),Y set workspace[sprite page] = page after workspace JSR .resetCurrentSpriteAddress Copy the code at .gxrOSWRCH into our workspace so we can intercept OSWRCH calls. LDY #.notCompatible - .gxrOSWRCH - 1 - LDA .gxrOSWRCH,Y STA (.privateWorkspaceLow),Y DEY BPL - Patch in the appropriate addresses / values into the code LDY #.jmpOldWRCHPatch - .gxrOSWRCH } LDA .vectorWRCHVLow } PHA } STA (.privateWorkspaceLow),Y } workspace[patch] = } vectorWRCHVLow/High INY } LDA .vectorWRCHVHigh } STA (.privateWorkspaceLow),Y } LDY #.jsrOldWRCHPatch + 1 - .gxrOSWRCH } STA (.privateWorkspaceLow),Y } workspace[patch + 1] = } vectorWRCHVLow/High DEY } PLA } STA (.privateWorkspaceLow),Y } LDY #.ldaOurWRCHHighPatch - .gxrOSWRCH LDA .vduWorkspaceC insert high byte of our code into STA (.privateWorkspaceLow),Y .gxrOSWRCH to write into jump vector LDY #.ldaOurRomBankPatch - .gxrOSWRCH LDA .currentlySelectedROM insert currently selected ROM into STA (.privateWorkspaceLow),Y .gxrOSWRCH code at label '.ldaOurRomBankPatch' JSR .swapWorkspaceWithVDUVectors swap in our VDUV routine Handle the extended VDU vector LDA #<.extendedVectorTableVDUV } STA .vectorVDUVLow } LDA #>.extendedVectorTableVDUV } Extended vector E_VDUV at $FF39 STA .vectorVDUVHigh } LDA #<.extendedVectorVDURoutine } STA .extendedVDUVLow } LDA #>.extendedVectorVDURoutine } STA .extendedVDUVHigh } Point at our extended vector } routine LDA .currentlySelectedROM } and ROM number STA .extendedVDUVROM } Reset patterns JSR .resetPatternFillsToDefaults Reset dot-dash pattern LDA #0 JSR .setDotPatternAndRepeat Set the OSWRCH vector to point to our gxrOSWRCH (at the start of our private workspace) CLC LDA #0 STA .vectorWRCHVLow LDA .vduWorkspaceC STA .vectorWRCHVHigh Calculate sprite end page = sprite start page initially LDY #.workspaceOffsetTotalPagesWithoutSprites ADC (.privateWorkspaceLow),Y add total pages without sprites LDY #.workspaceOffsetSpriteEndPage STA (.privateWorkspaceLow),Y store as end page Leave with Y=end page (i.e. next unused page), X=ROM number, A=2 (the service call number) TAY LDX .currentlySelectedROM LDA .vduWorkspaceA A=2 RTS