mailto: blog -at- heyrick -dot- eu

Dumping the MEMC CAM table

This is something geeky to do with thirty year old hardware. If that doesn't sound like your cup of tea, don't bother reading the rest. There are no kitten pictures at the end. ☺


Why fiddle with the MEMC?

A recent discussion on the ROOL forums talked about the MEMC and how it seemed to use 8KiB memory pages on a 512KiB machine. Which was unusual.

As to the "why", well... the oldest reason in the book - because we can.


Some history

Rewinding a little: the original Acorn Archimedes range (circa 1987) comprised of four ICs. The ARM, which was the processor. The MEMC, which was the MEMory Controller. The IOC, which was the Input/Output Controller. And finally the VIDC, which was the VIDeo Controller.
I think they were also known as ARM, Anna, Arabella, and Albion respectively.

These four devices, when hooked together, formed the core of a small microcomputer system. The ARM was the processor. The VIDC dealt with video and sound. The IOC dealt with interfacing to the outside world, by onboard devices such as IIC, keyboard, and mouse as well as the podule bus for expansion card. Tying all of this together, along with ROM and DRAM, is the MEMC that organised the memory mapping and access rights.


The MEMC in more detail

Each MEMC could cope with up to 4MiB of DRAM. Some machines (such as the A540) had as much as 16MiB of memory onboard. This was achieved by chaining multiple MEMCs together. Ostensibly they operated in a master/slave (or primary/secondary if you're a snowflake) arrangement. How two became four is... clever hardware.

The MEMC operates backwards to a traditional MMU. Each MEMC has a lookup table of 128 entries, which is intended as a descriptor for every page of physical memory. It breaks the logical memory into 128 pages, which means the page size changes depending on how much memory is available. For 512KiB or less, the page size is normally 4KiB. For 1MiB, the pages are 8KiB. 2MiB gives 16KiB pages. And finally, the full 4MiB would give 32KiB pages.
This is not a hard and fast rule. Arthur 0.30 breaks its 1MiB into 32 pages of 32KiB size. And it seems all versions of RISC OS treat the A305's 512KiB as 64 pages of 8KiB size. In the first case (Arthur), this was probably just hardwired in to get the system running. In the other cases, it may have to do with how the memory is decoded, that using 128 4KiB pages might have suffered overlaps?

The first 32MiB are the logical pages. These are treated as a set of sequential pages, the exact number depending on the page size selected. If the page size is 4KiB, then there would be 8192 logical pages. If the page size is 32KiB, then there are 512 logical pages.
When the page is accessed, the MEMC attempts to convert the logical page into a physical page. If the mapping exists, and the request is in a mode of suitable privilege, then the access will be directed to the appropriate physical page.
The way that RISC OS task swaps is that every application believes it is loaded and running at &8000 onwards (according to how much memory it has claimed). What the Wimp does is fiddle the memory translation, so each task actually is at &8000 when it is running. When it yields, that task will be shuffled out and another moved to &8000 for the duration of its time of activity. What that task yields, repeat...

Back in those days, a software page table would have been very slow (you'll see how a system slows down when virtual memory kicks in). As for a full table walker and TLB as is common these days? Much more complicated to implement. The MEMC was designed to be simple and fast.

The next 16MiB (from address &2000000) is the physical memory. The 128 pages of the MEMC relate to this area (rolling over if necessary). With a 32KiB page, for example, the physical memory is broken into 512 pages, populated in the MEMC's table as appropriate for how much is actually present. Now, since each MEMC only has a table of 128 pages and the ability to address up to 4MiB, to actually address 16MiB would require four MEMCs (4*4=16), which would provide a page table of 128*4, or 512 pages, thus allowing all of the physical memory to be addressed.

It's worth mentioning that the processor is capable of accessing memory either by using the logically mapped pages, or by addressing the physical memory directly.

Why the change in page size? Perhaps because on a lower memory machine it is better to have a smaller granularity of paging. If you, for example, only have 1MiB, that means your entire memory would be represented in 32 chunks if you had a 32KiB page size. Changing the size of anything (font area, application slot, RMA...) would would in 32KiB increments and you would rapidly see that memory is wasted if, for instance, you had an 8KiB program claiming a 32KiB slot. So by spreading out the 128 potential pages to fit the amount of RAM present, your 1MiB machine would have 128 pages of 8KiB each, meaning a finer control of how much memory is allocated to things.
Of course, this means that on a machine of 4MiB or more, you may waste memory with a 32KiB page size, but on the other hand you just have that much more memory to be working with.

To put this into context, a 1MiB RISC OS 2.00 machine starts up with 80KiB for the screen (MODE 12), 32KiB workspace, 16KiB system heap/stack, 112KiB RMA, 24KiB font cache, and 8KiB system sprites. Leaving you 720KiB. You can reduce some of that to get a maximum of 776KiB.
RISC OS 3 is worse, 32KiB system heap/stack, 32KiB accounted for page zero, and 32KiB font area, and no system sprites leaving you 704KiB. You can't shrink the RMA, the system heap/stack will go down to 24KiB, and the font cache can be removed, leaving a maximum of 744KiB.
And, just think, I once used exactly that as a proper DTP system creating documents that were printed at 300dpi on a laser printer!

The next 4MiB (from address &3000000) is reserved for I/O devices. This has special significance as it engages I/O timing. This is fixed in memory.

The final section, &34000000 to &3FFFFFF, serves a dual purpose. If this area is read, then it will be a read from ROM. The ROM is split into two banks, a 4MiB Low ROM at &3400000 and an 8MiB High ROM at &3800000. RISC OS uses the high bank, so the ROMs of a RISC OS (and Arthur) machine begin at &3800000.
If this area is written, it is a write to the VIDC (&3400000 to &35FFFFF) or the DMA address generators and MEMC control register (&3600000 to &37FFFFF), or the page table translator (&3800000 to &3FFFFFF). The reason for the large address space of the latter two is because the MEMC is not connected to the processor's data bus. It is sent information by encoding it on the address bus, and accessing these addresses.

The information actually held within the MEMC is not the same as the CAM within RISC OS. The MEMC holds a list of entries containing a logical page number and the associated physical page number. Generally programming is acomplished by writing to the address: %111LLLLLLLLLLLLLAAXPPPPPPP
Where L is the logical page, P is the physical page, A is the access rights, and X is undefined. This example is for 32KiB pages. P and A are always th same. How much of L is used depends upon the page size.

This all adds up to 64MiB, which is the most that can be addressed by an ARM when using a 26 bit program counter (the other bits being status flags).


Working out how it all fits together

Poking around in the sources to RISC OS 2.00, it was possible to work out where things are, and how memory is arranged. The top of MemSize begins with a résumé of the actual testing performed, which helps to explain what the code is doing, and why RISC OS uses 8KiB pages for a 512KiB system.
; Set MEMC for 32-k page then analyse signature of possible
; external RAM configurations...
; The configurations are:
; Ram Size    Page Size    Configuration    (Phys RAM) Signature
;   4Mbyte      32k          32*1Mx1         A13,A21,A20 distinct
;   4Mbyte      32k         4*8*256kx4       A13,A21,A20 distinct
;   2Mbyte      32k    expandable 2*8*256kx4 A13,A20 distinct, A21 fails
;   2Mbyte      16k      fixed 2*8*256kx4    A13,A21 distinct, A20 fails
;   1Mbyte       8k          32*256kx1       A13,A20 fail, A19,A18,A12 distinct
;   1Mbyte       8k           8*256kx1       A13,A20 fail, A19,A18,A12 distinct
;   1Mbyte       8k          4*8*64kx4       A13,A20 fail, A19,A18,A12 distinct
; 512Kbyte       8k    expandable 2*8*64kx4  A13,A20,A19 fail, A12,A18 distinct
; 512Kbyte       4k      fixed 2*8*64kx4     A13,A21,A12 fail, A21,A19 distinct
; 256Kbyte       4K           8*64kx4        A13,A20,A12,A18 fail, A21,A19 ok  
; 256Kbyte       4K          32*64kx1        A13,A20,A12,A18 fail, A21,A19 ok  

It looks like the only way to determine the difference between the original MEMC and the improved MEMC1a is by running some difficult code and timing it. There's nothing in the control register that indicates which is which... which is to be expected given the control register is write only.


Memory mapping in practice

On the MEMC machines, we can split them into two - and old and a new.

The old is Arthur 0.30, Arthur 1.20, and RISC OS 2.00. This has a word representing the RAM size at &160, and a page table from &164. On RISC OS 2.00, it is 256 words long, even though the OS itself is only capable of using one MEMC (thus up to 128 words would be used).

The new is RISC OS 2.01, RISC OS 3.00, and RISC OS 3.1x. The word giving the memory size is at &56C. There is a word at &564 that gives the address of the page table. If there are one or two MEMCs (up to 8MiB), then the page table will be located at &164. If there is 12MiB or 16MiB of RAM (3 or 4 MEMCs), then the page table will be located elsewhere. The word at &564 will point to it, and the memory at &164 will be unused.

This code has been written to be entirely 26/32 bit neutral. It was developed on a Pi2, and runs on the very first Acorn ARM system - an ARM2 machine running Arthur 0.30.
Which looked like this:

The Arthur 0.30 desktop
The Arthur 0.30 desktop

Strangely enough, when run, it reports a peculiar memory arrangement:

Arthur 0.30 page mapping
Arthur 0.30 page mapping
This version of Arthur seems be hardwired to 1MiB of memory (give it more, it doesn't see it; give it less, it hangs). I would imagine this was primarily a development version where the memory was hardwired to 1MiB, and the page size hardwired to 32KiB. I've dumped the contents of the MEMC's control register (soft copy) and indeed bits 2 and 3 are set, so it really is treating the memory as 32 pages of 32KiB!

Arthur 1.20 looks superficially better, but there is a fair bit of internal stuff going on. The desktop is still that ridiculous BASIC "demo", but the memory handling is improved. For some reason, the version of 1.20 that I have seems to crash on startup (wonky screen and only having started Utility Manager) if there's anything other than 4MiB present. I wonder if this is an issue with the emulator or a patched copy of the OS? I have 1.20 installed on my 1MiB A310 and it started up correctly.
Here's how it looks:

The Arthur 1.20 desktop
The Arthur 1.20 desktop

The icons have been redesigned, and the iconbar is smaller. Calculator still looks rubbish, and the colours are...

Enter RISC OS 2.00. The iconbar is much taller again, but now all the garish colours have gone and we're now looking at a proper multitasking desktop. Arguably the first useful version of an operating system for Acorn machines. It was still rather basic, but miles ahead of Arthur. It's come a long way in a short time. Arthur 0.30 is dated 17th June 1987. RISC OS 2.00 is dated 5th October 1988.
Here is a screenshot of a 512KiB RISC OS 2 system, showing it using 64 of the 128 pages because it has split the memory into 8KiB pages.

RISC OS 2.00 running a single tasking program in the desktop
RISC OS 2.00 running a single tasking program in the desktop

And finally, here is RISC OS 2.01 from boot showing the start of the page tables of a 16MiB system.

A 16MiB RISC OS 2.01 machine!
A 16MiB RISC OS 2.01 machine!


To put this into context, an A305 (512KiB) in 1987 would have cost £916.60 for a base system. An A310 (1MiB) would have cost £1004.00, and an A440 (4MiB) would have cost £2610.60. Prices with VAT. When the A540 was released, it cost £2495 without VAT. So as you can understand, the ability to string together four MEMCs to have 16MiB onboard wasn't something that everybody would do. Back then, RAM was expensive, and the price of harddiscs would get your pants wet - you could blow several hundred on a drive large enough to store a couple of MP3s... No, not a couple of hundred, a couple. As in two. Maybe three if you're lucky. But you won't be able to listen to them - the interface is too slow, and the 8MHz processor isn't up to that much number crunching in realtime. Oh, and MP3 wouldn't be a thing until 1993, but asides from that..... ☺


RISC OS 3 is much the same as RISC OS 2. While the OS has had massive extension, potentially the largest feature jump in any version of RISC OS ever released, from the perspective of memory handling, it behaves in much the same way as 2.01. Visually, it's pretty much the same as RISC OS 2, with the exception of Apps on the left of the iconbar, and the Switcher icon on the right being a green Acorn rather than the Archimedes A. And, finally, icons that look sort of 3Dish (rendering the ugly Interface module and the uglier CC ABI obsolete), amusingly pretty much the same 3D designs that are present in RISC OS 5!


The code

I first thought of writing this in C. But that idea was quickly scrapped. It not only required baggage in the form of either SharedCLibrary or the statically linked ansilib; it also didn't stand a snowball's chance in hell of running on any version of Arthur.

So the next choice was to write it in assembler. Which is what I did. Crafted on a Pi2, this little program builds with ObjAsm and the DDE Linker, and will happily run on - as mentioned in the screenshot about - an emulation of a 33 year old operating system.

Why not BASIC? Because while the source is about 24KiB, there's something pleasing about the fact that the final executable is a mere 1284 bytes. I could perhaps be optimised even more, but with a nice little executable like that, and clear obvious code, it seems like optimisation for optimisation's sake would just be wasting time.

I have not tried this on real hardware. Pick a reason:

  • The machine is buried under a pile of stuff.
  • The A310 has a weirdo 9 pin video output socket and my monitors have fifteen pin plugs.
  • The floppy is broken, so it would need me to get Econet running too.
  • It's taken most of the day to hack, write the software, and then write this.
  • I can't be arsed.
  • All of the above.

It works on emulation. It ought to work on the real deal.


I won't break this into parts to explain things. The comments in the code ought to explain everything that needs explaining.
In terms of licence, I'm releasing it under CDDL.

; CAMdump  v0.01
; =======
; Little utility to dump the MEMC's CAM tables.
; Friday, 2nd October 2020
; < rick -at- heyrick -dot- eu >
; NOTE: This is intended for 26 bit machines, and will be expected to run
;       on EVERYTHING (yes, as far back as Arthur 0.30).
; It has been tested on Arthur 0.30 and 1.20, and RISC OS 2.00, 2.01, and 3.11 using ArchiEmu.
; It was developed on a Raspberry Pi under RISC OS 5.23, so will detect and reject such systems.
; This uses knowledge of the internals of RISC OS; and has been developed with the aid of the source
; code of the RISC OS 2.00 kernel, and disassembling parts of other versions.
; The contents of this file are subject to the terms of the Common Development and Distribution License
; (the "Licence").
; You may not use this file except in compliance with the Licence.
; You can obtain a copy of the licence at:
; See the Licence for the specific language governing permissions and limitations under the Licence.
; When distributing Covered Code, include this CDDL HEADER in each file and include the Licence file. If
; applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced
; with your own identifying information:
; Portions Copyright [yyyy] [name of copyright owner]
; Copyright 2020 Richard Murray.  All rights reserved.
; Use is subject to license terms.

        AREA    |camdump$code|, CODE, A32bit

        GET     h.SwiNos      ; taken from my version of DeskLib

        ; Fake up something to run on my Pi?
        GBLL    PiFake
PiFake  SETL    {FALSE}

        ; Define that which is truth and that which is fake news
TRUE      * 1
FALSE     * 0                 ; I was tempted to call this one "BREXIT" or "TRUMP" or something

        ; Define where stuff of interest is

MEMCCTRL  * &114              ; MEMC control register (copy)

RAMSIZE2  * &160              ; Total memory size (Arthur and RISC OS 2.00)
RAMSIZE   * &56C              ; Total memory size (RISC OS 2.01 and 3.10)

PAGETABLE * &164              ; Where the page tables reside (for up to 8MiB)
TBLADDR   * &564              ; Address of the page tables (RISC OS 2.01 and 3.10)

MAXPAGE   * &568              ; Highest page number (RISC OS 2.01 and 3.10)
PAGESIZE  * &C28              ; Page size in bytes (RISC OS 2.01 and 3.10)

        ; How this works is as follows:
        ; Normally, RISC OS uses the following page sizes:
        ;   Memory     Page
        ;   512KiB     8KiB (uses 64 of the 128 page entries)
        ;     1MiB     8KiB
        ;     2MiB    16KiB
        ;     4MiB    32KiB
        ;        >    32KiB (using multiple MEMCs)
        ; There are notes in the RISC OS 2.00 kernel that indicate certain memory
        ; arrangements and a 256KiB machine may have a 4KiB page size, but I've
        ; never experienced such a thing. Maybe it applied to some sort of dev machine?
        ; Arthur 0.30
        ;   As described below, but this OS version is only capable of seeing 1MiB
        ;   of RAM, regardless of how much is actually present. It is also interesting
        ;   in that it uses 32 pages of 32KiB.
        ;   UtilityModule -> 0.30 (17 Jun 1987)
        ; Arthur and RISC OS 2.00
        ;   This has the total memory size at &160, with the page size being in
        ;   the MEMC control register. The page tables are at &164. Up to 128 are
        ;   used, to support up to 4MiB of memory.
        ;   Multiple MEMCs are not supported.
        ;   Arthur 1.20 supplied with ArchiEmu fails to start if <>4 MiB!
        ;   UtilityModule -> 1.20 (25 Sep 1987)
        ;                 -> 2.00 (05 Oct 1988)
        ; RISC OS 2.01 and 3.10
        ;   This has the total memory size at &56C, with the page size being at
        ;   &C28 (as well as in the MEMC control register).
        ;   The word at &564 points to where the page tables are stored. For 8MiB
        ;   or less, this will be at &164 as normal. For more than 8MiB, the
        ;   *ENTIRE* page table is relocated (&1F01374 on RISC OS 2.01). The page
        ;   table is not split, if it is relocated, then the memory at &164-&560
        ;   will be empty.
        ;   UtilityModule -> 2.01 (05 Jul 1990)
        ;                 -> 3.00 (?)
        ;                 -> 3.10 (30 Apr 1992)
        ;                 -> 3.11 (29 Sep 1992)
        ;                 -> 3.19 (9. Jun 1993) [German version of 3.11]

        ; Workspace offsets
WOSVERS   * 0                 ; OS version (0 = RISC OS 2.00 or earlier; 1 = RISC OS 2.01 or later)
WMEMSIZE  * 4                 ; Installed memory size (bytes)
WPAGESIZE * 8                 ; Current page size (bytes)
WMEMCTRL  * 12                ; MEMC control register
WPAGECNT  * 16                ; Page count
WPAGETBL  * 20                ; Pointer to page table
WPAGEOFF  * 24                ; Offset into page table (when reading)
WOSVBYTES * 28                ; OS version bytes (as read from UtilityModule)
WSCRATCH  * 32                ; Scratch space in workspace

        ; Would you like a jellybaby?

        ; As we are a transient utility, registers on entry are:
        ;   R0  = Pointer to command line
        ;   R1  = Pointer to command tail
        ;   R12 = Pointer to workspace
        ;   R13 = Pointer to workspace end (stack)
        ;   R14 = Return address
        ;   User mode, interrupts.
        ; The workspace/stack is 1024 bytes.
        ; We use:
        ;   R7  = Pointer to page table (is updated)
        ;   R8  = Which page entry are we listing right now?
        ;   R9  = &01F08000   Duff entry, a mapping to "nowhere".
        ;   R10 = 0 if old (Arthur/RISC OS 2.00) or 1 if new table handling.
        ;   R11 = Pointer to scratch space (workspace + 32).

        ; Store our return address
        STR     R14, [R13, #-4]!

        ; Zero our important workspace bits
        MOV     R0, #0
        STR     R0, [R12, #WOSVERS]
        STR     R0, [R12, #WMEMSIZE]
        STR     R0, [R12, #WPAGESIZE]
        STR     R0, [R12, #WMEMCTRL]
        STR     R0, [R12, #WPAGECNT]
        STR     R0, [R12, #WPAGETBL]
        STR     R0, [R12, #WPAGEOFF]
        STR     R0, [R12, #WOSVBYTES]
        STR     R0, [R12, #WSCRATCH]

        ; Determine what OS version we are running on
        MOV     R0, #12       ; Extract module info
        MOV     R1, #0        ; module number (0 = UtilityModule)
        MOV     R2, #0        ; instance number
        SWI     (SWI_OS_Module + XOS_Bit)

        ; Work out which register holds the offset
        MOV     R0, R3        ; Put the module address in R0
        CMP     R1, #8000     ; Does R1 look like an address?
        MOVHI   R0, R1        ; If so, use R1 instead (Arthur 0.30)

        ; Sort out a pointer to the version number
        LDR     R1, [R0, #20] ; Get offset to help string
        ADD     R1, R0, R1    ; Make it a pointer
        ADD     R1, R1, #8    ; String is "MOS Utilities x.xx (dd mmm 19xx)" so skip first space
        LDRB    R0, [R1], #1  ; Load a byte
        CMP     R0, #9        ; Is it a tab character?
        BNE     modverloop    ; If not, keep going around

        ; R1 now points to the version in the form x.xx
        ; Note - this won't be word aligned so we must do it byte by byte
        ADD     R2, R12, #WOSVBYTES ; Workspace pointer
        LDRB    R0, [R1], #1  ; Read first byte
        STRB    R0, [R2], #1  ; Write it
        LDRB    R0, [R1], #1  ; Second byte (the '.')
        STRB    R0, [R2], #1
        LDRB    R0, [R1], #1  ; Third byte
        STRB    R0, [R2], #1
        LDRB    R0, [R1], #1  ; Fourth byte
        STRB    R0, [R2], #1

        ; Okay, we now have a copy of the version number, so
        ; let's figure out what this is.
        ADD     R2, R12, #WOSVBYTES
        LDRB    R0, [R2], #2  ; increment by two to skip '.'

        ; Determine if it's the old ways or the new ways
        CMP     R0, #'2'      ; If 0.xx or 1.xx
        BLO     osverdone     ; then it's an old style setup

        BHI     checkthree    ; Check which which version of 3 this is

        ; If we're here, it's either 2.00 or 2.01
        ADD     R2, R2, #1    ; Point to the final byte
        LDRB    R0, [R2]      ; Load it
        CMP     R0, #'0'      ; Is it zero?
        BEQ     osverdone     ; If so it's an old style setup

        MOV     R0, #1
        STR     R0, [R12, #WOSVERS] ; It's a new style setup (RISC OS 2.01)
        B       osverdone

        ; First, verify this is a RISC OS 3 machine!
        CMP     R0, #'3'
        BHI     nomemc        ; Anything later doesn't have an MEMC!
        LDRB    R0, [R2]
        CMP     R0, #'5'      ; Trap RISC OS 3.50 and later
        BHS     nomemc        ; No MEMC here either

        MOV     R0, #1
        STR     R0, [R12, #WOSVERS] ; It's a new style setup (RISC OS 3.xx)
        B       osverdone

        ; Print a message
        ADR     R0, msg_nomemc
        SWI     (SWI_OS_PrettyPrint + XOS_Bit)

 [ PiFake
        ; Fake up some rubbish
        SWI     (SWI_OS_WriteS + XOS_Bit)
        =       "** Faking a 4MiB RISC OS 3.11 machine **", 13, 10, 13, 10, 0

        LDR     R10, [R12, #WOSVERS]
        ADD     R11, R12, #WSCRATCH

        MOV     R0, #1
        STR     R0, [R12, #WOSVERS]
        ADR     R1, fake_mem
        LDR     R3, [R1], #4  ; needs to be in R3 for following code
        STR     R3, [R12, #WMEMSIZE]
        LDR     R0, [R1], #4
        STR     R0, [R12, #WOSVBYTES]
        LDR     R0, [R1]
        STR     R0, [R12, #WMEMCTRL]
        B       fake_jump

        &       4194304
        =       "3.11"
        &       &036E0D0C

        ; Then exit
        LDR     PC, [R13], #4

        =       "This machine does not have an MEMC installed.", 13, 10, 0

        =       "This is ", 0

        =       "Arthur ", 0

        =       "RISC OS ", 0

        =       ", using ", 0

        =       "old", 0

        =       "new", 0

        =       " style page tables.", 13, 10, 13, 10, 0

        =       22, 12, 0


        &       MEMCCTRL

        &       RAMSIZE

        &       RAMSIZE2

        &       PAGETABLE

        &       TBLADDR

        &       MAXPAGE

        &       PAGESIZE

        &       ( 4 * 1024)
        &       ( 8 * 1024)
        &       (16 * 1024)
        &       (32 * 1024)

        &       12
        &       13
        &       14
        &       15

        &       (31 * 1024 * 1024) + (32 * 1024)  ; nothing exists at this address


        ; Because it is used a lot, put the old/new type in R10.
        LDR     R10, [R12, #WOSVERS]

        ; And set R11 to point to the scratch space
        ADD     R11, R12, #WSCRATCH

        ; If this is Arthur, we need to switch to MODE 12 or
        ; else the Desktop will swallow all output.
        ; Not great if user is NOT in the desktop, but then
        ; Arthur is a bit rubbish... :-/
        LDRB    R0, [R12, #WOSVBYTES]
        CMP     R0, #'2'
        ADRLO   R0, msg_arthurfudge
        SWILO   (SWI_OS_Write0 + XOS_Bit)         ; MODE 12

        ; Report what we've found
        ADR     R0, msg_thisis
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; "This is "

        LDRB    R0, [R12, #WOSVBYTES]
        CMP     R0, #'2'      ; Is it
        ADRLO   R0, msg_arthur ; Arthur
        ADRHS   R0, msg_riscos ; or RISC OS?
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; "Arthur " / "RISC OS "

        ADD     R0, R12, #WOSVBYTES
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; x.xx (OS version)

        ADR     R0, msg_styleprefix
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; ", using "

        CMP     R10, #0
        ADREQ   R0, msg_styleold
        ADRNE   R0, msg_stylenew
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; "old" / "new"

        ADR     R0, msg_stylesuffix
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; " style page tables.\n\n"

        ; Now we can fill in the rest of the workspace data

        ; Memory size
        CMP     R10, #0       ; Work out where the memory size is
        ADREQ   R1, addr_memsizeold
        ADRNE   R1, addr_memsize
        LDR     R1, [R1]      ; Load the address
        LDR     R3, [R1]      ; Load the value from the address
        STR     R3, [R12, #WMEMSIZE]
        ; It's in R3 as we'll want it later

        ; MEMC control register
        ADR     R1, addr_memcctrl
        LDR     R1, [R1]
        LDR     R0, [R1]
        STR     R0, [R12, #WMEMCTRL]

        ; MEMC Control Register
        ;  25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
        ;  1  1  0  1  1  X  1  1  1  X  X  X  |  |  |  |   |  |  |  |  |  |  |  |  X  X
        ;                                      |  |  |  |   |--'  |--'--'--'  |--'
        ;                                      |  |  |  |   |     |           |
        ;  |                               |   |  |  |  |   |     |           '---- Page size
        ;  '---------------.---------------'   |  |  |  |   |     |
        ;                  |                   |  |  |  |   |     '---------------- ROM access time
        ;   These values are the same in the   |  |  |  |   |
        ;   MEMC and the MEMC1a, so I'm not    |  |  |  |   '---------------------- DRAM refresh
        ;   sure how we'd tell the two apart.  |  |  |  '-------------------------- Vid/Cur DMA
        ;                                      |  |  '----------------------------- Sound DMA
        ;                                      |  '-------------------------------- OS mode select
        ;                                      '----------------------------------- Test mode
        ; Page size:  00 =  4KiB
        ;             01 =  8KiB
        ;             10 = 16KiB
        ;             11 = 32KiB

 [ PiFake

        ; Work out the page size
        AND     R2, R0, #12   ; Bits 2 and 3 (in R2 as we'll need it later)
        ADR     R1, memc_pagesizes
        LDR     R0, [R1, R2]  ; because bits 2,3 is already x4'd
        STR     R0, [R12, #WPAGESIZE]

        ; Now work out the page count
        ADR     R1, memc_pageshifts
        LDR     R1, [R1, R2]
        MOV     R0, R3, LSR R1; pagecount = memsize >> pagesize_shift
        STR     R0, [R12, #WPAGECNT]

        ; We do not look at the page size and page count values
        ; held in page zero (at &C28 and &568 respectively) because
        ; these only exist in new style (as of RISC OS 2.01) and
        ; calculating them ourselves works just as well.

        ; Now report what we've found thus far.
        ADR     R0, msg_memone
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; "Your machine has "

        LDR     R0, [R12, #WMEMSIZE]              ; Convert the memory size value
        MOV     R1, R11
        MOV     R2, #32
        SWI     (SWI_OS_ConvertCardinal4 + XOS_Bit)
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; xxxxxxx (memory size in bytes)

        ADR     R0, msg_memtwo
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; " bytes of memory and a page size\n of "

        LDR     R0, [R12, #WPAGESIZE]             ; Convert the page size value
        MOV     R1, R11
        MOV     R2, #32
        SWI     (SWI_OS_ConvertCardinal4 + XOS_Bit)
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; xxxxxxx (page size in bytes)

        ADR     R0, msg_memthree
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; " bytes. There are "

        LDR     R0, [R12, #WPAGECNT]              ; Convert the page count value
        MOV     R1, R11
        MOV     R2, #32
        SWI     (SWI_OS_ConvertCardinal4 + XOS_Bit)
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; xxxxxxx (page count)

        ADR     R0, msg_memfour
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; " pages (using "

        LDR     R1, [R12, #WPAGECNT]
        MOV     R1, R1, LSR #7                    ; divide by 128 (pages) to get MEMC count

        CMP     R1, #0                            ; 512KiB would result in
        MOVEQ   R1, #1                            ; 0 MEMCs so fix this ;-)

        ; Cheap-ass way to make it a number
        ADD     R0, R1, #'0'
        SWI     (SWI_OS_WriteC + XOS_Bit)         ; x (MEMC count)

        ; One or more MEMCs?
        CMP     R1, #1
        ADREQ   R0, msg_memmemc
        ADRNE   R0, msg_memmemcs
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; " MEMC).\n\n" / " MEMCs).\nn"

        ; Now work out where the page table is
        CMP     R10, #0

        ; Old way, page table is fixed at &164
        ADREQ   R0, addr_pagetable
        LDREQ   R7, [R0]
        BEQ     dumppagetable

        ; New way, the table is pointed to by the word at &564
        ADR     R0, addr_tableaddr
        LDR     R0, [R0]      ; load address of table pointer
        LDR     R7, [R0]      ; load table pointer
        B       dumppagetable

        =       "Your machine has ", 0

        =       " bytes of memory and a page size", 13, 10, "of ", 0

        =       " bytes. There are ", 0

        =       " pages (using ", 0

        =       " MEMC).", 13, 10, 13, 10, 0

        =       " MEMCs).", 13, 10, 13, 10, 0

        =       "Dumping MEMC CAM table (stored at &", 0

        =       "):", 13, 10, 13, 10, 0

        =       ": &", 0

        =       "(Read/Write)", 0

        =       "(Read Only)", 0

        =       "(Inaccessible)", 0

        =       "(Inaccessible)", 0

        =       "(Nowhere)", 0

        =       9, 0

        =       13, 10, 0


        &       (msg_access00 - msg_access00)
        &       (msg_access01 - msg_access00)
        &       (msg_access10 - msg_access00)
        &       (msg_access11 - msg_access00)

        ; Mode          Protection level bits
        ;                00        01        10        11
        ;  Supervisor    R/W       R/W       R/W       R/W
        ;  OS mode       R/W       R/W       R         R
        ;  User mode     R/W       R         -inaccessible-

        ; Set R9 to point to the address of "nowhere"
        ADR     R9, memc_duffentry
        LDR     R9, [R9]

        ; Starting with page zero
        MOV     R8, #0

        ; For 'n' pages
        LDR     R6, [R12, #WPAGECNT]

        ; Output a message saying where the table is
        ADR     R0, msg_dumpone
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; Dumping MEMC CAM table (stored at &

        MOV     R0, R7                            ; Convert the table address
        MOV     R1, R11
        MOV     R2, #32
        SWI     (SWI_OS_ConvertHex8 + XOS_Bit)
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; XXXXXXXX (table address, hex)

        ADR     R0, msg_dumptwo
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; ):\n\n

        ;   R6  = Page count
        ;   R7  = Pointer to page table (is updated)
        ;   R8  = Which page entry are we listing right now?
        ;   R9  = &01F08000   Duff entry, a mapping to "nowhere".
        ;   R10 = 0 if old (Arthur/RISC OS 2.00) or 1 if new table handling.
        ;   R11 = Pointer to scratch space (workspace + 32).

        ; Now dump each entry.
        ; The layout is in the form:
        ;   <page>: &<addr> (note)

        ; Output the page number
        MOV     R0, R8
        MOV     R1, R11
        MOV     R2, #4
        SWI     (SWI_OS_ConvertCardinal2 + XOS_Bit)
        SUBS    R2, R2, #1    ; do we need leading spaces?
        BEQ     nospaces
        SWI     (SWI_OS_WriteI + ' ' + XOS_Bit)   ; " "
        B       spaces

        MOV     R0, R11
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; xxx (page number)

        ADR     R0, msg_dumpsep
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; ": &"

        ; Load the CAM entry (into R5)
        LDR     R5, [R7], #4  ; is updated to point to the next one

        ; CAM entry:
        ; Bits 31-28 : Page Protection Level
        ; Bits 27-0  : Address
        ; Page Protection Level
        ; Mode          Protection level bits
        ;                00        01        10        11
        ;  Supervisor    R/W       R/W       R/W       R/W
        ;  OS mode       R/W       R/W       R         R
        ;  User mode     R/W       R         -inaccessible-

        ; Split into address and PPL
        BIC     R4, R5, #&F0000000                ; R4 = address
        MOV     R5, R5, LSR #28                   ; R5 = PPL

        MOV     R0, R4
        MOV     R1, R11
        MOV     R2, #32
        SWI     (SWI_OS_ConvertHex8 + XOS_Bit)
        SWI     (SWI_OS_Write0 + XOS_Bit)         ; XXXXXXXX (entry address, hex)

        SWI     (SWI_OS_WriteI + ' ' + XOS_Bit)   ; " "

        ; This is a bit convoluted, but we don't know until runtime
        ; where we will be loaded, so we can't store any address in
        ; the table, only offsets relative to something...
        ADR     R1, access_table                  ; Point to the table
        LDR     R0, [R1, R5, LSL#2]               ; Load the offset
        ADR     R1, msg_access00                  ; Point to the first of the messages
        ADD     R0, R0, R1                        ; Now add in the offset

        ; Now compare the address to DuffEntry, to switch in
        ; a different message for pages pointing to nowhere.
        CMP     R4, R9
        ADREQ   R0, msg_accessxx

        SWI     (SWI_OS_Write0 + XOS_Bit)         ; "(xxx)" (appropriate message)

        ; We output two entries across the screen, so work out
        ; whether we need to space across to the next, or just
        ; output a newline.
        ; We can't use Tab and PrettyPrint (easier!) as we need
        ; this to fit into the RISC OS 2 command window.

        MOV     R0, #134      ; read text cursor position
        SWI     (SWI_OS_Byte + XOS_Bit)
        RSBS    R1, R1, #30
        BMI     dump_newline

        SWI     (SWI_OS_WriteI + ' ' + XOS_Bit)
        SUBS    R1, R1, #1
        BNE     dump_seploop
        B       dump_continue

        SWI     (SWI_OS_NewLine + XOS_Bit)
        ; fall through to...

        ; More to go?
        ; We don't dump the entire CAM table, we only dump that which is used.
        ADD     R8, R8, #1
        CMP     R8, R6
        BLT     dump_loop

        ; If we're here, then we're done.

        ; Is this an Arthur?
        LDRB    R0, [R12, #WOSVBYTES]             ; get first byte of version string
        CMP     R0, #'2'
        LDRHS   PC, [R13], #4                     ; RISC OS 2 or later, so we can ** EXIT **.

        ; The Arthur desktop doesn't prompt us before redrawing itself,
        ; so let's do a little something to fix that.
        ; [can't use OS_Confirm, Arthur 0.30 doesn't have it!]
        ADR     R0, msg_byebye
        SWI     (SWI_OS_Write0 + XOS_Bit)
        SWI     (SWI_OS_ReadC + XOS_Bit)          ; basically A=GET

        LDR     PC, [R13], #4                     ; ** EXIT **

        =       13, 10, "Press Enter to continue.", 13, 10, 0

        ; Fade to black...



As you can see, the fun part was not just in writing the program, but writing it in such a way that it would work on everything!


The archive

Pick up (22KiB) for the executable pre-built and the source code.


I lied

Anna (not the MEMC!) examining a log
Anna (not the MEMC!) examining a log



Tomorrow's article looks at how to interpret the MEMC page table.



Your comments:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

No comments yet...

Sorry, comments cannot be added at this time.
Please try again later.


French flagSpanish flagJapanese flag
«   October 2020   

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index



Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.


Thank you:
  • Fred
  • Bernard
  • Michael
  • David

QR code

Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0


© 2020 Rick Murray
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.


Have you noticed the watermarks on pictures?
Next entry - 2020/10/04
Return to top of page