1

In the device driver, I wanted to read the first data pointed to by the ttbr0_el1 register (translation table base register for EL1) like below. But referencing the the pointer causes trap(data access abort). What is wrong?

static inline uint64_t system_read_TTBR0_EL1(void)
{
    uint64_t val;
    asm volatile("mrs %0, ttbr0_el1" : "=r" (val));
    return val;
}

static inline uint64_t system_read_TTBR1_EL1(void)
{
    uint64_t val;
    asm volatile("mrs %0, ttbr1_el1" : "=r" (val));
    return val;
}

ttbr0_el1 = system_read_TTBR0_EL1();
ttbr1_el1 = system_read_TTBR1_EL1();
printk("ttbr0 = %llx\n", ttbr0_el1);
printk("ttbr1 = %llx\n", ttbr1_el1);
printk("*ttbr1 = %llx\n", *(uint64_t *)ttbr1_el1);
printk("*ttbr0 = %llx\n", *(uint64_t *)ttbr0_el1);

output

[100450.356279] ttbr0 = 27f0000042dd7001
[100450.356595] ttbr1 = 27f00000414ab001
[100450.357015] Unable to handle kernel paging request at virtual address 27f00000414ab001
[100450.357564] Mem abort info:
[100450.357803]   ESR = 0x96000004
[100450.358116]   EC = 0x25: DABT (current EL), IL = 32 bits
[100450.358535]   SET = 0, FnV = 0
[100450.359256]   EA = 0, S1PTW = 0
[100450.359537] Data abort info:
[100450.359785]   ISV = 0, ISS = 0x00000004
[100450.360101]   CM = 0, WnR = 0
[100450.360449] [27f00000414ab001] address between user and kernel address ranges
[100450.360986] Internal error: Oops: 96000004 [#22] SMP
Chan Kim
  • 5,177
  • 12
  • 57
  • 112
  • 1
    Only the low 48 bit of that register hold the base address. Refer to the ARM Architecture Reference Manual for details. – fuz Jul 26 '21 at 07:07

1 Answers1

3

For a start, you need to mask out some stuff:

TTBR0_EL1 field descriptions

But there's more to it than that, even. From the manual:

Translation table base address:

•   Bits A[47:x] of the stage 1 translation table base address bits are in
    register bits[47:x].
•   Bits A[(x-1):0] of the stage 1 translation table base address are zero.

Address bit x is the minimum address bit required to align the translation
table to the size of the table. The smallest permitted value of x is 6. The
AArch64 Virtual Memory System Architecture chapter describes how x is
calculated based on the value of TCR_EL1.T0SZ, the translation stage, and
the translation granule size.

-------- Note ------------
A translation table is required to be aligned to the size of the table. If
a table contains fewer than eight entries, it must be aligned on a 64 byte
address boundary.
--------------------------

If the value of TCR_EL1.IPS is not 0b110, then:

•   Register bits[(x-1):1] are RES0.
•   If the implementation supports 52-bit PAs and IPAs, then bits A[51:48]
    of the stage 1 translation table base address are 0b0000.

If FEAT_LPA is implemented and the value of TCR_EL1.IPS is 0b110, then:

•   Bits A[51:48] of the stage 1 translation table base address bits are
    in register bits[5:2].
•   Register bit[1] is RES0.

The description goes on to explain the allowed effects of invalid values, but if we assume only valid use of the TTBR registers, then this is how you extract the effective physical base address:

static inline uint64_t ttbr0_base_address(void)
{
    uint64_t val;
    asm volatile("mrs %0, ttbr0_el1" : "=r" (val));
    return (val & 0xffffffffffc0) | ((val & 0x3c) << 46);
}

static inline uint64_t ttbr1_base_address(void)
{
    uint64_t val;
    asm volatile("mrs %0, ttbr1_el1" : "=r" (val));
    return (val & 0xffffffffffc0) | ((val & 0x3c) << 46);
}

But if you're running Linux normally, then it's pretty unlikely that you will be able to dereference that address as-is. You'll need to get a virtual mapping for that physical address. Assuming a standard environment where stuff is in DRAM, phys_to_virt() should work.

Siguza
  • 21,155
  • 6
  • 52
  • 89
  • Thank you! Using your code (and phys_to_virt) I can see a value that looks like an entry pointing to the level 1(?) table. I've glossed over the document before but I think this is the time I should read it more thoroughly again... – Chan Kim Jul 26 '21 at 11:48