- index
- 256 (9 bits)
- bits
- 47:39
- entries in table
- 512
- descriptor
- next-level table descriptor
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
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
- index
- 0 (9 bits)
- bits
- 38:30
- entries in table
- 512
- descriptor
- next-level table descriptor
- index
- 64 (9 bits)
- bits
- 29:21
- entries in table
- 512
- descriptor
- next-level table descriptor
- 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
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.
At level 3 this is
a page descriptorMaps 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
Current value hover a field to locate it
Fields
| Bits | Field | Bits | Value | Description |
|---|---|---|---|---|
| 63:55 | — | 9 | Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. | |
| 54 | UXN[54] | 1 | 0x1 | 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. |
| 53 | PXN[53] | 1 | 0x1 | 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 | — | 5 | Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. | |
| 47:12 | Output address[47:12] | 36 | 0x8 (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. |
| 11 | nG[11] | 1 | 0x0 | 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. |
| 10 | AF[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.
| |
| 9:8 | SH[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.
| |
| 7:6 | AP[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.
| |
| 5 | — | 1 | Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. | |
| 4:2 | AttrIndx[4:2] | 3 | 0x0 | 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:0 | Type[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.
|
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
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:616059—Output address[47:12]47:12105:31:0byte 7byte 6byte 5byte 4byte 3byte 2byte 1byte 0Current value hover a field to locate it
B7B6B5B4B3B2B1B00x0000000000043403Fields
Bits Field Bits Value Description 63 — 1 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 62:61 S1AP[62:61] 2 0x0 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.
60 UXN[60] 1 0x0 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.
59 PXN[59] 1 0x0 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 — 11 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 47:12 Output address[47:12] 36 0x43 (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 — 1 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 10 AF[10] 1 Already 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 — 4 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 5:3 AttrIndx[5:3] 3 0x0 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 — 1 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 1:0 Type[1:0] 2 Table 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
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:616059—Output address[47:12]47:12105:31:0byte 7byte 6byte 5byte 4byte 3byte 2byte 1byte 0Current value hover a field to locate it
B7B6B5B4B3B2B1B00x0000000000044403Fields
Bits Field Bits Value Description 63 — 1 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 62:61 S1AP[62:61] 2 0x0 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.
60 UXN[60] 1 0x0 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.
59 PXN[59] 1 0x0 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 — 11 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 47:12 Output address[47:12] 36 0x44 (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 — 1 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 10 AF[10] 1 Already 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 — 4 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 5:3 AttrIndx[5:3] 3 0x0 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 — 1 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 1:0 Type[1:0] 2 Table 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
Level 2index 0 from bits 29:21stop — this one maps memory - table base
- 0x44000
- descriptor at
- 0x44000 (base + index × 8)
Field layout
—5453Output address[47:21]47:21—11109:87:64:21:0byte 7byte 6byte 5byte 4byte 3byte 2byte 1byte 0Current value hover a field to locate it
B7B6B5B4B3B2B1B00x0000000080000701Fields
Bits Field Bits Value Description 63:55 — 9 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 54 UXN[54] 1 0x0 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.
53 PXN[53] 1 0x0 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 — 5 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 47:21 Output address[47:21] 27 0x400 (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 — 9 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 11 nG[11] 1 0x0 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.
10 AF[10] 1 Already 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:8 SH[9:8] 2 Full 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:6 AP[7:6] 2 EL1 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 — 1 Not assigned to a documented field. Values written here are reserved and should be treated as read-as-zero unless the architecture says otherwise. 4:2 AttrIndx[4:2] 3 0x0 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:0 Type[1:0] 2 Block 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.
AttrIndxis 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.APis bits 7:6, not the 9:6 that an ASCII diagram suggests.- A table descriptor carries
PXN/UXNat 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 level4 - 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.