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