]> git.kernelconcepts.de Git - karo-tx-uboot.git/blob - common/bootstage.c
x86: Add CBMEM console driver for coreboot
[karo-tx-uboot.git] / common / bootstage.c
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 }