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