/src/u-boot/boot/vbe_common.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0 |
2 | | /* |
3 | | * Verified Boot for Embedded (VBE) common functions |
4 | | * |
5 | | * Copyright 2024 Google LLC |
6 | | * Written by Simon Glass <sjg@chromium.org> |
7 | | */ |
8 | | |
9 | | #include <bootstage.h> |
10 | | #include <dm.h> |
11 | | #include <blk.h> |
12 | | #include <image.h> |
13 | | #include <mapmem.h> |
14 | | #include <memalign.h> |
15 | | #include <spl.h> |
16 | | #include <u-boot/crc.h> |
17 | | #include "vbe_common.h" |
18 | | |
19 | | binman_sym_declare(ulong, u_boot_vpl_nodtb, size); |
20 | | binman_sym_declare(ulong, u_boot_vpl_bss_pad, size); |
21 | | binman_sym_declare(ulong, u_boot_spl_nodtb, size); |
22 | | binman_sym_declare(ulong, u_boot_spl_bss_pad, size); |
23 | | |
24 | | int vbe_get_blk(const char *storage, struct udevice **blkp) |
25 | 0 | { |
26 | 0 | struct blk_desc *desc; |
27 | 0 | char devname[16]; |
28 | 0 | const char *end; |
29 | 0 | int devnum; |
30 | | |
31 | | /* First figure out the block device */ |
32 | 0 | log_debug("storage=%s\n", storage); |
33 | 0 | devnum = trailing_strtoln_end(storage, NULL, &end); |
34 | 0 | if (devnum == -1) |
35 | 0 | return log_msg_ret("num", -ENODEV); |
36 | 0 | if (end - storage >= sizeof(devname)) |
37 | 0 | return log_msg_ret("end", -E2BIG); |
38 | 0 | strlcpy(devname, storage, end - storage + 1); |
39 | 0 | log_debug("dev=%s, %x\n", devname, devnum); |
40 | |
|
41 | 0 | desc = blk_get_dev(devname, devnum); |
42 | 0 | if (!desc) |
43 | 0 | return log_msg_ret("get", -ENXIO); |
44 | 0 | *blkp = desc->bdev; |
45 | |
|
46 | 0 | return 0; |
47 | 0 | } |
48 | | |
49 | | int vbe_read_version(struct udevice *blk, ulong offset, char *version, |
50 | | int max_size) |
51 | 0 | { |
52 | 0 | ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); |
53 | | |
54 | | /* we can use an assert() here since we already read only one block */ |
55 | 0 | assert(max_size <= MMC_MAX_BLOCK_LEN); |
56 | | |
57 | | /* |
58 | | * we can use an assert() here since reading the wrong block will just |
59 | | * cause an invalid version-string to be (safely) read |
60 | | */ |
61 | 0 | assert(!(offset & (MMC_MAX_BLOCK_LEN - 1))); |
62 | |
|
63 | 0 | offset /= MMC_MAX_BLOCK_LEN; |
64 | |
|
65 | 0 | if (blk_read(blk, offset, 1, buf) != 1) |
66 | 0 | return log_msg_ret("read", -EIO); |
67 | 0 | strlcpy(version, buf, max_size); |
68 | 0 | log_debug("version=%s\n", version); |
69 | |
|
70 | 0 | return 0; |
71 | 0 | } |
72 | | |
73 | | int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf) |
74 | 0 | { |
75 | 0 | uint hdr_ver, hdr_size, data_size, crc; |
76 | 0 | const struct vbe_nvdata *nvd; |
77 | | |
78 | | /* we can use an assert() here since we already read only one block */ |
79 | 0 | assert(size <= MMC_MAX_BLOCK_LEN); |
80 | | |
81 | | /* |
82 | | * We can use an assert() here since reading the wrong block will just |
83 | | * cause invalid state to be (safely) read. If the crc passes, then we |
84 | | * obtain invalid state and it will likely cause booting to fail. |
85 | | * |
86 | | * VBE relies on valid values being in U-Boot's devicetree, so this |
87 | | * should not every be wrong on a production device. |
88 | | */ |
89 | 0 | assert(!(offset & (MMC_MAX_BLOCK_LEN - 1))); |
90 | |
|
91 | 0 | if (offset & (MMC_MAX_BLOCK_LEN - 1)) |
92 | 0 | return log_msg_ret("get", -EBADF); |
93 | 0 | offset /= MMC_MAX_BLOCK_LEN; |
94 | |
|
95 | 0 | if (blk_read(blk, offset, 1, buf) != 1) |
96 | 0 | return log_msg_ret("read", -EIO); |
97 | 0 | nvd = (struct vbe_nvdata *)buf; |
98 | 0 | hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT; |
99 | 0 | hdr_size = (nvd->hdr & NVD_HDR_SIZE_MASK) >> NVD_HDR_SIZE_SHIFT; |
100 | 0 | if (hdr_ver != NVD_HDR_VER_CUR) |
101 | 0 | return log_msg_ret("hdr", -EPERM); |
102 | 0 | data_size = 1 << hdr_size; |
103 | 0 | if (!data_size || data_size > sizeof(*nvd)) |
104 | 0 | return log_msg_ret("sz", -EPERM); |
105 | | |
106 | 0 | crc = crc8(0, buf + 1, data_size - 1); |
107 | 0 | if (crc != nvd->crc8) |
108 | 0 | return log_msg_ret("crc", -EPERM); |
109 | | |
110 | 0 | return 0; |
111 | 0 | } |
112 | | |
113 | | /** |
114 | | * h_vbe_load_read() - Handler for reading an SPL image from a FIT |
115 | | * |
116 | | * See spl_load_reader for the definition |
117 | | */ |
118 | | ulong h_vbe_load_read(struct spl_load_info *load, ulong off, ulong size, |
119 | | void *buf) |
120 | 0 | { |
121 | 0 | struct blk_desc *desc = load->priv; |
122 | 0 | lbaint_t sector = off >> desc->log2blksz; |
123 | 0 | lbaint_t count = size >> desc->log2blksz; |
124 | 0 | int ret; |
125 | |
|
126 | 0 | log_debug("vbe read log2blksz %x offset %lx sector %lx count %lx\n", |
127 | 0 | desc->log2blksz, (ulong)off, (long)sector, (ulong)count); |
128 | |
|
129 | 0 | ret = blk_dread(desc, sector, count, buf); |
130 | 0 | log_debug("ret=%x\n", ret); |
131 | 0 | if (ret < 0) |
132 | 0 | return ret; |
133 | | |
134 | 0 | return ret << desc->log2blksz; |
135 | 0 | } |
136 | | |
137 | | int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, |
138 | | struct spl_image_info *image, ulong *load_addrp, ulong *lenp, |
139 | | char **namep) |
140 | 0 | { |
141 | 0 | ALLOC_CACHE_ALIGN_BUFFER(u8, sbuf, MMC_MAX_BLOCK_LEN); |
142 | 0 | ulong size, blknum, addr, len, load_addr, num_blks, spl_load_addr; |
143 | 0 | ulong aligned_size, fdt_load_addr, fdt_size; |
144 | 0 | const char *fit_uname, *fit_uname_config; |
145 | 0 | struct bootm_headers images = {}; |
146 | 0 | enum image_phase_t phase; |
147 | 0 | struct blk_desc *desc; |
148 | 0 | int node, ret; |
149 | 0 | bool for_xpl; |
150 | 0 | void *buf; |
151 | |
|
152 | 0 | desc = dev_get_uclass_plat(blk); |
153 | | |
154 | | /* read in one block to find the FIT size */ |
155 | 0 | blknum = area_offset / desc->blksz; |
156 | 0 | log_debug("read at %lx, blknum %lx\n", area_offset, blknum); |
157 | 0 | ret = blk_read(blk, blknum, 1, sbuf); |
158 | 0 | if (ret < 0) |
159 | 0 | return log_msg_ret("rd", ret); |
160 | 0 | else if (ret != 1) |
161 | 0 | return log_msg_ret("rd2", -EIO); |
162 | | |
163 | 0 | ret = fdt_check_header(sbuf); |
164 | 0 | if (ret < 0) |
165 | 0 | return log_msg_ret("fdt", -EINVAL); |
166 | 0 | size = fdt_totalsize(sbuf); |
167 | 0 | if (size > area_size) |
168 | 0 | return log_msg_ret("fdt", -E2BIG); |
169 | 0 | log_debug("FIT size %lx\n", size); |
170 | 0 | aligned_size = ALIGN(size, desc->blksz); |
171 | | |
172 | | /* |
173 | | * Load the FIT into the SPL memory. This is typically a FIT with |
174 | | * external data, so this is quite small, perhaps a few KB. |
175 | | */ |
176 | 0 | if (IS_ENABLED(CONFIG_SANDBOX)) { |
177 | 0 | addr = CONFIG_VAL(TEXT_BASE); |
178 | 0 | buf = map_sysmem(addr, size); |
179 | 0 | } else { |
180 | 0 | buf = malloc(aligned_size); |
181 | 0 | if (!buf) |
182 | 0 | return log_msg_ret("fit", -ENOMEM); |
183 | 0 | addr = map_to_sysmem(buf); |
184 | 0 | } |
185 | 0 | num_blks = aligned_size / desc->blksz; |
186 | 0 | log_debug("read %lx, %lx blocks to %lx / %p\n", aligned_size, num_blks, |
187 | 0 | addr, buf); |
188 | 0 | ret = blk_read(blk, blknum, num_blks, buf); |
189 | 0 | if (ret < 0) |
190 | 0 | return log_msg_ret("rd3", ret); |
191 | 0 | else if (ret != num_blks) |
192 | 0 | return log_msg_ret("rd4", -EIO); |
193 | 0 | log_debug("check total size %x off_dt_strings %x\n", fdt_totalsize(buf), |
194 | 0 | fdt_off_dt_strings(buf)); |
195 | |
|
196 | 0 | #if CONFIG_IS_ENABLED(SYS_MALLOC_F) |
197 | 0 | log_debug("malloc base %lx ptr %x limit %x top %lx\n", |
198 | 0 | gd->malloc_base, gd->malloc_ptr, gd->malloc_limit, |
199 | 0 | gd->malloc_base + gd->malloc_limit); |
200 | 0 | #endif |
201 | | /* figure out the phase to load */ |
202 | 0 | phase = IS_ENABLED(CONFIG_TPL_BUILD) ? IH_PHASE_NONE : |
203 | 0 | IS_ENABLED(CONFIG_VPL_BUILD) ? IH_PHASE_SPL : IH_PHASE_U_BOOT; |
204 | |
|
205 | 0 | log_debug("loading FIT\n"); |
206 | |
|
207 | 0 | if (xpl_phase() == PHASE_SPL && !IS_ENABLED(CONFIG_SANDBOX)) { |
208 | 0 | struct spl_load_info info; |
209 | |
|
210 | 0 | spl_load_init(&info, h_vbe_load_read, desc, desc->blksz); |
211 | 0 | xpl_set_fdt_update(&info, false); |
212 | 0 | xpl_set_phase(&info, IH_PHASE_U_BOOT); |
213 | 0 | log_debug("doing SPL from %s blksz %lx log2blksz %x area_offset %lx + fdt_size %lx\n", |
214 | 0 | blk->name, desc->blksz, desc->log2blksz, area_offset, ALIGN(size, 4)); |
215 | 0 | ret = spl_load_simple_fit(image, &info, area_offset, buf); |
216 | 0 | log_debug("spl_load_simple_fit() ret=%d\n", ret); |
217 | |
|
218 | 0 | return ret; |
219 | 0 | } |
220 | | |
221 | | /* |
222 | | * Load the image from the FIT. We ignore any load-address information |
223 | | * so in practice this simply locates the image in the external-data |
224 | | * region and returns its address and size. Since we only loaded the FIT |
225 | | * itself, only a part of the image will be present, at best. |
226 | | */ |
227 | 0 | fit_uname = NULL; |
228 | 0 | fit_uname_config = NULL; |
229 | 0 | ret = fit_image_load(&images, addr, &fit_uname, &fit_uname_config, |
230 | 0 | IH_ARCH_DEFAULT, image_ph(phase, IH_TYPE_FIRMWARE), |
231 | 0 | BOOTSTAGE_ID_FIT_SPL_START, FIT_LOAD_IGNORED, |
232 | 0 | &load_addr, &len); |
233 | 0 | if (ret == -ENOENT) { |
234 | 0 | ret = fit_image_load(&images, addr, &fit_uname, |
235 | 0 | &fit_uname_config, IH_ARCH_DEFAULT, |
236 | 0 | image_ph(phase, IH_TYPE_LOADABLE), |
237 | 0 | BOOTSTAGE_ID_FIT_SPL_START, |
238 | 0 | FIT_LOAD_IGNORED, &load_addr, &len); |
239 | 0 | } |
240 | 0 | if (ret < 0) |
241 | 0 | return log_msg_ret("ld", ret); |
242 | 0 | node = ret; |
243 | 0 | log_debug("load %lx size %lx\n", load_addr, len); |
244 | |
|
245 | 0 | fdt_load_addr = 0; |
246 | 0 | fdt_size = 0; |
247 | 0 | if ((xpl_phase() == PHASE_TPL || xpl_phase() == PHASE_VPL) && |
248 | 0 | !IS_ENABLED(CONFIG_SANDBOX)) { |
249 | | /* allow use of a different image from the configuration node */ |
250 | 0 | fit_uname = NULL; |
251 | 0 | ret = fit_image_load(&images, addr, &fit_uname, |
252 | 0 | &fit_uname_config, IH_ARCH_DEFAULT, |
253 | 0 | image_ph(phase, IH_TYPE_FLATDT), |
254 | 0 | BOOTSTAGE_ID_FIT_SPL_START, |
255 | 0 | FIT_LOAD_IGNORED, &fdt_load_addr, |
256 | 0 | &fdt_size); |
257 | 0 | fdt_size = ALIGN(fdt_size, desc->blksz); |
258 | 0 | log_debug("FDT noload to %lx size %lx\n", fdt_load_addr, |
259 | 0 | fdt_size); |
260 | 0 | } |
261 | |
|
262 | 0 | for_xpl = !USE_BOOTMETH && CONFIG_IS_ENABLED(RELOC_LOADER); |
263 | 0 | if (for_xpl) { |
264 | 0 | image->size = len; |
265 | 0 | image->fdt_size = fdt_size; |
266 | 0 | ret = spl_reloc_prepare(image, &spl_load_addr); |
267 | 0 | if (ret) |
268 | 0 | return log_msg_ret("spl", ret); |
269 | 0 | } |
270 | 0 | if (!IS_ENABLED(CONFIG_SANDBOX)) |
271 | 0 | image->os = IH_OS_U_BOOT; |
272 | | |
273 | | /* For FIT external data, read in the external data */ |
274 | 0 | log_debug("load_addr %lx len %lx addr %lx aligned_size %lx\n", |
275 | 0 | load_addr, len, addr, aligned_size); |
276 | 0 | if (load_addr + len > addr + aligned_size) { |
277 | 0 | ulong base, full_size, offset, extra, fdt_base, fdt_full_size; |
278 | 0 | ulong fdt_offset; |
279 | 0 | void *base_buf, *fdt_base_buf; |
280 | | |
281 | | /* Find the start address to load from */ |
282 | 0 | base = ALIGN_DOWN(load_addr, desc->blksz); |
283 | |
|
284 | 0 | offset = area_offset + load_addr - addr; |
285 | 0 | blknum = offset / desc->blksz; |
286 | 0 | extra = offset % desc->blksz; |
287 | | |
288 | | /* |
289 | | * Get the total number of bytes to load, taking care of |
290 | | * block alignment |
291 | | */ |
292 | 0 | full_size = len + extra; |
293 | | |
294 | | /* |
295 | | * Get the start block number, number of blocks and the address |
296 | | * to load to, then load the blocks |
297 | | */ |
298 | 0 | num_blks = DIV_ROUND_UP(full_size, desc->blksz); |
299 | 0 | if (for_xpl) |
300 | 0 | base = spl_load_addr; |
301 | 0 | base_buf = map_sysmem(base, full_size); |
302 | 0 | ret = blk_read(blk, blknum, num_blks, base_buf); |
303 | 0 | log_debug("read foffset %lx blknum %lx full_size %lx num_blks %lx to %lx / %p: ret=%d\n", |
304 | 0 | offset - 0x8000, blknum, full_size, num_blks, base, base_buf, |
305 | 0 | ret); |
306 | 0 | if (ret < 0) |
307 | 0 | return log_msg_ret("rd", ret); |
308 | 0 | if (ret != num_blks) |
309 | 0 | return log_msg_ret("rd", -EIO); |
310 | 0 | if (extra && !IS_ENABLED(CONFIG_SANDBOX)) { |
311 | 0 | log_debug("move %p %p %lx\n", base_buf, |
312 | 0 | base_buf + extra, len); |
313 | 0 | memmove(base_buf, base_buf + extra, len); |
314 | 0 | } |
315 | |
|
316 | 0 | if ((xpl_phase() == PHASE_VPL || xpl_phase() == PHASE_TPL) && |
317 | 0 | !IS_ENABLED(CONFIG_SANDBOX)) { |
318 | 0 | image->load_addr = spl_get_image_text_base(); |
319 | 0 | image->entry_point = image->load_addr; |
320 | 0 | } |
321 | | |
322 | | /* now the FDT */ |
323 | 0 | if (fdt_size) { |
324 | 0 | fdt_offset = area_offset + fdt_load_addr - addr; |
325 | 0 | blknum = fdt_offset / desc->blksz; |
326 | 0 | extra = fdt_offset % desc->blksz; |
327 | 0 | fdt_full_size = fdt_size + extra; |
328 | 0 | num_blks = DIV_ROUND_UP(fdt_full_size, desc->blksz); |
329 | 0 | fdt_base = ALIGN(base + len, 4); |
330 | 0 | fdt_base_buf = map_sysmem(fdt_base, fdt_size); |
331 | 0 | ret = blk_read(blk, blknum, num_blks, fdt_base_buf); |
332 | 0 | log_debug("fdt read foffset %lx blknum %lx full_size %lx num_blks %lx to %lx / %p: ret=%d\n", |
333 | 0 | fdt_offset - 0x8000, blknum, fdt_full_size, num_blks, |
334 | 0 | fdt_base, fdt_base_buf, ret); |
335 | 0 | if (ret != num_blks) |
336 | 0 | return log_msg_ret("rdf", -EIO); |
337 | 0 | if (extra) { |
338 | 0 | log_debug("move %p %p %lx\n", fdt_base_buf, |
339 | 0 | fdt_base_buf + extra, fdt_size); |
340 | 0 | memmove(fdt_base_buf, fdt_base_buf + extra, |
341 | 0 | fdt_size); |
342 | 0 | } |
343 | | #if CONFIG_IS_ENABLED(RELOC_LOADER) |
344 | | image->fdt_buf = fdt_base_buf; |
345 | | |
346 | | ulong xpl_size; |
347 | | ulong xpl_pad; |
348 | | ulong fdt_start; |
349 | | |
350 | | if (xpl_phase() == PHASE_TPL) { |
351 | | xpl_size = binman_sym(ulong, u_boot_vpl_nodtb, size); |
352 | | xpl_pad = binman_sym(ulong, u_boot_vpl_bss_pad, size); |
353 | | } else { |
354 | | xpl_size = binman_sym(ulong, u_boot_spl_nodtb, size); |
355 | | xpl_pad = binman_sym(ulong, u_boot_spl_bss_pad, size); |
356 | | } |
357 | | fdt_start = image->load_addr + xpl_size + xpl_pad; |
358 | | log_debug("load_addr %lx xpl_size %lx copy-to %lx\n", |
359 | | image->load_addr, xpl_size + xpl_pad, |
360 | | fdt_start); |
361 | | image->fdt_start = map_sysmem(fdt_start, fdt_size); |
362 | | #endif |
363 | 0 | } |
364 | 0 | } |
365 | 0 | if (load_addrp) |
366 | 0 | *load_addrp = load_addr; |
367 | 0 | if (lenp) |
368 | 0 | *lenp = len; |
369 | 0 | if (namep) { |
370 | 0 | *namep = strdup(fdt_get_name(buf, node, NULL)); |
371 | 0 | if (!namep) |
372 | 0 | return log_msg_ret("nam", -ENOMEM); |
373 | 0 | } |
374 | | |
375 | 0 | return 0; |
376 | 0 | } |
377 | | |
378 | | ofnode vbe_get_node(void) |
379 | 0 | { |
380 | 0 | return ofnode_path("/bootstd/firmware0"); |
381 | 0 | } |