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