/* SPDX-License-Identifier: GPL-2.0-only */ /* * s390x MMU * * Copyright (c) 2017 Red Hat Inc * * Authors: * David Hildenbrand */ #include #include #include #include #include #include "mmu.h" static pgd_t *table_root; void configure_dat(int enable) { uint64_t mask; if (enable) mask = extract_psw_mask() | PSW_MASK_DAT; else mask = extract_psw_mask() & ~PSW_MASK_DAT; load_psw_mask(mask); } static void mmu_enable(pgd_t *pgtable) { struct lowcore *lc = NULL; const uint64_t asce = __pa(pgtable) | ASCE_DT_REGION1 | REGION_TABLE_LENGTH; /* set primary asce */ lctlg(1, asce); assert(stctg(1) == asce); /* enable dat (primary == 0 set as default) */ configure_dat(1); /* we can now also use DAT unconditionally in our PGM handler */ lc->pgm_new_psw.mask |= PSW_MASK_DAT; } static pteval_t *get_pte(pgd_t *pgtable, uintptr_t vaddr) { pgd_t *pgd = pgd_offset(pgtable, vaddr); p4d_t *p4d = p4d_alloc(pgd, vaddr); pud_t *pud = pud_alloc(p4d, vaddr); pmd_t *pmd = pmd_alloc(pud, vaddr); pte_t *pte = pte_alloc(pmd, vaddr); return &pte_val(*pte); } phys_addr_t virt_to_pte_phys(pgd_t *pgtable, void *vaddr) { return (*get_pte(pgtable, (uintptr_t)vaddr) & PAGE_MASK) + ((unsigned long)vaddr & ~PAGE_MASK); } static pteval_t *set_pte(pgd_t *pgtable, pteval_t val, void *vaddr) { pteval_t *p_pte = get_pte(pgtable, (uintptr_t)vaddr); /* first flush the old entry (if we're replacing anything) */ if (!(*p_pte & PAGE_ENTRY_I)) ipte((uintptr_t)vaddr, p_pte); *p_pte = val; return p_pte; } pteval_t *install_page(pgd_t *pgtable, phys_addr_t phys, void *vaddr) { return set_pte(pgtable, __pa(phys), vaddr); } void protect_page(void *vaddr, unsigned long prot) { pteval_t *p_pte = get_pte(table_root, (uintptr_t)vaddr); pteval_t n_pte = *p_pte | prot; set_pte(table_root, n_pte, vaddr); } void unprotect_page(void *vaddr, unsigned long prot) { pteval_t *p_pte = get_pte(table_root, (uintptr_t)vaddr); pteval_t n_pte = *p_pte & ~prot; set_pte(table_root, n_pte, vaddr); } void protect_range(void *start, unsigned long len, unsigned long prot) { uintptr_t curr = (uintptr_t)start & PAGE_MASK; len &= PAGE_MASK; for (; len; len -= PAGE_SIZE, curr += PAGE_SIZE) protect_page((void *)curr, prot); } void unprotect_range(void *start, unsigned long len, unsigned long prot) { uintptr_t curr = (uintptr_t)start & PAGE_MASK; len &= PAGE_MASK; for (; len; len -= PAGE_SIZE, curr += PAGE_SIZE) unprotect_page((void *)curr, prot); } static void setup_identity(pgd_t *pgtable, phys_addr_t start_addr, phys_addr_t end_addr) { phys_addr_t cur; start_addr &= PAGE_MASK; for (cur = start_addr; true; cur += PAGE_SIZE) { if (start_addr < end_addr && cur >= end_addr) break; if (start_addr > end_addr && cur <= end_addr) break; install_page(pgtable, cur, __va(cur)); } } void *setup_mmu(phys_addr_t phys_end){ pgd_t *page_root; /* allocate a region-1 table */ page_root = pgd_alloc_one(); /* map all physical memory 1:1 */ setup_identity(page_root, 0, phys_end); /* generate 128MB of invalid adresses at the end (for testing PGM) */ init_alloc_vpage((void *) -(1UL << 27)); setup_identity(page_root, -(1UL << 27), 0); /* finally enable DAT with the new table */ mmu_enable(page_root); table_root = page_root; return page_root; }