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