linux/arch/s390/mm/pageattr.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright IBM Corp. 2011
   4 * Author(s): Jan Glauber <jang@linux.vnet.ibm.com>
   5 */
   6#include <linux/hugetlb.h>
   7#include <linux/mm.h>
   8#include <asm/cacheflush.h>
   9#include <asm/facility.h>
  10#include <asm/pgtable.h>
  11#include <asm/pgalloc.h>
  12#include <asm/page.h>
  13#include <asm/set_memory.h>
  14
  15static inline unsigned long sske_frame(unsigned long addr, unsigned char skey)
  16{
  17        asm volatile(".insn rrf,0xb22b0000,%[skey],%[addr],1,0"
  18                     : [addr] "+a" (addr) : [skey] "d" (skey));
  19        return addr;
  20}
  21
  22void __storage_key_init_range(unsigned long start, unsigned long end)
  23{
  24        unsigned long boundary, size;
  25
  26        while (start < end) {
  27                if (MACHINE_HAS_EDAT1) {
  28                        /* set storage keys for a 1MB frame */
  29                        size = 1UL << 20;
  30                        boundary = (start + size) & ~(size - 1);
  31                        if (boundary <= end) {
  32                                do {
  33                                        start = sske_frame(start, PAGE_DEFAULT_KEY);
  34                                } while (start < boundary);
  35                                continue;
  36                        }
  37                }
  38                page_set_storage_key(start, PAGE_DEFAULT_KEY, 1);
  39                start += PAGE_SIZE;
  40        }
  41}
  42
  43#ifdef CONFIG_PROC_FS
  44atomic_long_t direct_pages_count[PG_DIRECT_MAP_MAX];
  45
  46void arch_report_meminfo(struct seq_file *m)
  47{
  48        seq_printf(m, "DirectMap4k:    %8lu kB\n",
  49                   atomic_long_read(&direct_pages_count[PG_DIRECT_MAP_4K]) << 2);
  50        seq_printf(m, "DirectMap1M:    %8lu kB\n",
  51                   atomic_long_read(&direct_pages_count[PG_DIRECT_MAP_1M]) << 10);
  52        seq_printf(m, "DirectMap2G:    %8lu kB\n",
  53                   atomic_long_read(&direct_pages_count[PG_DIRECT_MAP_2G]) << 21);
  54}
  55#endif /* CONFIG_PROC_FS */
  56
  57static void pgt_set(unsigned long *old, unsigned long new, unsigned long addr,
  58                    unsigned long dtt)
  59{
  60        unsigned long table, mask;
  61
  62        mask = 0;
  63        if (MACHINE_HAS_EDAT2) {
  64                switch (dtt) {
  65                case CRDTE_DTT_REGION3:
  66                        mask = ~(PTRS_PER_PUD * sizeof(pud_t) - 1);
  67                        break;
  68                case CRDTE_DTT_SEGMENT:
  69                        mask = ~(PTRS_PER_PMD * sizeof(pmd_t) - 1);
  70                        break;
  71                case CRDTE_DTT_PAGE:
  72                        mask = ~(PTRS_PER_PTE * sizeof(pte_t) - 1);
  73                        break;
  74                }
  75                table = (unsigned long)old & mask;
  76                crdte(*old, new, table, dtt, addr, S390_lowcore.kernel_asce);
  77        } else if (MACHINE_HAS_IDTE) {
  78                cspg(old, *old, new);
  79        } else {
  80                csp((unsigned int *)old + 1, *old, new);
  81        }
  82}
  83
  84static int walk_pte_level(pmd_t *pmdp, unsigned long addr, unsigned long end,
  85                          unsigned long flags)
  86{
  87        pte_t *ptep, new;
  88
  89        ptep = pte_offset(pmdp, addr);
  90        do {
  91                new = *ptep;
  92                if (pte_none(new))
  93                        return -EINVAL;
  94                if (flags & SET_MEMORY_RO)
  95                        new = pte_wrprotect(new);
  96                else if (flags & SET_MEMORY_RW)
  97                        new = pte_mkwrite(pte_mkdirty(new));
  98                if (flags & SET_MEMORY_NX)
  99                        pte_val(new) |= _PAGE_NOEXEC;
 100                else if (flags & SET_MEMORY_X)
 101                        pte_val(new) &= ~_PAGE_NOEXEC;
 102                pgt_set((unsigned long *)ptep, pte_val(new), addr, CRDTE_DTT_PAGE);
 103                ptep++;
 104                addr += PAGE_SIZE;
 105                cond_resched();
 106        } while (addr < end);
 107        return 0;
 108}
 109
 110static int split_pmd_page(pmd_t *pmdp, unsigned long addr)
 111{
 112        unsigned long pte_addr, prot;
 113        pte_t *pt_dir, *ptep;
 114        pmd_t new;
 115        int i, ro, nx;
 116
 117        pt_dir = vmem_pte_alloc();
 118        if (!pt_dir)
 119                return -ENOMEM;
 120        pte_addr = pmd_pfn(*pmdp) << PAGE_SHIFT;
 121        ro = !!(pmd_val(*pmdp) & _SEGMENT_ENTRY_PROTECT);
 122        nx = !!(pmd_val(*pmdp) & _SEGMENT_ENTRY_NOEXEC);
 123        prot = pgprot_val(ro ? PAGE_KERNEL_RO : PAGE_KERNEL);
 124        if (!nx)
 125                prot &= ~_PAGE_NOEXEC;
 126        ptep = pt_dir;
 127        for (i = 0; i < PTRS_PER_PTE; i++) {
 128                pte_val(*ptep) = pte_addr | prot;
 129                pte_addr += PAGE_SIZE;
 130                ptep++;
 131        }
 132        pmd_val(new) = __pa(pt_dir) | _SEGMENT_ENTRY;
 133        pgt_set((unsigned long *)pmdp, pmd_val(new), addr, CRDTE_DTT_SEGMENT);
 134        update_page_count(PG_DIRECT_MAP_4K, PTRS_PER_PTE);
 135        update_page_count(PG_DIRECT_MAP_1M, -1);
 136        return 0;
 137}
 138
 139static void modify_pmd_page(pmd_t *pmdp, unsigned long addr,
 140                            unsigned long flags)
 141{
 142        pmd_t new = *pmdp;
 143
 144        if (flags & SET_MEMORY_RO)
 145                new = pmd_wrprotect(new);
 146        else if (flags & SET_MEMORY_RW)
 147                new = pmd_mkwrite(pmd_mkdirty(new));
 148        if (flags & SET_MEMORY_NX)
 149                pmd_val(new) |= _SEGMENT_ENTRY_NOEXEC;
 150        else if (flags & SET_MEMORY_X)
 151                pmd_val(new) &= ~_SEGMENT_ENTRY_NOEXEC;
 152        pgt_set((unsigned long *)pmdp, pmd_val(new), addr, CRDTE_DTT_SEGMENT);
 153}
 154
 155static int walk_pmd_level(pud_t *pudp, unsigned long addr, unsigned long end,
 156                          unsigned long flags)
 157{
 158        unsigned long next;
 159        pmd_t *pmdp;
 160        int rc = 0;
 161
 162        pmdp = pmd_offset(pudp, addr);
 163        do {
 164                if (pmd_none(*pmdp))
 165                        return -EINVAL;
 166                next = pmd_addr_end(addr, end);
 167                if (pmd_large(*pmdp)) {
 168                        if (addr & ~PMD_MASK || addr + PMD_SIZE > next) {
 169                                rc = split_pmd_page(pmdp, addr);
 170                                if (rc)
 171                                        return rc;
 172                                continue;
 173                        }
 174                        modify_pmd_page(pmdp, addr, flags);
 175                } else {
 176                        rc = walk_pte_level(pmdp, addr, next, flags);
 177                        if (rc)
 178                                return rc;
 179                }
 180                pmdp++;
 181                addr = next;
 182                cond_resched();
 183        } while (addr < end);
 184        return rc;
 185}
 186
 187static int split_pud_page(pud_t *pudp, unsigned long addr)
 188{
 189        unsigned long pmd_addr, prot;
 190        pmd_t *pm_dir, *pmdp;
 191        pud_t new;
 192        int i, ro, nx;
 193
 194        pm_dir = vmem_crst_alloc(_SEGMENT_ENTRY_EMPTY);
 195        if (!pm_dir)
 196                return -ENOMEM;
 197        pmd_addr = pud_pfn(*pudp) << PAGE_SHIFT;
 198        ro = !!(pud_val(*pudp) & _REGION_ENTRY_PROTECT);
 199        nx = !!(pud_val(*pudp) & _REGION_ENTRY_NOEXEC);
 200        prot = pgprot_val(ro ? SEGMENT_KERNEL_RO : SEGMENT_KERNEL);
 201        if (!nx)
 202                prot &= ~_SEGMENT_ENTRY_NOEXEC;
 203        pmdp = pm_dir;
 204        for (i = 0; i < PTRS_PER_PMD; i++) {
 205                pmd_val(*pmdp) = pmd_addr | prot;
 206                pmd_addr += PMD_SIZE;
 207                pmdp++;
 208        }
 209        pud_val(new) = __pa(pm_dir) | _REGION3_ENTRY;
 210        pgt_set((unsigned long *)pudp, pud_val(new), addr, CRDTE_DTT_REGION3);
 211        update_page_count(PG_DIRECT_MAP_1M, PTRS_PER_PMD);
 212        update_page_count(PG_DIRECT_MAP_2G, -1);
 213        return 0;
 214}
 215
 216static void modify_pud_page(pud_t *pudp, unsigned long addr,
 217                            unsigned long flags)
 218{
 219        pud_t new = *pudp;
 220
 221        if (flags & SET_MEMORY_RO)
 222                new = pud_wrprotect(new);
 223        else if (flags & SET_MEMORY_RW)
 224                new = pud_mkwrite(pud_mkdirty(new));
 225        if (flags & SET_MEMORY_NX)
 226                pud_val(new) |= _REGION_ENTRY_NOEXEC;
 227        else if (flags & SET_MEMORY_X)
 228                pud_val(new) &= ~_REGION_ENTRY_NOEXEC;
 229        pgt_set((unsigned long *)pudp, pud_val(new), addr, CRDTE_DTT_REGION3);
 230}
 231
 232static int walk_pud_level(p4d_t *p4d, unsigned long addr, unsigned long end,
 233                          unsigned long flags)
 234{
 235        unsigned long next;
 236        pud_t *pudp;
 237        int rc = 0;
 238
 239        pudp = pud_offset(p4d, addr);
 240        do {
 241                if (pud_none(*pudp))
 242                        return -EINVAL;
 243                next = pud_addr_end(addr, end);
 244                if (pud_large(*pudp)) {
 245                        if (addr & ~PUD_MASK || addr + PUD_SIZE > next) {
 246                                rc = split_pud_page(pudp, addr);
 247                                if (rc)
 248                                        break;
 249                                continue;
 250                        }
 251                        modify_pud_page(pudp, addr, flags);
 252                } else {
 253                        rc = walk_pmd_level(pudp, addr, next, flags);
 254                }
 255                pudp++;
 256                addr = next;
 257                cond_resched();
 258        } while (addr < end && !rc);
 259        return rc;
 260}
 261
 262static int walk_p4d_level(pgd_t *pgd, unsigned long addr, unsigned long end,
 263                          unsigned long flags)
 264{
 265        unsigned long next;
 266        p4d_t *p4dp;
 267        int rc = 0;
 268
 269        p4dp = p4d_offset(pgd, addr);
 270        do {
 271                if (p4d_none(*p4dp))
 272                        return -EINVAL;
 273                next = p4d_addr_end(addr, end);
 274                rc = walk_pud_level(p4dp, addr, next, flags);
 275                p4dp++;
 276                addr = next;
 277                cond_resched();
 278        } while (addr < end && !rc);
 279        return rc;
 280}
 281
 282static DEFINE_MUTEX(cpa_mutex);
 283
 284static int change_page_attr(unsigned long addr, unsigned long end,
 285                            unsigned long flags)
 286{
 287        unsigned long next;
 288        int rc = -EINVAL;
 289        pgd_t *pgdp;
 290
 291        if (addr == end)
 292                return 0;
 293        if (end >= MODULES_END)
 294                return -EINVAL;
 295        mutex_lock(&cpa_mutex);
 296        pgdp = pgd_offset_k(addr);
 297        do {
 298                if (pgd_none(*pgdp))
 299                        break;
 300                next = pgd_addr_end(addr, end);
 301                rc = walk_p4d_level(pgdp, addr, next, flags);
 302                if (rc)
 303                        break;
 304                cond_resched();
 305        } while (pgdp++, addr = next, addr < end && !rc);
 306        mutex_unlock(&cpa_mutex);
 307        return rc;
 308}
 309
 310int __set_memory(unsigned long addr, int numpages, unsigned long flags)
 311{
 312        if (!MACHINE_HAS_NX)
 313                flags &= ~(SET_MEMORY_NX | SET_MEMORY_X);
 314        if (!flags)
 315                return 0;
 316        addr &= PAGE_MASK;
 317        return change_page_attr(addr, addr + numpages * PAGE_SIZE, flags);
 318}
 319
 320#ifdef CONFIG_DEBUG_PAGEALLOC
 321
 322static void ipte_range(pte_t *pte, unsigned long address, int nr)
 323{
 324        int i;
 325
 326        if (test_facility(13)) {
 327                __ptep_ipte_range(address, nr - 1, pte, IPTE_GLOBAL);
 328                return;
 329        }
 330        for (i = 0; i < nr; i++) {
 331                __ptep_ipte(address, pte, 0, 0, IPTE_GLOBAL);
 332                address += PAGE_SIZE;
 333                pte++;
 334        }
 335}
 336
 337void __kernel_map_pages(struct page *page, int numpages, int enable)
 338{
 339        unsigned long address;
 340        int nr, i, j;
 341        pgd_t *pgd;
 342        p4d_t *p4d;
 343        pud_t *pud;
 344        pmd_t *pmd;
 345        pte_t *pte;
 346
 347        for (i = 0; i < numpages;) {
 348                address = page_to_phys(page + i);
 349                pgd = pgd_offset_k(address);
 350                p4d = p4d_offset(pgd, address);
 351                pud = pud_offset(p4d, address);
 352                pmd = pmd_offset(pud, address);
 353                pte = pte_offset_kernel(pmd, address);
 354                nr = (unsigned long)pte >> ilog2(sizeof(long));
 355                nr = PTRS_PER_PTE - (nr & (PTRS_PER_PTE - 1));
 356                nr = min(numpages - i, nr);
 357                if (enable) {
 358                        for (j = 0; j < nr; j++) {
 359                                pte_val(*pte) &= ~_PAGE_INVALID;
 360                                address += PAGE_SIZE;
 361                                pte++;
 362                        }
 363                } else {
 364                        ipte_range(pte, address, nr);
 365                }
 366                i += nr;
 367        }
 368}
 369
 370#ifdef CONFIG_HIBERNATION
 371bool kernel_page_present(struct page *page)
 372{
 373        unsigned long addr;
 374        int cc;
 375
 376        addr = page_to_phys(page);
 377        asm volatile(
 378                "       lra     %1,0(%1)\n"
 379                "       ipm     %0\n"
 380                "       srl     %0,28"
 381                : "=d" (cc), "+a" (addr) : : "cc");
 382        return cc == 0;
 383}
 384#endif /* CONFIG_HIBERNATION */
 385
 386#endif /* CONFIG_DEBUG_PAGEALLOC */
 387