armviz

Address translation

Two halves of the same question. Given a virtual address and the configuration in TCR_EL1, which entry of which table does the processor read? And given that entry, what does it grant, and to whom?

The level arithmetic is not the nine-bits-per-level rule it looks like. The index is as wide as the granule minus three, because entries are eight bytes, so the 16 KiB and 64 KiB granule sizes give eleven and thirteen. Two things follow that are easy to get wrong. Every level including the last is indexed — the page offset sits below all of them, not in place of one. And the root index is only as wide as the address size leaves over after the other levels have taken their share, so a 48-bit address gets a full nine-bit level 0 index while a 42-bit one gets three bits.

Address breakdown

Change the granule or the address-space size and watch the same address get carved up differently.

Address split into levels

ignored
offset0x0[11:0]

Each index is 9 bits wide, which is the granule size minus three: an entry is eight bytes, so three bits of an index are the entry's own position within the table and the rest select which table. There are 4 of them here, stacked down to the 12-bit page offset at the bottom. Changing the granule changes the stride, which is why 16 KiB and 64 KiB pages need fewer levels to cover the same address space.

Level by level

Level 0root512 GiB
index
256 (9 bits)
bits
47:39
entries in table
512
descriptor
next-level table descriptor
Level 11 GiB
index
0 (9 bits)
bits
38:30
entries in table
512
descriptor
next-level table descriptor
Level 22 MiB
index
64 (9 bits)
bits
29:21
entries in table
512
descriptor
next-level table descriptor
Level 3leaf4 KiB
index
0 (9 bits)
bits
20:12
entries in table
512
descriptor
page descriptor

An entry is eight bytes wide, so a 4 KiB table holds 2^9 entries. Every level but the root has exactly that many; the root has 2^9, which is the same. A wider address space therefore means more levels, not bigger tables.

Walk shape

walk
4 indexed levels, from L0 to L3
granule
4 KiB (12 bits)
index stride
9 bits per level
root index
9 bits (same as the stride)
address space
48 bits, top 16 ignored
page offset
0x0 — page aligned
root base
TCR_EL1 points at a level 0 table via TTBR0_EL1

Descriptor explorer

Paste a translation table entry and see which bits mean what. The level matters: the two lowest bits are 0b11 for a table at levels 0 to 2 and for a page at level 3.

Examples
Read as

At level 3 this is

a page descriptor

Maps 4 KiB at physical address 0x8000. Readable and writable at EL0. Read/write at EL1. Unprivileged execute never. Privileged execute never.

output address
0x8000
AttrIndx
0 — an attribute set in MAIR_EL1
SH
full system
AF
set, no fault expected
nG
clear, global mapping
permissions
EL0 RW, EL1+ RW
execute
UXN set, PXN set

Field layout

—
54
53
Output address[47:12]47:12
11
10
9:8
7:6
4:2
1:0
byte 7
byte 6
byte 5
byte 4
byte 3
byte 2
byte 1
byte 0

Current value hover a field to locate it

B7
B6
B5
B4
B3
B2
B1
B0

Fields

BitsFieldBitsValueDescription
63:55—9Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
54UXN[54]10x1

Unprivileged Execute Never. When set, code running at EL0 may not execute from this mapping. It gates the instruction fetch, not the data the code reads. A table descriptor carries its own UXN at bit 60.

53PXN[53]10x1

Privileged Execute Never. When set, code running at EL1 to EL3 may not execute from this mapping. Linux sets this on every user mapping, which is why jumping to user code means copying it somewhere executable first.

52:48—5Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
47:12Output address[47:12]360x8 (8)

Base of the page. With a 4 KiB granule the low 12 bits are always zero, so this field is effectively the page's physical address. A 64 KiB granule raises the base to bit 16.

11nG[11]10x0

Not Global. When set the entry is tagged with the current ASID and affects no other address space. When clear the mapping is global, shared by every address space, and survives an ASID switch.

10AF[10]1

Access Flag. When clear, the first access to this mapping raises an Access Flag fault and the fault handler is expected to set the bit. Software has to implement this unless hardware-managed access flags are enabled via TCR_ELx.HA.

0
Access Flag Fault raised on first access
1
Already accessed; no fault
9:8SH[9:8]2

Shareability: which observers see a coherent copy. Non-shareable is the right choice for device registers, because a device that is coherent with the caches is a device that hangs. That is why device mappings almost always set this to 00.

0
Non-shareable
1
Inner shareable — coherent inside the inner domain, which on AArch64 Linux means the cluster
2
Outer shareable — coherent out to the outer domain boundary
3
Full system — coherent everywhere, if the interconnect supports it
7:6AP[7:6]2

Access permissions. Bit 7 is AP[2], the read-only bit; bit 6 is AP[1], the user-access bit. Between them they decide who may read the mapping, who may write it, and who may not touch it at all.

0
EL1 read/write, no access at EL0
1
EL1 read/write, EL0 read/write
2
EL1 read-only, EL0 read-only
3
EL1 read-only, no access at EL0
5—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
4:2AttrIndx[4:2]30x0

Index into MAIR_EL1, selecting one of eight attribute sets that together describe memory type and cacheability. At bits 4:2 here; the same field on a table descriptor is at bits 5:3.

1:0Type[1:0]2

Bit 0 alone decides validity, and it is checked before anything else. With bit 0 set, 0b11 is the only valid shape at level 3: a block would be 0b01, and there is no level below 3 for a table to point at.

0
Invalid — bit 0 clear, so the walk raises a translation fault
1
Invalid — a block is not valid at level 3
3
Valid page — translation stops here

The walk

Follow one address through the tables to a physical one, one descriptor at a time. Pick a mapping to see a case worth looking at — including the ones that fault.

Start from a mapping

Resolved

VA 0xffff800000000000 → PA 0x80000000
mapped by
a level 2 block descriptor
extent
2 MiB
EL1 read
permitted
permissions
EL0 --, EL1+ RW, UXN clear, PXN clear
shareability
full system
attributes
AttrIndx 0 in MAIR_EL1

3 steps

  1. Level 0index 256 from bits 47:39descend to the next table
    table base
    0x42000
    descriptor at
    0x42800 (base + index × 8)
    points at
    0x43000

    Field layout

    62:61
    60
    59
    —
    Output address[47:12]47:12
    10
    5:3
    1:0
    byte 7
    byte 6
    byte 5
    byte 4
    byte 3
    byte 2
    byte 1
    byte 0

    Current value hover a field to locate it

    B7
    B6
    B5
    B4
    B3
    B2
    B1
    B0
    0x0000000000043403

    Fields

    BitsFieldBitsValueDescription
    63—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    62:61S1AP[62:61]20x0

    Hierarchical access permissions. Where FEAT_HAFDBS is implemented these override whatever any leaf below this table says, which is how a kernel can apply one policy to a whole subtree. RES0 without that feature.

    60UXN[60]10x0

    Unprivileged Execute Never, applied to every leaf beneath this table. A table descriptor carries UXN at bit 60 where a leaf carries it at bit 54.

    59PXN[59]10x0

    Privileged Execute Never, applied to every leaf beneath this table. A table descriptor carries PXN at bit 59 where a leaf carries it at bit 53.

    58:48—11Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    47:12Output address[47:12]360x43 (67)

    Base of the next-level table. The index of the entry used inside that table overlays the low bits of this field during the walk, so the physical entry address is this base with the next index ORed in, not simply concatenated.

    11—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    10AF[10]1Already accessed; no fault

    Access Flag. When clear, the first access to this mapping raises an Access Flag fault and the fault handler is expected to set the bit. Software has to implement this unless hardware-managed access flags are enabled via TCR_ELx.HA.

    0
    Access Flag Fault raised on first access
    1
    Already accessed; no fault
    9:6—4Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    5:3AttrIndx[5:3]30x0

    Index into MAIR_EL1, describing the accesses the walk itself makes while reading the next-level table. It sits one position higher here than the same field on a leaf descriptor.

    2—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    1:0Type[1:0]2Table descriptor — descend one more level

    Bit 0 is the validity bit and is checked first. Bit 1 says whether the entry points at another table: set to descend, clear to stop here. So a table descriptor is 0b11 and a block is 0b01 — never 0b00, which would have bit 0 clear and be invalid.

    1
    Block descriptor — translation stops here
    3
    Table descriptor — descend one more level
  2. Level 1index 0 from bits 38:30descend to the next table
    table base
    0x43000
    descriptor at
    0x43000 (base + index × 8)
    points at
    0x44000

    Field layout

    62:61
    60
    59
    —
    Output address[47:12]47:12
    10
    5:3
    1:0
    byte 7
    byte 6
    byte 5
    byte 4
    byte 3
    byte 2
    byte 1
    byte 0

    Current value hover a field to locate it

    B7
    B6
    B5
    B4
    B3
    B2
    B1
    B0
    0x0000000000044403

    Fields

    BitsFieldBitsValueDescription
    63—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    62:61S1AP[62:61]20x0

    Hierarchical access permissions. Where FEAT_HAFDBS is implemented these override whatever any leaf below this table says, which is how a kernel can apply one policy to a whole subtree. RES0 without that feature.

    60UXN[60]10x0

    Unprivileged Execute Never, applied to every leaf beneath this table. A table descriptor carries UXN at bit 60 where a leaf carries it at bit 54.

    59PXN[59]10x0

    Privileged Execute Never, applied to every leaf beneath this table. A table descriptor carries PXN at bit 59 where a leaf carries it at bit 53.

    58:48—11Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    47:12Output address[47:12]360x44 (68)

    Base of the next-level table. The index of the entry used inside that table overlays the low bits of this field during the walk, so the physical entry address is this base with the next index ORed in, not simply concatenated.

    11—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    10AF[10]1Already accessed; no fault

    Access Flag. When clear, the first access to this mapping raises an Access Flag fault and the fault handler is expected to set the bit. Software has to implement this unless hardware-managed access flags are enabled via TCR_ELx.HA.

    0
    Access Flag Fault raised on first access
    1
    Already accessed; no fault
    9:6—4Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    5:3AttrIndx[5:3]30x0

    Index into MAIR_EL1, describing the accesses the walk itself makes while reading the next-level table. It sits one position higher here than the same field on a leaf descriptor.

    2—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    1:0Type[1:0]2Table descriptor — descend one more level

    Bit 0 is the validity bit and is checked first. Bit 1 says whether the entry points at another table: set to descend, clear to stop here. So a table descriptor is 0b11 and a block is 0b01 — never 0b00, which would have bit 0 clear and be invalid.

    1
    Block descriptor — translation stops here
    3
    Table descriptor — descend one more level
  3. Level 2index 0 from bits 29:21stop — this one maps memory
    table base
    0x44000
    descriptor at
    0x44000 (base + index × 8)

    Field layout

    —
    54
    53
    Output address[47:21]47:21
    —
    11
    10
    9:8
    7:6
    4:2
    1:0
    byte 7
    byte 6
    byte 5
    byte 4
    byte 3
    byte 2
    byte 1
    byte 0

    Current value hover a field to locate it

    B7
    B6
    B5
    B4
    B3
    B2
    B1
    B0
    0x0000000080000701

    Fields

    BitsFieldBitsValueDescription
    63:55—9Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    54UXN[54]10x0

    Unprivileged Execute Never. When set, code running at EL0 may not execute from this mapping. It gates the instruction fetch, not the data the code reads. A table descriptor carries its own UXN at bit 60.

    53PXN[53]10x0

    Privileged Execute Never. When set, code running at EL1 to EL3 may not execute from this mapping. Linux sets this on every user mapping, which is why jumping to user code means copying it somewhere executable first.

    52:48—5Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    47:21Output address[47:21]270x400 (1024)

    Base of the block. A level 2 block spans 2 MiB, so bits 20:0 must be zero; a level 1 block spans 1 GiB, so bits 29:0 must be zero. A non-zero bit below the block size is a misaligned descriptor and faults.

    20:12—9Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    11nG[11]10x0

    Not Global. When set the entry is tagged with the current ASID and affects no other address space. When clear the mapping is global, shared by every address space, and survives an ASID switch.

    10AF[10]1Already accessed; no fault

    Access Flag. When clear, the first access to this mapping raises an Access Flag fault and the fault handler is expected to set the bit. Software has to implement this unless hardware-managed access flags are enabled via TCR_ELx.HA.

    0
    Access Flag Fault raised on first access
    1
    Already accessed; no fault
    9:8SH[9:8]2Full system — coherent everywhere, if the interconnect supports it

    Shareability: which observers see a coherent copy. Non-shareable is the right choice for device registers, because a device that is coherent with the caches is a device that hangs. That is why device mappings almost always set this to 00.

    0
    Non-shareable
    1
    Inner shareable — coherent inside the inner domain, which on AArch64 Linux means the cluster
    2
    Outer shareable — coherent out to the outer domain boundary
    3
    Full system — coherent everywhere, if the interconnect supports it
    7:6AP[7:6]2EL1 read/write, no access at EL0

    Access permissions. Bit 7 is AP[2], the read-only bit; bit 6 is AP[1], the user-access bit. Between them they decide who may read the mapping, who may write it, and who may not touch it at all.

    0
    EL1 read/write, no access at EL0
    1
    EL1 read/write, EL0 read/write
    2
    EL1 read-only, EL0 read-only
    3
    EL1 read-only, no access at EL0
    5—1Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise.
    4:2AttrIndx[4:2]30x0

    Index into MAIR_EL1, selecting one of eight attribute sets that together describe memory type and cacheability. At bits 4:2 here; the same field on a table descriptor is at bits 5:3.

    1:0Type[1:0]2Block descriptor — translation stops here

    Bit 0 is the validity bit and is checked first. Bit 1 clear makes this a leaf, so a block is 0b01 — not 0b00, which has bit 0 clear and is therefore invalid however plausible the rest of the word looks. At levels 1 and 2 the value 0b11 is equally legal here and means a table instead.

    1
    Block descriptor — translation stops here
    3
    Table descriptor — descend one more level

The tables are built in the browser from named mappings rather than loaded from a file, so what is being walked is visible in the source of this page rather than hidden in a binary. Data pages start at physical address 0x800000 and each one is stamped with its own page number, so a walk that lands in the right place reads a byte that says where it landed.

Where the numbers come from

The bit positions are not from memory.

Field positions were read out of Linux's arch/arm64/include/asm/pgtable-hwdef.h and QEMU's target/arm/ptw.c, both of which are precise and public. That mattered: several positions contradict the obvious reading, and a layout written from memory would have got them wrong.

  • AttrIndx is at bits 4:2 on a leaf but 5:3 on a table descriptor. It is not in the same place in the two shapes.
  • AP is bits 7:6, not the 9:6 that an ASCII diagram suggests.
  • A table descriptor carries PXN/ UXN at 59/60; a block descriptor carries them at 53/54.
  • The level count is (va bits - 4) / stride, integer division, counting indexed levels, and the root is level 4 - levels. Every level has an index; the page offset is below them all.
  • Cross-checked against Linux's PTRS_PER_PGD = 1 << (va bits - PGDIR_SHIFT), which pins the root table's entry count from the address size alone. The test asserts all thirty combinations of granule and address size tile every bit and match that count.

What is not modelled: stage 2 descriptors, which are a separate shape with their own attribute fields, and the TTBR1 regime, whose physical address size this project does not read from the register. Bits belonging to optional architectural features are shown as gaps rather than invented, so a field you cannot see is a field this project has not verified, not a field that does not exist.

The walk itself is checked against QEMU: a real AArch64 CPU with the MMU enabled, reading through the same mappings this page draws, for all three granule sizes. That is the only check in the project that does not compare our output to a document or to another of our own functions — and the three granule sizes are checked separately because each gives a different level structure, so agreeing on one says nothing about the others.

The prose version of all of this, with the arithmetic worked through step by step, is in How a virtual address becomes a physical one.