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