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