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