Concept 2 told you how to read a fault. This concept is about the thing that made the fault: the translation regime, which is the pair of questions "where do I look" and "how much of the address is a position in it." The second question is the interesting one, and on AArch64 it is answered by a five-bit field in a register — which means the answer is not a number the architecture chose but a number the platform chose, and the field has a floor.
So this concept is two subjects that look unrelated and are one. The first is the split: two translation base registers, two regions, and a field each that says how big its region is. The second is the reach: a fixed 32-bit instruction with a 21-bit field can address a page four gibibytes away or a byte one mebibyte away, and every page table in a system is on the first side of that line while every array index is on the second. The second subject is what makes the first one expensive, and concept 5 is where the bill arrives.
Three registers, and the encodings name them with nine bits. The first measured result on the page is that they are all CRn = 2 and are told apart by a two-bit op2 field:
$ python3 a64sys.py --section 7 | sed -n '/three registers/,/^$/p' | head -10
[MEASURED-ON-BYTES] three registers, and the encodings that name them
mrs x0, TTBR0_EL1 0xd5382000 CRn=0x2 CRm=0x0 op2=0 op1=0 mrs x0, TTBR0_EL1
mrs x0, TTBR1_EL1 0xd5382020 CRn=0x2 CRm=0x0 op2=1 op1=0 mrs x0, TTBR1_EL1
mrs x0, TCR_EL1 0xd5382040 CRn=0x2 CRm=0x0 op2=2 op1=0 mrs x0, TCR_EL1
mrs x0, SCTLR_EL1 0xd5381000 CRn=0x1 CRm=0x0 op2=0 op1=0 mrs x0, SCTLR_EL1
msr TTBR0_EL1, x0 0xd5182000 CRn=0x2 CRm=0x0 op2=0 op1=0 msr TTBR0_EL1, x0
mrs x0, TCR_EL2 0xd53c2040 CRn=0x2 CRm=0x0 op2=2 op1=4 mrs x0, TCR_EL2
Three registers whose addresses a kernel sets once at boot, whose names every reader will look up, and whose encodings are adjacent numbers 0, 1, 2 in a two-bit field. Read that as a gift to a decoder and a trap for a page: there is nothing about the relationship between the three registers in the encoding. The relationship — TTBR0 and TTBR1 describe two halves of one address space — is architecture, and it is quoted.
Now the formula, and it is the entire measured content of the concept:
T0SZ is bits[63:48] of TCR_EL1 and T1SZ is bits[47:32]
(QUOTED), and the region TTBRn addresses is 2^(64-TnSZ)
bytes. Every row below is that identity and nothing else.
configuration VA bits T0SZ T1SZ TTBR0 region TTBR1 region
48-bit, no LPA2 48 16 16 2^48 = 0x1000000000000 2^48 = 0x1000000000000
48/52, with LPA2 48 16 12 2^48 = 0x1000000000000 2^52 = 0x10000000000000
39/39, the 3-level case 39 25 25 2^39 = 0x008000000000 2^39 = 0x008000000000
39/48 39 25 16 2^39 = 0x008000000000 2^48 = 0x1000000000000
42/48 42 22 16 2^42 = 0x040000000000 2^48 = 0x1000000000000
52/52 52 12 12 2^52 = 0x10000000000000 2^52 = 0x10000000000000
33/33, the SMALLEST 33 31 31 2^33 = 0x000200000000 2^33 = 0x000200000000
[RESULT] T0SZ = 64 - 48 = 16, and T1SZ = 64 - 52 = 12 with LPA2,
64 - 48 = 16 without
The section plan gave three of those numbers — T0SZ = 16, T1SZ = 16 without LPA2, T1SZ = 12 with it — and every one is right. What the plan did not have is the last row, and the last row is the concept.
and the field is FIVE bits, so the SIZE of a regime is
bounded below as well as above:
64-bit VA -> T0SZ = 0 region 2^64 bytes, FITS
60-bit VA -> T0SZ = 4 region 2^60 bytes, FITS
52-bit VA -> T0SZ = 12 region 2^52 bytes, FITS
48-bit VA -> T0SZ = 16 region 2^48 bytes, FITS
44-bit VA -> T0SZ = 20 region 2^44 bytes, FITS
42-bit VA -> T0SZ = 22 region 2^42 bytes, FITS
39-bit VA -> T0SZ = 25 region 2^39 bytes, FITS
36-bit VA -> T0SZ = 28 region 2^36 bytes, FITS
33-bit VA -> T0SZ = 31 region 2^33 bytes, FITS
32-bit VA -> T0SZ = 32 region 2^32 bytes, DOES NOT FIT in a 5-bit field
30-bit VA -> T0SZ = 34 region 2^30 bytes, DOES NOT FIT in a 5-bit field
25-bit VA -> T0SZ = 39 region 2^25 bytes, DOES NOT FIT in a 5-bit field
And the other direction, because it is the one that matters for understanding why 48: a regime can cover the whole address space, 264 bytes, with T0SZ = 0. So the 48/52 split is a default and not a limit. The reason a real system uses 48 is not that 52 will not fit in the field — it is that each extra level of table costs another 4 KiB of memory and four more dependent memory accesses for every translation. That cost is a real number on a real machine and it is not measurable here. The five-bit field and the identity are, and they are what this page can defend.
The identity says how big each region is. It does not say where, and the constraint that decides where is that the two must not overlap. That is arithmetic, and the gap it leaves is the first thing a reader should know about the 48/52 configuration:
the boundary, as pure arithmetic. TTBRn is at the END of
its region, so the high regime is mapped at the TOP of the
address space -- that is the whole content of R10.
48/48 TTBR0 covers 0x0000000000000000..0x0000ffffffffffff
TTBR1 covers 0xffff000000000000..0xffffffffffffffff
between them: 0xfffe000000000000 bytes, which is UNTRANSLATED
48/52 TTBR0 covers 0x0000000000000000..0x0000ffffffffffff
TTBR1 covers 0xfff0000000000000..0xffffffffffffffff
between them: 0xffef000000000000 bytes, which is UNTRANSLATED
39/48 TTBR0 covers 0x0000000000000000..0x0000007fffffffff
TTBR1 covers 0xffff000000000000..0xffffffffffffffff
between them: 0xfffeff8000000000 bytes, which is UNTRANSLATED
Three things in that block, and the first is the one the plan did not have.
0xffff000000000000, not 0x0001000000000000. That is the retraction: "52-bit" describes the size of the region, not the width of the address. The top four bits of a kernel pointer are 0xf and the next 48 are the position in the regime. A reader who takes the size for the width will place a kernel pointer wrongly, and the mistake shows up as a translation fault three layers from its cause.T1SZ = 16 puts the high regime's base at 0xffff000000000000; T1SZ = 12 moves it to 0xfff0000000000000 and quadruples the kernel's address space. One five-bit field, one nibble of a pointer, and four times the address space — which is the clearest demonstration on this page that a field width is a constraint and a field value is a decision.Now the second subject of the concept, and it is the one a backend author needs. Two instructions, one field shape, two units:
instruction immediate field signed bits unit reach
ADR bits[20:5]+[30:29] 21 1 BYTE +-2^20 = 1 MiB
ADRP bits[20:5]+[30:29] 21 4096 BYTES +-2^20 pages = 4 GiB
And one of those two numbers is measured and the other is not, and the difference is worth understanding rather than papering over.
$ python3 - <<'PY' # bisecting the distance the assembler will accept
ADR largest accepted distance 0xffff8 -> from the insn: 0xffffc
ADR next step up 0xffffc REFUSED: fixup value out of range
ADR backward max D=0xffff8 -> from the insn: -0x100000
ADRP the assembler does NOT check it: every distance tried up to
0x1fff000 ACCEPTED, and a 4 GiB gap is ACCEPTED too
0xffffc and then a refusal at 0x100000 is exactly a 21-bit signed field — the largest positive value is 0xfffff, and the next multiple of four below it is 0xffffc. The assembler found the field's edge for us, and it found it in the only way that works: by refusing.
The ADRP row is the interesting one, and it is a measurement of an absence. The assembler does not range-check ADRP at all — a 4 GiB reference is accepted — because the value is not known until link time and the check belongs to the linker. There is no AArch64 linker on this host, so the 4 GiB figure is an arithmetic identity on a 21-bit field, not a measurement. Say that to yourself every time you see a reach quoted for an instruction whose target is not yet known: who checks it, and is that person present?
And the arithmetic the compiler emits, which is the whole structure of a walk expressed as five shifts:
$ python3 a64sys.py --section 7 | sed -n '/the compiler emits for the regime/,/^$/p'
t0sz_for 2 sub x0, xzr, x1 ; ret
regime_bytes 2 lsl x0, x1, x0 ; ret
ttbr1_52 2 movz/movk pair ; ret
l0_index 2 lsr x0, x0, #39 ; and x0, x0, #0x1ff ; ret
l1_index 2 lsr x0, x0, #30 ; and x0, x0, #0x1ff ; ret
l2_index 2 lsr x0, x0, #21 ; and x0, x0, #0x1ff ; ret
l3_index 2 lsr x0, x0, #12 ; and x0, x0, #0x1ff ; ret
page_off 2 and x0, x0, #0xfff ; ret
Nine bits per level, four levels, twelve bits of offset — and that is the 48-bit split arriving as a table shape. x86-64 needs the same five extractions and neither architecture stores the level in the descriptor: the level is implied by which table you are in, which is why the same three bits in a descriptor mean different things at different depths. The neutral paging course owns the principle; the x86-64 paging concept is the sibling reference; and this page owns the AArch64 numbers.
Put the pieces together for the configuration a 64 KiB-granule system actually uses, because the numbers are more instructive than the general case:
STAGE 1, EL1 & EL0, 64 KiB granule, 48/52 split (FEAT_LPA2)
TCR_EL1.TCR_EL1 fields, QUOTED with their bit positions:
[63:48] T0SZ = 16 TTBR0 region = 2^(64-16) = 2^48
[47:32] T1SZ = 12 TTBR1 region = 2^(64-12) = 2^52
[31:30] TG1 = 10 64 KiB
[23:22] SH0 = 11 inner shareable
[15:14] TG0 = 10 64 KiB
[9:8] IRGN0 = 01 inner cacheable
[7:6] ORGN0 = 01 inner cacheable
TLB: 64 entries, 4 KiB pages, 64 sets
-> 4 x 4 KiB = 16 KiB of page tables before you
have mapped a single byte, and that number is
NOT measurable on this host.
Three observations, and only the first is a fact this course can stand behind.
T0SZ is six bits and the floor moves to 232. That is the escape hatch from the five-bit floor, and it is why the two-level configurations exist at all: a 2-level walk of 2 MiB blocks over 232 bytes is exactly one table of 512 entries. Concept 5 does that arithmetic properly.IRGN0 and ORGN0 are how you tell the hardware where the tables are cached, and a wrong value is a performance bug that looks like a correctness bug on a machine with a coherent cache and a mystery on one without. Quoted, documented, and outside what this course can check.It cannot show a page walk, a TLB hit, a translation, or a fault. Not one address in this concept was translated by anything. The field positions, the granule encodings and the region formula are quoted with document and section. The encodings of the seven mrs/msr words, the five shifts, the seven-configuration identity and the ADR boundary are measured. The one measurement a reader should hold on to is the reach asymmetry: a fixed 32-bit instruction with a 21-bit field can address a page 4 GiB away or a byte 1 MiB away, and that single fact is why the ELF side of the subject — the relocation pair, the alignment, the linker script — is the half worth studying and the half a toolchain can show you.
.s file with adr x0, far, a .space and a far: label; assemble at a range of distances and find where it stops working. (Expect the last accepted distance to be 0xffffc and the first refused to be 0x100000, with fixup value out of range. Then read the field: 21 bits, signed, in bytes, and 0xfffff rounded down to a multiple of four is 0xffffc. Exactly. Now repeat with adrp at 32 MiB, 256 MiB, 2 GiB and 4 GiB, and expect all four to be accepted — the assembler does not check ADRP, and finding out that for yourself is worth more than being told.)T0SZ = 64 - VA in every row by hand. (Expect all seven to check. Then put a 30-bit row in and verify that — and find T0SZ = 34, which does not fit in five bits. That is retraction R16 reproduced in thirty seconds, and reproducing a retraction is worth more than reading one, because the thirty seconds is what convinces you the arithmetic is the reason and not the author's preference.)aarch64-linux-gnu, and read llvm-readelf-21 -r on each. (Expect every reference to be an adrp plus a :lo12: partner, and expect the partner's relocation name to be R_AARCH64_ADD_ABS_LO12_NC when the second instruction is an add and R_AARCH64_LDST64_ABS_LO12_NC when it is a ldr. Two instructions, two relocations, one address — and the address is a page that cannot be named in one instruction on this architecture at all.)Backwards, tightly. Concept 3 is where a translation fault arrives, and this is where it came from. Concept 2 is where its address is read: the FAR holds a virtual address, and everything on this page exists to explain what that word means. The ABI course's calling convention is needed for one number only, and it is a number you already know: x0 holds a pointer, and a pointer is a virtual address, and so every function call in your program is a translation. The ABI did not say so; the ABI just moved the number.
Sideways, three neighbours that own the edges. The neutral paging course owns the walk as a mechanism, in terms neither architecture requires; the x86-64 virtual-memory concept is the sibling reference, where the split is a CR3 bit rather than two base registers and the field is a page-directory-pointer count rather than a region size. The relocation course's PC-relative concept owns what adrpis, and this page's reach table is the same subject measured on a different architecture — which is exactly the division of labour this course is for.
Outward, and the general form is the thing to take away. A field width is a constraint, and a constraint is more useful than a default.T0SZ being five bits is quoted; T0SZ = 16 being what a 48-bit system writes is a choice; and the difference between the two is the difference between "this is impossible" and "this is a decision somebody made". A reader who has only the second will assume a system could be configured any way and will not notice a configuration that the field forbids. That instinct — look for the field width before you look for the value — is what killed this course's own first draft, and it is the third time in this section that it has been the thing that worked.
Forward, the hinge. Concept 5 is the ELF side of this page: the tables this regime points at have an alignment requirement and an address, and both are things an object file has to record and a linker has to honour. That is the step of the chain where the contract becomes checkable — parsing and IR are about meaning, and a page table is the first object in this course whose requirements are a property of its role and not of its type.