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