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