| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * Common EFI (Extensible Firmware Interface) support functions | 
|  | 3 | * Based on Extensible Firmware Interface Specification version 1.0 | 
|  | 4 | * | 
|  | 5 | * Copyright (C) 1999 VA Linux Systems | 
|  | 6 | * Copyright (C) 1999 Walt Drummond <drummond@valinux.com> | 
|  | 7 | * Copyright (C) 1999-2002 Hewlett-Packard Co. | 
|  | 8 | *	David Mosberger-Tang <davidm@hpl.hp.com> | 
|  | 9 | *	Stephane Eranian <eranian@hpl.hp.com> | 
|  | 10 | * Copyright (C) 2005-2008 Intel Co. | 
|  | 11 | *	Fenghua Yu <fenghua.yu@intel.com> | 
|  | 12 | *	Bibo Mao <bibo.mao@intel.com> | 
|  | 13 | *	Chandramouli Narayanan <mouli@linux.intel.com> | 
|  | 14 | *	Huang Ying <ying.huang@intel.com> | 
|  | 15 | * | 
|  | 16 | * Copied from efi_32.c to eliminate the duplicated code between EFI | 
|  | 17 | * 32/64 support code. --ying 2007-10-26 | 
|  | 18 | * | 
|  | 19 | * All EFI Runtime Services are not implemented yet as EFI only | 
|  | 20 | * supports physical mode addressing on SoftSDV. This is to be fixed | 
|  | 21 | * in a future version.  --drummond 1999-07-20 | 
|  | 22 | * | 
|  | 23 | * Implemented EFI runtime services and virtual mode calls.  --davidm | 
|  | 24 | * | 
|  | 25 | * Goutham Rao: <goutham.rao@intel.com> | 
|  | 26 | *	Skip non-WB memory and ignore empty memory ranges. | 
|  | 27 | */ | 
|  | 28 |  | 
|  | 29 | #include <linux/kernel.h> | 
|  | 30 | #include <linux/init.h> | 
|  | 31 | #include <linux/efi.h> | 
|  | 32 | #include <linux/bootmem.h> | 
|  | 33 | #include <linux/spinlock.h> | 
|  | 34 | #include <linux/uaccess.h> | 
|  | 35 | #include <linux/time.h> | 
|  | 36 | #include <linux/io.h> | 
|  | 37 | #include <linux/reboot.h> | 
|  | 38 | #include <linux/bcd.h> | 
|  | 39 |  | 
|  | 40 | #include <asm/setup.h> | 
|  | 41 | #include <asm/efi.h> | 
|  | 42 | #include <asm/time.h> | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 43 | #include <asm/cacheflush.h> | 
|  | 44 | #include <asm/tlbflush.h> | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 45 |  | 
|  | 46 | #define EFI_DEBUG	1 | 
|  | 47 | #define PFX 		"EFI: " | 
|  | 48 |  | 
|  | 49 | int efi_enabled; | 
|  | 50 | EXPORT_SYMBOL(efi_enabled); | 
|  | 51 |  | 
|  | 52 | struct efi efi; | 
|  | 53 | EXPORT_SYMBOL(efi); | 
|  | 54 |  | 
|  | 55 | struct efi_memory_map memmap; | 
|  | 56 |  | 
| Harvey Harrison | ecaea42 | 2008-02-13 13:26:13 -0800 | [diff] [blame] | 57 | static struct efi efi_phys __initdata; | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 58 | static efi_system_table_t efi_systab __initdata; | 
|  | 59 |  | 
| Huang, Ying | 8b2cb7a | 2008-01-30 13:32:11 +0100 | [diff] [blame] | 60 | static int __init setup_noefi(char *arg) | 
|  | 61 | { | 
|  | 62 | efi_enabled = 0; | 
|  | 63 | return 0; | 
|  | 64 | } | 
|  | 65 | early_param("noefi", setup_noefi); | 
|  | 66 |  | 
| Paul Jackson | 200001e | 2008-06-25 05:44:46 -0700 | [diff] [blame] | 67 | int add_efi_memmap; | 
|  | 68 | EXPORT_SYMBOL(add_efi_memmap); | 
|  | 69 |  | 
|  | 70 | static int __init setup_add_efi_memmap(char *arg) | 
|  | 71 | { | 
|  | 72 | add_efi_memmap = 1; | 
|  | 73 | return 0; | 
|  | 74 | } | 
|  | 75 | early_param("add_efi_memmap", setup_add_efi_memmap); | 
|  | 76 |  | 
|  | 77 |  | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 78 | static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc) | 
|  | 79 | { | 
|  | 80 | return efi_call_virt2(get_time, tm, tc); | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | static efi_status_t virt_efi_set_time(efi_time_t *tm) | 
|  | 84 | { | 
|  | 85 | return efi_call_virt1(set_time, tm); | 
|  | 86 | } | 
|  | 87 |  | 
|  | 88 | static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled, | 
|  | 89 | efi_bool_t *pending, | 
|  | 90 | efi_time_t *tm) | 
|  | 91 | { | 
|  | 92 | return efi_call_virt3(get_wakeup_time, | 
|  | 93 | enabled, pending, tm); | 
|  | 94 | } | 
|  | 95 |  | 
|  | 96 | static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm) | 
|  | 97 | { | 
|  | 98 | return efi_call_virt2(set_wakeup_time, | 
|  | 99 | enabled, tm); | 
|  | 100 | } | 
|  | 101 |  | 
|  | 102 | static efi_status_t virt_efi_get_variable(efi_char16_t *name, | 
|  | 103 | efi_guid_t *vendor, | 
|  | 104 | u32 *attr, | 
|  | 105 | unsigned long *data_size, | 
|  | 106 | void *data) | 
|  | 107 | { | 
|  | 108 | return efi_call_virt5(get_variable, | 
|  | 109 | name, vendor, attr, | 
|  | 110 | data_size, data); | 
|  | 111 | } | 
|  | 112 |  | 
|  | 113 | static efi_status_t virt_efi_get_next_variable(unsigned long *name_size, | 
|  | 114 | efi_char16_t *name, | 
|  | 115 | efi_guid_t *vendor) | 
|  | 116 | { | 
|  | 117 | return efi_call_virt3(get_next_variable, | 
|  | 118 | name_size, name, vendor); | 
|  | 119 | } | 
|  | 120 |  | 
|  | 121 | static efi_status_t virt_efi_set_variable(efi_char16_t *name, | 
|  | 122 | efi_guid_t *vendor, | 
|  | 123 | unsigned long attr, | 
|  | 124 | unsigned long data_size, | 
|  | 125 | void *data) | 
|  | 126 | { | 
|  | 127 | return efi_call_virt5(set_variable, | 
|  | 128 | name, vendor, attr, | 
|  | 129 | data_size, data); | 
|  | 130 | } | 
|  | 131 |  | 
|  | 132 | static efi_status_t virt_efi_get_next_high_mono_count(u32 *count) | 
|  | 133 | { | 
|  | 134 | return efi_call_virt1(get_next_high_mono_count, count); | 
|  | 135 | } | 
|  | 136 |  | 
|  | 137 | static void virt_efi_reset_system(int reset_type, | 
|  | 138 | efi_status_t status, | 
|  | 139 | unsigned long data_size, | 
|  | 140 | efi_char16_t *data) | 
|  | 141 | { | 
|  | 142 | efi_call_virt4(reset_system, reset_type, status, | 
|  | 143 | data_size, data); | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | static efi_status_t virt_efi_set_virtual_address_map( | 
|  | 147 | unsigned long memory_map_size, | 
|  | 148 | unsigned long descriptor_size, | 
|  | 149 | u32 descriptor_version, | 
|  | 150 | efi_memory_desc_t *virtual_map) | 
|  | 151 | { | 
|  | 152 | return efi_call_virt4(set_virtual_address_map, | 
|  | 153 | memory_map_size, descriptor_size, | 
|  | 154 | descriptor_version, virtual_map); | 
|  | 155 | } | 
|  | 156 |  | 
|  | 157 | static efi_status_t __init phys_efi_set_virtual_address_map( | 
|  | 158 | unsigned long memory_map_size, | 
|  | 159 | unsigned long descriptor_size, | 
|  | 160 | u32 descriptor_version, | 
|  | 161 | efi_memory_desc_t *virtual_map) | 
|  | 162 | { | 
|  | 163 | efi_status_t status; | 
|  | 164 |  | 
|  | 165 | efi_call_phys_prelog(); | 
|  | 166 | status = efi_call_phys4(efi_phys.set_virtual_address_map, | 
|  | 167 | memory_map_size, descriptor_size, | 
|  | 168 | descriptor_version, virtual_map); | 
|  | 169 | efi_call_phys_epilog(); | 
|  | 170 | return status; | 
|  | 171 | } | 
|  | 172 |  | 
|  | 173 | static efi_status_t __init phys_efi_get_time(efi_time_t *tm, | 
|  | 174 | efi_time_cap_t *tc) | 
|  | 175 | { | 
|  | 176 | efi_status_t status; | 
|  | 177 |  | 
|  | 178 | efi_call_phys_prelog(); | 
|  | 179 | status = efi_call_phys2(efi_phys.get_time, tm, tc); | 
|  | 180 | efi_call_phys_epilog(); | 
|  | 181 | return status; | 
|  | 182 | } | 
|  | 183 |  | 
|  | 184 | int efi_set_rtc_mmss(unsigned long nowtime) | 
|  | 185 | { | 
|  | 186 | int real_seconds, real_minutes; | 
|  | 187 | efi_status_t 	status; | 
|  | 188 | efi_time_t 	eft; | 
|  | 189 | efi_time_cap_t 	cap; | 
|  | 190 |  | 
|  | 191 | status = efi.get_time(&eft, &cap); | 
|  | 192 | if (status != EFI_SUCCESS) { | 
|  | 193 | printk(KERN_ERR "Oops: efitime: can't read time!\n"); | 
|  | 194 | return -1; | 
|  | 195 | } | 
|  | 196 |  | 
|  | 197 | real_seconds = nowtime % 60; | 
|  | 198 | real_minutes = nowtime / 60; | 
|  | 199 | if (((abs(real_minutes - eft.minute) + 15)/30) & 1) | 
|  | 200 | real_minutes += 30; | 
|  | 201 | real_minutes %= 60; | 
|  | 202 | eft.minute = real_minutes; | 
|  | 203 | eft.second = real_seconds; | 
|  | 204 |  | 
|  | 205 | status = efi.set_time(&eft); | 
|  | 206 | if (status != EFI_SUCCESS) { | 
|  | 207 | printk(KERN_ERR "Oops: efitime: can't write time!\n"); | 
|  | 208 | return -1; | 
|  | 209 | } | 
|  | 210 | return 0; | 
|  | 211 | } | 
|  | 212 |  | 
|  | 213 | unsigned long efi_get_time(void) | 
|  | 214 | { | 
|  | 215 | efi_status_t status; | 
|  | 216 | efi_time_t eft; | 
|  | 217 | efi_time_cap_t cap; | 
|  | 218 |  | 
|  | 219 | status = efi.get_time(&eft, &cap); | 
|  | 220 | if (status != EFI_SUCCESS) | 
|  | 221 | printk(KERN_ERR "Oops: efitime: can't read time!\n"); | 
|  | 222 |  | 
|  | 223 | return mktime(eft.year, eft.month, eft.day, eft.hour, | 
|  | 224 | eft.minute, eft.second); | 
|  | 225 | } | 
|  | 226 |  | 
| Paul Jackson | 69c9189 | 2008-05-14 08:15:58 -0700 | [diff] [blame] | 227 | /* | 
|  | 228 | * Tell the kernel about the EFI memory map.  This might include | 
|  | 229 | * more than the max 128 entries that can fit in the e820 legacy | 
|  | 230 | * (zeropage) memory map. | 
|  | 231 | */ | 
|  | 232 |  | 
| Paul Jackson | 200001e | 2008-06-25 05:44:46 -0700 | [diff] [blame] | 233 | static void __init do_add_efi_memmap(void) | 
| Paul Jackson | 69c9189 | 2008-05-14 08:15:58 -0700 | [diff] [blame] | 234 | { | 
|  | 235 | void *p; | 
|  | 236 |  | 
|  | 237 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | 
|  | 238 | efi_memory_desc_t *md = p; | 
|  | 239 | unsigned long long start = md->phys_addr; | 
|  | 240 | unsigned long long size = md->num_pages << EFI_PAGE_SHIFT; | 
|  | 241 | int e820_type; | 
|  | 242 |  | 
|  | 243 | if (md->attribute & EFI_MEMORY_WB) | 
|  | 244 | e820_type = E820_RAM; | 
|  | 245 | else | 
|  | 246 | e820_type = E820_RESERVED; | 
| Yinghai Lu | d0be6bd | 2008-06-15 18:58:51 -0700 | [diff] [blame] | 247 | e820_add_region(start, size, e820_type); | 
| Paul Jackson | 69c9189 | 2008-05-14 08:15:58 -0700 | [diff] [blame] | 248 | } | 
|  | 249 | sanitize_e820_map(e820.map, ARRAY_SIZE(e820.map), &e820.nr_map); | 
|  | 250 | } | 
|  | 251 |  | 
| Huang, Ying | ecacf09 | 2008-06-02 14:26:21 +0800 | [diff] [blame] | 252 | void __init efi_reserve_early(void) | 
|  | 253 | { | 
|  | 254 | unsigned long pmap; | 
|  | 255 |  | 
| Paul Jackson | 05486fa | 2008-06-22 07:22:02 -0700 | [diff] [blame] | 256 | #ifdef CONFIG_X86_32 | 
| Huang, Ying | ecacf09 | 2008-06-02 14:26:21 +0800 | [diff] [blame] | 257 | pmap = boot_params.efi_info.efi_memmap; | 
| Paul Jackson | 05486fa | 2008-06-22 07:22:02 -0700 | [diff] [blame] | 258 | #else | 
|  | 259 | pmap = (boot_params.efi_info.efi_memmap | | 
|  | 260 | ((__u64)boot_params.efi_info.efi_memmap_hi<<32)); | 
| Huang, Ying | ecacf09 | 2008-06-02 14:26:21 +0800 | [diff] [blame] | 261 | #endif | 
|  | 262 | memmap.phys_map = (void *)pmap; | 
|  | 263 | memmap.nr_map = boot_params.efi_info.efi_memmap_size / | 
|  | 264 | boot_params.efi_info.efi_memdesc_size; | 
|  | 265 | memmap.desc_version = boot_params.efi_info.efi_memdesc_version; | 
|  | 266 | memmap.desc_size = boot_params.efi_info.efi_memdesc_size; | 
|  | 267 | reserve_early(pmap, pmap + memmap.nr_map * memmap.desc_size, | 
|  | 268 | "EFI memmap"); | 
|  | 269 | } | 
|  | 270 |  | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 271 | #if EFI_DEBUG | 
|  | 272 | static void __init print_efi_memmap(void) | 
|  | 273 | { | 
|  | 274 | efi_memory_desc_t *md; | 
|  | 275 | void *p; | 
|  | 276 | int i; | 
|  | 277 |  | 
|  | 278 | for (p = memmap.map, i = 0; | 
|  | 279 | p < memmap.map_end; | 
|  | 280 | p += memmap.desc_size, i++) { | 
|  | 281 | md = p; | 
|  | 282 | printk(KERN_INFO PFX "mem%02u: type=%u, attr=0x%llx, " | 
|  | 283 | "range=[0x%016llx-0x%016llx) (%lluMB)\n", | 
|  | 284 | i, md->type, md->attribute, md->phys_addr, | 
|  | 285 | md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT), | 
|  | 286 | (md->num_pages >> (20 - EFI_PAGE_SHIFT))); | 
|  | 287 | } | 
|  | 288 | } | 
|  | 289 | #endif  /*  EFI_DEBUG  */ | 
|  | 290 |  | 
|  | 291 | void __init efi_init(void) | 
|  | 292 | { | 
|  | 293 | efi_config_table_t *config_tables; | 
|  | 294 | efi_runtime_services_t *runtime; | 
|  | 295 | efi_char16_t *c16; | 
|  | 296 | char vendor[100] = "unknown"; | 
|  | 297 | int i = 0; | 
|  | 298 | void *tmp; | 
|  | 299 |  | 
| Paul Jackson | 05486fa | 2008-06-22 07:22:02 -0700 | [diff] [blame] | 300 | #ifdef CONFIG_X86_32 | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 301 | efi_phys.systab = (efi_system_table_t *)boot_params.efi_info.efi_systab; | 
| Paul Jackson | 05486fa | 2008-06-22 07:22:02 -0700 | [diff] [blame] | 302 | #else | 
|  | 303 | efi_phys.systab = (efi_system_table_t *) | 
|  | 304 | (boot_params.efi_info.efi_systab | | 
|  | 305 | ((__u64)boot_params.efi_info.efi_systab_hi<<32)); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 306 | #endif | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 307 |  | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 308 | efi.systab = early_ioremap((unsigned long)efi_phys.systab, | 
|  | 309 | sizeof(efi_system_table_t)); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 310 | if (efi.systab == NULL) | 
|  | 311 | printk(KERN_ERR "Couldn't map the EFI system table!\n"); | 
|  | 312 | memcpy(&efi_systab, efi.systab, sizeof(efi_system_table_t)); | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 313 | early_iounmap(efi.systab, sizeof(efi_system_table_t)); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 314 | efi.systab = &efi_systab; | 
|  | 315 |  | 
|  | 316 | /* | 
|  | 317 | * Verify the EFI Table | 
|  | 318 | */ | 
|  | 319 | if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) | 
|  | 320 | printk(KERN_ERR "EFI system table signature incorrect!\n"); | 
|  | 321 | if ((efi.systab->hdr.revision >> 16) == 0) | 
|  | 322 | printk(KERN_ERR "Warning: EFI system table version " | 
|  | 323 | "%d.%02d, expected 1.00 or greater!\n", | 
|  | 324 | efi.systab->hdr.revision >> 16, | 
|  | 325 | efi.systab->hdr.revision & 0xffff); | 
|  | 326 |  | 
|  | 327 | /* | 
|  | 328 | * Show what we know for posterity | 
|  | 329 | */ | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 330 | c16 = tmp = early_ioremap(efi.systab->fw_vendor, 2); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 331 | if (c16) { | 
|  | 332 | for (i = 0; i < sizeof(vendor) && *c16; ++i) | 
|  | 333 | vendor[i] = *c16++; | 
|  | 334 | vendor[i] = '\0'; | 
|  | 335 | } else | 
|  | 336 | printk(KERN_ERR PFX "Could not map the firmware vendor!\n"); | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 337 | early_iounmap(tmp, 2); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 338 |  | 
|  | 339 | printk(KERN_INFO "EFI v%u.%.02u by %s \n", | 
|  | 340 | efi.systab->hdr.revision >> 16, | 
|  | 341 | efi.systab->hdr.revision & 0xffff, vendor); | 
|  | 342 |  | 
|  | 343 | /* | 
|  | 344 | * Let's see what config tables the firmware passed to us. | 
|  | 345 | */ | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 346 | config_tables = early_ioremap( | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 347 | efi.systab->tables, | 
|  | 348 | efi.systab->nr_tables * sizeof(efi_config_table_t)); | 
|  | 349 | if (config_tables == NULL) | 
|  | 350 | printk(KERN_ERR "Could not map EFI Configuration Table!\n"); | 
|  | 351 |  | 
|  | 352 | printk(KERN_INFO); | 
|  | 353 | for (i = 0; i < efi.systab->nr_tables; i++) { | 
|  | 354 | if (!efi_guidcmp(config_tables[i].guid, MPS_TABLE_GUID)) { | 
|  | 355 | efi.mps = config_tables[i].table; | 
|  | 356 | printk(" MPS=0x%lx ", config_tables[i].table); | 
|  | 357 | } else if (!efi_guidcmp(config_tables[i].guid, | 
|  | 358 | ACPI_20_TABLE_GUID)) { | 
|  | 359 | efi.acpi20 = config_tables[i].table; | 
|  | 360 | printk(" ACPI 2.0=0x%lx ", config_tables[i].table); | 
|  | 361 | } else if (!efi_guidcmp(config_tables[i].guid, | 
|  | 362 | ACPI_TABLE_GUID)) { | 
|  | 363 | efi.acpi = config_tables[i].table; | 
|  | 364 | printk(" ACPI=0x%lx ", config_tables[i].table); | 
|  | 365 | } else if (!efi_guidcmp(config_tables[i].guid, | 
|  | 366 | SMBIOS_TABLE_GUID)) { | 
|  | 367 | efi.smbios = config_tables[i].table; | 
|  | 368 | printk(" SMBIOS=0x%lx ", config_tables[i].table); | 
|  | 369 | } else if (!efi_guidcmp(config_tables[i].guid, | 
| Russ Anderson | a50f70b | 2008-10-03 11:58:54 -0500 | [diff] [blame] | 370 | UV_SYSTEM_TABLE_GUID)) { | 
|  | 371 | efi.uv_systab = config_tables[i].table; | 
|  | 372 | printk(" UVsystab=0x%lx ", config_tables[i].table); | 
|  | 373 | } else if (!efi_guidcmp(config_tables[i].guid, | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 374 | HCDP_TABLE_GUID)) { | 
|  | 375 | efi.hcdp = config_tables[i].table; | 
|  | 376 | printk(" HCDP=0x%lx ", config_tables[i].table); | 
|  | 377 | } else if (!efi_guidcmp(config_tables[i].guid, | 
|  | 378 | UGA_IO_PROTOCOL_GUID)) { | 
|  | 379 | efi.uga = config_tables[i].table; | 
|  | 380 | printk(" UGA=0x%lx ", config_tables[i].table); | 
|  | 381 | } | 
|  | 382 | } | 
|  | 383 | printk("\n"); | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 384 | early_iounmap(config_tables, | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 385 | efi.systab->nr_tables * sizeof(efi_config_table_t)); | 
|  | 386 |  | 
|  | 387 | /* | 
|  | 388 | * Check out the runtime services table. We need to map | 
|  | 389 | * the runtime services table so that we can grab the physical | 
|  | 390 | * address of several of the EFI runtime functions, needed to | 
|  | 391 | * set the firmware into virtual mode. | 
|  | 392 | */ | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 393 | runtime = early_ioremap((unsigned long)efi.systab->runtime, | 
|  | 394 | sizeof(efi_runtime_services_t)); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 395 | if (runtime != NULL) { | 
|  | 396 | /* | 
|  | 397 | * We will only need *early* access to the following | 
|  | 398 | * two EFI runtime services before set_virtual_address_map | 
|  | 399 | * is invoked. | 
|  | 400 | */ | 
|  | 401 | efi_phys.get_time = (efi_get_time_t *)runtime->get_time; | 
|  | 402 | efi_phys.set_virtual_address_map = | 
|  | 403 | (efi_set_virtual_address_map_t *) | 
|  | 404 | runtime->set_virtual_address_map; | 
|  | 405 | /* | 
|  | 406 | * Make efi_get_time can be called before entering | 
|  | 407 | * virtual mode. | 
|  | 408 | */ | 
|  | 409 | efi.get_time = phys_efi_get_time; | 
|  | 410 | } else | 
|  | 411 | printk(KERN_ERR "Could not map the EFI runtime service " | 
|  | 412 | "table!\n"); | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 413 | early_iounmap(runtime, sizeof(efi_runtime_services_t)); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 414 |  | 
|  | 415 | /* Map the EFI memory map */ | 
| Huang, Ying | beacfaa | 2008-01-30 13:33:44 +0100 | [diff] [blame] | 416 | memmap.map = early_ioremap((unsigned long)memmap.phys_map, | 
|  | 417 | memmap.nr_map * memmap.desc_size); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 418 | if (memmap.map == NULL) | 
|  | 419 | printk(KERN_ERR "Could not map the EFI memory map!\n"); | 
|  | 420 | memmap.map_end = memmap.map + (memmap.nr_map * memmap.desc_size); | 
| Russ Anderson | 175e438 | 2008-10-02 17:32:06 -0500 | [diff] [blame] | 421 |  | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 422 | if (memmap.desc_size != sizeof(efi_memory_desc_t)) | 
| Russ Anderson | 175e438 | 2008-10-02 17:32:06 -0500 | [diff] [blame] | 423 | printk(KERN_WARNING | 
|  | 424 | "Kernel-defined memdesc doesn't match the one from EFI!\n"); | 
|  | 425 |  | 
| Paul Jackson | 200001e | 2008-06-25 05:44:46 -0700 | [diff] [blame] | 426 | if (add_efi_memmap) | 
|  | 427 | do_add_efi_memmap(); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 428 |  | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 429 | /* Setup for EFI runtime service */ | 
|  | 430 | reboot_type = BOOT_EFI; | 
|  | 431 |  | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 432 | #if EFI_DEBUG | 
|  | 433 | print_efi_memmap(); | 
|  | 434 | #endif | 
|  | 435 | } | 
|  | 436 |  | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 437 | static void __init runtime_code_page_mkexec(void) | 
|  | 438 | { | 
|  | 439 | efi_memory_desc_t *md; | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 440 | void *p; | 
| Huang, Ying | 4a3575f | 2008-02-25 15:18:37 +0800 | [diff] [blame] | 441 | u64 addr, npages; | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 442 |  | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 443 | /* Make EFI runtime service code area executable */ | 
|  | 444 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | 
|  | 445 | md = p; | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 446 |  | 
|  | 447 | if (md->type != EFI_RUNTIME_SERVICES_CODE) | 
|  | 448 | continue; | 
|  | 449 |  | 
| Huang, Ying | 4a3575f | 2008-02-25 15:18:37 +0800 | [diff] [blame] | 450 | addr = md->virt_addr; | 
|  | 451 | npages = md->num_pages; | 
|  | 452 | memrange_efi_to_native(&addr, &npages); | 
|  | 453 | set_memory_x(addr, npages); | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 454 | } | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 455 | } | 
| Huang, Ying | a2172e2 | 2008-01-30 13:33:55 +0100 | [diff] [blame] | 456 |  | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 457 | /* | 
|  | 458 | * This function will switch the EFI runtime services to virtual mode. | 
|  | 459 | * Essentially, look through the EFI memmap and map every region that | 
|  | 460 | * has the runtime attribute bit set in its memory descriptor and update | 
|  | 461 | * that memory descriptor with the virtual address obtained from ioremap(). | 
|  | 462 | * This enables the runtime services to be called without having to | 
|  | 463 | * thunk back into physical mode for every invocation. | 
|  | 464 | */ | 
|  | 465 | void __init efi_enter_virtual_mode(void) | 
|  | 466 | { | 
|  | 467 | efi_memory_desc_t *md; | 
|  | 468 | efi_status_t status; | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 469 | unsigned long size; | 
| Huang, Ying | 4a3575f | 2008-02-25 15:18:37 +0800 | [diff] [blame] | 470 | u64 end, systab, addr, npages; | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 471 | void *p, *va; | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 472 |  | 
|  | 473 | efi.systab = NULL; | 
|  | 474 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | 
|  | 475 | md = p; | 
|  | 476 | if (!(md->attribute & EFI_MEMORY_RUNTIME)) | 
|  | 477 | continue; | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 478 |  | 
|  | 479 | size = md->num_pages << EFI_PAGE_SHIFT; | 
|  | 480 | end = md->phys_addr + size; | 
|  | 481 |  | 
| Yinghai Lu | f361a45 | 2008-07-10 20:38:26 -0700 | [diff] [blame] | 482 | if (PFN_UP(end) <= max_low_pfn_mapped) | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 483 | va = __va(md->phys_addr); | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 484 | else | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 485 | va = efi_ioremap(md->phys_addr, size); | 
|  | 486 |  | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 487 | md->virt_addr = (u64) (unsigned long) va; | 
|  | 488 |  | 
|  | 489 | if (!va) { | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 490 | printk(KERN_ERR PFX "ioremap of 0x%llX failed!\n", | 
|  | 491 | (unsigned long long)md->phys_addr); | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 492 | continue; | 
|  | 493 | } | 
|  | 494 |  | 
| Huang, Ying | 4a3575f | 2008-02-25 15:18:37 +0800 | [diff] [blame] | 495 | if (!(md->attribute & EFI_MEMORY_WB)) { | 
|  | 496 | addr = md->virt_addr; | 
|  | 497 | npages = md->num_pages; | 
|  | 498 | memrange_efi_to_native(&addr, &npages); | 
|  | 499 | set_memory_uc(addr, npages); | 
|  | 500 | } | 
| Thomas Gleixner | e85f205 | 2008-02-12 19:46:48 +0100 | [diff] [blame] | 501 |  | 
| Huang, Ying | 1c083eb | 2008-02-04 16:48:06 +0100 | [diff] [blame] | 502 | systab = (u64) (unsigned long) efi_phys.systab; | 
|  | 503 | if (md->phys_addr <= systab && systab < end) { | 
|  | 504 | systab += md->virt_addr - md->phys_addr; | 
|  | 505 | efi.systab = (efi_system_table_t *) (unsigned long) systab; | 
|  | 506 | } | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 507 | } | 
|  | 508 |  | 
|  | 509 | BUG_ON(!efi.systab); | 
|  | 510 |  | 
|  | 511 | status = phys_efi_set_virtual_address_map( | 
|  | 512 | memmap.desc_size * memmap.nr_map, | 
|  | 513 | memmap.desc_size, | 
|  | 514 | memmap.desc_version, | 
|  | 515 | memmap.phys_map); | 
|  | 516 |  | 
|  | 517 | if (status != EFI_SUCCESS) { | 
|  | 518 | printk(KERN_ALERT "Unable to switch EFI into virtual mode " | 
|  | 519 | "(status=%lx)!\n", status); | 
|  | 520 | panic("EFI call to SetVirtualAddressMap() failed!"); | 
|  | 521 | } | 
|  | 522 |  | 
|  | 523 | /* | 
|  | 524 | * Now that EFI is in virtual mode, update the function | 
|  | 525 | * pointers in the runtime service table to the new virtual addresses. | 
|  | 526 | * | 
|  | 527 | * Call EFI services through wrapper functions. | 
|  | 528 | */ | 
|  | 529 | efi.get_time = virt_efi_get_time; | 
|  | 530 | efi.set_time = virt_efi_set_time; | 
|  | 531 | efi.get_wakeup_time = virt_efi_get_wakeup_time; | 
|  | 532 | efi.set_wakeup_time = virt_efi_set_wakeup_time; | 
|  | 533 | efi.get_variable = virt_efi_get_variable; | 
|  | 534 | efi.get_next_variable = virt_efi_get_next_variable; | 
|  | 535 | efi.set_variable = virt_efi_set_variable; | 
|  | 536 | efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count; | 
|  | 537 | efi.reset_system = virt_efi_reset_system; | 
|  | 538 | efi.set_virtual_address_map = virt_efi_set_virtual_address_map; | 
| Huang, Ying | 4de0d4a | 2008-02-13 17:22:41 +0800 | [diff] [blame] | 539 | if (__supported_pte_mask & _PAGE_NX) | 
|  | 540 | runtime_code_page_mkexec(); | 
| Huang, Ying | a382806 | 2008-01-30 13:34:10 +0100 | [diff] [blame] | 541 | early_iounmap(memmap.map, memmap.nr_map * memmap.desc_size); | 
|  | 542 | memmap.map = NULL; | 
| Huang, Ying | 5b83683 | 2008-01-30 13:31:19 +0100 | [diff] [blame] | 543 | } | 
|  | 544 |  | 
|  | 545 | /* | 
|  | 546 | * Convenience functions to obtain memory types and attributes | 
|  | 547 | */ | 
|  | 548 | u32 efi_mem_type(unsigned long phys_addr) | 
|  | 549 | { | 
|  | 550 | efi_memory_desc_t *md; | 
|  | 551 | void *p; | 
|  | 552 |  | 
|  | 553 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | 
|  | 554 | md = p; | 
|  | 555 | if ((md->phys_addr <= phys_addr) && | 
|  | 556 | (phys_addr < (md->phys_addr + | 
|  | 557 | (md->num_pages << EFI_PAGE_SHIFT)))) | 
|  | 558 | return md->type; | 
|  | 559 | } | 
|  | 560 | return 0; | 
|  | 561 | } | 
|  | 562 |  | 
|  | 563 | u64 efi_mem_attributes(unsigned long phys_addr) | 
|  | 564 | { | 
|  | 565 | efi_memory_desc_t *md; | 
|  | 566 | void *p; | 
|  | 567 |  | 
|  | 568 | for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { | 
|  | 569 | md = p; | 
|  | 570 | if ((md->phys_addr <= phys_addr) && | 
|  | 571 | (phys_addr < (md->phys_addr + | 
|  | 572 | (md->num_pages << EFI_PAGE_SHIFT)))) | 
|  | 573 | return md->attribute; | 
|  | 574 | } | 
|  | 575 | return 0; | 
|  | 576 | } |