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 ulong bootstage_add_record(enum bootstage_id id, const char *name, 48 int flags, ulong mark) 49 { 50 struct bootstage_record *rec; 51 52 if (flags & BOOTSTAGEF_ALLOC) 53 id = next_id++; 54 55 if (id < BOOTSTAGE_ID_COUNT) { 56 rec = &record[id]; 57 58 /* Only record the first event for each */ 59 if (!rec->time_us) { 60 rec->time_us = mark; 61 rec->name = name; 62 rec->flags = flags; 63 rec->id = id; 64 } 65 } 66 67 /* Tell the board about this progress */ 68 show_boot_progress(flags & BOOTSTAGEF_ERROR ? -id : id); 69 return mark; 70 } 71 72 73 ulong bootstage_mark(enum bootstage_id id) 74 { 75 return bootstage_add_record(id, NULL, 0, timer_get_boot_us()); 76 } 77 78 ulong bootstage_error(enum bootstage_id id) 79 { 80 return bootstage_add_record(id, NULL, BOOTSTAGEF_ERROR, 81 timer_get_boot_us()); 82 } 83 84 ulong bootstage_mark_name(enum bootstage_id id, const char *name) 85 { 86 int flags = 0; 87 88 if (id == BOOTSTAGE_ID_ALLOC) 89 flags = BOOTSTAGEF_ALLOC; 90 return bootstage_add_record(id, name, flags, timer_get_boot_us()); 91 } 92 93 uint32_t bootstage_start(enum bootstage_id id, const char *name) 94 { 95 struct bootstage_record *rec = &record[id]; 96 97 rec->start_us = timer_get_boot_us(); 98 rec->name = name; 99 return rec->start_us; 100 } 101 102 uint32_t bootstage_accum(enum bootstage_id id) 103 { 104 struct bootstage_record *rec = &record[id]; 105 uint32_t duration; 106 107 duration = (uint32_t)timer_get_boot_us() - rec->start_us; 108 rec->time_us += duration; 109 return duration; 110 } 111 112 static void print_time(unsigned long us_time) 113 { 114 char str[15], *s; 115 int grab = 3; 116 117 /* We don't seem to have %'d in U-Boot */ 118 sprintf(str, "%12lu", us_time); 119 for (s = str + 3; *s; s += grab) { 120 if (s != str + 3) 121 putc(s[-1] != ' ' ? ',' : ' '); 122 printf("%.*s", grab, s); 123 grab = 3; 124 } 125 } 126 127 /** 128 * Get a record name as a printable string 129 * 130 * @param buf Buffer to put name if needed 131 * @param len Length of buffer 132 * @param rec Boot stage record to get the name from 133 * @return pointer to name, either from the record or pointing to buf. 134 */ 135 static const char *get_record_name(char *buf, int len, 136 struct bootstage_record *rec) 137 { 138 if (rec->name) 139 return rec->name; 140 else if (rec->id >= BOOTSTAGE_ID_USER) 141 snprintf(buf, len, "user_%d", rec->id - BOOTSTAGE_ID_USER); 142 else 143 snprintf(buf, len, "id=%d", rec->id); 144 145 return buf; 146 } 147 148 static uint32_t print_time_record(enum bootstage_id id, 149 struct bootstage_record *rec, uint32_t prev) 150 { 151 char buf[20]; 152 153 if (prev == -1U) { 154 printf("%11s", ""); 155 print_time(rec->time_us); 156 } else { 157 print_time(rec->time_us); 158 print_time(rec->time_us - prev); 159 } 160 printf(" %s\n", get_record_name(buf, sizeof(buf), rec)); 161 162 return rec->time_us; 163 } 164 165 static int h_compare_record(const void *r1, const void *r2) 166 { 167 const struct bootstage_record *rec1 = r1, *rec2 = r2; 168 169 return rec1->time_us > rec2->time_us ? 1 : -1; 170 } 171 172 #ifdef CONFIG_OF_LIBFDT 173 /** 174 * Add all bootstage timings to a device tree. 175 * 176 * @param blob Device tree blob 177 * @return 0 on success, != 0 on failure. 178 */ 179 static int add_bootstages_devicetree(struct fdt_header *blob) 180 { 181 int bootstage; 182 char buf[20]; 183 int id; 184 int i; 185 186 if (!blob) 187 return 0; 188 189 /* 190 * Create the node for bootstage. 191 * The address of flat device tree is set up by the command bootm. 192 */ 193 bootstage = fdt_add_subnode(blob, 0, "bootstage"); 194 if (bootstage < 0) 195 return -1; 196 197 /* 198 * Insert the timings to the device tree in the reverse order so 199 * that they can be printed in the Linux kernel in the right order. 200 */ 201 for (id = BOOTSTAGE_ID_COUNT - 1, i = 0; id >= 0; id--, i++) { 202 struct bootstage_record *rec = &record[id]; 203 int node; 204 205 if (id != BOOTSTAGE_ID_AWAKE && rec->time_us == 0) 206 continue; 207 208 node = fdt_add_subnode(blob, bootstage, simple_itoa(i)); 209 if (node < 0) 210 break; 211 212 /* add properties to the node. */ 213 if (fdt_setprop_string(blob, node, "name", 214 get_record_name(buf, sizeof(buf), rec))) 215 return -1; 216 217 /* Check if this is a 'mark' or 'accum' record */ 218 if (fdt_setprop_cell(blob, node, 219 rec->start_us ? "accum" : "mark", 220 rec->time_us)) 221 return -1; 222 } 223 224 return 0; 225 } 226 227 int bootstage_fdt_add_report(void) 228 { 229 if (add_bootstages_devicetree(working_fdt)) 230 puts("bootstage: Failed to add to device tree\n"); 231 232 return 0; 233 } 234 #endif 235 236 void bootstage_report(void) 237 { 238 struct bootstage_record *rec = record; 239 int id; 240 uint32_t prev; 241 242 puts("Timer summary in microseconds:\n"); 243 printf("%11s%11s %s\n", "Mark", "Elapsed", "Stage"); 244 245 /* Fake the first record - we could get it from early boot */ 246 rec->name = "reset"; 247 rec->time_us = 0; 248 prev = print_time_record(BOOTSTAGE_ID_AWAKE, rec, 0); 249 250 /* Sort records by increasing time */ 251 qsort(record, ARRAY_SIZE(record), sizeof(*rec), h_compare_record); 252 253 for (id = 0; id < BOOTSTAGE_ID_COUNT; id++, rec++) { 254 if (rec->time_us != 0 && !rec->start_us) 255 prev = print_time_record(rec->id, rec, prev); 256 } 257 if (next_id > BOOTSTAGE_ID_COUNT) 258 printf("(Overflowed internal boot id table by %d entries\n" 259 "- please increase CONFIG_BOOTSTAGE_USER_COUNT\n", 260 next_id - BOOTSTAGE_ID_COUNT); 261 262 puts("\nAccumulated time:\n"); 263 for (id = 0, rec = record; id < BOOTSTAGE_ID_COUNT; id++, rec++) { 264 if (rec->start_us) 265 prev = print_time_record(id, rec, -1); 266 } 267 } 268 269 ulong __timer_get_boot_us(void) 270 { 271 static ulong base_time; 272 273 /* 274 * We can't implement this properly. Return 0 on the first call and 275 * larger values after that. 276 */ 277 if (base_time) 278 return get_timer(base_time) * 1000; 279 base_time = get_timer(0); 280 return 0; 281 } 282 283 ulong timer_get_boot_us(void) 284 __attribute__((weak, alias("__timer_get_boot_us"))); 285