xref: /kvm-unit-tests/lib/ppc64/mmu.c (revision 93c847c1e5cbe266496ee66dc83dcfa24c401c96)
1d4c8e725SNicholas Piggin // SPDX-License-Identifier: GPL-2.0-only
2d4c8e725SNicholas Piggin /*
3d4c8e725SNicholas Piggin  * Radix MMU support
4d4c8e725SNicholas Piggin  *
5d4c8e725SNicholas Piggin  * Copyright (C) 2024, IBM Inc, Nicholas Piggin <npiggin@gmail.com>
6d4c8e725SNicholas Piggin  *
7d4c8e725SNicholas Piggin  * Derived from Linux kernel MMU code.
8d4c8e725SNicholas Piggin  */
9d4c8e725SNicholas Piggin #include <asm/mmu.h>
10d4c8e725SNicholas Piggin #include <asm/setup.h>
11d4c8e725SNicholas Piggin #include <asm/smp.h>
12d4c8e725SNicholas Piggin #include <asm/page.h>
13d4c8e725SNicholas Piggin #include <asm/io.h>
14d4c8e725SNicholas Piggin #include <asm/processor.h>
15d4c8e725SNicholas Piggin #include <asm/hcall.h>
16d4c8e725SNicholas Piggin 
17d4c8e725SNicholas Piggin #include "alloc_page.h"
18d4c8e725SNicholas Piggin #include "vmalloc.h"
19d4c8e725SNicholas Piggin #include <asm/pgtable-hwdef.h>
20d4c8e725SNicholas Piggin #include <asm/pgtable.h>
21d4c8e725SNicholas Piggin 
22d4c8e725SNicholas Piggin #include <linux/compiler.h>
23d4c8e725SNicholas Piggin 
24d4c8e725SNicholas Piggin static pgd_t *identity_pgd;
25d4c8e725SNicholas Piggin 
vm_available(void)26b9289d76SNicholas Piggin bool vm_available(void) /* weak override */
27d4c8e725SNicholas Piggin {
28d4c8e725SNicholas Piggin 	return cpu_has_radix;
29d4c8e725SNicholas Piggin }
30d4c8e725SNicholas Piggin 
mmu_enabled(void)31d4c8e725SNicholas Piggin bool mmu_enabled(void)
32d4c8e725SNicholas Piggin {
33d4c8e725SNicholas Piggin 	return current_cpu()->pgtable != NULL;
34d4c8e725SNicholas Piggin }
35d4c8e725SNicholas Piggin 
mmu_enable(pgd_t * pgtable)36d4c8e725SNicholas Piggin void mmu_enable(pgd_t *pgtable)
37d4c8e725SNicholas Piggin {
38d4c8e725SNicholas Piggin 	struct cpu *cpu = current_cpu();
39d4c8e725SNicholas Piggin 
40d4c8e725SNicholas Piggin 	if (!pgtable)
41d4c8e725SNicholas Piggin 		pgtable = identity_pgd;
42d4c8e725SNicholas Piggin 
43d4c8e725SNicholas Piggin 	cpu->pgtable = pgtable;
44d4c8e725SNicholas Piggin 
45*93c847c1SNicholas Piggin 	assert(!in_usermode());
46d4c8e725SNicholas Piggin 	mtmsr(mfmsr() | (MSR_IR|MSR_DR));
47d4c8e725SNicholas Piggin }
48d4c8e725SNicholas Piggin 
mmu_disable(void)49d4c8e725SNicholas Piggin void mmu_disable(void)
50d4c8e725SNicholas Piggin {
51d4c8e725SNicholas Piggin 	struct cpu *cpu = current_cpu();
52d4c8e725SNicholas Piggin 
53d4c8e725SNicholas Piggin 	cpu->pgtable = NULL;
54d4c8e725SNicholas Piggin 
55*93c847c1SNicholas Piggin 	assert(!in_usermode());
56d4c8e725SNicholas Piggin 	mtmsr(mfmsr() & ~(MSR_IR|MSR_DR));
57d4c8e725SNicholas Piggin }
58d4c8e725SNicholas Piggin 
get_pte(pgd_t * pgtable,uintptr_t vaddr)59d4c8e725SNicholas Piggin static pteval_t *get_pte(pgd_t *pgtable, uintptr_t vaddr)
60d4c8e725SNicholas Piggin {
61d4c8e725SNicholas Piggin 	pgd_t *pgd = pgd_offset(pgtable, vaddr);
62d4c8e725SNicholas Piggin 	pud_t *pud = pud_alloc(pgd, vaddr);
63d4c8e725SNicholas Piggin 	pmd_t *pmd = pmd_alloc(pud, vaddr);
64d4c8e725SNicholas Piggin 	pte_t *pte = pte_alloc(pmd, vaddr);
65d4c8e725SNicholas Piggin 
66d4c8e725SNicholas Piggin 	return &pte_val(*pte);
67d4c8e725SNicholas Piggin }
68d4c8e725SNicholas Piggin 
install_pte(pgd_t * pgtable,uintptr_t vaddr,pteval_t pte)69d4c8e725SNicholas Piggin static pteval_t *install_pte(pgd_t *pgtable, uintptr_t vaddr, pteval_t pte)
70d4c8e725SNicholas Piggin {
71d4c8e725SNicholas Piggin 	pteval_t *p_pte = get_pte(pgtable, vaddr);
72d4c8e725SNicholas Piggin 
73d4c8e725SNicholas Piggin 	if (READ_ONCE(*p_pte) & cpu_to_be64(_PAGE_VALID)) {
74d4c8e725SNicholas Piggin 		WRITE_ONCE(*p_pte, 0);
75d4c8e725SNicholas Piggin 		flush_tlb_page(vaddr);
76d4c8e725SNicholas Piggin 	}
77d4c8e725SNicholas Piggin 
78d4c8e725SNicholas Piggin 	WRITE_ONCE(*p_pte, cpu_to_be64(pte));
79d4c8e725SNicholas Piggin 
80d4c8e725SNicholas Piggin 	return p_pte;
81d4c8e725SNicholas Piggin }
82d4c8e725SNicholas Piggin 
install_page_prot(pgd_t * pgtable,phys_addr_t phys,uintptr_t vaddr,pgprot_t prot)83d4c8e725SNicholas Piggin static pteval_t *install_page_prot(pgd_t *pgtable, phys_addr_t phys,
84d4c8e725SNicholas Piggin 				   uintptr_t vaddr, pgprot_t prot)
85d4c8e725SNicholas Piggin {
86d4c8e725SNicholas Piggin 	pteval_t pte = phys;
87d4c8e725SNicholas Piggin 	pte |= _PAGE_VALID | _PAGE_PTE;
88d4c8e725SNicholas Piggin 	pte |= pgprot_val(prot);
89d4c8e725SNicholas Piggin 	return install_pte(pgtable, vaddr, pte);
90d4c8e725SNicholas Piggin }
91d4c8e725SNicholas Piggin 
install_page(pgd_t * pgtable,phys_addr_t phys,void * virt)92d4c8e725SNicholas Piggin pteval_t *install_page(pgd_t *pgtable, phys_addr_t phys, void *virt)
93d4c8e725SNicholas Piggin {
94d4c8e725SNicholas Piggin 	if (!pgtable)
95d4c8e725SNicholas Piggin 		pgtable = identity_pgd;
96d4c8e725SNicholas Piggin 
97d4c8e725SNicholas Piggin 	return install_page_prot(pgtable, phys, (uintptr_t)virt,
98d4c8e725SNicholas Piggin 				 __pgprot(_PAGE_VALID | _PAGE_PTE |
99d4c8e725SNicholas Piggin 					  _PAGE_READ | _PAGE_WRITE |
100d4c8e725SNicholas Piggin 					  _PAGE_EXEC | _PAGE_ACCESSED |
101d4c8e725SNicholas Piggin 					  _PAGE_DIRTY));
102d4c8e725SNicholas Piggin }
103d4c8e725SNicholas Piggin 
follow_pte(pgd_t * pgtable,uintptr_t vaddr)104d4c8e725SNicholas Piggin static pteval_t *follow_pte(pgd_t *pgtable, uintptr_t vaddr)
105d4c8e725SNicholas Piggin {
106d4c8e725SNicholas Piggin 	pgd_t *pgd;
107d4c8e725SNicholas Piggin 	pud_t *pud;
108d4c8e725SNicholas Piggin 	pmd_t *pmd;
109d4c8e725SNicholas Piggin 	pte_t *pte;
110d4c8e725SNicholas Piggin 
111d4c8e725SNicholas Piggin 	pgd = pgd_offset(pgtable, vaddr);
112d4c8e725SNicholas Piggin 	if (!pgd_valid(*pgd))
113d4c8e725SNicholas Piggin 		return NULL;
114d4c8e725SNicholas Piggin 
115d4c8e725SNicholas Piggin 	pud = pud_offset(pgd, vaddr);
116d4c8e725SNicholas Piggin 	if (!pud_valid(*pud))
117d4c8e725SNicholas Piggin 		return NULL;
118d4c8e725SNicholas Piggin 
119d4c8e725SNicholas Piggin 	pmd = pmd_offset(pud, vaddr);
120d4c8e725SNicholas Piggin 	if (!pmd_valid(*pmd))
121d4c8e725SNicholas Piggin 		return NULL;
122d4c8e725SNicholas Piggin 	if (pmd_huge(*pmd))
123d4c8e725SNicholas Piggin 		return &pmd_val(*pmd);
124d4c8e725SNicholas Piggin 
125d4c8e725SNicholas Piggin 	pte = pte_offset(pmd, vaddr);
126d4c8e725SNicholas Piggin 	if (!pte_valid(*pte))
127d4c8e725SNicholas Piggin 		return NULL;
128d4c8e725SNicholas Piggin 
129d4c8e725SNicholas Piggin 	return &pte_val(*pte);
130d4c8e725SNicholas Piggin }
131d4c8e725SNicholas Piggin 
virt_to_pte_phys(pgd_t * pgtable,void * virt)132d4c8e725SNicholas Piggin phys_addr_t virt_to_pte_phys(pgd_t *pgtable, void *virt)
133d4c8e725SNicholas Piggin {
134d4c8e725SNicholas Piggin 	phys_addr_t mask;
135d4c8e725SNicholas Piggin 	pteval_t *pteval;
136d4c8e725SNicholas Piggin 
137d4c8e725SNicholas Piggin 	if (!pgtable)
138d4c8e725SNicholas Piggin 		pgtable = identity_pgd;
139d4c8e725SNicholas Piggin 
140d4c8e725SNicholas Piggin 	pteval = follow_pte(pgtable, (uintptr_t)virt);
141d4c8e725SNicholas Piggin 	if (!pteval) {
142d4c8e725SNicholas Piggin 		install_page(pgtable, (phys_addr_t)(unsigned long)virt, virt);
143d4c8e725SNicholas Piggin 		return (phys_addr_t)(unsigned long)virt;
144d4c8e725SNicholas Piggin 	}
145d4c8e725SNicholas Piggin 
146d4c8e725SNicholas Piggin 	if (pmd_huge(__pmd(*pteval)))
147d4c8e725SNicholas Piggin 		mask = PMD_MASK;
148d4c8e725SNicholas Piggin 	else
149d4c8e725SNicholas Piggin 		mask = PAGE_MASK;
150d4c8e725SNicholas Piggin 
151d4c8e725SNicholas Piggin 	return (be64_to_cpu(*pteval) & PHYS_MASK & mask) |
152d4c8e725SNicholas Piggin 		((phys_addr_t)(unsigned long)virt & ~mask);
153d4c8e725SNicholas Piggin }
154d4c8e725SNicholas Piggin 
155d4c8e725SNicholas Piggin struct partition_table_entry {
156d4c8e725SNicholas Piggin 	uint64_t dw0;
157d4c8e725SNicholas Piggin 	uint64_t dw1;
158d4c8e725SNicholas Piggin };
159d4c8e725SNicholas Piggin 
160d4c8e725SNicholas Piggin static struct partition_table_entry *partition_table;
161d4c8e725SNicholas Piggin 
162d4c8e725SNicholas Piggin struct process_table_entry {
163d4c8e725SNicholas Piggin 	uint64_t dw0;
164d4c8e725SNicholas Piggin 	uint64_t dw1;
165d4c8e725SNicholas Piggin };
166d4c8e725SNicholas Piggin 
167d4c8e725SNicholas Piggin static struct process_table_entry *process_table;
168d4c8e725SNicholas Piggin 
setup_mmu(phys_addr_t phys_end,void * unused)169d4c8e725SNicholas Piggin void *setup_mmu(phys_addr_t phys_end, void *unused)
170d4c8e725SNicholas Piggin {
171d4c8e725SNicholas Piggin 	phys_addr_t addr;
172d4c8e725SNicholas Piggin 	uint64_t dw0, dw1;
173d4c8e725SNicholas Piggin 
174d4c8e725SNicholas Piggin 	if (identity_pgd)
175d4c8e725SNicholas Piggin 		goto enable;
176d4c8e725SNicholas Piggin 
177d4c8e725SNicholas Piggin 	assert_msg(cpu_has_radix, "MMU support requires radix MMU.");
178d4c8e725SNicholas Piggin 
179d4c8e725SNicholas Piggin 	/* 32G address is reserved for vmalloc, cap phys_end at 31G */
180d4c8e725SNicholas Piggin 	if (phys_end > (31ul << 30)) {
181d4c8e725SNicholas Piggin 		/* print warning */
182d4c8e725SNicholas Piggin 		phys_end = 31ul << 30;
183d4c8e725SNicholas Piggin 	}
184d4c8e725SNicholas Piggin 
185d4c8e725SNicholas Piggin 	init_alloc_vpage((void *)(32ul << 30));
186d4c8e725SNicholas Piggin 
187d4c8e725SNicholas Piggin 	process_table = memalign_pages(SZ_4K, SZ_4K);
188d4c8e725SNicholas Piggin 	memset(process_table, 0, SZ_4K);
189d4c8e725SNicholas Piggin 
190d4c8e725SNicholas Piggin 	identity_pgd = pgd_alloc_one();
191d4c8e725SNicholas Piggin 
192d4c8e725SNicholas Piggin 	dw0 = (unsigned long)identity_pgd;
193d4c8e725SNicholas Piggin 	dw0 |= 16UL - 3; /* 64K pgd size */
194d4c8e725SNicholas Piggin 	dw0 |= (0x2UL << 61) | (0x5UL << 5); /* 52-bit virt */
195d4c8e725SNicholas Piggin 	process_table[1].dw0 = cpu_to_be64(dw0);
196d4c8e725SNicholas Piggin 
197d4c8e725SNicholas Piggin 	if (machine_is_pseries()) {
198d4c8e725SNicholas Piggin 		int ret;
199d4c8e725SNicholas Piggin 
200d4c8e725SNicholas Piggin 		ret = hcall(H_REGISTER_PROCESS_TABLE, PTBL_NEW | PTBL_RADIX | PTBL_GTSE, process_table, 0, 0 /* 4K size */);
201d4c8e725SNicholas Piggin 		assert_msg(!ret, "H_REGISTER_PROCESS_TABLE failed! err=%d\n", ret);
202d4c8e725SNicholas Piggin 	} else if (machine_is_powernv()) {
203d4c8e725SNicholas Piggin 		partition_table = memalign_pages(SZ_4K, SZ_4K);
204d4c8e725SNicholas Piggin 		memset(partition_table, 0, SZ_4K);
205d4c8e725SNicholas Piggin 
206d4c8e725SNicholas Piggin 		/* Reuse dw0 for partition table */
207d4c8e725SNicholas Piggin 		dw0 |= 1ULL << 63; /* Host radix */
208d4c8e725SNicholas Piggin 		dw1 = (unsigned long)process_table; /* 4K size */
209d4c8e725SNicholas Piggin 		partition_table[0].dw0 = cpu_to_be64(dw0);
210d4c8e725SNicholas Piggin 		partition_table[0].dw1 = cpu_to_be64(dw1);
211d4c8e725SNicholas Piggin 
212d4c8e725SNicholas Piggin 	} else {
213d4c8e725SNicholas Piggin 		/* Only pseries and powernv support radix so far */
214d4c8e725SNicholas Piggin 		assert(0);
215d4c8e725SNicholas Piggin 	}
216d4c8e725SNicholas Piggin 
217d4c8e725SNicholas Piggin 	/*
218d4c8e725SNicholas Piggin 	 * Avoid mapping page 0 so NULL dereferences fault. Test images
219d4c8e725SNicholas Piggin 	 * run relocated well above 0, so nothing useful here except
220d4c8e725SNicholas Piggin 	 * real-mode interrupt entry code.
221d4c8e725SNicholas Piggin 	 */
222d4c8e725SNicholas Piggin 	for (addr = PAGE_SIZE; addr < phys_end; addr += PAGE_SIZE)
223d4c8e725SNicholas Piggin 		install_page(identity_pgd, addr, __va(addr));
224d4c8e725SNicholas Piggin 
225d4c8e725SNicholas Piggin enable:
226d4c8e725SNicholas Piggin 	if (machine_is_powernv()) {
227d4c8e725SNicholas Piggin 		mtspr(SPR_PTCR, (unsigned long)partition_table); /* 4KB size */
228d4c8e725SNicholas Piggin 
229d4c8e725SNicholas Piggin 		mtspr(SPR_LPIDR, 0);
230d4c8e725SNicholas Piggin 		/* Set LPCR[UPRT] and LPCR[HR] for radix */
231d4c8e725SNicholas Piggin 		mtspr(SPR_LPCR, mfspr(SPR_LPCR) | (1ULL << 22) | (1ULL << 20));
232d4c8e725SNicholas Piggin 	}
233d4c8e725SNicholas Piggin 
234d4c8e725SNicholas Piggin 	/* PID=1 is used because PID=0 is also mapped in quadrant 3 */
235d4c8e725SNicholas Piggin 	mtspr(SPR_PIDR, 1);
236d4c8e725SNicholas Piggin 
237d4c8e725SNicholas Piggin 	mmu_enable(identity_pgd);
238d4c8e725SNicholas Piggin 
239d4c8e725SNicholas Piggin 	return identity_pgd;
240d4c8e725SNicholas Piggin }
241d4c8e725SNicholas Piggin 
__virt_to_phys(unsigned long addr)242d4c8e725SNicholas Piggin phys_addr_t __virt_to_phys(unsigned long addr)
243d4c8e725SNicholas Piggin {
244d4c8e725SNicholas Piggin 	if (mmu_enabled()) {
245d4c8e725SNicholas Piggin 		pgd_t *pgtable = current_cpu()->pgtable;
246d4c8e725SNicholas Piggin 		return virt_to_pte_phys(pgtable, (void *)addr);
247d4c8e725SNicholas Piggin 	}
248d4c8e725SNicholas Piggin 	return addr;
249d4c8e725SNicholas Piggin }
250d4c8e725SNicholas Piggin 
__phys_to_virt(phys_addr_t addr)251d4c8e725SNicholas Piggin unsigned long __phys_to_virt(phys_addr_t addr)
252d4c8e725SNicholas Piggin {
253d4c8e725SNicholas Piggin 	/*
254d4c8e725SNicholas Piggin 	 * We don't guarantee that phys_to_virt(virt_to_phys(vaddr)) == vaddr, but
255d4c8e725SNicholas Piggin 	 * the default page tables do identity map all physical addresses, which
256d4c8e725SNicholas Piggin 	 * means phys_to_virt(virt_to_phys((void *)paddr)) == paddr.
257d4c8e725SNicholas Piggin 	 */
258d4c8e725SNicholas Piggin 	assert(!mmu_enabled() || __virt_to_phys(addr) == addr);
259d4c8e725SNicholas Piggin 	return addr;
260d4c8e725SNicholas Piggin }
261