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