a loose pile of links, notes, and things worth keeping.
< back
QEMU/SPARC64 MMU 256MB page enabling Part 2: QEMU side and the fix

Continuing from Part 1.

Let’s look at what this looks like in practice, and where QEMU’s handling of it actually falls over. The latest good breakpoint was alloc_page_freelists+0x54 (I spent some time catching it):

[0]> alloc_page_freelists+0x54::bp
[0]> :c
kmdb: stop at alloc_page_freelists+0x54
kmdb: target stopped at:
alloc_page_freelists+0x54:      stx       %i0, [%l6 + %l7]
[0]> mmu_exported_pagesize_mask/X
mmu_exported_pagesize_mask:
mmu_exported_pagesize_mask:     2b
[0]> kmem64_szc/X
kmem64_szc:
kmem64_szc:     5 - <this is 256M page for this specific allocation >
[0]> kmem64_base/J
kmem64_base:
kmem64_base:    70000000000
[0]> kmem64_aligned_end/J
kmem64_aligned_end:
kmem64_aligned_end:             70010000000
[0]> 70000000000/J
0x70000000000:  0
[0]> 7000000F000/J
0x7000000f000:  0
[0]> 7000000FF00/J
0x7000000ff00:  0
[0]> 7000000FFF0/J
0x7000000fff0:  0
[0]> 7000000FFFE/J -<hang where we try to look the code out of 64K>

Here, kmem64_szc is 5, which corresponds directly to TTE256M from the table in Part 1 - confirming that the kernel is indeed trying to map the kmem64 segment using 256M pages, driven by the broken_md_flag logic, but it can’t do that.

The hang happens exactly at the boundary where we step outside the 64K range - right where QEMU would need to service a translation for the 256M-sized mapping. This lines up with the suspicion from Part 1: QEMU’s MMU emulation may not correctly handle the 256M page size that the guest kernel enables as a side effect of the missing domaining-enabled property. The guest isn’t doing anything unusual - it’s following the exact logic in niagara.c - but QEMU’s handling of that particular [sz] encoding looks like the likely point of failure, producing the endless stream of Hyperprivileged Trap Instruction / Data Access MMU Miss entries seen in the log at the start of this investigation. This remains a suspicion rather than a confirmed root cause.

When we tried to suppress the pagesize to 0x9 (8K+4M), like so:

[0]> ::bp alloc_kmem64
[0]> :c
[0]> mmu_exported_pagesize_mask/W b

the kernel booted successfully, but without 256MB pages.

I then started looking at the QEMU code. The TTE page-size field is defined in QEMU’s /target/sparc/cpu.h as:

#define TTE_PGSIZE(tte)     (((tte) >> 61) & 3ULL)

and the sun4v_tte_to_sun4u() conversion inside QEMU’s target/sparc/ldst_helper.c only preserves 2 bits for the page size:

    sun4u_tte |= (sun4v_tte & 3ULL) << 61; /* TTE_PGSIZE */

It seems the logic only preserves two bits (61/62), giving a value of 0-3, when the highest bit - bit 48 (szh) - is also supposed to be part of it, which makes the operation a bit more involved: we need to reconstruct it as

| szh[48] | szl[62] | szl[61] | |—|—|—|

to build the table described below in the UltraSPARC T1 Supplement draft: TTE data format to hold all the properties in our case.

Interestingly, when I tried to figure out when this extension was introduced into the CPU spec, I found the Panther/USIV CPU listed as the first to support it, then T1 supported the same TTE pagesize bits, but T2 changed the format:

Document Where it lands
JPS1 Commonality, Working Draft 1.0.5, TABLE F-1, p.441 The state before the extension: 2-bit size, Data<49:47> “Reserved, read as 0” (bit 48 included). Joint Sun+Fujitsu, so it covers US-III and SPARC64 V.
Panther Impl. Supplement (UltraSPARC IV+), Preliminary Draft 17 Mar 2006, TABLE F-1-4, p.337 The citation to use. States plainly: “Bit 48 is the most significant bit of the page size and is concatenated with bits <62:61>”, with the full 000-101 encoding.
UltraSPARC T1 Supplement to the UltraSPARC Architecture 2005, Draft D2.1, sec. 13.1.2, TABLE 13-1, p.182 Same bit in T1’s sun4u-format TTE, named szl/szh.
UltraSPARC T2 Supplement, TABLE 12-4, p.107 T2 dropped the sun4u format entirely: one contiguous 4-bit field, and only 4 sizes (512K and 32M Reserved).

When I started prototyping the fix with Claude, it took five iterations to land on this patch and send it to the qemu-devel community. Each of the first four had something wrong: they used the wrong bitfields for storing the [szh] bit - the first time we built it on unused bit 42, which is out of UltraSparc spec :), and it even worked, sort of; or used suboptimal extra operations to store this bit in the TTE. All of these worked, but…

With it applied, the guest started to boot:

[0]> alloc_page_freelists+0x54::bp
[0]> :c
kmdb: stop at alloc_page_freelists+0x54
kmdb: target stopped at:
alloc_page_freelists+0x54:      stx       %i0, [%l6 + %l7]
[0]> kmem64_szc/X
kmem64_szc:
kmem64_szc:     5
[0]> kmem64_base/J
kmem64_base:
kmem64_base:    70000000000
[0]> kmem64_aligned_end/J
kmem64_aligned_end:
kmem64_aligned_end:             70010000000
[0]> mmu_exported_pagesize_mask/X
mmu_exported_pagesize_mask:
mmu_exported_pagesize_mask:     2b
[0]> 7000000FFFE/J <- it's survived!
0x7000000fffe:  0
[0]> 70000010000/J
0x70000010000:  0
[0]> 70000100000/J <- also survived
0x70000100000:  0
[0]> :c
kmdb: stop at alloc_page_freelists+0x54
kmdb: target stopped at:
alloc_page_freelists+0x54:      stx       %i0, [%l6 + %l7] - <then it stopped the second time
[0]> 70000100000/J
0x70000100000:  0
[0]> mmu_exported_pagesize_mask/X
mmu_exported_pagesize_mask:
mmu_exported_pagesize_mask:     2b
[0]> :c
SunOS Release 5.11 Version snv_151a 64-bit
Copyright (c) 1983, 2010, Oracle and/or its affiliates. All rights reserved.

And here’s how it looks under the QEMU monitor:

(qemu) info tlb
(qemu) info tlb
MMU contexts: Primary: 0, Secondary: 0
DMMU Tag Access: 180a000, TSB Tag Target: 0
DMMU dump
[00] VA: 70000000000, PA: d0000000, 256M, priv, RW, unlocked, ie no, ctx 0 local <<<<<-- there's the first 256M chunk, visible to QEMU!
[01] VA: ffffffffffffc000, PA: 801ee000,   8k, priv, RW, unlocked, ie no, ctx 0 local
[02] VA: ffffffffff300000, PA: f1f0c000,   8k, priv, RW, unlocked, ie no, ctx 0 local
[03] VA: fffffffcc000a000, PA: f1394000,   8k, priv, RW, unlocked, ie no, ctx 0 local
[04] VA: 1930000, PA: ff530000,   8k, priv, RW, unlocked, ie no, ctx 0 local