xref: /kvm-unit-tests/lib/s390x/mmu.c (revision 6c9f99df2fa51f58bd6a8b6775810b7f249bd0d7)
1*6c9f99dfSJanosch Frank /* SPDX-License-Identifier: GPL-2.0-only */
2c08c320bSDavid Hildenbrand /*
3c08c320bSDavid Hildenbrand  * s390x MMU
4c08c320bSDavid Hildenbrand  *
5c08c320bSDavid Hildenbrand  * Copyright (c) 2017 Red Hat Inc
6c08c320bSDavid Hildenbrand  *
7c08c320bSDavid Hildenbrand  * Authors:
8c08c320bSDavid Hildenbrand  *  David Hildenbrand <david@redhat.com>
9c08c320bSDavid Hildenbrand  */
10c08c320bSDavid Hildenbrand 
11c08c320bSDavid Hildenbrand #include <libcflat.h>
12c08c320bSDavid Hildenbrand #include <asm/pgtable.h>
13c08c320bSDavid Hildenbrand #include <asm/arch_def.h>
14c08c320bSDavid Hildenbrand #include <asm/barrier.h>
15c08c320bSDavid Hildenbrand #include <vmalloc.h>
16dfe993b0SThomas Huth #include "mmu.h"
17c08c320bSDavid Hildenbrand 
1849a732c7SJanosch Frank static pgd_t *table_root;
1949a732c7SJanosch Frank 
20c08c320bSDavid Hildenbrand void configure_dat(int enable)
21c08c320bSDavid Hildenbrand {
22c08c320bSDavid Hildenbrand 	uint64_t mask;
23c08c320bSDavid Hildenbrand 
24c08c320bSDavid Hildenbrand 	if (enable)
25c08c320bSDavid Hildenbrand 		mask = extract_psw_mask() | PSW_MASK_DAT;
26c08c320bSDavid Hildenbrand 	else
27c08c320bSDavid Hildenbrand 		mask = extract_psw_mask() & ~PSW_MASK_DAT;
28c08c320bSDavid Hildenbrand 
29c08c320bSDavid Hildenbrand 	load_psw_mask(mask);
30c08c320bSDavid Hildenbrand }
31c08c320bSDavid Hildenbrand 
32c08c320bSDavid Hildenbrand static void mmu_enable(pgd_t *pgtable)
33c08c320bSDavid Hildenbrand {
3483e3ed62SDavid Hildenbrand 	struct lowcore *lc = NULL;
35c08c320bSDavid Hildenbrand 	const uint64_t asce = __pa(pgtable) | ASCE_DT_REGION1 |
36c08c320bSDavid Hildenbrand 			      REGION_TABLE_LENGTH;
37c08c320bSDavid Hildenbrand 
38c08c320bSDavid Hildenbrand 	/* set primary asce */
39c08c320bSDavid Hildenbrand 	lctlg(1, asce);
40c08c320bSDavid Hildenbrand 	assert(stctg(1) == asce);
41c08c320bSDavid Hildenbrand 
42c08c320bSDavid Hildenbrand 	/* enable dat (primary == 0 set as default) */
43c08c320bSDavid Hildenbrand 	configure_dat(1);
4483e3ed62SDavid Hildenbrand 
4583e3ed62SDavid Hildenbrand 	/* we can now also use DAT unconditionally in our PGM handler */
4683e3ed62SDavid Hildenbrand 	lc->pgm_new_psw.mask |= PSW_MASK_DAT;
47c08c320bSDavid Hildenbrand }
48c08c320bSDavid Hildenbrand 
49c08c320bSDavid Hildenbrand static pteval_t *get_pte(pgd_t *pgtable, uintptr_t vaddr)
50c08c320bSDavid Hildenbrand {
51c08c320bSDavid Hildenbrand 	pgd_t *pgd = pgd_offset(pgtable, vaddr);
52c08c320bSDavid Hildenbrand 	p4d_t *p4d = p4d_alloc(pgd, vaddr);
53c08c320bSDavid Hildenbrand 	pud_t *pud = pud_alloc(p4d, vaddr);
54c08c320bSDavid Hildenbrand 	pmd_t *pmd = pmd_alloc(pud, vaddr);
55c08c320bSDavid Hildenbrand 	pte_t *pte = pte_alloc(pmd, vaddr);
56c08c320bSDavid Hildenbrand 
57c08c320bSDavid Hildenbrand 	return &pte_val(*pte);
58c08c320bSDavid Hildenbrand }
59c08c320bSDavid Hildenbrand 
60c08c320bSDavid Hildenbrand phys_addr_t virt_to_pte_phys(pgd_t *pgtable, void *vaddr)
61c08c320bSDavid Hildenbrand {
62c08c320bSDavid Hildenbrand 	return (*get_pte(pgtable, (uintptr_t)vaddr) & PAGE_MASK) +
63c08c320bSDavid Hildenbrand 	       ((unsigned long)vaddr & ~PAGE_MASK);
64c08c320bSDavid Hildenbrand }
65c08c320bSDavid Hildenbrand 
6649a732c7SJanosch Frank static pteval_t *set_pte(pgd_t *pgtable, pteval_t val, void *vaddr)
67c08c320bSDavid Hildenbrand {
68c08c320bSDavid Hildenbrand 	pteval_t *p_pte = get_pte(pgtable, (uintptr_t)vaddr);
69c08c320bSDavid Hildenbrand 
70c08c320bSDavid Hildenbrand 	/* first flush the old entry (if we're replacing anything) */
71c08c320bSDavid Hildenbrand 	if (!(*p_pte & PAGE_ENTRY_I))
72c08c320bSDavid Hildenbrand 		ipte((uintptr_t)vaddr, p_pte);
73c08c320bSDavid Hildenbrand 
7449a732c7SJanosch Frank 	*p_pte = val;
75c08c320bSDavid Hildenbrand 	return p_pte;
76c08c320bSDavid Hildenbrand }
77c08c320bSDavid Hildenbrand 
7849a732c7SJanosch Frank pteval_t *install_page(pgd_t *pgtable, phys_addr_t phys, void *vaddr)
7949a732c7SJanosch Frank {
8049a732c7SJanosch Frank 	return set_pte(pgtable, __pa(phys), vaddr);
8149a732c7SJanosch Frank }
8249a732c7SJanosch Frank 
8349a732c7SJanosch Frank void protect_page(void *vaddr, unsigned long prot)
8449a732c7SJanosch Frank {
8549a732c7SJanosch Frank 	pteval_t *p_pte = get_pte(table_root, (uintptr_t)vaddr);
8649a732c7SJanosch Frank 	pteval_t n_pte = *p_pte | prot;
8749a732c7SJanosch Frank 
8849a732c7SJanosch Frank 	set_pte(table_root, n_pte, vaddr);
8949a732c7SJanosch Frank }
9049a732c7SJanosch Frank 
9149a732c7SJanosch Frank void unprotect_page(void *vaddr, unsigned long prot)
9249a732c7SJanosch Frank {
9349a732c7SJanosch Frank 	pteval_t *p_pte = get_pte(table_root, (uintptr_t)vaddr);
9449a732c7SJanosch Frank 	pteval_t n_pte = *p_pte & ~prot;
9549a732c7SJanosch Frank 
9649a732c7SJanosch Frank 	set_pte(table_root, n_pte, vaddr);
9749a732c7SJanosch Frank }
9849a732c7SJanosch Frank 
9949a732c7SJanosch Frank void protect_range(void *start, unsigned long len, unsigned long prot)
10049a732c7SJanosch Frank {
10149a732c7SJanosch Frank 	uintptr_t curr = (uintptr_t)start & PAGE_MASK;
10249a732c7SJanosch Frank 
10349a732c7SJanosch Frank 	len &= PAGE_MASK;
10449a732c7SJanosch Frank 	for (; len; len -= PAGE_SIZE, curr += PAGE_SIZE)
10549a732c7SJanosch Frank 		protect_page((void *)curr, prot);
10649a732c7SJanosch Frank }
10749a732c7SJanosch Frank 
10849a732c7SJanosch Frank void unprotect_range(void *start, unsigned long len, unsigned long prot)
10949a732c7SJanosch Frank {
11049a732c7SJanosch Frank 	uintptr_t curr = (uintptr_t)start & PAGE_MASK;
11149a732c7SJanosch Frank 
11249a732c7SJanosch Frank 	len &= PAGE_MASK;
11349a732c7SJanosch Frank 	for (; len; len -= PAGE_SIZE, curr += PAGE_SIZE)
11449a732c7SJanosch Frank 		unprotect_page((void *)curr, prot);
11549a732c7SJanosch Frank }
11649a732c7SJanosch Frank 
117c08c320bSDavid Hildenbrand static void setup_identity(pgd_t *pgtable, phys_addr_t start_addr,
118c08c320bSDavid Hildenbrand 			   phys_addr_t end_addr)
119c08c320bSDavid Hildenbrand {
120c08c320bSDavid Hildenbrand 	phys_addr_t cur;
121c08c320bSDavid Hildenbrand 
122c08c320bSDavid Hildenbrand 	start_addr &= PAGE_MASK;
123c08c320bSDavid Hildenbrand 	for (cur = start_addr; true; cur += PAGE_SIZE) {
124c08c320bSDavid Hildenbrand 		if (start_addr < end_addr && cur >= end_addr)
125c08c320bSDavid Hildenbrand 			break;
126c08c320bSDavid Hildenbrand 		if (start_addr > end_addr && cur <= end_addr)
127c08c320bSDavid Hildenbrand 			break;
128c08c320bSDavid Hildenbrand 		install_page(pgtable, cur, __va(cur));
129c08c320bSDavid Hildenbrand 	}
130c08c320bSDavid Hildenbrand }
131c08c320bSDavid Hildenbrand 
132c08c320bSDavid Hildenbrand void *setup_mmu(phys_addr_t phys_end){
133c08c320bSDavid Hildenbrand 	pgd_t *page_root;
134c08c320bSDavid Hildenbrand 
135c08c320bSDavid Hildenbrand 	/* allocate a region-1 table */
136c08c320bSDavid Hildenbrand 	page_root = pgd_alloc_one();
137c08c320bSDavid Hildenbrand 
138c08c320bSDavid Hildenbrand 	/* map all physical memory 1:1 */
139c08c320bSDavid Hildenbrand 	setup_identity(page_root, 0, phys_end);
140c08c320bSDavid Hildenbrand 
141c08c320bSDavid Hildenbrand 	/* generate 128MB of invalid adresses at the end (for testing PGM) */
142c08c320bSDavid Hildenbrand 	init_alloc_vpage((void *) -(1UL << 27));
143c08c320bSDavid Hildenbrand 	setup_identity(page_root, -(1UL << 27), 0);
144c08c320bSDavid Hildenbrand 
145c08c320bSDavid Hildenbrand 	/* finally enable DAT with the new table */
146c08c320bSDavid Hildenbrand 	mmu_enable(page_root);
14749a732c7SJanosch Frank 	table_root = page_root;
148c08c320bSDavid Hildenbrand 	return page_root;
149c08c320bSDavid Hildenbrand }
150