xref: /openbmc/u-boot/arch/x86/lib/physmem.c (revision c79cba37b3b42cf8fbd71babcd8998867f76fead)
1ac31a7b8SGabe Black /*
2ac31a7b8SGabe Black  * Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3ac31a7b8SGabe Black  * Use of this source code is governed by a BSD-style license that can be
4ac31a7b8SGabe Black  * found in the LICENSE file.
5ac31a7b8SGabe Black  *
6ac31a7b8SGabe Black  * Alternatively, this software may be distributed under the terms of the
7ac31a7b8SGabe Black  * GNU General Public License ("GPL") version 2 as published by the Free
8ac31a7b8SGabe Black  * Software Foundation.
9ac31a7b8SGabe Black  */
10ac31a7b8SGabe Black 
11ac31a7b8SGabe Black #include <common.h>
12ac31a7b8SGabe Black #include <physmem.h>
13ac31a7b8SGabe Black #include <linux/compiler.h>
14ac31a7b8SGabe Black 
157282d834SSimon Glass DECLARE_GLOBAL_DATA_PTR;
167282d834SSimon Glass 
17ac31a7b8SGabe Black /* Large pages are 2MB. */
18ac31a7b8SGabe Black #define LARGE_PAGE_SIZE ((1 << 20) * 2)
19ac31a7b8SGabe Black 
20ac31a7b8SGabe Black /*
21ac31a7b8SGabe Black  * Paging data structures.
22ac31a7b8SGabe Black  */
23ac31a7b8SGabe Black 
24ac31a7b8SGabe Black struct pdpe {
25ac31a7b8SGabe Black 	uint64_t p:1;
26ac31a7b8SGabe Black 	uint64_t mbz_0:2;
27ac31a7b8SGabe Black 	uint64_t pwt:1;
28ac31a7b8SGabe Black 	uint64_t pcd:1;
29ac31a7b8SGabe Black 	uint64_t mbz_1:4;
30ac31a7b8SGabe Black 	uint64_t avl:3;
31ac31a7b8SGabe Black 	uint64_t base:40;
32ac31a7b8SGabe Black 	uint64_t mbz_2:12;
33ac31a7b8SGabe Black };
34ac31a7b8SGabe Black 
35ac31a7b8SGabe Black typedef struct pdpe pdpt_t[512];
36ac31a7b8SGabe Black 
37ac31a7b8SGabe Black struct pde {
38ac31a7b8SGabe Black 	uint64_t p:1;      /* present */
39ac31a7b8SGabe Black 	uint64_t rw:1;     /* read/write */
40ac31a7b8SGabe Black 	uint64_t us:1;     /* user/supervisor */
41ac31a7b8SGabe Black 	uint64_t pwt:1;    /* page-level writethrough */
42ac31a7b8SGabe Black 	uint64_t pcd:1;    /* page-level cache disable */
43ac31a7b8SGabe Black 	uint64_t a:1;      /* accessed */
44ac31a7b8SGabe Black 	uint64_t d:1;      /* dirty */
45ac31a7b8SGabe Black 	uint64_t ps:1;     /* page size */
46ac31a7b8SGabe Black 	uint64_t g:1;      /* global page */
47ac31a7b8SGabe Black 	uint64_t avl:3;    /* available to software */
48ac31a7b8SGabe Black 	uint64_t pat:1;    /* page-attribute table */
49ac31a7b8SGabe Black 	uint64_t mbz_0:8;  /* must be zero */
50ac31a7b8SGabe Black 	uint64_t base:31;  /* base address */
51ac31a7b8SGabe Black };
52ac31a7b8SGabe Black 
53ac31a7b8SGabe Black typedef struct pde pdt_t[512];
54ac31a7b8SGabe Black 
55ac31a7b8SGabe Black static pdpt_t pdpt __aligned(4096);
56ac31a7b8SGabe Black static pdt_t pdts[4] __aligned(4096);
57ac31a7b8SGabe Black 
58ac31a7b8SGabe Black /*
59ac31a7b8SGabe Black  * Map a virtual address to a physical address and optionally invalidate any
60ac31a7b8SGabe Black  * old mapping.
61ac31a7b8SGabe Black  *
62ac31a7b8SGabe Black  * @param virt		The virtual address to use.
63ac31a7b8SGabe Black  * @param phys		The physical address to use.
64ac31a7b8SGabe Black  * @param invlpg	Whether to use invlpg to clear any old mappings.
65ac31a7b8SGabe Black  */
66ac31a7b8SGabe Black static void x86_phys_map_page(uintptr_t virt, phys_addr_t phys, int invlpg)
67ac31a7b8SGabe Black {
68ac31a7b8SGabe Black 	/* Extract the two bit PDPT index and the 9 bit PDT index. */
69ac31a7b8SGabe Black 	uintptr_t pdpt_idx = (virt >> 30) & 0x3;
70ac31a7b8SGabe Black 	uintptr_t pdt_idx = (virt >> 21) & 0x1ff;
71ac31a7b8SGabe Black 
72ac31a7b8SGabe Black 	/* Set up a handy pointer to the appropriate PDE. */
73ac31a7b8SGabe Black 	struct pde *pde = &(pdts[pdpt_idx][pdt_idx]);
74ac31a7b8SGabe Black 
75ac31a7b8SGabe Black 	memset(pde, 0, sizeof(struct pde));
76ac31a7b8SGabe Black 	pde->p = 1;
77ac31a7b8SGabe Black 	pde->rw = 1;
78ac31a7b8SGabe Black 	pde->us = 1;
79ac31a7b8SGabe Black 	pde->ps = 1;
80ac31a7b8SGabe Black 	pde->base = phys >> 21;
81ac31a7b8SGabe Black 
82ac31a7b8SGabe Black 	if (invlpg) {
83ac31a7b8SGabe Black 		/* Flush any stale mapping out of the TLBs. */
84ac31a7b8SGabe Black 		__asm__ __volatile__(
85ac31a7b8SGabe Black 			"invlpg %0\n\t"
86ac31a7b8SGabe Black 			:
87ac31a7b8SGabe Black 			: "m" (*(uint8_t *)virt)
88ac31a7b8SGabe Black 		);
89ac31a7b8SGabe Black 	}
90ac31a7b8SGabe Black }
91ac31a7b8SGabe Black 
92ac31a7b8SGabe Black /* Identity map the lower 4GB and turn on paging with PAE. */
93ac31a7b8SGabe Black static void x86_phys_enter_paging(void)
94ac31a7b8SGabe Black {
95ac31a7b8SGabe Black 	phys_addr_t page_addr;
96ac31a7b8SGabe Black 	unsigned i;
97ac31a7b8SGabe Black 
98ac31a7b8SGabe Black 	/* Zero out the page tables. */
99ac31a7b8SGabe Black 	memset(pdpt, 0, sizeof(pdpt));
100ac31a7b8SGabe Black 	memset(pdts, 0, sizeof(pdts));
101ac31a7b8SGabe Black 
102ac31a7b8SGabe Black 	/* Set up the PDPT. */
103ac31a7b8SGabe Black 	for (i = 0; i < ARRAY_SIZE(pdts); i++) {
104ac31a7b8SGabe Black 		pdpt[i].p = 1;
105ac31a7b8SGabe Black 		pdpt[i].base = ((uintptr_t)&pdts[i]) >> 12;
106ac31a7b8SGabe Black 	}
107ac31a7b8SGabe Black 
108ac31a7b8SGabe Black 	/* Identity map everything up to 4GB. */
109ac31a7b8SGabe Black 	for (page_addr = 0; page_addr < (1ULL << 32);
110ac31a7b8SGabe Black 			page_addr += LARGE_PAGE_SIZE) {
111ac31a7b8SGabe Black 		/* There's no reason to invalidate the TLB with paging off. */
112ac31a7b8SGabe Black 		x86_phys_map_page(page_addr, page_addr, 0);
113ac31a7b8SGabe Black 	}
114ac31a7b8SGabe Black 
115ac31a7b8SGabe Black 	/* Turn on paging */
116ac31a7b8SGabe Black 	__asm__ __volatile__(
117ac31a7b8SGabe Black 		/* Load the page table address */
118ac31a7b8SGabe Black 		"movl	%0, %%cr3\n\t"
119ac31a7b8SGabe Black 		/* Enable pae */
120ac31a7b8SGabe Black 		"movl	%%cr4, %%eax\n\t"
121ac31a7b8SGabe Black 		"orl	$0x00000020, %%eax\n\t"
122ac31a7b8SGabe Black 		"movl	%%eax, %%cr4\n\t"
123ac31a7b8SGabe Black 		/* Enable paging */
124ac31a7b8SGabe Black 		"movl	%%cr0, %%eax\n\t"
125ac31a7b8SGabe Black 		"orl	$0x80000000, %%eax\n\t"
126ac31a7b8SGabe Black 		"movl	%%eax, %%cr0\n\t"
127ac31a7b8SGabe Black 		:
128ac31a7b8SGabe Black 		: "r" (pdpt)
129ac31a7b8SGabe Black 		: "eax"
130ac31a7b8SGabe Black 	);
131ac31a7b8SGabe Black }
132ac31a7b8SGabe Black 
133ac31a7b8SGabe Black /* Disable paging and PAE mode. */
134ac31a7b8SGabe Black static void x86_phys_exit_paging(void)
135ac31a7b8SGabe Black {
136ac31a7b8SGabe Black 	/* Turn off paging */
137ac31a7b8SGabe Black 	__asm__ __volatile__ (
138ac31a7b8SGabe Black 		/* Disable paging */
139ac31a7b8SGabe Black 		"movl	%%cr0, %%eax\n\t"
140ac31a7b8SGabe Black 		"andl	$0x7fffffff, %%eax\n\t"
141ac31a7b8SGabe Black 		"movl	%%eax, %%cr0\n\t"
142ac31a7b8SGabe Black 		/* Disable pae */
143ac31a7b8SGabe Black 		"movl	%%cr4, %%eax\n\t"
144ac31a7b8SGabe Black 		"andl	$0xffffffdf, %%eax\n\t"
145ac31a7b8SGabe Black 		"movl	%%eax, %%cr4\n\t"
146ac31a7b8SGabe Black 		:
147ac31a7b8SGabe Black 		:
148ac31a7b8SGabe Black 		: "eax"
149ac31a7b8SGabe Black 	);
150ac31a7b8SGabe Black }
151ac31a7b8SGabe Black 
152ac31a7b8SGabe Black /*
153ac31a7b8SGabe Black  * Set physical memory to a particular value when the whole region fits on one
154ac31a7b8SGabe Black  * page.
155ac31a7b8SGabe Black  *
156ac31a7b8SGabe Black  * @param map_addr	The address that starts the physical page.
157ac31a7b8SGabe Black  * @param offset	How far into that page to start setting a value.
158ac31a7b8SGabe Black  * @param c		The value to set memory to.
159ac31a7b8SGabe Black  * @param size		The size in bytes of the area to set.
160ac31a7b8SGabe Black  */
161ac31a7b8SGabe Black static void x86_phys_memset_page(phys_addr_t map_addr, uintptr_t offset, int c,
162ac31a7b8SGabe Black 				 unsigned size)
163ac31a7b8SGabe Black {
164ac31a7b8SGabe Black 	/*
165ac31a7b8SGabe Black 	 * U-Boot should be far away from the beginning of memory, so that's a
166ac31a7b8SGabe Black 	 * good place to map our window on top of.
167ac31a7b8SGabe Black 	 */
168ac31a7b8SGabe Black 	const uintptr_t window = LARGE_PAGE_SIZE;
169ac31a7b8SGabe Black 
170ac31a7b8SGabe Black 	/* Make sure the window is below U-Boot. */
171ac31a7b8SGabe Black 	assert(window + LARGE_PAGE_SIZE <
172ac31a7b8SGabe Black 	       gd->relocaddr - CONFIG_SYS_MALLOC_LEN - CONFIG_SYS_STACK_SIZE);
173ac31a7b8SGabe Black 	/* Map the page into the window and then memset the appropriate part. */
174ac31a7b8SGabe Black 	x86_phys_map_page(window, map_addr, 1);
175ac31a7b8SGabe Black 	memset((void *)(window + offset), c, size);
176ac31a7b8SGabe Black }
177ac31a7b8SGabe Black 
178ac31a7b8SGabe Black /*
179ac31a7b8SGabe Black  * A physical memory anologue to memset with matching parameters and return
180ac31a7b8SGabe Black  * value.
181ac31a7b8SGabe Black  */
182ac31a7b8SGabe Black phys_addr_t arch_phys_memset(phys_addr_t start, int c, phys_size_t size)
183ac31a7b8SGabe Black {
184ac31a7b8SGabe Black 	const phys_addr_t max_addr = (phys_addr_t)~(uintptr_t)0;
185ac31a7b8SGabe Black 	const phys_addr_t orig_start = start;
186ac31a7b8SGabe Black 
187ac31a7b8SGabe Black 	if (!size)
188ac31a7b8SGabe Black 		return orig_start;
189ac31a7b8SGabe Black 
190ac31a7b8SGabe Black 	/* Handle memory below 4GB. */
191ac31a7b8SGabe Black 	if (start <= max_addr) {
192*c79cba37SMasahiro Yamada 		phys_size_t low_size = min(max_addr + 1 - start, size);
193ac31a7b8SGabe Black 		void *start_ptr = (void *)(uintptr_t)start;
194ac31a7b8SGabe Black 
195ac31a7b8SGabe Black 		assert(((phys_addr_t)(uintptr_t)start) == start);
196ac31a7b8SGabe Black 		memset(start_ptr, c, low_size);
197ac31a7b8SGabe Black 		start += low_size;
198ac31a7b8SGabe Black 		size -= low_size;
199ac31a7b8SGabe Black 	}
200ac31a7b8SGabe Black 
201ac31a7b8SGabe Black 	/* Use paging and PAE to handle memory above 4GB up to 64GB. */
202ac31a7b8SGabe Black 	if (size) {
203ac31a7b8SGabe Black 		phys_addr_t map_addr = start & ~(LARGE_PAGE_SIZE - 1);
204ac31a7b8SGabe Black 		phys_addr_t offset = start - map_addr;
205ac31a7b8SGabe Black 
206ac31a7b8SGabe Black 		x86_phys_enter_paging();
207ac31a7b8SGabe Black 
208ac31a7b8SGabe Black 		/* Handle the first partial page. */
209ac31a7b8SGabe Black 		if (offset) {
210ac31a7b8SGabe Black 			phys_addr_t end =
211*c79cba37SMasahiro Yamada 				min(map_addr + LARGE_PAGE_SIZE, start + size);
212ac31a7b8SGabe Black 			phys_size_t cur_size = end - start;
213ac31a7b8SGabe Black 			x86_phys_memset_page(map_addr, offset, c, cur_size);
214ac31a7b8SGabe Black 			size -= cur_size;
215ac31a7b8SGabe Black 			map_addr += LARGE_PAGE_SIZE;
216ac31a7b8SGabe Black 		}
217ac31a7b8SGabe Black 		/* Handle the complete pages. */
218ac31a7b8SGabe Black 		while (size > LARGE_PAGE_SIZE) {
219ac31a7b8SGabe Black 			x86_phys_memset_page(map_addr, 0, c, LARGE_PAGE_SIZE);
220ac31a7b8SGabe Black 			size -= LARGE_PAGE_SIZE;
221ac31a7b8SGabe Black 			map_addr += LARGE_PAGE_SIZE;
222ac31a7b8SGabe Black 		}
223ac31a7b8SGabe Black 		/* Handle the last partial page. */
224ac31a7b8SGabe Black 		if (size)
225ac31a7b8SGabe Black 			x86_phys_memset_page(map_addr, 0, c, size);
226ac31a7b8SGabe Black 
227ac31a7b8SGabe Black 		x86_phys_exit_paging();
228ac31a7b8SGabe Black 	}
229ac31a7b8SGabe Black 	return orig_start;
230ac31a7b8SGabe Black }
231