1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * This file contains the routines setting up the linux page tables. |
4 | * -- paulus |
5 | * |
6 | * Derived from arch/ppc/mm/init.c: |
7 | * Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org) |
8 | * |
9 | * Modifications by Paul Mackerras (PowerMac) (paulus@cs.anu.edu.au) |
10 | * and Cort Dougan (PReP) (cort@cs.nmt.edu) |
11 | * Copyright (C) 1996 Paul Mackerras |
12 | * |
13 | * Derived from "arch/i386/mm/init.c" |
14 | * Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds |
15 | */ |
16 | |
17 | #include <linux/kernel.h> |
18 | #include <linux/module.h> |
19 | #include <linux/types.h> |
20 | #include <linux/mm.h> |
21 | #include <linux/vmalloc.h> |
22 | #include <linux/init.h> |
23 | #include <linux/highmem.h> |
24 | #include <linux/memblock.h> |
25 | #include <linux/slab.h> |
26 | #include <linux/set_memory.h> |
27 | |
28 | #include <asm/pgalloc.h> |
29 | #include <asm/fixmap.h> |
30 | #include <asm/setup.h> |
31 | #include <asm/sections.h> |
32 | #include <asm/early_ioremap.h> |
33 | |
34 | #include <mm/mmu_decl.h> |
35 | |
36 | static u8 early_fixmap_pagetable[FIXMAP_PTE_SIZE] __page_aligned_data; |
37 | |
38 | notrace void __init early_ioremap_init(void) |
39 | { |
40 | unsigned long addr = ALIGN_DOWN(FIXADDR_START, PGDIR_SIZE); |
41 | pte_t *ptep = (pte_t *)early_fixmap_pagetable; |
42 | pmd_t *pmdp = pmd_off_k(va: addr); |
43 | |
44 | for (; (s32)(FIXADDR_TOP - addr) > 0; |
45 | addr += PGDIR_SIZE, ptep += PTRS_PER_PTE, pmdp++) |
46 | pmd_populate_kernel(mm: &init_mm, pmd: pmdp, pte: ptep); |
47 | |
48 | early_ioremap_setup(); |
49 | } |
50 | |
51 | static void __init *early_alloc_pgtable(unsigned long size) |
52 | { |
53 | void *ptr = memblock_alloc(size, align: size); |
54 | |
55 | if (!ptr) |
56 | panic(fmt: "%s: Failed to allocate %lu bytes align=0x%lx\n" , |
57 | __func__, size, size); |
58 | |
59 | return ptr; |
60 | } |
61 | |
62 | pte_t __init *early_pte_alloc_kernel(pmd_t *pmdp, unsigned long va) |
63 | { |
64 | if (pmd_none(pmd: *pmdp)) { |
65 | pte_t *ptep = early_alloc_pgtable(size: PTE_FRAG_SIZE); |
66 | |
67 | pmd_populate_kernel(mm: &init_mm, pmd: pmdp, pte: ptep); |
68 | } |
69 | return pte_offset_kernel(pmd: pmdp, address: va); |
70 | } |
71 | |
72 | |
73 | int __ref map_kernel_page(unsigned long va, phys_addr_t pa, pgprot_t prot) |
74 | { |
75 | pmd_t *pd; |
76 | pte_t *pg; |
77 | int err = -ENOMEM; |
78 | |
79 | /* Use upper 10 bits of VA to index the first level map */ |
80 | pd = pmd_off_k(va); |
81 | /* Use middle 10 bits of VA to index the second-level map */ |
82 | if (likely(slab_is_available())) |
83 | pg = pte_alloc_kernel(pd, va); |
84 | else |
85 | pg = early_pte_alloc_kernel(pmdp: pd, va); |
86 | if (pg) { |
87 | err = 0; |
88 | /* The PTE should never be already set nor present in the |
89 | * hash table |
90 | */ |
91 | BUG_ON((pte_present(*pg) | pte_hashpte(*pg)) && pgprot_val(prot)); |
92 | set_pte_at(&init_mm, va, pg, pfn_pte(pa >> PAGE_SHIFT, prot)); |
93 | } |
94 | smp_wmb(); |
95 | return err; |
96 | } |
97 | |
98 | /* |
99 | * Map in a chunk of physical memory starting at start. |
100 | */ |
101 | static void __init __mapin_ram_chunk(unsigned long offset, unsigned long top) |
102 | { |
103 | unsigned long v, s; |
104 | phys_addr_t p; |
105 | bool ktext; |
106 | |
107 | s = offset; |
108 | v = PAGE_OFFSET + s; |
109 | p = memstart_addr + s; |
110 | for (; s < top; s += PAGE_SIZE) { |
111 | ktext = core_kernel_text(addr: v); |
112 | map_kernel_page(va: v, pa: p, prot: ktext ? PAGE_KERNEL_TEXT : PAGE_KERNEL); |
113 | v += PAGE_SIZE; |
114 | p += PAGE_SIZE; |
115 | } |
116 | } |
117 | |
118 | void __init mapin_ram(void) |
119 | { |
120 | phys_addr_t base, end; |
121 | u64 i; |
122 | |
123 | for_each_mem_range(i, &base, &end) { |
124 | phys_addr_t top = min(end, total_lowmem); |
125 | |
126 | if (base >= top) |
127 | continue; |
128 | base = mmu_mapin_ram(base, top); |
129 | __mapin_ram_chunk(offset: base, top); |
130 | } |
131 | } |
132 | |
133 | static int __mark_initmem_nx(void) |
134 | { |
135 | unsigned long numpages = PFN_UP((unsigned long)_einittext) - |
136 | PFN_DOWN((unsigned long)_sinittext); |
137 | int err; |
138 | |
139 | err = mmu_mark_initmem_nx(); |
140 | |
141 | if (!v_block_mapped((unsigned long)_sinittext)) { |
142 | err = set_memory_nx(addr: (unsigned long)_sinittext, numpages); |
143 | if (err) |
144 | return err; |
145 | err = set_memory_rw(addr: (unsigned long)_sinittext, numpages); |
146 | } |
147 | return err; |
148 | } |
149 | |
150 | void mark_initmem_nx(void) |
151 | { |
152 | int err = __mark_initmem_nx(); |
153 | |
154 | if (err) |
155 | panic(fmt: "%s() failed, err = %d\n" , __func__, err); |
156 | } |
157 | |
158 | #ifdef CONFIG_STRICT_KERNEL_RWX |
159 | static int __mark_rodata_ro(void) |
160 | { |
161 | unsigned long numpages; |
162 | |
163 | if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX) && mmu_has_feature(MMU_FTR_HPTE_TABLE)) |
164 | pr_warn("This platform has HASH MMU, STRICT_MODULE_RWX won't work\n" ); |
165 | |
166 | if (v_block_mapped((unsigned long)_stext + 1)) |
167 | return mmu_mark_rodata_ro(); |
168 | |
169 | /* |
170 | * mark text and rodata as read only. __end_rodata is set by |
171 | * powerpc's linker script and includes tables and data |
172 | * requiring relocation which are not put in RO_DATA. |
173 | */ |
174 | numpages = PFN_UP((unsigned long)__end_rodata) - |
175 | PFN_DOWN((unsigned long)_stext); |
176 | |
177 | return set_memory_ro(addr: (unsigned long)_stext, numpages); |
178 | } |
179 | |
180 | void mark_rodata_ro(void) |
181 | { |
182 | int err = __mark_rodata_ro(); |
183 | |
184 | if (err) |
185 | panic(fmt: "%s() failed, err = %d\n" , __func__, err); |
186 | } |
187 | #endif |
188 | |