August 23, 2025

Sun 3 bootrom souce - the Sun 3/80 - the MMU

This is very different from the MMU that sun built from static ram and used in most sun3 machines. This is the sun3x architecture and uses the MMU that is part of the MC68030 chip.

This introduction is particularly biased by my interest in the 3/80 bootrom. The bootrom disassembly is my only real life example of the sun3x MMU being used, so there you go.

You get these resources as part of the CPU:

CRP - 64 bit cpu root pointer
SRP - 64 bit system root pointer
TC - 32 bit "translation control" register
MMUSR - 16 bit mmu status (never used in bootrom)
TT0 - 32 bit transparent translation register (never used in bootrom)
TT1 - 32 bit transparent translation register (never used in bootrom)
The "pmove" instruction is used to access these registers.
pmove %srp,%a0@
pmovefd %a0@,%srp
pmove %tc,%a0@
pmovefd %a0@,%tc
pmove %psr,%a0@
pmove %a0@,%psr
The "fd" variant says "flush disable" and disables the flushing of the ATC as would normally take place. The pmove instruction did not exist on the 68020.

What is the %psr? How is it different than %sr? It took some time, but I figured it out. It is the MMUSR. The fact that it is accessed via the PMOVE instruction should make this clear.

Try to read the TC register

I write some code and get this:
Dump MMU
Exception 0x2C at 0x00004040.
Indeed, the offending instruction is:
pmove %tc,%d0
What exactly is exception 0x2c?

I try something different and now get:

Dump MMU
Error (PSR = 0x203) Timeout Bus Error:
  Vaddr: 80D07660, Paddr: 00001660, Read, FC 5, Size 1 at 0x000048A0.
This comes from deep inside printf(). But I am distracted by some bonus information in the above. First we are running with FC=5, which is no great surprise. It also says that the PSR is 0x203. If we can believe this and the upper 4 bits are 0, we are running in user state, not supervisor state (as I would hope and expect).

It turns out the above error is due to a stupid typo. (%s rather than %x in the format) When I fix that, I get:

Dump MMU
TC = 80D07660
CRP = 7FFF0003 00FFA000
SRP = 7FFF0003 00FFA000
This uses the following code to read the TC (as well as CRP and SRP
get_tc:
        moveal %sp@(4), %a0
        pmove %tc, %a0@
        rts
Did my first attempt fail due to pmove not allowing the %d0 register? So it would seem, and the bootrom never does that. It is odd that the assembler did not complain.

The fact that we can now execute the PMOVE instruction suggests that we are not in user mode. In fact we dump some more registers to investigate:

SR = 00002704
TC = 80D07660
PSR = 00030000
CRP = 7FFF0003 00FFA000
SRP = 7FFF0003 00FFA000
Indeed, just being able to read the %sr register proves we are not in user mode, and the value we read confirms that. Note that the PSR is only 16 bits, so only the value 0003 is valid.

What does the TC value tell us?

This is on page 9-42 of the manual I have.
80 = bits E---_--SF
	E = translation enabled
	S = (off) SRP is disabled
	F = (off) FC lookup disabled
D0 = pppp_iiii
	pppp = 0xD = 1101 = page size 8K
	iiii = 0x0 = 0000 = initial shift
76 = aaaa_bbbb
	aaaa = 0x7 = 7 bits in level A
	bbbb = 0x6 = 6 bits in level B
60 = cccc_dddd
	cccc = 0x6 = 6 bits in level C
	dddd = 0 = no level D
So, a virtual address is split 7:6:6:13 with 13 bits for an 8K page.

What does the CRP tell us?

Note above that the CRP and SRP are set identically, but the SRP is disabled.

CRP + 7FFF0003 00FFA000
This is a 64 bit object.
7FFF is a special value saying "no upper or lower limit"
0003 indicates that long (8 byte) descriptors are used.
00FFA000 is the table address.
So, we have an array of 8 byte objects at 0x00FFA000.

A naive attempt to access the table address of 0x00FFA000 nets us:

Error (PSR = 0x203) Timeout Bus Error:
  Vaddr: 00FFA000, Paddr: 00000000, Read, FC 5, Size 4 at 0x0000458E.
This is a fine opportunity to consider the value in the PSR (mmu status) of 0x203.
0200 = modified
0003 = 3 levels
The modified bit doesn't tell us much, but the 3 levels is consistent with the setup of A,B,C, but not D in the TC register. The value in %psr is not set by a fault, but is set when the ptest instruction is used to investigate an access.

So, how does this all work?

We have a 32 bit address split 7:6:6:13. So we have A, B, and C tables. The A table (indexed by the "7" bit part) has 128 entries, each would map a 32M zone. The B table would have 64 entries, with only 6 bits to index it. Each entry would map 512K. Then the C table, also with 6 bits, would have 64 entries also, each mapping an 8K page.

My machine currently has 16M of physical RAM, so a single entry in the A table would map it, using only half of the B entries.

The bootrom will help us!

Consider this command:
^r
TC: (0x80D07660) CRP: (0x7FFF0003) (0xFFA000)
SYS ENA: (0x8000) INT: (0x81) BERR: (0x20) VBR: (0xFEF72000)
MEM ERR CNTRL: (0x50505050) MEM ERR ADDR: (0xFFE9A0)
Also consider these:
>m a
TIA Map 00000000 [00FFA000] = 00FFA400?
TIA Map 02000000 [00FFA000] = 00000000?
TIA Map 04000000 [00FFA000] = 00000000?
TIA Map 06000000 [00FFA000] = 00000000?
TIA Map 08000000 [00FFA000] = 00000000? .
>m b
TIB Map 00000000 [00FFA400] = 00FFA60A?
TIB Map 00080000 [00FFA400] = 00FFA70A?
TIB Map 00100000 [00FFA400] = 00FFA80A?
TIB Map 00180000 [00FFA400] = 00FFA902?
TIB Map 00200000 [00FFA400] = 00FFAA02? .
One happy thing about these is that they confirm values I found my reading the registers.

Now consider 16M of physical memory. This would be at addresses:

0000_0000 to 00ff_ffff
So, it would appear that the bootrom located the page tables in the top end of physical ram.

I experiment, seeing what address ranges I can read from. After some trial and error, I see the following:

Poke 00000000 = 80D07660
Poke 00080000 = 00000000
Poke 00100000 = 00000000
Poke 00200000 = 00000000
Poke 00300000 = 00000000
Poke 00400000 = 00000000
Poke 00600000 = 00000000
Poke 00700000 = 00000000
Poke 00780000 = 00000000
Poke 007FFFF0 = 00000000
Error (PSR = 0x203) Timeout Bus Error:
  Vaddr: 00800000, Paddr: 7FFFE000, Read, FC 5, Size 4 at 0x000044BA.
So, it looks like, for whatever reason, I can only access the first 8M of memory.

Try the uart (SCC) address again

I try reading from the uart status register at 0FE02004 again and get:
Error (PSR = 0x203) Timeout Bus Error:
  Vaddr: 0FE02004, Paddr: 00FFA004, Read, FC 5, Size 1 at 0x00004ABC.
Let's use the monitor "m" command to see what it thinks the A and B mappings for that address might be:
m a 0fe02004
TIA Map 0FE02004 [00FFA000] = 00000000?
TIA Map 11E02004 [00FFA000] = 00000000? .
>m b 0fe02004
TIB Map 0FE02004 [00000000] = 00000000?
That seems pretty definite. These addresses aren't mapped.
There are only 128 addresses in the A table, let's plow through them all:
>m a 0
TIA Map 00000000 [00FFA000] = 00FFA400?
TIA Map 02000000 [00FFA000] = 00000000?
TIA Map 04000000 [00FFA000] = 00000000?
TIA Map 06000000 [00FFA000] = 00000000?
...
TIA Map FC000000 [00FFA000] = 00000000?
TIA Map FE000000 [00FFA000] = 00FFA500?
TIA Map 00000000 [00FFA000] = 00FFA400?
TIA Map 02000000 [00FFA000] = 00000000?
The "m" command just loops when we get past fe000000. But there is a zone of mapped addresses at the top of the virtual address space.

Let's look at the B table for the 0 zone. We expect it just to map the 8M of ram we discovered.

m b 0
TIB Map 00000000 [00FFA400] = 00FFA60A?
TIB Map 00080000 [00FFA400] = 00FFA70A?
TIB Map 00100000 [00FFA400] = 00FFA80A?
TIB Map 00180000 [00FFA400] = 00FFA902?
TIB Map 00200000 [00FFA400] = 00FFAA0A?
TIB Map 00280000 [00FFA400] = 00FFAB02?
TIB Map 00300000 [00FFA400] = 00FFAC0A?
TIB Map 00380000 [00FFA400] = 00FFAD02?
TIB Map 00400000 [00FFA400] = 00FFAE0A?
TIB Map 00480000 [00FFA400] = 00FFAF02?
TIB Map 00500000 [00FFA400] = 00FFB002?
TIB Map 00580000 [00FFA400] = 00FFB102?
TIB Map 00600000 [00FFA400] = 00FFB20A?
TIB Map 00680000 [00FFA400] = 00FFB302?
TIB Map 00700000 [00FFA400] = 00FFB40A?
TIB Map 00780000 [00FFA400] = 00FFB50A?
TIB Map 00800000 [00FFA400] = 00000000?
TIB Map 00880000 [00FFA400] = 00000000?
...
TIB Map 01E80000 [00FFA400] = 00000000?
TIB Map 01F00000 [00FFA400] = 00000000?
TIB Map 01F80000 [00FFA400] = 00000000?
Indeed, each B entry maps 0.5M and the mapping continues for 8M.
Now, what about the mapping at the high end.
m b FE000000
TIB Map FE000000 [00FFA500] = 00000000?
TIB Map FE080000 [00FFA500] = 00000000?
TIB Map FE100000 [00FFA500] = 00000000?
TIB Map FE180000 [00FFA500] = 00000000?
.....
TIB Map FED80000 [00FFA500] = 00000000?
TIB Map FEE00000 [00FFA500] = 00FFB602?
TIB Map FEE80000 [00FFA500] = 00FFB702?
TIB Map FEF00000 [00FFA500] = 00FFB80A?
TIB Map FEF80000 [00FFA500] = 00FFB90A?
TIB Map FF000000 [00FFA500] = 00000000?
.....
TIB Map FFE00000 [00FFA500] = 00000000?
TIB Map FFE80000 [00FFA500] = 00000000?
TIB Map FFF00000 [00FFA500] = 00FFBA0A?
TIB Map FFF80000 [00FFA500] = 00FFBB02?
That is interesting! A 2M zone at FEE0_0000 and a 1M zone at FFF0_0000.
Now let's take another look at this:
^a
Devcie:         Virtual Address: Physical Address:
Keybrd Mouse    0xFEF00000:      0x62000000:
Serial Port     0xFEF02000:      0x62002000:
EEPROM          0xFEF04000:      0x64000000:
TOD             0xFEF06000:      0x640007F8:
Mem Err Reg     0xFEF09000:      0x61001000:
Int Reg         0xFEF0B400:      0x61001400:
le Ethernet     0xFEF0E000:      0x65002000:
Sys ENA Reg     0xFEF16000:      0x61000000:
Bus ERR Reg     0xFEF18400:      0x61000400:
IDPROM          0xFEF047D8:      0x640007D8:
VIDEOMEM_BASE   0xFEF20000:      0x50400000:
P4 Video Reg    0xFEF1E000:      0x50300000:
P4 DAC          0xFEF0C000:      0x50200000:
P4 Overlay      0xFEF20000:      0x50400000:
P4 ENA Plane    0xFEF40000:      0x50600000:
IOMAPPER        0xFEF66000:      0x60000000:
FDC             0xFEF7A000:      0x6E000000:
FDC_CNTRL_REG   0xFEF7A400:      0x6E000400:
FDC_VEC_REG     0xFEF7A800:      0x6E000800:
PRINTER_PORT    0xFEF8003C:      0x6F00003C:
Now I realize my error with the serial port! I need to make this change:
// #define SCC_BASE 0x0fe02000
#define SCC_BASE    0xfef02000
I just tried to use the address from the 3/160 system, and only glanced at the virtual address table above.

Indeed! I can write to the serial port at these proper addresses.

How can I modify the page tables?

I need to write to memory addresses that have no virtual mapping. There may be some clever way to do this. I only have access to the CRP, TC, and TT registers.

The most civilized way would be to set up a new set of page tables at the top of the 8M mapped ram, then switch the MMU to use those. Those could map the entire 16M and let me do anything.

Perhaps if I put proper values into the NVRAM, it would map the entire 16M? I note these messages:

Sun Workstation, Model Sun-3/80 Series.
ROM Rev 3.0.2, 16MB memory installed, Serial #1193046.
Ethernet address 8:0:20:1:DB:2, Host ID 42123456.

Testing 0 Megabytes of Memory ... Completed.
Even though it at first recognizes the proper 16M of ram, it later says it is only testing 0 -- given that, who knows what is decided to initialize virtual addresses for only 8M

What does a MMU table entry look like

There are different formats, we are using the long 8 bytes format for the translation descriptor. Page 9-15 of my manual describes these.

The CRP itself is a long format table descriptor.

There are actually 4 types of long descriptors:

Normal table descriptor
Early termination page descriptor
Page descriptor
Invalid descriptor
The manual shows examples where long and short descriptors are mixed.

And then there is the page map

We can look at this with the rom "p" command:
p 0
PageMap 00000000 [00FFA600] = 00000049?
PageMap 00002000 [00FFA600] = 00002059?
PageMap 00004000 [00FFA600] = 00004059?
PageMap 00006000 [00FFA600] = 00006041?
PageMap 00008000 [00FFA600] = 00008041? .
This is perhaps the level 3 "C" map and consists of page descriptors (8K each).
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org