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 <physmem.h> 13 #include <linux/compiler.h> 14 15 DECLARE_GLOBAL_DATA_PTR; 16 17 /* Large pages are 2MB. */ 18 #define LARGE_PAGE_SIZE ((1 << 20) * 2) 19 20 /* 21 * Paging data structures. 22 */ 23 24 struct 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 35 typedef struct pdpe pdpt_t[512]; 36 37 struct 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 53 typedef struct pde pdt_t[512]; 54 55 static pdpt_t pdpt __aligned(4096); 56 static 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 */ 66 static 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. */ 93 static 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. */ 134 static 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 */ 161 static 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 */ 182 phys_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 } 231