An Intro to Kernel Development - MMU - Part 2

5 mins

Let's Setup and Enable MMU to jump into the kerenl...

In the previous blog we started of with why we need a MMU and how it works, we got the picture of the seperation between two processes achieved through having a table with virtual to physical address translation. MMU requires Tables to convert virtual to physical address we have two cases, user level table, kernel level table MMU excepts these table's base address to be in a special registers TTBR0_EL1 - User Table TTBR1_EL1 - Kernel Table we need to initialize these table base addresses.

            ldr     x0, =user_table_base
            ldr     x0, [x0]
            msr     TTBR0_EL1, x0
        
            ldr     x0, =kernel_table_base
            ldr     x0, [x0]
            msr     TTBR1_EL1, x0
FUN FACT: at this point, i had a stupid doubt, because, we just enabled MMU, will MMU read this address in these special register as virtual or physical, if it does take it as virtual, then we will have chicken or egg problem, where mmu has to translate an address that points to the table which has the translations, sad, however, MMU considers the address in this special registers to be pointing to physical address, so we are good. From user processes POV, if two of them were running, when we switch between them, we also have to update the Table base address (TTBR0_EL1) accordingly. Security Fact: if we can write to this register using some kernel expoit, we can effectivly get into another processes's Virtual space. now we have to tell the MMU about our address layout, the seperation between User Space and Kernel Space, total physical space. this is important, because user's process will use a Virtual address range that doesn't overlap with the kernel address EG: all kernel address will start with 0xffff and all user address will start with 0x0000 so if user process try to reach 0xffff, MMU can directly throw a fault instead of looking into its table this is set in the register TCR_EL1, this register is separated into multiple parts, we only care about three of them, T0SZ [5:0] = hold the size of user space T1SZ [21:16] = hold the size of kernel space IPS [34:32] = total size of the physical space Eg: Linux uses 42 bit physical space, ie 4TB, i have use 40 bit, 1TB 64 - 16 = 48 bits, virtual space, ie 128 TB for both user and kernel
Bit:   [63........35] [34:32] [31:22] [21:16] [15:6] [5:0]
Value:     0            010       0      10000    0    10000
Meaning:   —          IPS=40b    —     T1SZ=16   —   T0SZ=16

ldr     x0, =((16) | (16 << 16) | (2 << 32))
msr     TCR_EL1, x0
isb
let's finally enable the MMU, as we have set all need registers, SCTLR_EL1 register's bit 0 is the MMU unit
            MRS x0, SCTLR_EL1
            ORR x0, x0, #(1 << 0)   // Enable MMU only
            MSR SCTLR_EL1, x0
            ISB
        
now the MMU is ready, we can jump into kernel main.