How the OS and ROM communicate - 359 bytes (2.1%)


§1. ROM header.

 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

§3. Called on a hard reset.

 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