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