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

One of the more interesting and puzzling things I ran into was a suspicious hang on any configuration with 2048MB or more of memory under QEMU and Machine Description (MD) configuration. It was a glass wall (matrix here) I couldn’t break through for any Solaris 10 release higher than S10u1.

The symptoms were odd: it starts to boot and then completely hangs, without any further response to kmdb.

QEMU itself generates tons of logs - traps between hypervisor and MMU, like this:

58552984: Hyperprivileged Trap Instruction (v=0183)
58552985: Data Access MMU Miss (v=0068)
58552986: Hyperprivileged Trap Instruction (v=0183)
58552987: Data Access MMU Miss (v=0068)
58552988: Hyperprivileged Trap Instruction (v=0183)
58552989: Data Access MMU Miss (v=0068)

This made me think something was wrong with the software MMU code execution inside QEMU - but I had no idea what.

Moreover, it looked like a memory pagesize problem: the guest OS trying to reach a pagesize bigger than the host supports. This was a suspicion, not proven at the time: for decades, every new paging feature introduced in silicon was a bit of “fanfare” inside the Solaris community, and I suspect we have some limitations in our software emulation/QEMU also.

Let’s start from the guest/Solaris side with a bit of theory (i’m sorry):

A parameter named “mmu-pagesize-list” is read from the MD at a predetermined point before boot, and it uses the standard bitmask supported by the 3-bit [sz] parameter on the TSB Translation Table Entry (TTE):

This is described cleanly in UltraSPARC Architecture 2005, page 362 - though there it’s actually a 4-bit field:

Data – 3:0 sz The page size of this entry, encoded as shown below.
sz Page Size
0000 8 Kbyte
0001 64 Kbyte
0010 Reserved
0011 4 Mbyte
0100 Reserved
0101 256 Mbyte
0110 Reserved
0111 Reserved
1000-1111 Reserved

This converts to the following code table for T1:

code size bit value
0 8K 0x01
1 64K 0x02
2 512K 0x04
3 4M 0x08
4 32M 0x10
5 256M 0x20

So, to enable the standard support for 8K+64K+4M, we should have 0xb at this stage; the MD should be 0x9 without 64K support (and even that works!), and 0xb also seems valid with 64K pages enabled.

But in practice, the picture was the opposite:

The kernel mystically overrides the default values from 0x9 (or even 0xb) and silently changes them to 0x2b (+256M support) at an early stage of booting:

[0]> mmu_exported_pagesize_mask/X
mmu_exported_pagesize_mask:
mmu_exported_pagesize_mask:     2b

Why does the kernel insist on “0x2b” instead of “0xb”, which is recorded cleanly in our “firmware” / MD property?

I started tracing through Solaris and found a number of interesting things. Seems in OpenSolaris/Solaris there are two branches of control selected by “broken_md_flag”, set when the domaining-enabled parameter (p.62) is absent from the MD. “Domaining” refers to the host acting as the primary domain (of course, under the hypervisor as well - any sun4v system runs under the control of the hypervisor code from the sun4v firmware; this is the “q.bin” file underneath the Sun Legion/QEMU part).

The absence of the “domaining-enabled” property raises the broken_md_flag here:

[0]> broken_md_flag/D
broken_md_flag:
broken_md_flag: 1

And it comes from os/fillsysinfo.c in the OpenSolaris/Illumos code:

init_md_broken(md_t *mdp, mde_cookie_t *cpulist)
{
        int nrnode;
        mde_cookie_t *platlist, rootnode;
        uint64_t val = 0;
        char *namebuf;
        int namelen;

        rootnode = md_root_node(mdp);
        ASSERT(rootnode != MDE_INVAL_ELEM_COOKIE);
        ASSERT(cpulist);

        nrnode = md_alloc_scan_dag(mdp, rootnode, "platform", "fwd",
            &platlist);

        if (nrnode < 1)
                cmn_err(CE_PANIC, "init_md_broken: platform node missing");

        if (md_get_prop_data(mdp, cpulist[0],
            "compatible", (uint8_t **)&namebuf, &namelen)) {
                cmn_err(CE_PANIC, "init_md_broken: "
                    "Cannot read 'compatible' property of 'cpu' node");
        }

        if (md_get_prop_val(mdp, platlist[0],
            "domaining-enabled", &val) == -1 &&
            strcmp(namebuf, "SUNW,UltraSPARC-T1") == 0)
                broken_md_flag = 1;

        md_free_scan_dag(mdp, &platlist);
}
[0]> :c
kmdb: stop at init_md_broken+0x88
[0]> <o0/s
0xfff34ac0:     SUNW,UltraSPARC-T1
[0]>

(This is beside the point, but if we try to enable the “domaining-enabled” property, the kernel starts but panics in the CPU platform module when it tries to configure some cache/TLB/MMU properties extracted from the MD - i.e., it behaves as if it were a real control LDOM. It’s a good starting point for the next stage of MD structure research, to open up a new level of possibilities, IMO.)

So, since we don’t have the domaining-enabled property in the MD’s cpu node, as expected, broken_md_flag gets set:

cpu0: UltraSPARC-T1 (chipid 0, clock 5 MHz)
Loaded modules: [ scsi_vhci mac neti ufs s1394 hook ip usba specfs sctp arp
sockfs ]
kmdb: target stopped at:
kmdb_enter:     ta        %icc, 0x7d
[0]> broken_md_flag/D
broken_md_flag:
broken_md_flag: 1
[0]>

This triggers the setup of a specific memory pagesize mask in /usr/src/uts/sun4v/cpu/niagara.c:

  if (broken_md_flag) {
                /*
                 * Turn on the missing bits supported by Niagara CPU in
                 * MMU pagesize mask returned by MD.
                 */
                mmu_exported_pagesize_mask |= NI_MMU_PAGESIZE_MASK;
        } else {
                if ((mmu_exported_pagesize_mask &
                    DEFAULT_SUN4V_MMU_PAGESIZE_MASK) !=
                    DEFAULT_SUN4V_MMU_PAGESIZE_MASK)
                        cmn_err(CE_PANIC, "machine description"
                            " does not have required sun4v page sizes"
                            " 8K, 64K and 4M: MD mask is 0x%x",
                            mmu_exported_pagesize_mask);

So, when broken_md_flag=1, the MMU pagesizes come from NI_MMU_PAGESIZE_MASK:

#define	NI_MMU_PAGESIZE_MASK	((1 << TTE8K) | (1 << TTE64K) | (1 << TTE4M) \
				    | (1 << TTE256M))

otherwise, the predefined parameters above are used - the default CPU pagesize mask with 8K/64K/4M, from /usr/src/uts/sun4v/sys/cpu_module.h (not our case):

#define DEFAULT_SUN4V_MMU_PAGESIZE_MASK ((1 << TTE8K) | (1 << TTE64K) \
                                            | (1 << TTE4M))

(it seems the control domain didn’t use 256M at this point in Solaris Express 10/2011)

and the bitmask pagesize flags come from /usr/src/uts/sun4v/sys/pte.h:

/* Defines for sz field in tte */
#define TTE8K                   0x0
#define TTE64K                  0x1
#define TTE512K                 0x2
#define TTE4M                   0x3
#define TTE32M                  0x4
#define TTE256M                 0x5
#define TTE2G                   0x6
#define TTE16G                  0x7
NI_MMU_PAGESIZE_MASK = (1<<0)|(1<<1)|(1<<3)|(1<<5) = { 8K, 64K, 4M, 256M } = binary 0010 1011 = 0x2b
DEFAULT_SUN4V_MMU_PAGESIZE_MASK = (1<<0)|(1<<1)|(1<<3) = {8K, 64K, 4M } = 0000 1011 = 0xb

i.e., 00101011 -> 0x2b (256M enabled) vs. 00001011 -> 0xb for the basic value (8K+64K+4M).

It seems our host is trying to enable 256M pages. In other words, here’s the interesting logic: on a domaining-capable host (i.e., one that controls the hypervisor), we support 8K/64K/4M paging; but when “broken_md_flag” is set, we also get 256M support on T1. Note that 32M pages aren’t implemented in T1 silicon at all, as described in the niagara.c logic above.

Part 2 continues with what this looks like in practice, and what the QEMU side reveals.