aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware
diff options
context:
space:
mode:
authorRoy Franz <roy.franz@linaro.org>2014-01-08 20:54:19 -0500
committerMatt Fleming <matt.fleming@intel.com>2014-04-30 14:49:57 -0400
commit263b4a30bfdb0d756ae9c70c6ff2eef1eb951770 (patch)
treeb9f25eca710bc6200e21e2ab8324fa6d25808586 /drivers/firmware
parentd7ecbddf4caefbac1b99478dd2b679f83dfc2545 (diff)
efi: Add shared FDT related functions for ARM/ARM64
Both ARM and ARM64 stubs will update the device tree that they pass to the kernel. In both cases they primarily need to add the same UEFI related information, so the function can be shared. Create a new FDT related file for this to avoid use of architecture #ifdefs in efi-stub-helper.c. Signed-off-by: Roy Franz <roy.franz@linaro.org> [ Fixed memory node deletion code. ] Signed-off-by: Mark Rutland <mark.rutland@arm.com> Signed-off-by: Leif Lindholm <leif.lindholm@linaro.org> Acked-by: Grant Likely <grant.likely@linaro.org> Acked-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Matt Fleming <matt.fleming@intel.com>
Diffstat (limited to 'drivers/firmware')
-rw-r--r--drivers/firmware/efi/fdt.c285
1 files changed, 285 insertions, 0 deletions
diff --git a/drivers/firmware/efi/fdt.c b/drivers/firmware/efi/fdt.c
new file mode 100644
index 000000000000..5c6a8e8a9580
--- /dev/null
+++ b/drivers/firmware/efi/fdt.c
@@ -0,0 +1,285 @@
1/*
2 * FDT related Helper functions used by the EFI stub on multiple
3 * architectures. This should be #included by the EFI stub
4 * implementation files.
5 *
6 * Copyright 2013 Linaro Limited; author Roy Franz
7 *
8 * This file is part of the Linux kernel, and is made available
9 * under the terms of the GNU General Public License version 2.
10 *
11 */
12
13static efi_status_t update_fdt(efi_system_table_t *sys_table, void *orig_fdt,
14 unsigned long orig_fdt_size,
15 void *fdt, int new_fdt_size, char *cmdline_ptr,
16 u64 initrd_addr, u64 initrd_size,
17 efi_memory_desc_t *memory_map,
18 unsigned long map_size, unsigned long desc_size,
19 u32 desc_ver)
20{
21 int node, prev;
22 int status;
23 u32 fdt_val32;
24 u64 fdt_val64;
25
26 /*
27 * Copy definition of linux_banner here. Since this code is
28 * built as part of the decompressor for ARM v7, pulling
29 * in version.c where linux_banner is defined for the
30 * kernel brings other kernel dependencies with it.
31 */
32 const char linux_banner[] =
33 "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
34 LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";
35
36 /* Do some checks on provided FDT, if it exists*/
37 if (orig_fdt) {
38 if (fdt_check_header(orig_fdt)) {
39 pr_efi_err(sys_table, "Device Tree header not valid!\n");
40 return EFI_LOAD_ERROR;
41 }
42 /*
43 * We don't get the size of the FDT if we get if from a
44 * configuration table.
45 */
46 if (orig_fdt_size && fdt_totalsize(orig_fdt) > orig_fdt_size) {
47 pr_efi_err(sys_table, "Truncated device tree! foo!\n");
48 return EFI_LOAD_ERROR;
49 }
50 }
51
52 if (orig_fdt)
53 status = fdt_open_into(orig_fdt, fdt, new_fdt_size);
54 else
55 status = fdt_create_empty_tree(fdt, new_fdt_size);
56
57 if (status != 0)
58 goto fdt_set_fail;
59
60 /*
61 * Delete any memory nodes present. We must delete nodes which
62 * early_init_dt_scan_memory may try to use.
63 */
64 prev = 0;
65 for (;;) {
66 const char *type, *name;
67 int len;
68
69 node = fdt_next_node(fdt, prev, NULL);
70 if (node < 0)
71 break;
72
73 type = fdt_getprop(fdt, node, "device_type", &len);
74 if (type && strncmp(type, "memory", len) == 0) {
75 fdt_del_node(fdt, node);
76 continue;
77 }
78
79 prev = node;
80 }
81
82 node = fdt_subnode_offset(fdt, 0, "chosen");
83 if (node < 0) {
84 node = fdt_add_subnode(fdt, 0, "chosen");
85 if (node < 0) {
86 status = node; /* node is error code when negative */
87 goto fdt_set_fail;
88 }
89 }
90
91 if ((cmdline_ptr != NULL) && (strlen(cmdline_ptr) > 0)) {
92 status = fdt_setprop(fdt, node, "bootargs", cmdline_ptr,
93 strlen(cmdline_ptr) + 1);
94 if (status)
95 goto fdt_set_fail;
96 }
97
98 /* Set initrd address/end in device tree, if present */
99 if (initrd_size != 0) {
100 u64 initrd_image_end;
101 u64 initrd_image_start = cpu_to_fdt64(initrd_addr);
102
103 status = fdt_setprop(fdt, node, "linux,initrd-start",
104 &initrd_image_start, sizeof(u64));
105 if (status)
106 goto fdt_set_fail;
107 initrd_image_end = cpu_to_fdt64(initrd_addr + initrd_size);
108 status = fdt_setprop(fdt, node, "linux,initrd-end",
109 &initrd_image_end, sizeof(u64));
110 if (status)
111 goto fdt_set_fail;
112 }
113
114 /* Add FDT entries for EFI runtime services in chosen node. */
115 node = fdt_subnode_offset(fdt, 0, "chosen");
116 fdt_val64 = cpu_to_fdt64((u64)(unsigned long)sys_table);
117 status = fdt_setprop(fdt, node, "linux,uefi-system-table",
118 &fdt_val64, sizeof(fdt_val64));
119 if (status)
120 goto fdt_set_fail;
121
122 fdt_val64 = cpu_to_fdt64((u64)(unsigned long)memory_map);
123 status = fdt_setprop(fdt, node, "linux,uefi-mmap-start",
124 &fdt_val64, sizeof(fdt_val64));
125 if (status)
126 goto fdt_set_fail;
127
128 fdt_val32 = cpu_to_fdt32(map_size);
129 status = fdt_setprop(fdt, node, "linux,uefi-mmap-size",
130 &fdt_val32, sizeof(fdt_val32));
131 if (status)
132 goto fdt_set_fail;
133
134 fdt_val32 = cpu_to_fdt32(desc_size);
135 status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-size",
136 &fdt_val32, sizeof(fdt_val32));
137 if (status)
138 goto fdt_set_fail;
139
140 fdt_val32 = cpu_to_fdt32(desc_ver);
141 status = fdt_setprop(fdt, node, "linux,uefi-mmap-desc-ver",
142 &fdt_val32, sizeof(fdt_val32));
143 if (status)
144 goto fdt_set_fail;
145
146 /*
147 * Add kernel version banner so stub/kernel match can be
148 * verified.
149 */
150 status = fdt_setprop_string(fdt, node, "linux,uefi-stub-kern-ver",
151 linux_banner);
152 if (status)
153 goto fdt_set_fail;
154
155 return EFI_SUCCESS;
156
157fdt_set_fail:
158 if (status == -FDT_ERR_NOSPACE)
159 return EFI_BUFFER_TOO_SMALL;
160
161 return EFI_LOAD_ERROR;
162}
163
164#ifndef EFI_FDT_ALIGN
165#define EFI_FDT_ALIGN EFI_PAGE_SIZE
166#endif
167
168/*
169 * Allocate memory for a new FDT, then add EFI, commandline, and
170 * initrd related fields to the FDT. This routine increases the
171 * FDT allocation size until the allocated memory is large
172 * enough. EFI allocations are in EFI_PAGE_SIZE granules,
173 * which are fixed at 4K bytes, so in most cases the first
174 * allocation should succeed.
175 * EFI boot services are exited at the end of this function.
176 * There must be no allocations between the get_memory_map()
177 * call and the exit_boot_services() call, so the exiting of
178 * boot services is very tightly tied to the creation of the FDT
179 * with the final memory map in it.
180 */
181
182efi_status_t allocate_new_fdt_and_exit_boot(efi_system_table_t *sys_table,
183 void *handle,
184 unsigned long *new_fdt_addr,
185 unsigned long max_addr,
186 u64 initrd_addr, u64 initrd_size,
187 char *cmdline_ptr,
188 unsigned long fdt_addr,
189 unsigned long fdt_size)
190{
191 unsigned long map_size, desc_size;
192 u32 desc_ver;
193 unsigned long mmap_key;
194 efi_memory_desc_t *memory_map;
195 unsigned long new_fdt_size;
196 efi_status_t status;
197
198 /*
199 * Estimate size of new FDT, and allocate memory for it. We
200 * will allocate a bigger buffer if this ends up being too
201 * small, so a rough guess is OK here.
202 */
203 new_fdt_size = fdt_size + EFI_PAGE_SIZE;
204 while (1) {
205 status = efi_high_alloc(sys_table, new_fdt_size, EFI_FDT_ALIGN,
206 new_fdt_addr, max_addr);
207 if (status != EFI_SUCCESS) {
208 pr_efi_err(sys_table, "Unable to allocate memory for new device tree.\n");
209 goto fail;
210 }
211
212 /*
213 * Now that we have done our final memory allocation (and free)
214 * we can get the memory map key needed for
215 * exit_boot_services().
216 */
217 status = efi_get_memory_map(sys_table, &memory_map, &map_size,
218 &desc_size, &desc_ver, &mmap_key);
219 if (status != EFI_SUCCESS)
220 goto fail_free_new_fdt;
221
222 status = update_fdt(sys_table,
223 (void *)fdt_addr, fdt_size,
224 (void *)*new_fdt_addr, new_fdt_size,
225 cmdline_ptr, initrd_addr, initrd_size,
226 memory_map, map_size, desc_size, desc_ver);
227
228 /* Succeeding the first time is the expected case. */
229 if (status == EFI_SUCCESS)
230 break;
231
232 if (status == EFI_BUFFER_TOO_SMALL) {
233 /*
234 * We need to allocate more space for the new
235 * device tree, so free existing buffer that is
236 * too small. Also free memory map, as we will need
237 * to get new one that reflects the free/alloc we do
238 * on the device tree buffer.
239 */
240 efi_free(sys_table, new_fdt_size, *new_fdt_addr);
241 sys_table->boottime->free_pool(memory_map);
242 new_fdt_size += EFI_PAGE_SIZE;
243 } else {
244 pr_efi_err(sys_table, "Unable to constuct new device tree.\n");
245 goto fail_free_mmap;
246 }
247 }
248
249 /* Now we are ready to exit_boot_services.*/
250 status = sys_table->boottime->exit_boot_services(handle, mmap_key);
251
252
253 if (status == EFI_SUCCESS)
254 return status;
255
256 pr_efi_err(sys_table, "Exit boot services failed.\n");
257
258fail_free_mmap:
259 sys_table->boottime->free_pool(memory_map);
260
261fail_free_new_fdt:
262 efi_free(sys_table, new_fdt_size, *new_fdt_addr);
263
264fail:
265 return EFI_LOAD_ERROR;
266}
267
268static void *get_fdt(efi_system_table_t *sys_table)
269{
270 efi_guid_t fdt_guid = DEVICE_TREE_GUID;
271 efi_config_table_t *tables;
272 void *fdt;
273 int i;
274
275 tables = (efi_config_table_t *) sys_table->tables;
276 fdt = NULL;
277
278 for (i = 0; i < sys_table->nr_tables; i++)
279 if (efi_guidcmp(tables[i].guid, fdt_guid) == 0) {
280 fdt = (void *) tables[i].table;
281 break;
282 }
283
284 return fdt;
285}