Commit | Line | Data |
---|---|---|
a9499fa7 TG |
1 | /* |
2 | * efi.c - EFI subsystem | |
3 | * | |
4 | * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com> | |
5 | * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com> | |
6 | * Copyright (C) 2013 Tom Gundersen <teg@jklm.no> | |
7 | * | |
8 | * This code registers /sys/firmware/efi{,/efivars} when EFI is supported, | |
9 | * allowing the efivarfs to be mounted or the efivars module to be loaded. | |
10 | * The existance of /sys/firmware/efi may also be used by userspace to | |
11 | * determine that the system supports EFI. | |
12 | * | |
13 | * This file is released under the GPLv2. | |
14 | */ | |
15 | ||
272686bf LL |
16 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
17 | ||
a9499fa7 TG |
18 | #include <linux/kobject.h> |
19 | #include <linux/module.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/device.h> | |
22 | #include <linux/efi.h> | |
272686bf LL |
23 | #include <linux/io.h> |
24 | ||
25 | struct efi __read_mostly efi = { | |
26 | .mps = EFI_INVALID_TABLE_ADDR, | |
27 | .acpi = EFI_INVALID_TABLE_ADDR, | |
28 | .acpi20 = EFI_INVALID_TABLE_ADDR, | |
29 | .smbios = EFI_INVALID_TABLE_ADDR, | |
30 | .sal_systab = EFI_INVALID_TABLE_ADDR, | |
31 | .boot_info = EFI_INVALID_TABLE_ADDR, | |
32 | .hcdp = EFI_INVALID_TABLE_ADDR, | |
33 | .uga = EFI_INVALID_TABLE_ADDR, | |
34 | .uv_systab = EFI_INVALID_TABLE_ADDR, | |
35 | }; | |
36 | EXPORT_SYMBOL(efi); | |
a9499fa7 TG |
37 | |
38 | static struct kobject *efi_kobj; | |
39 | static struct kobject *efivars_kobj; | |
40 | ||
41 | /* | |
42 | * Let's not leave out systab information that snuck into | |
43 | * the efivars driver | |
44 | */ | |
45 | static ssize_t systab_show(struct kobject *kobj, | |
46 | struct kobj_attribute *attr, char *buf) | |
47 | { | |
48 | char *str = buf; | |
49 | ||
50 | if (!kobj || !buf) | |
51 | return -EINVAL; | |
52 | ||
53 | if (efi.mps != EFI_INVALID_TABLE_ADDR) | |
54 | str += sprintf(str, "MPS=0x%lx\n", efi.mps); | |
55 | if (efi.acpi20 != EFI_INVALID_TABLE_ADDR) | |
56 | str += sprintf(str, "ACPI20=0x%lx\n", efi.acpi20); | |
57 | if (efi.acpi != EFI_INVALID_TABLE_ADDR) | |
58 | str += sprintf(str, "ACPI=0x%lx\n", efi.acpi); | |
59 | if (efi.smbios != EFI_INVALID_TABLE_ADDR) | |
60 | str += sprintf(str, "SMBIOS=0x%lx\n", efi.smbios); | |
61 | if (efi.hcdp != EFI_INVALID_TABLE_ADDR) | |
62 | str += sprintf(str, "HCDP=0x%lx\n", efi.hcdp); | |
63 | if (efi.boot_info != EFI_INVALID_TABLE_ADDR) | |
64 | str += sprintf(str, "BOOTINFO=0x%lx\n", efi.boot_info); | |
65 | if (efi.uga != EFI_INVALID_TABLE_ADDR) | |
66 | str += sprintf(str, "UGA=0x%lx\n", efi.uga); | |
67 | ||
68 | return str - buf; | |
69 | } | |
70 | ||
71 | static struct kobj_attribute efi_attr_systab = | |
72 | __ATTR(systab, 0400, systab_show, NULL); | |
73 | ||
74 | static struct attribute *efi_subsys_attrs[] = { | |
75 | &efi_attr_systab.attr, | |
76 | NULL, /* maybe more in the future? */ | |
77 | }; | |
78 | ||
79 | static struct attribute_group efi_subsys_attr_group = { | |
80 | .attrs = efi_subsys_attrs, | |
81 | }; | |
82 | ||
83 | static struct efivars generic_efivars; | |
84 | static struct efivar_operations generic_ops; | |
85 | ||
86 | static int generic_ops_register(void) | |
87 | { | |
88 | generic_ops.get_variable = efi.get_variable; | |
89 | generic_ops.set_variable = efi.set_variable; | |
90 | generic_ops.get_next_variable = efi.get_next_variable; | |
a614e192 | 91 | generic_ops.query_variable_store = efi_query_variable_store; |
a9499fa7 TG |
92 | |
93 | return efivars_register(&generic_efivars, &generic_ops, efi_kobj); | |
94 | } | |
95 | ||
96 | static void generic_ops_unregister(void) | |
97 | { | |
98 | efivars_unregister(&generic_efivars); | |
99 | } | |
100 | ||
101 | /* | |
102 | * We register the efi subsystem with the firmware subsystem and the | |
103 | * efivars subsystem with the efi subsystem, if the system was booted with | |
104 | * EFI. | |
105 | */ | |
106 | static int __init efisubsys_init(void) | |
107 | { | |
108 | int error; | |
109 | ||
110 | if (!efi_enabled(EFI_BOOT)) | |
111 | return 0; | |
112 | ||
113 | /* We register the efi directory at /sys/firmware/efi */ | |
114 | efi_kobj = kobject_create_and_add("efi", firmware_kobj); | |
115 | if (!efi_kobj) { | |
116 | pr_err("efi: Firmware registration failed.\n"); | |
117 | return -ENOMEM; | |
118 | } | |
119 | ||
120 | error = generic_ops_register(); | |
121 | if (error) | |
122 | goto err_put; | |
123 | ||
124 | error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); | |
125 | if (error) { | |
126 | pr_err("efi: Sysfs attribute export failed with error %d.\n", | |
127 | error); | |
128 | goto err_unregister; | |
129 | } | |
130 | ||
131 | /* and the standard mountpoint for efivarfs */ | |
132 | efivars_kobj = kobject_create_and_add("efivars", efi_kobj); | |
133 | if (!efivars_kobj) { | |
134 | pr_err("efivars: Subsystem registration failed.\n"); | |
135 | error = -ENOMEM; | |
136 | goto err_remove_group; | |
137 | } | |
138 | ||
139 | return 0; | |
140 | ||
141 | err_remove_group: | |
142 | sysfs_remove_group(efi_kobj, &efi_subsys_attr_group); | |
143 | err_unregister: | |
144 | generic_ops_unregister(); | |
145 | err_put: | |
146 | kobject_put(efi_kobj); | |
147 | return error; | |
148 | } | |
149 | ||
150 | subsys_initcall(efisubsys_init); | |
272686bf LL |
151 | |
152 | ||
258f6fd7 LL |
153 | /* |
154 | * We can't ioremap data in EFI boot services RAM, because we've already mapped | |
155 | * it as RAM. So, look it up in the existing EFI memory map instead. Only | |
156 | * callable after efi_enter_virtual_mode and before efi_free_boot_services. | |
157 | */ | |
158 | void __iomem *efi_lookup_mapped_addr(u64 phys_addr) | |
159 | { | |
160 | struct efi_memory_map *map; | |
161 | void *p; | |
162 | map = efi.memmap; | |
163 | if (!map) | |
164 | return NULL; | |
165 | if (WARN_ON(!map->map)) | |
166 | return NULL; | |
167 | for (p = map->map; p < map->map_end; p += map->desc_size) { | |
168 | efi_memory_desc_t *md = p; | |
169 | u64 size = md->num_pages << EFI_PAGE_SHIFT; | |
170 | u64 end = md->phys_addr + size; | |
171 | if (!(md->attribute & EFI_MEMORY_RUNTIME) && | |
172 | md->type != EFI_BOOT_SERVICES_CODE && | |
173 | md->type != EFI_BOOT_SERVICES_DATA) | |
174 | continue; | |
175 | if (!md->virt_addr) | |
176 | continue; | |
177 | if (phys_addr >= md->phys_addr && phys_addr < end) { | |
178 | phys_addr += md->virt_addr - md->phys_addr; | |
179 | return (__force void __iomem *)(unsigned long)phys_addr; | |
180 | } | |
181 | } | |
182 | return NULL; | |
183 | } | |
184 | ||
272686bf LL |
185 | static __initdata efi_config_table_type_t common_tables[] = { |
186 | {ACPI_20_TABLE_GUID, "ACPI 2.0", &efi.acpi20}, | |
187 | {ACPI_TABLE_GUID, "ACPI", &efi.acpi}, | |
188 | {HCDP_TABLE_GUID, "HCDP", &efi.hcdp}, | |
189 | {MPS_TABLE_GUID, "MPS", &efi.mps}, | |
190 | {SAL_SYSTEM_TABLE_GUID, "SALsystab", &efi.sal_systab}, | |
191 | {SMBIOS_TABLE_GUID, "SMBIOS", &efi.smbios}, | |
192 | {UGA_IO_PROTOCOL_GUID, "UGA", &efi.uga}, | |
193 | {NULL_GUID, NULL, 0}, | |
194 | }; | |
195 | ||
196 | static __init int match_config_table(efi_guid_t *guid, | |
197 | unsigned long table, | |
198 | efi_config_table_type_t *table_types) | |
199 | { | |
200 | u8 str[EFI_VARIABLE_GUID_LEN + 1]; | |
201 | int i; | |
202 | ||
203 | if (table_types) { | |
204 | efi_guid_unparse(guid, str); | |
205 | ||
206 | for (i = 0; efi_guidcmp(table_types[i].guid, NULL_GUID); i++) { | |
207 | efi_guid_unparse(&table_types[i].guid, str); | |
208 | ||
209 | if (!efi_guidcmp(*guid, table_types[i].guid)) { | |
210 | *(table_types[i].ptr) = table; | |
211 | pr_cont(" %s=0x%lx ", | |
212 | table_types[i].name, table); | |
213 | return 1; | |
214 | } | |
215 | } | |
216 | } | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
221 | int __init efi_config_init(efi_config_table_type_t *arch_tables) | |
222 | { | |
223 | void *config_tables, *tablep; | |
224 | int i, sz; | |
225 | ||
226 | if (efi_enabled(EFI_64BIT)) | |
227 | sz = sizeof(efi_config_table_64_t); | |
228 | else | |
229 | sz = sizeof(efi_config_table_32_t); | |
230 | ||
231 | /* | |
232 | * Let's see what config tables the firmware passed to us. | |
233 | */ | |
234 | config_tables = early_memremap(efi.systab->tables, | |
235 | efi.systab->nr_tables * sz); | |
236 | if (config_tables == NULL) { | |
237 | pr_err("Could not map Configuration table!\n"); | |
238 | return -ENOMEM; | |
239 | } | |
240 | ||
241 | tablep = config_tables; | |
242 | pr_info(""); | |
243 | for (i = 0; i < efi.systab->nr_tables; i++) { | |
244 | efi_guid_t guid; | |
245 | unsigned long table; | |
246 | ||
247 | if (efi_enabled(EFI_64BIT)) { | |
248 | u64 table64; | |
249 | guid = ((efi_config_table_64_t *)tablep)->guid; | |
250 | table64 = ((efi_config_table_64_t *)tablep)->table; | |
251 | table = table64; | |
252 | #ifndef CONFIG_64BIT | |
253 | if (table64 >> 32) { | |
254 | pr_cont("\n"); | |
255 | pr_err("Table located above 4GB, disabling EFI.\n"); | |
256 | early_iounmap(config_tables, | |
257 | efi.systab->nr_tables * sz); | |
258 | return -EINVAL; | |
259 | } | |
260 | #endif | |
261 | } else { | |
262 | guid = ((efi_config_table_32_t *)tablep)->guid; | |
263 | table = ((efi_config_table_32_t *)tablep)->table; | |
264 | } | |
265 | ||
266 | if (!match_config_table(&guid, table, common_tables)) | |
267 | match_config_table(&guid, table, arch_tables); | |
268 | ||
269 | tablep += sz; | |
270 | } | |
271 | pr_cont("\n"); | |
272 | early_iounmap(config_tables, efi.systab->nr_tables * sz); | |
273 | return 0; | |
274 | } |