Currently, the TTE's page-size field, Size, is 2 bits wide:
#define TTE_PGSIZE(tte) (((tte) >> 61) & 3ULL)
That is the TTE of every UltraSPARC before UltraSPARC IV+.
The JPS1 implementation supplements for UltraSPARC III and IV+
describe the TTE word in tables F-1 and F-1-4 respectively, and differ
in one bit:
Before IV+ bit 48 is reserved and reads as 0, Size is 2 bits at 62:61;
On IV+ bit 48 is Size<2>, which "is the most significant bit of the
page size and is concatenated with bits <62:61>"
On T1 the same split exists, szl at 62:61 and szh at 48 (T1 Supplement,
sec. 13.1.2).
QEMU models parts on both sides of that change (UltraSPARC III/IIIi/IV,
IV+, T1, T2) with the III layout: Size<2> was never implemented.
Bit 48 gets lost in more than one place:
On sun4u the raw TTE word is stored as the guest wrote it, bit 48
included, but TTE_PGSIZE() only reads bits 62:61: the bit survives in
the TLB, it is just never decoded.
On sun4v, sun4v_tte_to_sun4u() converts the incoming three-bit code
(cpu.h defines TTE_PGSIZE_UA2005() for it, unused until now) but masked
it to two:
sun4u_tte |= (sun4v_tte & 3ULL) << 61; /* TTE_PGSIZE */
so the third bit was dropped before it was stored.
demap_tlb() duplicates the same 2-bit math instead of calling
TTE_PGSIZE(), so it misses bit 48 too, for a TTE from either origin.
Either way 256M decodes as 64K and 32M as 8K; the guest faults on the
first access past the truncated entry.
This patch implements Size<2> at bit 48, where the hardware keeps it.
TTE_PGSIZE() now reads bit 48 as the high bit; on parts that keep bit
48 reserved it reads as zero, so they decode as before.
sun4v_tte_to_sun4u() stores the full three-bit code, and demap_tlb()
now calls TTE_PGSIZE() instead of its own copy. dump_mmu() gains 32M
and 256M labels.
Link:
https://lore.kernel.org/qemu-devel/[email protected]/
AI-used-for: code, analysis
Signed-off-by: Dmitry Pimenov <[email protected]>
---
v2: described behaviour across sun4u/sun4v instead of the sun4v
conversion; no functional change. QEMU_BUILD_BUG_ON and comments in
cpu.h/ldst_helper.c are dropped.
This email's text -- beyond the code and analysis the trailer above
covers -- also had AI involvement: current policy (AGENTS.md)
declines AI-derived content outright, and even the not-yet-merged
llm-usage.rst RFC would require it to be human-written regardless of
any trailer. Sending as-is rather than silently -- happy to hear if
that's not acceptable here.
Happy to run more tests or provide more evidence if this isn't enough.
Guest is OpenSolaris snv_134 (osol-dev-134-ai-sparc.iso). Firmware is
the S10image/ set from OpenSPARCT1_Arch.1.5.tar.bz2, which
docs/system/target-sparc64.rst names for this machine, except the
MD/hv-config pair (1up-md.bin, 1up-hv.bin), from the repo below. The
archive's own config only boots disk.s10hw2, capped at 4M pages.
Observed on upstream 5f664cd37a, with and without this patch -- same
guest, firmware and RAM, plain `boot` at the `ok` prompt (occasionally
hits an unrelated, pre-existing trap in OBP's loader phase before the
kernel runs):
pristine: hangs after "Loading: /platform/sun4v/kernel/sparcv9/unix";
"info tlb" shows no 32M or 256M entry.
patched: boots to "Enter user name for system maintenance";
"info tlb" shows 256M entries, e.g.
[17] VA: 600108b8000, PA: 140000000, 256M, priv, RW, ...
Full info tlb dumps:
https://github.com/unix0cc/qemu-experimental-patches/blob/967a037/verification/0002-tte-size2/logs/pristine-plain-boot-tlb.txt
https://github.com/unix0cc/qemu-experimental-patches/blob/967a037/verification/0002-tte-size2/logs/patched-plain-boot-tlb.txt
The reproduction as a script, verify.sh (fetches firmware, MD and
ISO, checks their hashes, boots):
https://github.com/unix0cc/qemu-experimental-patches/blob/967a037/verification/0002-tte-size2/verify.sh
On sun4u the pre-extension page sizes are verified by
make check-qtest-sparc64 and make check-functional-sparc64 (sun4u,
tuxrun, migration), which pass on the patched build with logs:
https://github.com/unix0cc/qemu-experimental-patches/blob/967a037/verification/0002-tte-size2/check-qtest-sparc64.log
https://github.com/unix0cc/qemu-experimental-patches/blob/967a037/verification/0002-tte-size2/check-functional-sparc64.log
To check whether
Size<2> is exercised at all, I added a temporary log in
replace_tlb_entry() and ran both suites again: zero hits, as expected
-- IIi predates IV+ and doesn't implement Size<2>, so the guest
running under it never sets bit 48. Result:
https://github.com/unix0cc/qemu-experimental-patches/blob/967a037/verification/0002-tte-size2/sun4u-bit48-probe/RESULT
Probe and logs:
https://github.com/unix0cc/qemu-experimental-patches/tree/967a037/verification/0002-tte-size2/sun4u-bit48-probe/
32M/256M on sun4u stays untested by this suite; that would need a guest
running under Sun-UltraSparc-IV-plus.
Overview of everything above (scripts, logs, README):
https://github.com/unix0cc/qemu-experimental-patches/blob/967a037/verification/0002-tte-size2/README
target/sparc/cpu.h | 4 +++-
target/sparc/ldst_helper.c | 9 +++++++--
target/sparc/mmu_helper.c | 12 ++++++++++++
3 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/target/sparc/cpu.h b/target/sparc/cpu.h
index 31a16c2af0..1ab420d831 100644
--- a/target/sparc/cpu.h
+++ b/target/sparc/cpu.h
@@ -307,7 +307,9 @@ enum {
#define TTE_SET_USED(tte) ((tte) |= TTE_USED_BIT)
#define TTE_SET_UNUSED(tte) ((tte) &= ~TTE_USED_BIT)
-#define TTE_PGSIZE(tte) (((tte) >> 61) & 3ULL)
+#define TTE_PGSIZE_HI_BIT (1ULL << 48)
+#define TTE_PGSIZE(tte) ((((tte) >> 61) & 3ULL) | \
+ (((tte) & TTE_PGSIZE_HI_BIT) >> 46))
#define TTE_PGSIZE_UA2005(tte) ((tte) & 7ULL)
#define TTE_PA(tte) ((tte) & 0x1ffffffe000ULL)
diff --git a/target/sparc/ldst_helper.c b/target/sparc/ldst_helper.c
index 4ec8799d1f..142c1c24de 100644
--- a/target/sparc/ldst_helper.c
+++ b/target/sparc/ldst_helper.c
@@ -186,7 +186,7 @@ static void demap_tlb(SparcTLBEntry *tlb, target_ulong
demap_addr,
/* demap page
will remove any entry matching VA */
mask = 0xffffffffffffe000ULL;
- mask <<= 3 * ((tlb[i].tte >> 61) & 3);
+ mask <<= 3 * TTE_PGSIZE(tlb[i].tte);
if (!compare_masked(demap_addr, tlb[i].tag, mask)) {
continue;
@@ -217,7 +217,12 @@ static uint64_t sun4v_tte_to_sun4u(CPUSPARCState *env,
uint64_t tag,
return sun4v_tte;
}
sun4u_tte = TTE_PA(sun4v_tte) | (sun4v_tte & TTE_VALID_BIT);
- sun4u_tte |= (sun4v_tte & 3ULL) << 61; /* TTE_PGSIZE */
+ {
+ uint64_t pgsz = TTE_PGSIZE_UA2005(sun4v_tte);
+ sun4u_tte |= (pgsz & 3ULL) << 61; /* TTE_PGSIZE bits 61-62 */
+ sun4u_tte |= (pgsz & 4ULL) ?
+ TTE_PGSIZE_HI_BIT : 0; /* TTE_PGSIZE bit 48 */
+ }
sun4u_tte |= CONVERT_BIT(sun4v_tte, TTE_NFO_BIT_UA2005, TTE_NFO_BIT);
sun4u_tte |= CONVERT_BIT(sun4v_tte, TTE_USED_BIT_UA2005, TTE_USED_BIT);
sun4u_tte |= CONVERT_BIT(sun4v_tte, TTE_W_OK_BIT_UA2005, TTE_W_OK_BIT);
diff --git a/target/sparc/mmu_helper.c b/target/sparc/mmu_helper.c
index 07ba25dfce..8544de097d 100644
--- a/target/sparc/mmu_helper.c
+++ b/target/sparc/mmu_helper.c
@@ -830,6 +830,12 @@ void dump_mmu(CPUSPARCState *env)
case 0x3:
mask = " 4M";
break;
+ case 0x4:
+ mask = " 32M";
+ break;
+ case 0x5:
+ mask = "256M";
+ break;
}
if (TTE_IS_VALID(env->dtlb[i].tte)) {
qemu_printf("[%02u] VA: %" PRIx64 ", PA: %llx"
@@ -869,6 +875,12 @@ void dump_mmu(CPUSPARCState *env)
case 0x3:
mask = " 4M";
break;
+ case 0x4:
+ mask = " 32M";
+ break;
+ case 0x5:
+ mask = "256M";
+ break;
}
if (TTE_IS_VALID(env->itlb[i].tte)) {
qemu_printf("[%02u] VA: %" PRIx64 ", PA: %llx"
--
2.43.0