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