[mmu/cache]-ARM MMU的學習筆記-一篇就夠了【轉】


轉自:https://blog.csdn.net/weixin_42135087/article/details/109044386

★★★ 個人博客導讀首頁—點擊此處 ★★★
.
說明:
在默認情況下,本文講述的都是ARMV8-aarch64架構,linux kernel 64位
.
相關文章
1、ARM cache的學習筆記-一篇就夠了


自制《armv8的VMSA/MMU/Cache介紹》學習視頻:


文章目錄
ARMV8-aarch64的MMU
1、MMU概念介紹
2、MMU地址翻譯的過程
3、在secure和non-secure中使用MMU
4、在不同異常等級中使用MMU
5、memory attributes介紹
6、memory tagging介紹
7、啟用hypervisor
8、Access permissions
9、MMU/cache相關的寄存器總結
(1)、address translation
(2)、TLB maintenance
(3)、cache maintenance
(4)、Base system registers
TTBR0_ELx TTBR1_ELx
TCR_ELx
MAIR_ELx
10、系統寄存器 --- TCR寄存器介紹
(1)、T1SZ、T0SZ
(2)、ORGN1、IRGN1、ORGN0、IRGN0
(3)、SH1、SH0
(4)、TG0/TG1 - Granule size
(5)、IPS
(6)、EPD1、EPD0
(7)、TBI1、TBI0
(8)、A1
(10)、AS
11、代碼使用示例展
(1)、設置inner/outer cache的屬性(只寫模式/回寫模式/write allocate/No-write allocate)
optee系統中使用MMU
1、Optee中的TTBR0/TTBR1
2、optee中的頁表
3、tee內核頁表基地址的配置
4、tee內核頁表填充
5、virt_to_phys轉換的過程的舉例
ARMV8-aarch64的MMU
1、MMU概念介紹
MMU分為兩個部分: TLB maintenance 和 address translation


MMU的作用,主要是完成地址的翻譯,無論是main-memory地址(DDR地址),還是IO地址(設備device地址),在開啟了MMU的系統中,CPU發起的指令讀取、數據讀寫都是虛擬地址,在ARM Core內部,會先經過MMU將該虛擬地址自動轉換成物理地址,然后在將物理地址發送到AXI總線上,完成真正的物理內存、物理設備的讀寫訪問

下圖是一個linux kernel系統中宏觀的虛擬地址到物理地址轉換的視圖,可以看出在MMU進行地址轉換時,會依賴TTBRx_EL1寄存器指向的一個頁表基地址.
其中,TTBR1_EL1指向特權模式的頁表基地址,用於特權模式下的地址空間轉換;TTBR0_EL0指向非特權模式的頁表基地址,用於非特權模式下的地址空間轉換.
剛剛我們也提到,CPU發出讀寫后, MMU會自動的將虛擬地址轉換為為例地址,那么我們軟件需要做什么呢? 我們軟件需要做的其實就是管理這個頁表,按照ARM的技術要求去創建一個這樣的頁表,然后再將其基地址寫入到TTBR1_EL1或TTBR0_EL1。當然,根據實際的場景和需要,這個基地址和頁表中的內容都會發生動態變化. 例如,兩個user進程進行切換時,TTBR0_EL1是要從一個user的頁表地址,切換到另外一個user的頁表地址。

 

 


2、MMU地址翻譯的過程
using a 64KB granule with a 42-bit virtual address space,地址翻譯的過程(只用到一級頁表的情況):

 

 


使用二級頁表的情況舉例:

 

 


3、在secure和non-secure中使用MMU
TTBRx_EL1是banked的,在linux和optee雙系統的環境下,可同時開啟兩個系統的MMU.
在secure和non-secure中使用不同的頁表.secure的頁表可以映射non-secure的內存,而non-secure的頁表不能去映射secure的內存,否則在轉換時會發生錯誤

 

 


4、在不同異常等級中使用MMU
在ARMV8-aarch64架構下,頁表基地址寄存器有:

TTBR0_EL1 – banked
TTBR1_EL1 – banked
TTBR1_EL2
TTBR1_EL3
在EL0/EL1的系統中,MMU地址轉換時,如果虛擬地址在0x00000000_ffffffff - 0x0000ffff_ffffffff范圍,MMU會自動使用TTBR0_EL1指向的頁表,進行地址轉換;如果虛擬地址在0xffff0000_ffffffff - 0xffffffff_ffffffff范圍,MMU會自動使用TTBR1_EL1指向的頁表,進行地址轉換

在EL2系統中,MMU地址轉換時,會自動使用TTBR2_EL1指向的頁表
在EL3系統中,MMU地址轉換時,會自動使用TTBR3_EL1指向的頁表

 

 

 

5、memory attributes介紹
translation tables為每一塊region(entry)都定義了一個memory attributes條目,如同下面這個樣子:
TODO:以linux kernel為例,在創建頁表的時候,應該會設置這個memory attributes,有待看代碼去驗證

 

 

• Unprivileged eXecute Never (UXN) and Privileged eXecute Never (PXN) are execution
permissions.
• AF is the access flag.
• SH is the shareable attribute.
• AP is the access permission.
• NS is the security bit, but only at EL3 and Secure EL1. ---- secure權限配置
• Indx is the index into the MAIR_ELn

在這塊region(entry)的memory attributes條目中的BIT4:2(index)指向了系統寄存器MAIR_ELn中的attr,MAIR_ELn共有8中attr選擇

 

 

而每一個attr都有一種配置:

 

 

有人可能會問,這里的inner和outter是什么意思呢,請參見前面的cache介紹的文章

6、memory tagging介紹
When tagged addressing support is enabled, the top eight bits [63:56] of the virtual address are
ignored by the processor. It internally sets bit [55] to sign-extend the address to 64-bit format. The
top 8 bits can then be used to pass data. These bits are ignored for addressing and translation
faults. The TCR_EL1 has separate enable bits for EL0 and EL1

如果使用memory tagging, 虛擬地址的[63:56]用於傳輸簽名數據,bit[55]表示是否需要簽名.TCR_EL1也會有一個bit區分是給EL0用的還是給EL1用的

7、啟用hypervisor
(Two Stage Translations)
如果啟用了hypervisor那么虛擬地址轉換的過程將有VA—>PA變成了VA—>IPA—>PA, 也就是要經過兩次轉換.在guestos(如linux kernel)中轉換的物理地址,其實不是真實的物理地址(假物理地址),然后在EL2通過VTTBR0_EL2基地址的頁表轉換后的物理地址,才是真實的硬件地址

IPA : intermediate physical address

 

 


8、Access permissions
Access permissions are controlled through translation table entries. Access permissions control
whether a region is readable or writeable, or both, and can be set separately to EL0 for
unprivileged and access to EL1, EL2, and EL3 for privileged accesses, as shown in the following
table

參考 : SCTLR_EL1.WXN、SCTLR.UWXN

 

 

 

執行權限:

non-executable (Execute Never (XN))
The Unprivileged Execute Never (UXN)
Privileged Execute Never (PXN)
9、MMU/cache相關的寄存器總結
MMU(address translation /TLB maintenance)、cache maintenance相關的寄存器

(1)、address translation
address translation 共計14個寄存器

 

 


(2)、TLB maintenance
TLB maintenance數十個寄存器
(以下截取部分)

 

 


(3)、cache maintenance

 

 


(4)、Base system registers
系統寄存器中, 和MMU/Cache相關的寄存器有:

TTBR0_ELx TTBR1_ELx
(aarch64)

TTBR0_EL1
TTBR0_EL2
TTBR0_EL3
TTBR1_EL1
VTTBR_EL2
(aarch32)

TTBR0
TTBR1
HTTBR
VTTBR
TCR_ELx
(aarch64)

TCR_EL1
TCR_EL2
TCR_EL3
VTCR_EL2
(aarch32)

TTBCR(NS)
HTCR
TTBCR(S)
VTCR
MAIR_ELx
MAIR_EL1
MAIR_EL2
MAIR_EL3
10、系統寄存器 — TCR寄存器介紹
在ARM Core中(aarch64),還有幾個相關的系統寄存器:

TCR_EL1 banked
TCR_EL2
TCR_EL3

 

 

 

 

比特位 功能 說明
ORGN1、IRGN1、ORGN0、IRGN0 cache屬性** outer/inner cableability的屬性(如直寫模式、回寫模式)
SH1、SH0 cache的共享方式 cache的共享屬性配置(如non-shareable, outer/inner shareable)
TG0/TG1 Granule size Granule size(其實就是頁面的大小,4k/16k/64k)
IPS 物理地址size 物理地址size,如32bit/36bit/40bit
EPD1、EPD0 - TTBR_EL1/TTBR_EL0的enable和disable
TBI1、TBI0 - top addr是ignore,還是用於MTE的計算
A1 - ASID的選擇,是使用TTBR_EL1中的,還是使用TTBR_EL0中的
AS - ASID是使用8bit,還是使用16bit
(1)、T1SZ、T0SZ
T1SZ, bits [21:16] 通過TTBR1尋址的內存區域的大小偏移量,也就是TTBR1基地址下的一級頁表的大小
T0SZ, bits [5:0]


(2)、ORGN1、IRGN1、ORGN0、IRGN0

其實可以總結為這樣:

 

 


(3)、SH1、SH0
SH1, bits [29:28]
SH0, bits [13:12]

 

 

其實可以總結為這樣:

 

 

Shareable的很容易理解,就是某個地址的可能被別人使用。我們在定義某個頁屬性的時候會給出。Non-Shareable就是只有自己使用。當然,定義成Non-Shareable不表示別人不可以用。某個地址A如果在核1上映射成Shareable,核2映射成Non-Shareable,並且兩個核通過CCI400相連。那么核1在訪問A的時候,總線會去監聽核2,而核2訪問A的時候,總線直接訪問內存,不監聽核1。顯然這種做法是錯誤的。

對於Inner和Outer Shareable,有個簡單的的理解,就是認為他們都是一個東西。在最近的ARM A系列處理器上上,配置處理器RTL的時候,會選擇是不是把inner的傳輸送到ACE口上。當存在多個處理器簇或者需要雙向一致性的GPU時,就需要設成送到ACE端口。這樣,內部的操作,無論inner shareable還是outershareable,都會經由CCI廣播到別的ACE口上。

(4)、TG0/TG1 - Granule size

 

 


(5)、IPS

 

 


(6)、EPD1、EPD0

 

 


(7)、TBI1、TBI0

 

 


(8)、A1

 

 


(10)、AS

 

 


除了以上介紹的bit之外,剩余的bit都是特有功能使用或reserved的


11、代碼使用示例展
(1)、設置inner/outer cache的屬性(只寫模式/回寫模式/write allocate/No-write allocate)
如下代碼所示:

#define TCR_IRGN_WBWA ((UL(1) << 8) | (UL(1) << 24)) //使用TTBR0和使用TTBR1時后的inner cache的屬性設置

#define TCR_ORGN_WBWA ((UL(1) << 10) | (UL(1) << 26)) //使用TTBR0和使用TTBR1時后的outer cache的屬性設置

#define TCR_CACHE_FLAGS TCR_IRGN_WBWA | TCR_ORGN_WBWA // inner + outer cache的屬性值


ENTRY(__cpu_setup)
......
/*
* Set/prepare TCR and TTBR. We use 512GB (39-bit) address range for
* both user and kernel.
*/
ldr x10, =TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \
TCR_TG_FLAGS | TCR_ASID16 | TCR_TBI0 | TCR_A1
tcr_set_idmap_t0sz x10, x9

......
msr tcr_el1, x10
ret // return to head.S
ENDPROC(__cpu_setup)


屬性設置了1,也就是回寫模式、write allocate模式

 

 


optee系統中使用MMU
1、Optee中的TTBR0/TTBR1
我們知道,在linux中將虛擬空間划分為了userspace和kernel space:
例如:
aarch64 :
0x0000_0000_0000_0000 - 0x0000_ffff_ffff_ffff是userspace
0xffff_0000_0000_0000 - 0xffff_ffff_ffff_ffff是kernel space
aarch32 : 0-3G是userspace,3-4G是kernel space

當cpu發起讀寫內存時,cpu發起的是虛擬地址,如果是kernel地址,那么MMU將自動使用TTBR1做為頁表基地址進行轉換程物理地址,然后發送到AXI總線,完成真正物理地址的讀寫;
如果cpu發起的虛擬地址是userspace地址,那么MMU將使用TTBR0做為頁表基地址進行轉換程物理地址,然后發送到AXI總線,完成真正物理地址的讀寫;

那么在optee中是怎么樣的呢?
在optee中,沒用特別的將虛擬地址划分成kernel space和userspace,統一使用0-4G地址空間(無論是aarch32還是aarch64).
在optee初始化時,禁用了TTBR1,所有整個optee的虛擬地址的專業,都是使用TTBR0做為基地址轉換成物理地址,發送到AXI總線,完成真正的物理地址讀寫

void core_init_mmu_regs(void)
{
uint64_t mair;
uint64_t tcr;
paddr_t ttbr0;
uint64_t ips = calc_physical_addr_size_bits(max_pa);

ttbr0 = virt_to_phys(l1_xlation_table[0][get_core_pos()]);

mair = MAIR_ATTR_SET(ATTR_DEVICE, ATTR_DEVICE_INDEX);
mair |= MAIR_ATTR_SET(ATTR_IWBWA_OWBWA_NTR, ATTR_IWBWA_OWBWA_NTR_INDEX);
write_mair_el1(mair);

tcr = TCR_RES1;
tcr |= TCR_XRGNX_WBWA << TCR_IRGN0_SHIFT;
tcr |= TCR_XRGNX_WBWA << TCR_ORGN0_SHIFT;
tcr |= TCR_SHX_ISH << TCR_SH0_SHIFT;
tcr |= ips << TCR_EL1_IPS_SHIFT;
tcr |= 64 - __builtin_ctzl(CFG_LPAE_ADDR_SPACE_SIZE);

/* Disable the use of TTBR1 */
tcr |= TCR_EPD1;

/*
* TCR.A1 = 0 => ASID is stored in TTBR0
* TCR.AS = 0 => Same ASID size as in Aarch32/ARMv7
*/

write_tcr_el1(tcr);
write_ttbr0_el1(ttbr0);
write_ttbr1_el1(0);
}


2、optee中的頁表
optee在在內核中使用一個4GB的大頁表,在user mode使用多個32M的小頁表
注意下面的圖來自官網,有點小問題,代碼中沒用使用到TTBR1,kernel和user都使用TTBR0

 

 


3、tee內核頁表基地址的配置
將基地址寫入到TTBR0

在optee的start函數和cpu_on_handler函數中,都會調用core_init_mmu_regs()

FUNC _start , :
......
bl core_init_mmu_regs

FUNC cpu_on_handler , :
......
bl core_init_mmu_regs

在core_init_mmu_regs中,獲取當前cpu的頁表基地址,寫入到ttbr0寄存器中

void core_init_mmu_regs(void)
{
uint64_t mair;
uint64_t tcr;
paddr_t ttbr0;
uint64_t ips = calc_physical_addr_size_bits(max_pa);

ttbr0 = virt_to_phys(l1_xlation_table[0][get_core_pos()]); //獲取當前cpu的頁表基地址

mair = MAIR_ATTR_SET(ATTR_DEVICE, ATTR_DEVICE_INDEX);
mair |= MAIR_ATTR_SET(ATTR_IWBWA_OWBWA_NTR, ATTR_IWBWA_OWBWA_NTR_INDEX);
write_mair_el1(mair);

tcr = TCR_RES1;
tcr |= TCR_XRGNX_WBWA << TCR_IRGN0_SHIFT;
tcr |= TCR_XRGNX_WBWA << TCR_ORGN0_SHIFT;
tcr |= TCR_SHX_ISH << TCR_SH0_SHIFT;
tcr |= ips << TCR_EL1_IPS_SHIFT;
tcr |= 64 - __builtin_ctzl(CFG_LPAE_ADDR_SPACE_SIZE);

/* Disable the use of TTBR1 */
tcr |= TCR_EPD1;

/*
* TCR.A1 = 0 => ASID is stored in TTBR0
* TCR.AS = 0 => Same ASID size as in Aarch32/ARMv7
*/

write_tcr_el1(tcr);
write_ttbr0_el1(ttbr0); //將頁表基地址寫入到ttbr0寄存器
write_ttbr1_el1(0);
}


4、tee內核頁表填充
在optee內核中,實現的是一個4G的大頁表(一級頁表, 32bit可表示4G空間)

在section段定義了一個三元數組,用於存放頁表

uint64_t l1_xlation_table[NUM_L1_TABLES][CFG_TEE_CORE_NB_CORE][NUM_L1_ENTRIES]
__aligned(NUM_L1_ENTRIES * XLAT_ENTRY_SIZE) __section(".nozi.mmu.l1");

形態入下圖所示

 

 

填充頁表,那么要將哪些地址填進去呢?
在core_init_mmu_map()函數中,調用core_init_mmu_tables創建頁表,參數static_memory_map是一個數組,它指向系統中向optee已注冊了的所有內存區域的地址

void core_init_mmu_map(void)
{
......
core_init_mmu_tables(static_memory_map);
......
}

static struct tee_mmap_region static_memory_map[CFG_MMAP_REGIONS + 1];

#define CFG_MMAP_REGIONS 13,//表示當前optee,最多支持注冊13塊內存區域

調用core_init_mmu_tables()填充的頁表
參數mm就是剛才的static_memory_map,小於等於13塊的內存區域, 填充方法如下

void core_init_mmu_tables(struct tee_mmap_region *mm)
{
uint64_t max_va = 0;
size_t n;

#ifdef CFG_CORE_UNMAP_CORE_AT_EL0
COMPILE_TIME_ASSERT(CORE_MMU_L1_TBL_OFFSET ==
sizeof(l1_xlation_table) / 2);
#endif
max_pa = get_nsec_ddr_max_pa();

for (n = 0; !core_mmap_is_end_of_table(mm + n); n++) {
paddr_t pa_end;
vaddr_t va_end;

debug_print(" %010" PRIxVA " %010" PRIxPA " %10zx %x",
mm[n].va, mm[n].pa, mm[n].size, mm[n].attr);

if (!IS_PAGE_ALIGNED(mm[n].pa) || !IS_PAGE_ALIGNED(mm[n].size))
panic("unaligned region");

pa_end = mm[n].pa + mm[n].size - 1;
va_end = mm[n].va + mm[n].size - 1;
if (pa_end > max_pa)
max_pa = pa_end;
if (va_end > max_va)
max_va = va_end;
}

/* Clear table before use */
memset(l1_xlation_table, 0, sizeof(l1_xlation_table)); ------------------------清空頁表

for (n = 0; !core_mmap_is_end_of_table(mm + n); n++)
if (!core_mmu_is_dynamic_vaspace(mm + n))
core_mmu_map_region(mm + n); ------------------------填充當前cpu的頁表

/*
* Primary mapping table is ready at index `get_core_pos()`
* whose value may not be ZERO. Take this index as copy source.
*/
for (n = 0; n < CFG_TEE_CORE_NB_CORE; n++) {
if (n == get_core_pos())
continue;

memcpy(l1_xlation_table[0][n],
l1_xlation_table[0][get_core_pos()], ------------------------將當前cpu的頁表,拷貝到其它cpu的頁表中
XLAT_ENTRY_SIZE * NUM_L1_ENTRIES);
}

for (n = 1; n < NUM_L1_ENTRIES; n++) {
if (!l1_xlation_table[0][0][n]) {
user_va_idx = n;
break;
}
}
assert(user_va_idx != -1);

COMPILE_TIME_ASSERT(CFG_LPAE_ADDR_SPACE_SIZE > 0);
assert(max_va < CFG_LPAE_ADDR_SPACE_SIZE);
}


在下列段代碼中,循環遍歷mm遍歷,對於每個內存區域,做core_mmu_map_region運算

for (n = 0; !core_mmap_is_end_of_table(mm + n); n++)
if (!core_mmu_is_dynamic_vaspace(mm + n))
core_mmu_map_region(mm + n);
1
2
3
core_mmu_map_region就是將每塊內存區域,進行拆分,拆分程若干entry

void core_mmu_map_region(struct tee_mmap_region *mm)
{
struct core_mmu_table_info tbl_info;
unsigned int idx;
vaddr_t vaddr = mm->va;
paddr_t paddr = mm->pa;
ssize_t size_left = mm->size;
int level;
bool table_found;
uint32_t old_attr;

assert(!((vaddr | paddr) & SMALL_PAGE_MASK));

while (size_left > 0) {
level = 1;

while (true) {
assert(level <= CORE_MMU_PGDIR_LEVEL);

table_found = core_mmu_find_table(vaddr, level,
&tbl_info);
if (!table_found)
panic("can't find table for mapping");

idx = core_mmu_va2idx(&tbl_info, vaddr);

if (!can_map_at_level(paddr, vaddr, size_left,
1 << tbl_info.shift, mm)) {
/*
* This part of the region can't be mapped at
* this level. Need to go deeper.
*/
if (!core_mmu_entry_to_finer_grained(&tbl_info,
idx, mm->attr & TEE_MATTR_SECURE))
panic("Can't divide MMU entry");
level++;
continue;
}

/* We can map part of the region at current level */
core_mmu_get_entry(&tbl_info, idx, NULL, &old_attr);
if (old_attr)
panic("Page is already mapped");

core_mmu_set_entry(&tbl_info, idx, paddr, mm->attr);
paddr += 1 << tbl_info.shift;
vaddr += 1 << tbl_info.shift;
size_left -= 1 << tbl_info.shift;

break;
}
}
}


5、virt_to_phys轉換的過程的舉例
通過畫圖講述了,在optee中,構建頁表,然后完成一個virt_to_phys轉換的過程.

1、系統注冊了的若干塊內存,划分為若干個大小為4K/8K的region,每個region的地址,寫入到頁表的entry中,這樣就構建出了頁表.
2、將頁表物理地址寫入到TTBR0中
3、開啟MMU
4、調用virt_to_phys時,使用MMU的AT S1E1R指令,將虛擬地址寫入到MMU的Address translation中
5、從par_el1中讀出的物理地址,就是經過MMU轉換后的
注意:
1、在optee中禁止了TTBR1,所以在optee的kernel mode中,也是使用TTBR0
2、optee中,只使用到了一級頁表

 

 


歡迎添加微信、微信群,多多交流


文章知識點與官方知識檔案匹配,可進一步學習相關知識
————————————————
版權聲明:本文為CSDN博主「代碼改變世界ctw」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42135087/article/details/109044386


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM