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 /** 296 * Append data to a memory buffer 297 * 298 * Write data to the buffer if there is space. Whether there is space or not, 299 * the buffer pointer is incremented. 300 * 301 * @param ptrp Pointer to buffer, updated by this function 302 * @param end Pointer to end of buffer 303 * @param data Data to write to buffer 304 * @param size Size of data 305 */ 306 static void append_data(char **ptrp, char *end, const void *data, int size) 307 { 308 char *ptr = *ptrp; 309 310 *ptrp += size; 311 if (*ptrp > end) 312 return; 313 314 memcpy(ptr, data, size); 315 } 316 317 int bootstage_stash(void *base, int size) 318 { 319 struct bootstage_hdr *hdr = (struct bootstage_hdr *)base; 320 struct bootstage_record *rec; 321 char buf[20]; 322 char *ptr = base, *end = ptr + size; 323 uint32_t count; 324 int id; 325 326 if (hdr + 1 > (struct bootstage_hdr *)end) { 327 debug("%s: Not enough space for bootstage hdr\n", __func__); 328 return -1; 329 } 330 331 /* Write an arbitrary version number */ 332 hdr->version = BOOTSTAGE_VERSION; 333 334 /* Count the number of records, and write that value first */ 335 for (rec = record, id = count = 0; id < BOOTSTAGE_ID_COUNT; 336 id++, rec++) { 337 if (rec->time_us != 0) 338 count++; 339 } 340 hdr->count = count; 341 hdr->size = 0; 342 hdr->magic = BOOTSTAGE_MAGIC; 343 ptr += sizeof(*hdr); 344 345 /* Write the records, silently stopping when we run out of space */ 346 for (rec = record, id = 0; id < BOOTSTAGE_ID_COUNT; id++, rec++) { 347 if (rec->time_us != 0) 348 append_data(&ptr, end, rec, sizeof(*rec)); 349 } 350 351 /* Write the name strings */ 352 for (rec = record, id = 0; id < BOOTSTAGE_ID_COUNT; id++, rec++) { 353 if (rec->time_us != 0) { 354 const char *name; 355 356 name = get_record_name(buf, sizeof(buf), rec); 357 append_data(&ptr, end, name, strlen(name) + 1); 358 } 359 } 360 361 /* Check for buffer overflow */ 362 if (ptr > end) { 363 debug("%s: Not enough space for bootstage stash\n", __func__); 364 return -1; 365 } 366 367 /* Update total data size */ 368 hdr->size = ptr - (char *)base; 369 printf("Stashed %d records\n", hdr->count); 370 371 return 0; 372 } 373 374 int bootstage_unstash(void *base, int size) 375 { 376 struct bootstage_hdr *hdr = (struct bootstage_hdr *)base; 377 struct bootstage_record *rec; 378 char *ptr = base, *end = ptr + size; 379 uint rec_size; 380 int id; 381 382 if (size == -1) 383 end = (char *)(~(uintptr_t)0); 384 385 if (hdr + 1 > (struct bootstage_hdr *)end) { 386 debug("%s: Not enough space for bootstage hdr\n", __func__); 387 return -1; 388 } 389 390 if (hdr->magic != BOOTSTAGE_MAGIC) { 391 debug("%s: Invalid bootstage magic\n", __func__); 392 return -1; 393 } 394 395 if (ptr + hdr->size > end) { 396 debug("%s: Bootstage data runs past buffer end\n", __func__); 397 return -1; 398 } 399 400 if (hdr->count * sizeof(*rec) > hdr->size) { 401 debug("%s: Bootstage has %d records needing %lu bytes, but " 402 "only %d bytes is available\n", __func__, hdr->count, 403 (ulong)hdr->count * sizeof(*rec), hdr->size); 404 return -1; 405 } 406 407 if (hdr->version != BOOTSTAGE_VERSION) { 408 debug("%s: Bootstage data version %#0x unrecognised\n", 409 __func__, hdr->version); 410 return -1; 411 } 412 413 if (next_id + hdr->count > BOOTSTAGE_ID_COUNT) { 414 debug("%s: Bootstage has %d records, we have space for %d\n" 415 "- please increase CONFIG_BOOTSTAGE_USER_COUNT\n", 416 __func__, hdr->count, BOOTSTAGE_ID_COUNT - next_id); 417 return -1; 418 } 419 420 ptr += sizeof(*hdr); 421 422 /* Read the records */ 423 rec_size = hdr->count * sizeof(*record); 424 memcpy(record + next_id, ptr, rec_size); 425 426 /* Read the name strings */ 427 ptr += rec_size; 428 for (rec = record + next_id, id = 0; id < hdr->count; id++, rec++) { 429 rec->name = ptr; 430 431 /* Assume no data corruption here */ 432 ptr += strlen(ptr) + 1; 433 } 434 435 /* Mark the records as read */ 436 next_id += hdr->count; 437 printf("Unstashed %d records\n", hdr->count); 438 439 return 0; 440 } 441