summaryrefslogtreecommitdiffstats
path: root/arch/nds32/mm/init.c
blob: fa86f7b2f4166d802bcd4569d9bce0a8d134477f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 1995-2005 Russell King
// Copyright (C) 2012 ARM Ltd.
// Copyright (C) 2013-2017 Andes Technology Corporation

#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/swap.h>
#include <linux/init.h>
#include <linux/memblock.h>
#include <linux/mman.h>
#include <linux/nodemask.h>
#include <linux/initrd.h>
#include <linux/highmem.h>

#include <asm/sections.h>
#include <asm/setup.h>
#include <asm/tlb.h>
#include <asm/page.h>

DEFINE_PER_CPU(struct mmu_gather, mmu_gathers);
DEFINE_SPINLOCK(anon_alias_lock);
extern pgd_t swapper_pg_dir[PTRS_PER_PGD];

/*
 * empty_zero_page is a special page that is used for
 * zero-initialized data and COW.
 */
struct page *empty_zero_page;
EXPORT_SYMBOL(empty_zero_page);

static void __init zone_sizes_init(void)
{
	unsigned long max_zone_pfn[MAX_NR_ZONES] = { 0 };

	max_zone_pfn[ZONE_NORMAL] = max_low_pfn;
#ifdef CONFIG_HIGHMEM
	max_zone_pfn[ZONE_HIGHMEM] = max_pfn;
#endif
	free_area_init(max_zone_pfn);

}

/*
 * Map all physical memory under high_memory into kernel's address space.
 *
 * This is explicitly coded for two-level page tables, so if you need
 * something else then this needs to change.
 */
static void __init map_ram(void)
{
	unsigned long v, p, e;
	pgd_t *pge;
	p4d_t *p4e;
	pud_t *pue;
	pmd_t *pme;
	pte_t *pte;
	/* These mark extents of read-only kernel pages...
	 * ...from vmlinux.lds.S
	 */

	p = (u32) memblock_start_of_DRAM() & PAGE_MASK;
	e = min((u32) memblock_end_of_DRAM(), (u32) __pa(high_memory));

	v = (u32) __va(p);
	pge = pgd_offset_k(v);

	while (p < e) {
		int j;
		p4e = p4d_offset(pge, v);
		pue = pud_offset(p4e, v);
		pme = pmd_offset(pue, v);

		if ((u32) pue != (u32) pge || (u32) pme != (u32) pge) {
			panic("%s: Kernel hardcoded for "
			      "two-level page tables", __func__);
		}

		/* Alloc one page for holding PTE's... */
		pte = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
		if (!pte)
			panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
			      __func__, PAGE_SIZE, PAGE_SIZE);
		set_pmd(pme, __pmd(__pa(pte) + _PAGE_KERNEL_TABLE));

		/* Fill the newly allocated page with PTE'S */
		for (j = 0; p < e && j < PTRS_PER_PTE;
		     v += PAGE_SIZE, p += PAGE_SIZE, j++, pte++) {
			/* Create mapping between p and v. */
			/* TODO: more fine grant for page access permission */
			set_pte(pte, __pte(p + pgprot_val(PAGE_KERNEL)));
		}

		pge++;
	}
}
static pmd_t *fixmap_pmd_p;
static void __init fixedrange_init(void)
{
	unsigned long vaddr;
	pmd_t *pmd;
#ifdef CONFIG_HIGHMEM
	pte_t *pte;
#endif /* CONFIG_HIGHMEM */

	/*
	 * Fixed mappings:
	 */
	vaddr = __fix_to_virt(__end_of_fixed_addresses - 1);
	pmd = pmd_off_k(vaddr);
	fixmap_pmd_p = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
	if (!fixmap_pmd_p)
		panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
		      __func__, PAGE_SIZE, PAGE_SIZE);
	set_pmd(pmd, __pmd(__pa(fixmap_pmd_p) + _PAGE_KERNEL_TABLE));

#ifdef CONFIG_HIGHMEM
	/*
	 * Permanent kmaps:
	 */
	vaddr = PKMAP_BASE;

	pmd = pmd_off_k(vaddr);
	pte = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
	if (!pte)
		panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
		      __func__, PAGE_SIZE, PAGE_SIZE);
	set_pmd(pmd, __pmd(__pa(pte) + _PAGE_KERNEL_TABLE));
	pkmap_page_table = pte;
#endif /* CONFIG_HIGHMEM */
}

/*
 * paging_init() sets up the page tables, initialises the zone memory
 * maps, and sets up the zero page, bad page and bad page tables.
 */
void __init paging_init(void)
{
	int i;
	void *zero_page;

	pr_info("Setting up paging and PTEs.\n");
	/* clear out the init_mm.pgd that will contain the kernel's mappings */
	for (i = 0; i < PTRS_PER_PGD; i++)
		swapper_pg_dir[i] = __pgd(1);

	map_ram();

	fixedrange_init();

	/* allocate space for empty_zero_page */
	zero_page = memblock_alloc(PAGE_SIZE, PAGE_SIZE);
	if (!zero_page)
		panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
		      __func__, PAGE_SIZE, PAGE_SIZE);
	zone_sizes_init();

	empty_zero_page = virt_to_page(zero_page);
	flush_dcache_page(empty_zero_page);
}

static inline void __init free_highmem(void)
{
#ifdef CONFIG_HIGHMEM
	unsigned long pfn;
	for (pfn = PFN_UP(__pa(high_memory)); pfn < max_pfn; pfn++) {
		phys_addr_t paddr = (phys_addr_t) pfn << PAGE_SHIFT;
		if (!memblock_is_reserved(paddr))
			free_highmem_page(pfn_to_page(pfn));
	}
#endif
}

static void __init set_max_mapnr_init(void)
{
	max_mapnr = max_pfn;
}

/*
 * mem_init() marks the free areas in the mem_map and tells us how much
 * memory is free.  This is done after various parts of the system have
 * claimed their memory after the kernel image.
 */
void __init mem_init(void)
{
	phys_addr_t memory_start = memblock_start_of_DRAM();
	BUG_ON(!mem_map);
	set_max_mapnr_init();

	free_highmem();

	/* this will put all low memory onto the freelists */
	memblock_free_all();
	mem_init_print_info(NULL);

	pr_info("virtual kernel memory layout:\n"
		"    fixmap  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#ifdef CONFIG_HIGHMEM
		"    pkmap   : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#endif
		"    consist : 0x%08lx - 0x%08lx   (%4ld MB)\n"
		"    vmalloc : 0x%08lx - 0x%08lx   (%4ld MB)\n"
		"    lowmem  : 0x%08lx - 0x%08lx   (%4ld MB)\n"
		"      .init : 0x%08lx - 0x%08lx   (%4ld kB)\n"
		"      .data : 0x%08lx - 0x%08lx   (%4ld kB)\n"
		"      .text : 0x%08lx - 0x%08lx   (%4ld kB)\n",
		FIXADDR_START, FIXADDR_TOP, (FIXADDR_TOP - FIXADDR_START) >> 10,
#ifdef CONFIG_HIGHMEM
		PKMAP_BASE, PKMAP_BASE + LAST_PKMAP * PAGE_SIZE,
		(LAST_PKMAP * PAGE_SIZE) >> 10,
#endif
		CONSISTENT_BASE, CONSISTENT_END,
		((CONSISTENT_END) - (CONSISTENT_BASE)) >> 20, VMALLOC_START,
		(unsigned long)VMALLOC_END, (VMALLOC_END - VMALLOC_START) >> 20,
		(unsigned long)__va(memory_start), (unsigned long)high_memory,
		((unsigned long)high_memory -
		 (unsigned long)__va(memory_start)) >> 20,
		(unsigned long)&__init_begin, (unsigned long)&__init_end,
		((unsigned long)&__init_end -
		 (unsigned long)&__init_begin) >> 10, (unsigned long)&_etext,
		(unsigned long)&_edata,
		((unsigned long)&_edata - (unsigned long)&_etext) >> 10,
		(unsigned long)&_text, (unsigned long)&_etext,
		((unsigned long)&_etext - (unsigned long)&_text) >> 10);

	/*
	 * Check boundaries twice: Some fundamental inconsistencies can
	 * be detected at build time already.
	 */
#ifdef CONFIG_HIGHMEM
	BUILD_BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > FIXADDR_START);
	BUILD_BUG_ON((CONSISTENT_END) > PKMAP_BASE);
#endif
	BUILD_BUG_ON(VMALLOC_END > CONSISTENT_BASE);
	BUILD_BUG_ON(VMALLOC_START >= VMALLOC_END);

#ifdef CONFIG_HIGHMEM
	BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > FIXADDR_START);
	BUG_ON(CONSISTENT_END > PKMAP_BASE);
#endif
	BUG_ON(VMALLOC_END > CONSISTENT_BASE);
	BUG_ON(VMALLOC_START >= VMALLOC_END);
	BUG_ON((unsigned long)high_memory > VMALLOC_START);

	return;
}

void __set_fixmap(enum fixed_addresses idx,
			       phys_addr_t phys, pgprot_t flags)
{
	unsigned long addr = __fix_to_virt(idx);
	pte_t *pte;

	BUG_ON(idx <= FIX_HOLE || idx >= __end_of_fixed_addresses);

	pte = (pte_t *)&fixmap_pmd_p[pte_index(addr)];

	if (pgprot_val(flags)) {
		set_pte(pte, pfn_pte(phys >> PAGE_SHIFT, flags));
	} else {
		pte_clear(&init_mm, addr, pte);
		flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
	}
}