It is the 1739th of March 2020 (aka the 3rd of December 2024)
You are 3.15.17.60,
pleased to meet you!
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.
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
Strangely enough, when run, it reports a peculiar memory arrangement:
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 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
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!
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.
;
;
;
; CDDL HEADER START
;
; 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:
; https://www.riscosopen.org/viewer/view/~checkout~/cddl/RiscOS/Sources/FileSys/SDFS/SDFS/LICENCE?rev=1.1.1.1
; 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]
;
; CDDL HEADER END
;
; 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?
entry_point
ENTRY
; 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.
; WE MUST ONLY CALL X FORM SWIS.
;
; 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
modverloop
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
checkthree
; 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
nomemc
; 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
ALIGN
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
fake_mem
& 4194304
fake_os
= "3.11"
fake_memc
& &036E0D0C
|
; Then exit
LDR PC, [R13], #4
]
msg_nomemc
= "This machine does not have an MEMC installed.", 13, 10, 0
msg_thisis
= "This is ", 0
msg_arthur
= "Arthur ", 0
msg_riscos
= "RISC OS ", 0
msg_styleprefix
= ", using ", 0
msg_styleold
= "old", 0
msg_stylenew
= "new", 0
msg_stylesuffix
= " style page tables.", 13, 10, 13, 10, 0
msg_arthurfudge
= 22, 12, 0
ALIGN
addr_memcctrl
& MEMCCTRL
addr_memsize
& RAMSIZE
addr_memsizeold
& RAMSIZE2
addr_pagetable
& PAGETABLE
addr_tableaddr
& TBLADDR
addr_maxpage
& MAXPAGE
addr_pagesize
& PAGESIZE
memc_pagesizes
& ( 4 * 1024)
& ( 8 * 1024)
& (16 * 1024)
& (32 * 1024)
memc_pageshifts
& 12
& 13
& 14
& 15
memc_duffentry
& (31 * 1024 * 1024) + (32 * 1024) ; nothing exists at this address
osverdone
; 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
fake_jump
]
; 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
msg_memone
= "Your machine has ", 0
msg_memtwo
= " bytes of memory and a page size", 13, 10, "of ", 0
msg_memthree
= " bytes. There are ", 0
msg_memfour
= " pages (using ", 0
msg_memmemc
= " MEMC).", 13, 10, 13, 10, 0
msg_memmemcs
= " MEMCs).", 13, 10, 13, 10, 0
msg_dumpone
= "Dumping MEMC CAM table (stored at &", 0
msg_dumptwo
= "):", 13, 10, 13, 10, 0
msg_dumpsep
= ": &", 0
msg_access00
= "(Read/Write)", 0
msg_access01
= "(Read Only)", 0
msg_access10
= "(Inaccessible)", 0
msg_access11
= "(Inaccessible)", 0
msg_accessxx
= "(Nowhere)", 0
msg_dumptab
= 9, 0
msg_dumpcrlf
= 13, 10, 0
ALIGN
access_table
& (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-
dumppagetable
; 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).
dump_loop
; 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)
spaces
SUBS R2, R2, #1 ; do we need leading spaces?
BEQ nospaces
SWI (SWI_OS_WriteI + ' ' + XOS_Bit) ; " "
B spaces
nospaces
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
dump_seploop
SWI (SWI_OS_WriteI + ' ' + XOS_Bit)
SUBS R1, R1, #1
BNE dump_seploop
B dump_continue
dump_newline
SWI (SWI_OS_NewLine + XOS_Bit)
; fall through to...
dump_continue
; 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 **
msg_byebye
= 13, 10, "Press Enter to continue.", 13, 10, 0
ALIGN
; Fade to black...
END
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 camdump.zip (22KiB) for the executable pre-built and the source code.
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.
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.