OS RAM advanced software example

Introduction

This example demostrates running 6502 code directly from the OS RAM module.

It copies the operating system ROM into the RAM but alters the IRQ vector to insert a function before the OS handler executes. The OS runs from RAM bank A.

OS RAM Bank B could then be used from BASIC while the OS copy is running from bank A.

Source

   10 MODE 7
   20 DIM mc 512
   30 DIM page 256
   40 bank%=0
   50 bank2%=bank% EOR 1
   60 PROCmode_rom:REM start from known mode
   70 PROCmode_romram
   90 REM copy OS
  100 PRINT"Copy OS":PROCcopyos
  130 REM copy top OS page
  140 PRINT'"Store OS top page"
  150 PROCmode_rom
  160 FORI%=0TO&FFSTEP4:page!I%=I%!&FF00:NEXT
  180 PRINT"Assemble IRQ handler & init"
  190 PROCmode_romram
  200 PROCbank(bank2%)
  210 PROCassemble
  230 PRINT"Run init"
  240 CALL init
  260 PROCmode_ram(bank%)
  280 STOP
  300 DEFPROCmode_rom:?&FF00=2:ENDPROC
  310 DEFPROCmode_romram:!&FF00=&600:ENDPROC
  320 DEFPROCmode_ram(B%):?&FF00=4+B%:ENDPROC
  340 DEFPROCbank(B%):?&FF00=8+B%:ENDPROC
  350 DEFPROCbankwrite(B%):?&FF00=&A+B%:ENDPROC
  360 DEFPROCbankread(B%):?&FF00=&C+B%:ENDPROC
  370 DEFPROCbankrnw(B%):?&FF00=&E+B%:ENDPROC
  390 REM copy OS
  400 DEFPROCcopyos
  410 FORJ%=&C000TO&FBFFSTEP256
  420   PRINT;~J%;" ";
  430   PROCmode_rom
  440   FORI%=0TO&FFSTEP4:page!I%=J%!I%:NEXT
  450   PROCmode_romram
  460   PROCbank(bank%)
  470   FORI%=0TO&FFSTEP4:J%!I%=page!I%:NEXT
  480   NEXT
  490 ENDPROC
  510 REM assemble irq handler and init
  520 DEFPROCassemble
  530 FORpass%=0TO3STEP3
  540   P%=mc
  550   [opt pass%
  560   .irq_handler
  570   php:inc &7C00:plp:jmp &DC1C
  580   .init
  590   lda #8+bank%:sta &FF00 \ set OS copy bank
  600   sei:lda #4+bank2%:sta &FF00:jsr inithigh \RAM exec
  610   lda #6:sta &FF00 \switch to romram
  620   cli:RTS
  630   ]
  650   P%=&C000
  660   [opt pass%
  670   .inithigh
  680   ldx #0
  690   .tploop:lda page,X:sta &FF00,X:inx:bne tploop \copy top page
  700   lda #irq_handler DIV 256:sta &FFFF:lda #irq_handler AND &FF:sta &FFFE \ patch IRQ
  710   RTS
  720   ]
  740   NEXT
  750 ENDPROC

Operation

The program first copies the OS ROM into bank A.

In order to copy the top page with the interrupt vectors code must execute from the top 16K. So next it assembles an interrupt handler to main memory and an initialisation function to bank B.

The initialisation function runs and copies the top page to bank A but overwrites the IRQ handler address with our hook function.

Lastly it selects execute from RAM bank A mode. The OS is now running from the RAM copy and our hook function is being called before the OS handles every IRQ!

Bank B is now free for use by BASIC or for more code/data.

Key lines & function

L 20,30: Reserve memory for the 6502 assembly code and a temporary page copy buffer
L 40,50: Set which bank to use in bank%
L 70: Enable ROM+RAM mode
L 100: Copy the OS to OS RAM
L 160: Copy the OS page &FF to temporary buffer
L 200: Select bank2 for 6502 init code
L 210: Assemble 6502 intialisation code
L 240: Perform initialisation
L 260: Switch to RAM copy of operating system

L 300-490: Helper functions

L 510: Assemble init code function
L 560,570: New injected IRQ function. Then jump to OS function.
L 580: Dnit entry point
L 600: Disable interrupts, execute from bank B, call high init function
L 610,620: Cleanup and return to BASIC
L 670: High init entry point
L 680,690: Copy page &FF from temporary buffer to bank A top page
L 700: Overwrite IRQ vector with our hook function

User Code Notes

Initialisation

To run code from an OS RAM bank you must set up handlers for IRQ, NMI and RESET and set the vector values. The top page (&FF) can only be read/written by code running from the top 16K of the address space.

A minimal intialistion routine would just set the three 6502 vectors.

The rest of the top page is available for user code.

Don't assume power on OS RAM register values during initialisation. Explicity set mode and bank during init.

Register access

The OS RAM control register is only writeable by code running in the lower 48K of memory (main memory or paged ROM). As such any routines which need to change OS RAM mode or set bank R/W/E registers must run from the lower 48K.

Code running from the top 16K sees RAM in the top page.

Bank Access

Read, write and execute banks are stored in independent registers.

User code can execute from one bank and read/write to the other bank allowing all 30.5K to be used with no runtime bank switching after initialisation.

In this example the init code executing in bank B writes the top page for bank A.

OS access

Running in RAM mode evicts the OS. If you want to call OS routines then you need to marshal these calls via a low memory function. This function would need to disable RAM mode, call the OS routine, enable RAM mode and return.

One way to handle this in a generic way would be to zero out all the OS ROM entry addresses in the top page of OS RAM. Then when you call any OS function address (e.g. OSBYTE &FFF4) a BRK instruction will execute calling your IRQ handler. The IRQ handler needs to detect the BRK, fetch the function address off the stack, disable RAM mode, call the OS function, enable RAM mode and return from interrupt. OS functions are now available again to all code including filesystems.

Reset

The OS RAM module retains the current settings across reset. Only a power cycle clears the module registers. This is desirable when running a different OS as the replacement OS reset handler will be called.

User applications may wish to revert to the OS on BREAK - especially during development to avoid accidentally latching the machine up while bug fixing. Just reselect ROM mode in the reset handler and then jump to the OS reset handler (the user handler would need to be in the lower 48K to access the OS RAM register).

.reset:lda #2:sta &FF00:JMP &FFFC
Loading code/data

6502 code and data can be loaded to OS RAM by *LOAD or the OS file functions from ROMRAM mode. Except for the top page which would need initialising from code running in the top 16K.

Hardware IO

Memory mapped IO is always visible in the address space regardless of the 6502 execution address, OS RAM mode or bank settings.