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