AArch64 EL2 hypervisor for QEMU virt that boots at EL2
1

Configure Feed

Select the types of activity you want to include in your feed.

ariel / core / fdt.c
23 kB 696 lines
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_probe(uint64_t fdt_pa, struct fdt_platform_info *info) 529{ 530 const uint8_t *fdt = (const uint8_t *)(uintptr_t)fdt_pa; 531 struct fdt_header_view h; 532 const uint8_t *struct_p; 533 const uint8_t *struct_end; 534 const uint8_t *strings; 535 const uint8_t *strings_end; 536 bool memory_node[FDT_MAX_DEPTH]; 537 bool chosen_node[FDT_MAX_DEPTH]; 538 uint32_t depth = 0; 539 uint32_t address_cells = 2u; 540 uint32_t size_cells = 2u; 541 542 if (info == (struct fdt_platform_info *)0) { 543 return false; 544 } 545 hv_memset(info, 0, sizeof(*info)); 546 if (fdt_pa == 0u || !hv_is_aligned_u64(fdt_pa, 8u)) { 547 return false; 548 } 549 if (!read_header(fdt, &h)) { 550 return false; 551 } 552 553 hv_memset(memory_node, 0, sizeof(memory_node)); 554 hv_memset(chosen_node, 0, sizeof(chosen_node)); 555 struct_p = fdt + h.off_dt_struct; 556 struct_end = struct_p + h.size_dt_struct; 557 strings = fdt + h.off_dt_strings; 558 strings_end = strings + h.size_dt_strings; 559 560 while (struct_p + 4u <= struct_end) { 561 uint32_t token = be32(struct_p); 562 struct_p += 4u; 563 564 if (token == FDT_BEGIN_NODE) { 565 const char *name = (const char *)struct_p; 566 if (!string_in_block(name, struct_p, struct_end) || depth + 1u >= FDT_MAX_DEPTH) { 567 return false; 568 } 569 memory_node[depth] = node_name_is_memory(name); 570 chosen_node[depth] = hv_strcmp(name, "chosen") == 0; 571 while (struct_p < struct_end && *struct_p != 0u) { 572 struct_p++; 573 } 574 if (struct_p >= struct_end) { 575 return false; 576 } 577 struct_p = align4(struct_p + 1u); 578 depth++; 579 } else if (token == FDT_END_NODE) { 580 if (depth == 0u) { 581 return false; 582 } 583 depth--; 584 memory_node[depth] = false; 585 chosen_node[depth] = false; 586 } else if (token == FDT_PROP) { 587 uint32_t len; 588 uint32_t nameoff; 589 const char *prop_name; 590 const uint8_t *value; 591 592 if (struct_p + 8u > struct_end) { 593 return false; 594 } 595 len = be32(struct_p); 596 nameoff = be32(struct_p + 4u); 597 struct_p += 8u; 598 if (nameoff >= h.size_dt_strings || len > (uint32_t)(struct_end - struct_p)) { 599 return false; 600 } 601 prop_name = (const char *)(strings + nameoff); 602 if (!string_in_block(prop_name, strings, strings_end)) { 603 return false; 604 } 605 value = struct_p; 606 607 if (depth == 1u && hv_strcmp(prop_name, "#address-cells") == 0 && len >= 4u) { 608 address_cells = be32(value); 609 } else if (depth == 1u && hv_strcmp(prop_name, "#size-cells") == 0 && len >= 4u) { 610 size_cells = be32(value); 611 } else if (depth != 0u && hv_strcmp(prop_name, "device_type") == 0 && 612 len >= 7u && hv_strcmp((const char *)value, "memory") == 0) { 613 memory_node[depth - 1u] = true; 614 } else if (depth != 0u && memory_node[depth - 1u] && 615 hv_strcmp(prop_name, "reg") == 0 && 616 address_cells <= 2u && size_cells <= 2u && 617 len >= (address_cells + size_cells) * 4u) { 618 const uint32_t *cells = (const uint32_t *)value; 619 info->memory_base = read_cells(cells, address_cells); 620 info->memory_size = read_cells(cells + address_cells, size_cells); 621 } else if (depth != 0u && chosen_node[depth - 1u]) { 622 if (hv_strcmp(prop_name, "bootargs") == 0 && len != 0u) { 623 info->bootargs = (const char *)value; 624 } else if (hv_strcmp(prop_name, "linux,initrd-start") == 0 && len >= 4u) { 625 info->initrd_start = read_cells((const uint32_t *)value, len >= 8u ? 2u : 1u); 626 } else if (hv_strcmp(prop_name, "linux,initrd-end") == 0 && len >= 4u) { 627 info->initrd_end = read_cells((const uint32_t *)value, len >= 8u ? 2u : 1u); 628 } 629 } 630 631 struct_p = align4(struct_p + len); 632 } else if (token == FDT_NOP) { 633 continue; 634 } else if (token == FDT_END) { 635 info->valid = true; 636 info->fdt_pa = fdt_pa; 637 info->size = h.totalsize; 638 return true; 639 } else { 640 return false; 641 } 642 } 643 644 return false; 645} 646 647bool fdt_patch_linux_guest(uint64_t fdt_pa, uint64_t max_size, const char *bootargs, 648 uint64_t initrd_start, uint64_t initrd_end) 649{ 650 struct fdt_platform_info info; 651 bool ok = true; 652 653 if (!fdt_probe(fdt_pa, &info)) { 654 return false; 655 } 656 if (bootargs != (const char *)0) { 657 ok = patch_string_prop(fdt_pa, "chosen", "bootargs", bootargs) || 658 repurpose_string_prop(fdt_pa, "chosen", "bootargs", bootargs) || 659 append_chosen_prop(fdt_pa, max_size, "bootargs", 660 (const uint8_t *)bootargs, 661 (uint32_t)hv_strlen(bootargs) + 1u); 662 } 663 if (initrd_start != 0u && initrd_end > initrd_start) { 664 ok = ok && ensure_chosen_u64(fdt_pa, max_size, "linux,initrd-start", initrd_start); 665 ok = ok && ensure_chosen_u64(fdt_pa, max_size, "linux,initrd-end", initrd_end); 666 } 667 return ok; 668} 669 670void fdt_dump(const struct fdt_platform_info *info) 671{ 672 if (info == (const struct fdt_platform_info *)0 || !info->valid) { 673 uart_puts("fdt unavailable\n"); 674 return; 675 } 676 uart_puts("fdt pa="); 677 uart_put_hex64(info->fdt_pa); 678 uart_puts(" size="); 679 uart_put_dec64(info->size); 680 uart_puts(" memory="); 681 uart_put_hex64(info->memory_base); 682 uart_puts("+"); 683 uart_put_hex64(info->memory_size); 684 if (info->initrd_start != 0u || info->initrd_end != 0u) { 685 uart_puts(" initrd="); 686 uart_put_hex64(info->initrd_start); 687 uart_puts("-"); 688 uart_put_hex64(info->initrd_end); 689 } 690 uart_puts("\n"); 691 if (info->bootargs != (const char *)0) { 692 uart_puts("bootargs "); 693 uart_puts(info->bootargs); 694 uart_puts("\n"); 695 } 696}