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