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