AArch64 EL2 hypervisor for QEMU virt that boots at EL2
1#include "hv/fdt.h"
2#include "hv/string.h"
3#include "hv/uart.h"
4
5#define FDT_MAGIC 0xd00dfeedu
6#define FDT_BEGIN_NODE 1u
7#define FDT_END_NODE 2u
8#define FDT_PROP 3u
9#define FDT_NOP 4u
10#define FDT_END 9u
11#define FDT_MAX_SIZE 0x00200000u
12#define FDT_MAX_DEPTH 16u
13
14struct fdt_header_view {
15 uint32_t totalsize;
16 uint32_t off_dt_struct;
17 uint32_t off_dt_strings;
18 uint32_t size_dt_strings;
19 uint32_t size_dt_struct;
20};
21
22struct fdt_property_ref {
23 uint8_t *value;
24 uint32_t len;
25};
26
27static uint32_t be32(const void *ptr)
28{
29 const uint8_t *p = (const uint8_t *)ptr;
30 return ((uint32_t)p[0] << 24u) | ((uint32_t)p[1] << 16u) |
31 ((uint32_t)p[2] << 8u) | (uint32_t)p[3];
32}
33
34static uint64_t read_cells(const uint32_t *cells, uint32_t count)
35{
36 uint64_t value = 0;
37 for (uint32_t i = 0; i < count; i++) {
38 value = (value << 32u) | (uint64_t)be32(&cells[i]);
39 }
40 return value;
41}
42
43static uint32_t align4_u32(uint32_t value)
44{
45 return (value + 3u) & ~3u;
46}
47
48static const uint8_t *align4(const uint8_t *p)
49{
50 uintptr_t v = (uintptr_t)p;
51 v = (v + 3u) & ~(uintptr_t)3u;
52 return (const uint8_t *)v;
53}
54
55static bool read_header(const uint8_t *fdt, struct fdt_header_view *h)
56{
57 if (fdt == (const uint8_t *)0 || h == (struct fdt_header_view *)0) {
58 return false;
59 }
60 if (be32(fdt) != FDT_MAGIC) {
61 return false;
62 }
63
64 h->totalsize = be32(fdt + 4u);
65 h->off_dt_struct = be32(fdt + 8u);
66 h->off_dt_strings = be32(fdt + 12u);
67 h->size_dt_strings = be32(fdt + 32u);
68 h->size_dt_struct = be32(fdt + 36u);
69 if (h->totalsize < 40u || h->totalsize > FDT_MAX_SIZE) {
70 return false;
71 }
72 if (h->off_dt_struct >= h->totalsize || h->off_dt_strings >= h->totalsize) {
73 return false;
74 }
75 if (h->size_dt_struct > h->totalsize - h->off_dt_struct ||
76 h->size_dt_strings > h->totalsize - h->off_dt_strings) {
77 return false;
78 }
79 return true;
80}
81
82static bool string_in_block(const char *s, const uint8_t *start, const uint8_t *end)
83{
84 const uint8_t *p = (const uint8_t *)s;
85 if (p < start || p >= end) {
86 return false;
87 }
88 while (p < end) {
89 if (*p == 0u) {
90 return true;
91 }
92 p++;
93 }
94 return false;
95}
96
97static bool node_name_is_memory(const char *name)
98{
99 return hv_strncmp(name, "memory", 6u) == 0;
100}
101
102static bool find_property(uint64_t fdt_pa, const char *node_name,
103 const char *prop_name, struct fdt_property_ref *ref)
104{
105 uint8_t *fdt = (uint8_t *)(uintptr_t)fdt_pa;
106 struct fdt_header_view h;
107 uint8_t *struct_p;
108 uint8_t *struct_end;
109 const uint8_t *strings;
110 const uint8_t *strings_end;
111 bool wanted_node[FDT_MAX_DEPTH];
112 uint32_t depth = 0;
113
114 if (node_name == (const char *)0 || prop_name == (const char *)0 ||
115 ref == (struct fdt_property_ref *)0 || !read_header(fdt, &h)) {
116 return false;
117 }
118 hv_memset(wanted_node, 0, sizeof(wanted_node));
119 struct_p = fdt + h.off_dt_struct;
120 struct_end = struct_p + h.size_dt_struct;
121 strings = fdt + h.off_dt_strings;
122 strings_end = strings + h.size_dt_strings;
123
124 while (struct_p + 4u <= struct_end) {
125 uint32_t token = be32(struct_p);
126 struct_p += 4u;
127 if (token == FDT_BEGIN_NODE) {
128 const char *name = (const char *)struct_p;
129 if (!string_in_block(name, struct_p, struct_end) || depth + 1u >= FDT_MAX_DEPTH) {
130 return false;
131 }
132 wanted_node[depth] = hv_strcmp(name, node_name) == 0;
133 while (struct_p < struct_end && *struct_p != 0u) {
134 struct_p++;
135 }
136 if (struct_p >= struct_end) {
137 return false;
138 }
139 struct_p = (uint8_t *)align4(struct_p + 1u);
140 depth++;
141 } else if (token == FDT_END_NODE) {
142 if (depth == 0u) {
143 return false;
144 }
145 depth--;
146 wanted_node[depth] = false;
147 } else if (token == FDT_PROP) {
148 uint32_t len;
149 uint32_t nameoff;
150 const char *name;
151 if (struct_p + 8u > struct_end) {
152 return false;
153 }
154 len = be32(struct_p);
155 nameoff = be32(struct_p + 4u);
156 struct_p += 8u;
157 if (nameoff >= h.size_dt_strings || len > (uint32_t)(struct_end - struct_p)) {
158 return false;
159 }
160 name = (const char *)(strings + nameoff);
161 if (!string_in_block(name, strings, strings_end)) {
162 return false;
163 }
164 if (depth != 0u && wanted_node[depth - 1u] && hv_strcmp(name, prop_name) == 0) {
165 ref->value = struct_p;
166 ref->len = len;
167 return true;
168 }
169 struct_p = (uint8_t *)align4(struct_p + len);
170 } else if (token == FDT_NOP) {
171 continue;
172 } else if (token == FDT_END) {
173 return false;
174 } else {
175 return false;
176 }
177 }
178 return false;
179}
180
181static bool find_memory_property(uint64_t fdt_pa, const char *prop_name,
182 struct fdt_property_ref *ref)
183{
184 uint8_t *fdt = (uint8_t *)(uintptr_t)fdt_pa;
185 struct fdt_header_view h;
186 uint8_t *struct_p;
187 uint8_t *struct_end;
188 const uint8_t *strings;
189 const uint8_t *strings_end;
190 bool memory_node[FDT_MAX_DEPTH];
191 uint32_t depth = 0;
192
193 if (prop_name == (const char *)0 || ref == (struct fdt_property_ref *)0 ||
194 !read_header(fdt, &h)) {
195 return false;
196 }
197 hv_memset(memory_node, 0, sizeof(memory_node));
198 struct_p = fdt + h.off_dt_struct;
199 struct_end = struct_p + h.size_dt_struct;
200 strings = fdt + h.off_dt_strings;
201 strings_end = strings + h.size_dt_strings;
202
203 while (struct_p + 4u <= struct_end) {
204 uint32_t token = be32(struct_p);
205 struct_p += 4u;
206 if (token == FDT_BEGIN_NODE) {
207 const char *name = (const char *)struct_p;
208 if (!string_in_block(name, struct_p, struct_end) || depth + 1u >= FDT_MAX_DEPTH) {
209 return false;
210 }
211 memory_node[depth] = node_name_is_memory(name);
212 while (struct_p < struct_end && *struct_p != 0u) {
213 struct_p++;
214 }
215 if (struct_p >= struct_end) {
216 return false;
217 }
218 struct_p = (uint8_t *)align4(struct_p + 1u);
219 depth++;
220 } else if (token == FDT_END_NODE) {
221 if (depth == 0u) {
222 return false;
223 }
224 depth--;
225 memory_node[depth] = false;
226 } else if (token == FDT_PROP) {
227 uint32_t len;
228 uint32_t nameoff;
229 const char *name;
230 if (struct_p + 8u > struct_end) {
231 return false;
232 }
233 len = be32(struct_p);
234 nameoff = be32(struct_p + 4u);
235 struct_p += 8u;
236 if (nameoff >= h.size_dt_strings || len > (uint32_t)(struct_end - struct_p)) {
237 return false;
238 }
239 name = (const char *)(strings + nameoff);
240 if (!string_in_block(name, strings, strings_end)) {
241 return false;
242 }
243 if (depth != 0u && memory_node[depth - 1u] &&
244 hv_strcmp(name, prop_name) == 0) {
245 ref->value = struct_p;
246 ref->len = len;
247 return true;
248 }
249 struct_p = (uint8_t *)align4(struct_p + len);
250 } else if (token == FDT_NOP) {
251 continue;
252 } else if (token == FDT_END) {
253 return false;
254 } else {
255 return false;
256 }
257 }
258 return false;
259}
260
261static void write_be64(uint8_t *p, uint64_t value)
262{
263 for (uint32_t i = 0; i < 8u; i++) {
264 p[i] = (uint8_t)(value >> (56u - (i * 8u)));
265 }
266}
267
268static void write_be32(uint8_t *p, uint32_t value)
269{
270 p[0] = (uint8_t)(value >> 24u);
271 p[1] = (uint8_t)(value >> 16u);
272 p[2] = (uint8_t)(value >> 8u);
273 p[3] = (uint8_t)value;
274}
275
276static bool patch_string_prop(uint64_t fdt_pa, const char *node, const char *prop,
277 const char *value)
278{
279 struct fdt_property_ref ref;
280 size_t len;
281 if (value == (const char *)0 || !find_property(fdt_pa, node, prop, &ref)) {
282 return false;
283 }
284 len = hv_strlen(value) + 1u;
285 if (len > ref.len) {
286 return false;
287 }
288 hv_memset(ref.value, 0, ref.len);
289 hv_memcpy(ref.value, value, len);
290 return true;
291}
292
293static bool repurpose_string_prop(uint64_t fdt_pa, const char *node_name,
294 const char *prop_name, const char *value)
295{
296 uint8_t *fdt = (uint8_t *)(uintptr_t)fdt_pa;
297 struct fdt_header_view h;
298 uint8_t *struct_p;
299 uint8_t *struct_end;
300 uint8_t *strings;
301 const uint8_t *strings_end;
302 bool wanted_node[FDT_MAX_DEPTH];
303 uint32_t depth = 0;
304 size_t value_len;
305 size_t prop_len;
306
307 if (node_name == (const char *)0 || prop_name == (const char *)0 ||
308 value == (const char *)0 || !read_header(fdt, &h)) {
309 return false;
310 }
311
312 value_len = hv_strlen(value) + 1u;
313 prop_len = hv_strlen(prop_name) + 1u;
314 hv_memset(wanted_node, 0, sizeof(wanted_node));
315 struct_p = fdt + h.off_dt_struct;
316 struct_end = struct_p + h.size_dt_struct;
317 strings = fdt + h.off_dt_strings;
318 strings_end = strings + h.size_dt_strings;
319
320 while (struct_p + 4u <= struct_end) {
321 uint32_t token = be32(struct_p);
322 struct_p += 4u;
323 if (token == FDT_BEGIN_NODE) {
324 const char *name = (const char *)struct_p;
325 if (!string_in_block(name, struct_p, struct_end) || depth + 1u >= FDT_MAX_DEPTH) {
326 return false;
327 }
328 wanted_node[depth] = hv_strcmp(name, node_name) == 0;
329 while (struct_p < struct_end && *struct_p != 0u) {
330 struct_p++;
331 }
332 if (struct_p >= struct_end) {
333 return false;
334 }
335 struct_p = (uint8_t *)align4(struct_p + 1u);
336 depth++;
337 } else if (token == FDT_END_NODE) {
338 if (depth == 0u) {
339 return false;
340 }
341 depth--;
342 wanted_node[depth] = false;
343 } else if (token == FDT_PROP) {
344 uint32_t len;
345 uint32_t nameoff;
346 char *name;
347 if (struct_p + 8u > struct_end) {
348 return false;
349 }
350 len = be32(struct_p);
351 nameoff = be32(struct_p + 4u);
352 struct_p += 8u;
353 if (nameoff >= h.size_dt_strings || len > (uint32_t)(struct_end - struct_p)) {
354 return false;
355 }
356 name = (char *)(strings + nameoff);
357 if (!string_in_block(name, strings, strings_end)) {
358 return false;
359 }
360 if (depth != 0u && wanted_node[depth - 1u] &&
361 len >= value_len && hv_strlen(name) + 1u >= prop_len) {
362 hv_memset(name, 0, hv_strlen(name) + 1u);
363 hv_memcpy(name, prop_name, prop_len);
364 hv_memset(struct_p, 0, len);
365 hv_memcpy(struct_p, value, value_len);
366 return true;
367 }
368 struct_p = (uint8_t *)align4(struct_p + len);
369 } else if (token == FDT_NOP) {
370 continue;
371 } else if (token == FDT_END) {
372 return false;
373 } else {
374 return false;
375 }
376 }
377 return false;
378}
379
380static bool patch_u64_prop(uint64_t fdt_pa, const char *node, const char *prop,
381 uint64_t value)
382{
383 struct fdt_property_ref ref;
384 if (!find_property(fdt_pa, node, prop, &ref) || ref.len < 8u) {
385 return false;
386 }
387 write_be64(ref.value, value);
388 return true;
389}
390
391static bool find_node_end(uint64_t fdt_pa, const char *node_name, uint8_t **out)
392{
393 uint8_t *fdt = (uint8_t *)(uintptr_t)fdt_pa;
394 struct fdt_header_view h;
395 uint8_t *struct_p;
396 uint8_t *struct_end;
397 bool wanted_node[FDT_MAX_DEPTH];
398 uint32_t depth = 0;
399
400 if (node_name == (const char *)0 || out == (uint8_t **)0 || !read_header(fdt, &h)) {
401 return false;
402 }
403 hv_memset(wanted_node, 0, sizeof(wanted_node));
404 struct_p = fdt + h.off_dt_struct;
405 struct_end = struct_p + h.size_dt_struct;
406
407 while (struct_p + 4u <= struct_end) {
408 uint8_t *token_p = struct_p;
409 uint32_t token = be32(struct_p);
410 struct_p += 4u;
411 if (token == FDT_BEGIN_NODE) {
412 const char *name = (const char *)struct_p;
413 if (!string_in_block(name, struct_p, struct_end) || depth + 1u >= FDT_MAX_DEPTH) {
414 return false;
415 }
416 wanted_node[depth] = hv_strcmp(name, node_name) == 0;
417 while (struct_p < struct_end && *struct_p != 0u) {
418 struct_p++;
419 }
420 if (struct_p >= struct_end) {
421 return false;
422 }
423 struct_p = (uint8_t *)align4(struct_p + 1u);
424 depth++;
425 } else if (token == FDT_END_NODE) {
426 if (depth == 0u) {
427 return false;
428 }
429 if (wanted_node[depth - 1u]) {
430 *out = token_p;
431 return true;
432 }
433 depth--;
434 wanted_node[depth] = false;
435 } else if (token == FDT_PROP) {
436 uint32_t len;
437 if (struct_p + 8u > struct_end) {
438 return false;
439 }
440 len = be32(struct_p);
441 struct_p += 8u;
442 if (len > (uint32_t)(struct_end - struct_p)) {
443 return false;
444 }
445 struct_p = (uint8_t *)align4(struct_p + len);
446 } else if (token == FDT_NOP) {
447 continue;
448 } else if (token == FDT_END) {
449 return false;
450 } else {
451 return false;
452 }
453 }
454 return false;
455}
456
457static bool append_chosen_prop(uint64_t fdt_pa, uint64_t max_size, const char *prop,
458 const uint8_t *value, uint32_t len)
459{
460 uint8_t *fdt = (uint8_t *)(uintptr_t)fdt_pa;
461 struct fdt_header_view h;
462 uint8_t *insert;
463 uint8_t *strings;
464 uint32_t prop_name_len;
465 uint32_t value_len;
466 uint32_t record_size;
467 uint32_t growth;
468 uint32_t nameoff;
469
470 if (prop == (const char *)0 || value == (const uint8_t *)0 ||
471 max_size > FDT_MAX_SIZE || !read_header(fdt, &h) ||
472 !find_node_end(fdt_pa, "chosen", &insert)) {
473 return false;
474 }
475
476 prop_name_len = (uint32_t)hv_strlen(prop) + 1u;
477 value_len = align4_u32(len);
478 record_size = 12u + value_len;
479 growth = record_size + prop_name_len;
480 if (max_size < h.totalsize || growth > max_size - h.totalsize) {
481 return false;
482 }
483
484 nameoff = h.size_dt_strings;
485 hv_memmove(insert + record_size, insert, h.totalsize - (uint32_t)(insert - fdt));
486 write_be32(insert, FDT_PROP);
487 write_be32(insert + 4u, len);
488 write_be32(insert + 8u, nameoff);
489 hv_memset(insert + 12u, 0, value_len);
490 hv_memcpy(insert + 12u, value, len);
491
492 strings = fdt + h.off_dt_strings + record_size;
493 hv_memcpy(strings + h.size_dt_strings, prop, prop_name_len);
494 write_be32(fdt + 4u, h.totalsize + growth);
495 write_be32(fdt + 12u, h.off_dt_strings + record_size);
496 write_be32(fdt + 32u, h.size_dt_strings + prop_name_len);
497 write_be32(fdt + 36u, h.size_dt_struct + record_size);
498 return true;
499}
500
501static bool ensure_chosen_u64(uint64_t fdt_pa, uint64_t max_size, const char *prop,
502 uint64_t value)
503{
504 uint8_t buf[8];
505 if (patch_u64_prop(fdt_pa, "chosen", prop, value)) {
506 return true;
507 }
508 write_be64(buf, value);
509 return append_chosen_prop(fdt_pa, max_size, prop, buf, sizeof(buf));
510}
511
512bool fdt_patch_memory(uint64_t fdt_pa, uint64_t base, uint64_t size)
513{
514 struct fdt_property_ref ref;
515
516 if (size == 0u || !find_memory_property(fdt_pa, "reg", &ref) || ref.len < 16u) {
517 return false;
518 }
519
520 write_be64(ref.value, base);
521 write_be64(ref.value + 8u, size);
522 if (ref.len > 16u) {
523 hv_memset(ref.value + 16u, 0, ref.len - 16u);
524 }
525 return true;
526}
527
528bool fdt_bootargs_valid(const char *bootargs)
529{
530 size_t len = 0;
531 if (bootargs == (const char *)0) {
532 return true;
533 }
534 while (bootargs[len] != '\0') {
535 if ((unsigned char)bootargs[len] < 0x20u || (unsigned char)bootargs[len] > 0x7eu) {
536 return false;
537 }
538 len++;
539 if (len >= FDT_BOOTARGS_MAX) {
540 return false;
541 }
542 }
543 return len != 0u;
544}
545
546bool fdt_probe(uint64_t fdt_pa, struct fdt_platform_info *info)
547{
548 const uint8_t *fdt = (const uint8_t *)(uintptr_t)fdt_pa;
549 struct fdt_header_view h;
550 const uint8_t *struct_p;
551 const uint8_t *struct_end;
552 const uint8_t *strings;
553 const uint8_t *strings_end;
554 bool memory_node[FDT_MAX_DEPTH];
555 bool chosen_node[FDT_MAX_DEPTH];
556 uint32_t depth = 0;
557 uint32_t address_cells = 2u;
558 uint32_t size_cells = 2u;
559
560 if (info == (struct fdt_platform_info *)0) {
561 return false;
562 }
563 hv_memset(info, 0, sizeof(*info));
564 if (fdt_pa == 0u || !hv_is_aligned_u64(fdt_pa, 8u)) {
565 return false;
566 }
567 if (!read_header(fdt, &h)) {
568 return false;
569 }
570
571 hv_memset(memory_node, 0, sizeof(memory_node));
572 hv_memset(chosen_node, 0, sizeof(chosen_node));
573 struct_p = fdt + h.off_dt_struct;
574 struct_end = struct_p + h.size_dt_struct;
575 strings = fdt + h.off_dt_strings;
576 strings_end = strings + h.size_dt_strings;
577
578 while (struct_p + 4u <= struct_end) {
579 uint32_t token = be32(struct_p);
580 struct_p += 4u;
581
582 if (token == FDT_BEGIN_NODE) {
583 const char *name = (const char *)struct_p;
584 if (!string_in_block(name, struct_p, struct_end) || depth + 1u >= FDT_MAX_DEPTH) {
585 return false;
586 }
587 memory_node[depth] = node_name_is_memory(name);
588 chosen_node[depth] = hv_strcmp(name, "chosen") == 0;
589 while (struct_p < struct_end && *struct_p != 0u) {
590 struct_p++;
591 }
592 if (struct_p >= struct_end) {
593 return false;
594 }
595 struct_p = align4(struct_p + 1u);
596 depth++;
597 } else if (token == FDT_END_NODE) {
598 if (depth == 0u) {
599 return false;
600 }
601 depth--;
602 memory_node[depth] = false;
603 chosen_node[depth] = false;
604 } else if (token == FDT_PROP) {
605 uint32_t len;
606 uint32_t nameoff;
607 const char *prop_name;
608 const uint8_t *value;
609
610 if (struct_p + 8u > struct_end) {
611 return false;
612 }
613 len = be32(struct_p);
614 nameoff = be32(struct_p + 4u);
615 struct_p += 8u;
616 if (nameoff >= h.size_dt_strings || len > (uint32_t)(struct_end - struct_p)) {
617 return false;
618 }
619 prop_name = (const char *)(strings + nameoff);
620 if (!string_in_block(prop_name, strings, strings_end)) {
621 return false;
622 }
623 value = struct_p;
624
625 if (depth == 1u && hv_strcmp(prop_name, "#address-cells") == 0 && len >= 4u) {
626 address_cells = be32(value);
627 } else if (depth == 1u && hv_strcmp(prop_name, "#size-cells") == 0 && len >= 4u) {
628 size_cells = be32(value);
629 } else if (depth != 0u && hv_strcmp(prop_name, "device_type") == 0 &&
630 len >= 7u && hv_strcmp((const char *)value, "memory") == 0) {
631 memory_node[depth - 1u] = true;
632 } else if (depth != 0u && memory_node[depth - 1u] &&
633 hv_strcmp(prop_name, "reg") == 0 &&
634 address_cells <= 2u && size_cells <= 2u &&
635 len >= (address_cells + size_cells) * 4u) {
636 const uint32_t *cells = (const uint32_t *)value;
637 info->memory_base = read_cells(cells, address_cells);
638 info->memory_size = read_cells(cells + address_cells, size_cells);
639 } else if (depth != 0u && chosen_node[depth - 1u]) {
640 if (hv_strcmp(prop_name, "bootargs") == 0 && len != 0u) {
641 info->bootargs = (const char *)value;
642 } else if (hv_strcmp(prop_name, "linux,initrd-start") == 0 && len >= 4u) {
643 info->initrd_start = read_cells((const uint32_t *)value, len >= 8u ? 2u : 1u);
644 } else if (hv_strcmp(prop_name, "linux,initrd-end") == 0 && len >= 4u) {
645 info->initrd_end = read_cells((const uint32_t *)value, len >= 8u ? 2u : 1u);
646 }
647 }
648
649 struct_p = align4(struct_p + len);
650 } else if (token == FDT_NOP) {
651 continue;
652 } else if (token == FDT_END) {
653 info->valid = true;
654 info->fdt_pa = fdt_pa;
655 info->size = h.totalsize;
656 return true;
657 } else {
658 return false;
659 }
660 }
661
662 return false;
663}
664
665bool fdt_patch_linux_guest(uint64_t fdt_pa, uint64_t max_size, const char *bootargs,
666 uint64_t initrd_start, uint64_t initrd_end)
667{
668 struct fdt_platform_info info;
669 bool ok = true;
670
671 if (!fdt_bootargs_valid(bootargs) || !fdt_probe(fdt_pa, &info)) {
672 return false;
673 }
674 if (bootargs != (const char *)0) {
675 ok = patch_string_prop(fdt_pa, "chosen", "bootargs", bootargs) ||
676 repurpose_string_prop(fdt_pa, "chosen", "bootargs", bootargs) ||
677 append_chosen_prop(fdt_pa, max_size, "bootargs",
678 (const uint8_t *)bootargs,
679 (uint32_t)hv_strlen(bootargs) + 1u);
680 }
681 if (initrd_start != 0u && initrd_end > initrd_start) {
682 ok = ok && ensure_chosen_u64(fdt_pa, max_size, "linux,initrd-start", initrd_start);
683 ok = ok && ensure_chosen_u64(fdt_pa, max_size, "linux,initrd-end", initrd_end);
684 }
685 return ok;
686}
687
688void fdt_dump(const struct fdt_platform_info *info)
689{
690 if (info == (const struct fdt_platform_info *)0 || !info->valid) {
691 uart_puts("fdt unavailable\n");
692 return;
693 }
694 uart_puts("fdt pa=");
695 uart_put_hex64(info->fdt_pa);
696 uart_puts(" size=");
697 uart_put_dec64(info->size);
698 uart_puts(" memory=");
699 uart_put_hex64(info->memory_base);
700 uart_puts("+");
701 uart_put_hex64(info->memory_size);
702 if (info->initrd_start != 0u || info->initrd_end != 0u) {
703 uart_puts(" initrd=");
704 uart_put_hex64(info->initrd_start);
705 uart_puts("-");
706 uart_put_hex64(info->initrd_end);
707 }
708 uart_puts("\n");
709 if (info->bootargs != (const char *)0) {
710 uart_puts("bootargs ");
711 uart_puts(info->bootargs);
712 uart_puts("\n");
713 }
714}