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.
