]>
Commit | Line | Data |
---|---|---|
ac31a7b8 GB |
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> | |
f7ae49fc | 12 | #include <log.h> |
ac31a7b8 | 13 | #include <physmem.h> |
7bddac94 | 14 | #include <asm/cpu.h> |
401d1c4f | 15 | #include <asm/global_data.h> |
ac31a7b8 GB |
16 | #include <linux/compiler.h> |
17 | ||
7282d834 SG |
18 | DECLARE_GLOBAL_DATA_PTR; |
19 | ||
ac31a7b8 GB |
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 | */ | |
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. */ | |
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 | ||
7bddac94 | 118 | cpu_enable_paging_pae((ulong)pdpt); |
ac31a7b8 GB |
119 | } |
120 | ||
121 | /* Disable paging and PAE mode. */ | |
122 | static void x86_phys_exit_paging(void) | |
123 | { | |
7bddac94 | 124 | cpu_disable_paging_pae(); |
ac31a7b8 GB |
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 | */ | |
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 | */ | |
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) { | |
c79cba37 | 167 | phys_size_t low_size = min(max_addr + 1 - start, size); |
ac31a7b8 GB |
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 = | |
c79cba37 | 186 | min(map_addr + LARGE_PAGE_SIZE, start + size); |
ac31a7b8 GB |
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 | } |