"Das U-Boot" Source Tree
at master 548 lines 13 kB view raw
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (c) 2011, Google Inc. All rights reserved. 4 */ 5 6/* 7 * This module records the progress of boot and arbitrary commands, and 8 * permits accurate timestamping of each. 9 */ 10 11#define LOG_CATEGORY LOGC_BOOT 12 13#include <bootstage.h> 14#include <hang.h> 15#include <log.h> 16#include <malloc.h> 17#include <sort.h> 18#include <spl.h> 19#include <asm/global_data.h> 20#include <linux/compiler.h> 21#include <linux/libfdt.h> 22 23DECLARE_GLOBAL_DATA_PTR; 24 25enum { 26 RECORD_COUNT = CONFIG_VAL(BOOTSTAGE_RECORD_COUNT), 27}; 28 29struct bootstage_record { 30 ulong time_us; 31 uint32_t start_us; 32 const char *name; 33 int flags; /* see enum bootstage_flags */ 34 enum bootstage_id id; 35}; 36 37struct bootstage_data { 38 uint rec_count; 39 uint next_id; 40 struct bootstage_record record[RECORD_COUNT]; 41}; 42 43enum { 44 BOOTSTAGE_VERSION = 0, 45 BOOTSTAGE_MAGIC = 0xb00757a3, 46 BOOTSTAGE_DIGITS = 9, 47}; 48 49struct bootstage_hdr { 50 u32 version; /* BOOTSTAGE_VERSION */ 51 u32 count; /* Number of records */ 52 u32 size; /* Total data size (non-zero if valid) */ 53 u32 magic; /* Magic number */ 54 u32 next_id; /* Next ID to use for bootstage */ 55}; 56 57int bootstage_relocate(void *to) 58{ 59 struct bootstage_data *data; 60 int i; 61 char *ptr; 62 63 debug("Copying bootstage from %p to %p\n", gd->bootstage, to); 64 memcpy(to, gd->bootstage, sizeof(struct bootstage_data)); 65 data = gd->bootstage = to; 66 67 /* Figure out where to relocate the strings to */ 68 ptr = (char *)(data + 1); 69 70 /* 71 * Duplicate all strings. They may point to an old location in the 72 * program .text section that can eventually get trashed. 73 */ 74 debug("Relocating %d records\n", data->rec_count); 75 for (i = 0; i < data->rec_count; i++) { 76 const char *from = data->record[i].name; 77 78 strcpy(ptr, from); 79 data->record[i].name = ptr; 80 ptr += strlen(ptr) + 1; 81 } 82 83 return 0; 84} 85 86struct bootstage_record *find_id(struct bootstage_data *data, 87 enum bootstage_id id) 88{ 89 struct bootstage_record *rec; 90 struct bootstage_record *end; 91 92 for (rec = data->record, end = rec + data->rec_count; rec < end; 93 rec++) { 94 if (rec->id == id) 95 return rec; 96 } 97 98 return NULL; 99} 100 101struct bootstage_record *ensure_id(struct bootstage_data *data, 102 enum bootstage_id id) 103{ 104 struct bootstage_record *rec; 105 106 rec = find_id(data, id); 107 if (!rec && data->rec_count < RECORD_COUNT) { 108 rec = &data->record[data->rec_count++]; 109 rec->id = id; 110 return rec; 111 } 112 113 return rec; 114} 115 116ulong bootstage_add_record(enum bootstage_id id, const char *name, 117 int flags, ulong mark) 118{ 119 struct bootstage_data *data = gd->bootstage; 120 struct bootstage_record *rec; 121 122 /* 123 * initf_bootstage() is called very early during boot but since hang() 124 * calls bootstage_error() we can be called before bootstage is set up. 125 * Add a check to avoid this. 126 */ 127 if (!data) 128 return mark; 129 if (flags & BOOTSTAGEF_ALLOC) 130 id = data->next_id++; 131 132 /* Only record the first event for each */ 133 rec = find_id(data, id); 134 if (!rec) { 135 if (data->rec_count < RECORD_COUNT) { 136 rec = &data->record[data->rec_count++]; 137 rec->time_us = mark; 138 rec->name = name; 139 rec->flags = flags; 140 rec->id = id; 141 } else { 142 log_warning("Bootstage space exhausted\n"); 143 } 144 } 145 146 /* Tell the board about this progress */ 147 show_boot_progress(flags & BOOTSTAGEF_ERROR ? -id : id); 148 149 return mark; 150} 151 152ulong bootstage_error_name(enum bootstage_id id, const char *name) 153{ 154 return bootstage_add_record(id, name, BOOTSTAGEF_ERROR, 155 timer_get_boot_us()); 156} 157 158ulong bootstage_mark_name(enum bootstage_id id, const char *name) 159{ 160 int flags = 0; 161 162 if (id == BOOTSTAGE_ID_ALLOC) 163 flags = BOOTSTAGEF_ALLOC; 164 165 return bootstage_add_record(id, name, flags, timer_get_boot_us()); 166} 167 168ulong bootstage_mark_code(const char *file, const char *func, int linenum) 169{ 170 char *str, *p; 171 __maybe_unused char *end; 172 int len = 0; 173 174 /* First work out the length we need to allocate */ 175 if (linenum != -1) 176 len = 11; 177 if (func) 178 len += strlen(func); 179 if (file) 180 len += strlen(file); 181 182 str = malloc(len + 1); 183 p = str; 184 end = p + len; 185 if (file) 186 p += snprintf(p, end - p, "%s,", file); 187 if (linenum != -1) 188 p += snprintf(p, end - p, "%d", linenum); 189 if (func) 190 p += snprintf(p, end - p, ": %s", func); 191 192 return bootstage_mark_name(BOOTSTAGE_ID_ALLOC, str); 193} 194 195uint32_t bootstage_start(enum bootstage_id id, const char *name) 196{ 197 struct bootstage_data *data = gd->bootstage; 198 struct bootstage_record *rec = ensure_id(data, id); 199 ulong start_us = timer_get_boot_us(); 200 201 if (rec) { 202 rec->start_us = start_us; 203 rec->name = name; 204 } 205 206 return start_us; 207} 208 209uint32_t bootstage_accum(enum bootstage_id id) 210{ 211 struct bootstage_data *data = gd->bootstage; 212 struct bootstage_record *rec = ensure_id(data, id); 213 uint32_t duration; 214 215 if (!rec) 216 return 0; 217 duration = (uint32_t)timer_get_boot_us() - rec->start_us; 218 rec->time_us += duration; 219 220 return duration; 221} 222 223/** 224 * Get a record name as a printable string 225 * 226 * @param buf Buffer to put name if needed 227 * @param len Length of buffer 228 * @param rec Boot stage record to get the name from 229 * Return: pointer to name, either from the record or pointing to buf. 230 */ 231static const char *get_record_name(char *buf, int len, 232 const struct bootstage_record *rec) 233{ 234 if (rec->name) 235 return rec->name; 236 else if (rec->id >= BOOTSTAGE_ID_USER) 237 snprintf(buf, len, "user_%d", rec->id - BOOTSTAGE_ID_USER); 238 else 239 snprintf(buf, len, "id=%d", rec->id); 240 241 return buf; 242} 243 244static uint32_t print_time_record(struct bootstage_record *rec, uint32_t prev) 245{ 246 char buf[20]; 247 248 if (prev == -1U) { 249 printf("%11s", ""); 250 print_grouped_ull(rec->time_us, BOOTSTAGE_DIGITS); 251 } else { 252 if (prev > rec->time_us) 253 prev = 0; 254 print_grouped_ull(rec->time_us, BOOTSTAGE_DIGITS); 255 print_grouped_ull(rec->time_us - prev, BOOTSTAGE_DIGITS); 256 } 257 printf(" %s\n", get_record_name(buf, sizeof(buf), rec)); 258 259 return rec->time_us; 260} 261 262#ifdef CONFIG_OF_LIBFDT 263/** 264 * Add all bootstage timings to a device tree. 265 * 266 * @param blob Device tree blob 267 * Return: 0 on success, != 0 on failure. 268 */ 269static int add_bootstages_devicetree(struct fdt_header *blob) 270{ 271 struct bootstage_data *data = gd->bootstage; 272 int bootstage; 273 char buf[20]; 274 int recnum; 275 int i; 276 277 if (!blob) 278 return 0; 279 280 /* 281 * Create the node for bootstage. 282 * The address of flat device tree is set up by the command bootm. 283 */ 284 bootstage = fdt_add_subnode(blob, 0, "bootstage"); 285 if (bootstage < 0) 286 return -EINVAL; 287 288 /* 289 * Insert the timings to the device tree in the reverse order so 290 * that they can be printed in the Linux kernel in the right order. 291 */ 292 for (recnum = data->rec_count - 1, i = 0; recnum >= 0; recnum--, i++) { 293 struct bootstage_record *rec = &data->record[recnum]; 294 int node; 295 296 if (rec->id != BOOTSTAGE_ID_AWAKE && rec->time_us == 0) 297 continue; 298 299 node = fdt_add_subnode(blob, bootstage, simple_itoa(i)); 300 if (node < 0) 301 break; 302 303 /* add properties to the node. */ 304 if (fdt_setprop_string(blob, node, "name", 305 get_record_name(buf, sizeof(buf), rec))) 306 return -EINVAL; 307 308 /* Check if this is a 'mark' or 'accum' record */ 309 if (fdt_setprop_cell(blob, node, 310 rec->start_us ? "accum" : "mark", 311 rec->time_us)) 312 return -EINVAL; 313 } 314 315 return 0; 316} 317 318int bootstage_fdt_add_report(void) 319{ 320 if (add_bootstages_devicetree(working_fdt)) 321 puts("bootstage: Failed to add to device tree\n"); 322 323 return 0; 324} 325#endif 326 327void bootstage_report(void) 328{ 329 struct bootstage_data *data = gd->bootstage; 330 struct bootstage_record *rec = data->record; 331 uint32_t prev; 332 int i; 333 334 printf("Timer summary in microseconds (%d records):\n", 335 data->rec_count); 336 printf("%11s%11s %s\n", "Mark", "Elapsed", "Stage"); 337 338 prev = print_time_record(rec, 0); 339 340 for (i = 1, rec++; i < data->rec_count; i++, rec++) { 341 if (rec->id && !rec->start_us) 342 prev = print_time_record(rec, prev); 343 } 344 if (data->rec_count > RECORD_COUNT) 345 printf("Overflowed internal boot id table by %d entries\n" 346 "Please increase CONFIG_(PHASE_)BOOTSTAGE_RECORD_COUNT\n", 347 data->rec_count - RECORD_COUNT); 348 349 puts("\nAccumulated time:\n"); 350 for (i = 0, rec = data->record; i < data->rec_count; i++, rec++) { 351 if (rec->start_us) 352 prev = print_time_record(rec, -1); 353 } 354} 355 356/** 357 * Append data to a memory buffer 358 * 359 * Write data to the buffer if there is space. Whether there is space or not, 360 * the buffer pointer is incremented. 361 * 362 * @param ptrp Pointer to buffer, updated by this function 363 * @param end Pointer to end of buffer 364 * @param data Data to write to buffer 365 * @param size Size of data 366 */ 367static void append_data(char **ptrp, char *end, const void *data, int size) 368{ 369 char *ptr = *ptrp; 370 371 *ptrp += size; 372 if (*ptrp > end) 373 return; 374 375 memcpy(ptr, data, size); 376} 377 378int bootstage_stash(void *base, int size) 379{ 380 const struct bootstage_data *data = gd->bootstage; 381 struct bootstage_hdr *hdr = (struct bootstage_hdr *)base; 382 const struct bootstage_record *rec; 383 char buf[20]; 384 char *ptr = base, *end = ptr + size; 385 int i; 386 387 if (hdr + 1 > (struct bootstage_hdr *)end) { 388 debug("%s: Not enough space for bootstage hdr\n", __func__); 389 return -ENOSPC; 390 } 391 392 /* Write an arbitrary version number */ 393 hdr->version = BOOTSTAGE_VERSION; 394 395 hdr->count = data->rec_count; 396 hdr->size = 0; 397 hdr->magic = BOOTSTAGE_MAGIC; 398 hdr->next_id = data->next_id; 399 ptr += sizeof(*hdr); 400 401 /* Write the records, silently stopping when we run out of space */ 402 for (rec = data->record, i = 0; i < data->rec_count; i++, rec++) 403 append_data(&ptr, end, rec, sizeof(*rec)); 404 405 /* Write the name strings */ 406 for (rec = data->record, i = 0; i < data->rec_count; i++, rec++) { 407 const char *name; 408 409 name = get_record_name(buf, sizeof(buf), rec); 410 append_data(&ptr, end, name, strlen(name) + 1); 411 } 412 413 /* Check for buffer overflow */ 414 if (ptr > end) { 415 debug("%s: Not enough space for bootstage stash\n", __func__); 416 return -ENOSPC; 417 } 418 419 /* Update total data size */ 420 hdr->size = ptr - (char *)base; 421 debug("Stashed %d records\n", hdr->count); 422 423 return 0; 424} 425 426int bootstage_unstash(const void *base, int size) 427{ 428 const struct bootstage_hdr *hdr = (struct bootstage_hdr *)base; 429 struct bootstage_data *data = gd->bootstage; 430 const char *ptr = base, *end = ptr + size; 431 struct bootstage_record *rec; 432 uint rec_size; 433 int i; 434 435 if (size == -1) 436 end = (char *)(~(uintptr_t)0); 437 438 if (hdr + 1 > (struct bootstage_hdr *)end) { 439 debug("%s: Not enough space for bootstage hdr\n", __func__); 440 return -EPERM; 441 } 442 443 if (hdr->magic != BOOTSTAGE_MAGIC) { 444 debug("%s: Invalid bootstage magic\n", __func__); 445 return -ENOENT; 446 } 447 448 if (ptr + hdr->size > end) { 449 debug("%s: Bootstage data runs past buffer end\n", __func__); 450 return -ENOSPC; 451 } 452 453 if (hdr->count * sizeof(*rec) > hdr->size) { 454 debug("%s: Bootstage has %d records needing %lu bytes, but " 455 "only %d bytes is available\n", __func__, hdr->count, 456 (ulong)hdr->count * sizeof(*rec), hdr->size); 457 return -ENOSPC; 458 } 459 460 if (hdr->version != BOOTSTAGE_VERSION) { 461 debug("%s: Bootstage data version %#0x unrecognised\n", 462 __func__, hdr->version); 463 return -EINVAL; 464 } 465 466 if (data->rec_count + hdr->count > RECORD_COUNT) { 467 debug("%s: Bootstage has %d records, we have space for %d\n" 468 "Please increase CONFIG_(PHASE_)BOOTSTAGE_RECORD_COUNT\n", 469 __func__, hdr->count, RECORD_COUNT - data->rec_count); 470 return -ENOSPC; 471 } 472 473 ptr += sizeof(*hdr); 474 475 /* Read the records */ 476 rec_size = hdr->count * sizeof(*data->record); 477 memcpy(data->record + data->rec_count, ptr, rec_size); 478 479 /* Read the name strings */ 480 ptr += rec_size; 481 for (rec = data->record + data->next_id, i = 0; i < hdr->count; 482 i++, rec++) { 483 rec->name = ptr; 484 if (xpl_phase() == PHASE_SPL) 485 rec->name = strdup(ptr); 486 487 /* Assume no data corruption here */ 488 ptr += strlen(ptr) + 1; 489 } 490 491 /* Mark the records as read */ 492 data->rec_count += hdr->count; 493 data->next_id = hdr->next_id; 494 debug("Unstashed %d records\n", hdr->count); 495 496 return 0; 497} 498 499#if IS_ENABLED(CONFIG_BOOTSTAGE_STASH) 500int _bootstage_stash_default(void) 501{ 502 return bootstage_stash(map_sysmem(CONFIG_BOOTSTAGE_STASH_ADDR, 0), 503 CONFIG_BOOTSTAGE_STASH_SIZE); 504} 505 506int _bootstage_unstash_default(void) 507{ 508 const void *stash = map_sysmem(CONFIG_BOOTSTAGE_STASH_ADDR, 509 CONFIG_BOOTSTAGE_STASH_SIZE); 510 511 return bootstage_unstash(stash, CONFIG_BOOTSTAGE_STASH_SIZE); 512} 513#endif 514 515int bootstage_get_size(bool add_strings) 516{ 517 int size; 518 519 size = sizeof(struct bootstage_data); 520 if (add_strings) { 521 struct bootstage_data *data = gd->bootstage; 522 struct bootstage_record *rec; 523 int i; 524 525 for (rec = data->record, i = 0; i < data->rec_count; i++, rec++) 526 size += strlen(rec->name) + 1; 527 } 528 529 return size; 530} 531 532int bootstage_init(bool first) 533{ 534 struct bootstage_data *data; 535 int size = sizeof(struct bootstage_data); 536 537 gd->bootstage = (struct bootstage_data *)malloc(size); 538 if (!gd->bootstage) 539 return -ENOMEM; 540 data = gd->bootstage; 541 memset(data, '\0', size); 542 if (first) { 543 data->next_id = BOOTSTAGE_ID_USER; 544 bootstage_add_record(BOOTSTAGE_ID_AWAKE, "reset", 0, 0); 545 } 546 547 return 0; 548}