Merge branch 'agust@denx.de-next' of git://git.denx.de/u-boot-staging

This commit is contained in:
Tom Rini 2012-10-15 13:37:22 -07:00
commit bd23b22bad
6 changed files with 517 additions and 19 deletions

25
README
View File

@ -2391,6 +2391,31 @@ The following options need to be configured:
29,916,167 26,005,792 bootm_start
30,361,327 445,160 start_kernel
CONFIG_CMD_BOOTSTAGE
Add a 'bootstage' command which supports printing a report
and un/stashing of bootstage data.
CONFIG_BOOTSTAGE_FDT
Stash the bootstage information in the FDT. A root 'bootstage'
node is created with each bootstage id as a child. Each child
has a 'name' property and either 'mark' containing the
mark time in microsecond, or 'accum' containing the
accumulated time for that bootstage id in microseconds.
For example:
bootstage {
154 {
name = "board_init_f";
mark = <3575678>;
};
170 {
name = "lcd";
accum = <33482>;
};
};
Code in the Linux kernel can find this in /proc/devicetree.
Legacy uImage format:
Arg Where When

View File

@ -96,6 +96,9 @@ static void announce_and_cleanup(void)
{
printf("\nStarting kernel ...\n\n");
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF, "start_kernel");
#ifdef CONFIG_BOOTSTAGE_FDT
bootstage_fdt_add_report();
#endif
#ifdef CONFIG_BOOTSTAGE_REPORT
bootstage_report();
#endif

View File

@ -68,6 +68,7 @@ COBJS-$(CONFIG_CMD_BDI) += cmd_bdinfo.o
COBJS-$(CONFIG_CMD_BEDBUG) += bedbug.o cmd_bedbug.o
COBJS-$(CONFIG_CMD_BMP) += cmd_bmp.o
COBJS-$(CONFIG_CMD_BOOTLDR) += cmd_bootldr.o
COBJS-$(CONFIG_CMD_BOOTSTAGE) += cmd_bootstage.o
COBJS-$(CONFIG_CMD_CACHE) += cmd_cache.o
COBJS-$(CONFIG_CMD_CONSOLE) += cmd_console.o
COBJS-$(CONFIG_CMD_CPLBINFO) += cmd_cplbinfo.o

View File

@ -33,13 +33,9 @@
DECLARE_GLOBAL_DATA_PTR;
enum bootstage_flags {
BOOTSTAGEF_ERROR = 1 << 0, /* Error record */
BOOTSTAGEF_ALLOC = 1 << 1, /* Allocate an id */
};
struct bootstage_record {
ulong time_us;
uint32_t start_us;
const char *name;
int flags; /* see enum bootstage_flags */
enum bootstage_id id;
@ -48,11 +44,22 @@ struct bootstage_record {
static struct bootstage_record record[BOOTSTAGE_ID_COUNT] = { {1} };
static int next_id = BOOTSTAGE_ID_USER;
enum {
BOOTSTAGE_VERSION = 0,
BOOTSTAGE_MAGIC = 0xb00757a3,
};
struct bootstage_hdr {
uint32_t version; /* BOOTSTAGE_VERSION */
uint32_t count; /* Number of records */
uint32_t size; /* Total data size (non-zero if valid) */
uint32_t magic; /* Unused */
};
ulong bootstage_add_record(enum bootstage_id id, const char *name,
int flags)
int flags, ulong mark)
{
struct bootstage_record *rec;
ulong mark = timer_get_boot_us();
if (flags & BOOTSTAGEF_ALLOC)
id = next_id++;
@ -77,12 +84,13 @@ ulong bootstage_add_record(enum bootstage_id id, const char *name,
ulong bootstage_mark(enum bootstage_id id)
{
return bootstage_add_record(id, NULL, 0);
return bootstage_add_record(id, NULL, 0, timer_get_boot_us());
}
ulong bootstage_error(enum bootstage_id id)
{
return bootstage_add_record(id, NULL, BOOTSTAGEF_ERROR);
return bootstage_add_record(id, NULL, BOOTSTAGEF_ERROR,
timer_get_boot_us());
}
ulong bootstage_mark_name(enum bootstage_id id, const char *name)
@ -91,7 +99,26 @@ ulong bootstage_mark_name(enum bootstage_id id, const char *name)
if (id == BOOTSTAGE_ID_ALLOC)
flags = BOOTSTAGEF_ALLOC;
return bootstage_add_record(id, name, flags);
return bootstage_add_record(id, name, flags, timer_get_boot_us());
}
uint32_t bootstage_start(enum bootstage_id id, const char *name)
{
struct bootstage_record *rec = &record[id];
rec->start_us = timer_get_boot_us();
rec->name = name;
return rec->start_us;
}
uint32_t bootstage_accum(enum bootstage_id id)
{
struct bootstage_record *rec = &record[id];
uint32_t duration;
duration = (uint32_t)timer_get_boot_us() - rec->start_us;
rec->time_us += duration;
return duration;
}
static void print_time(unsigned long us_time)
@ -109,17 +136,41 @@ static void print_time(unsigned long us_time)
}
}
/**
* Get a record name as a printable string
*
* @param buf Buffer to put name if needed
* @param len Length of buffer
* @param rec Boot stage record to get the name from
* @return pointer to name, either from the record or pointing to buf.
*/
static const char *get_record_name(char *buf, int len,
struct bootstage_record *rec)
{
if (rec->name)
return rec->name;
else if (rec->id >= BOOTSTAGE_ID_USER)
snprintf(buf, len, "user_%d", rec->id - BOOTSTAGE_ID_USER);
else
snprintf(buf, len, "id=%d", rec->id);
return buf;
}
static uint32_t print_time_record(enum bootstage_id id,
struct bootstage_record *rec, uint32_t prev)
{
print_time(rec->time_us);
print_time(rec->time_us - prev);
if (rec->name)
printf(" %s\n", rec->name);
else if (id >= BOOTSTAGE_ID_USER)
printf(" user_%d\n", id - BOOTSTAGE_ID_USER);
else
printf(" id=%d\n", id);
char buf[20];
if (prev == -1U) {
printf("%11s", "");
print_time(rec->time_us);
} else {
print_time(rec->time_us);
print_time(rec->time_us - prev);
}
printf(" %s\n", get_record_name(buf, sizeof(buf), rec));
return rec->time_us;
}
@ -130,6 +181,70 @@ static int h_compare_record(const void *r1, const void *r2)
return rec1->time_us > rec2->time_us ? 1 : -1;
}
#ifdef CONFIG_OF_LIBFDT
/**
* Add all bootstage timings to a device tree.
*
* @param blob Device tree blob
* @return 0 on success, != 0 on failure.
*/
static int add_bootstages_devicetree(struct fdt_header *blob)
{
int bootstage;
char buf[20];
int id;
int i;
if (!blob)
return 0;
/*
* Create the node for bootstage.
* The address of flat device tree is set up by the command bootm.
*/
bootstage = fdt_add_subnode(blob, 0, "bootstage");
if (bootstage < 0)
return -1;
/*
* Insert the timings to the device tree in the reverse order so
* that they can be printed in the Linux kernel in the right order.
*/
for (id = BOOTSTAGE_ID_COUNT - 1, i = 0; id >= 0; id--, i++) {
struct bootstage_record *rec = &record[id];
int node;
if (id != BOOTSTAGE_ID_AWAKE && rec->time_us == 0)
continue;
node = fdt_add_subnode(blob, bootstage, simple_itoa(i));
if (node < 0)
break;
/* add properties to the node. */
if (fdt_setprop_string(blob, node, "name",
get_record_name(buf, sizeof(buf), rec)))
return -1;
/* Check if this is a 'mark' or 'accum' record */
if (fdt_setprop_cell(blob, node,
rec->start_us ? "accum" : "mark",
rec->time_us))
return -1;
}
return 0;
}
int bootstage_fdt_add_report(void)
{
if (add_bootstages_devicetree(working_fdt))
puts("bootstage: Failed to add to device tree\n");
return 0;
}
#endif
void bootstage_report(void)
{
struct bootstage_record *rec = record;
@ -148,13 +263,19 @@ void bootstage_report(void)
qsort(record, ARRAY_SIZE(record), sizeof(*rec), h_compare_record);
for (id = 0; id < BOOTSTAGE_ID_COUNT; id++, rec++) {
if (rec->time_us != 0)
if (rec->time_us != 0 && !rec->start_us)
prev = print_time_record(rec->id, rec, prev);
}
if (next_id > BOOTSTAGE_ID_COUNT)
printf("(Overflowed internal boot id table by %d entries\n"
"- please increase CONFIG_BOOTSTAGE_USER_COUNT\n",
next_id - BOOTSTAGE_ID_COUNT);
puts("\nAccumulated time:\n");
for (id = 0, rec = record; id < BOOTSTAGE_ID_COUNT; id++, rec++) {
if (rec->start_us)
prev = print_time_record(id, rec, -1);
}
}
ulong __timer_get_boot_us(void)
@ -173,3 +294,150 @@ ulong __timer_get_boot_us(void)
ulong timer_get_boot_us(void)
__attribute__((weak, alias("__timer_get_boot_us")));
/**
* Append data to a memory buffer
*
* Write data to the buffer if there is space. Whether there is space or not,
* the buffer pointer is incremented.
*
* @param ptrp Pointer to buffer, updated by this function
* @param end Pointer to end of buffer
* @param data Data to write to buffer
* @param size Size of data
*/
static void append_data(char **ptrp, char *end, const void *data, int size)
{
char *ptr = *ptrp;
*ptrp += size;
if (*ptrp > end)
return;
memcpy(ptr, data, size);
}
int bootstage_stash(void *base, int size)
{
struct bootstage_hdr *hdr = (struct bootstage_hdr *)base;
struct bootstage_record *rec;
char buf[20];
char *ptr = base, *end = ptr + size;
uint32_t count;
int id;
if (hdr + 1 > (struct bootstage_hdr *)end) {
debug("%s: Not enough space for bootstage hdr\n", __func__);
return -1;
}
/* Write an arbitrary version number */
hdr->version = BOOTSTAGE_VERSION;
/* Count the number of records, and write that value first */
for (rec = record, id = count = 0; id < BOOTSTAGE_ID_COUNT;
id++, rec++) {
if (rec->time_us != 0)
count++;
}
hdr->count = count;
hdr->size = 0;
hdr->magic = BOOTSTAGE_MAGIC;
ptr += sizeof(*hdr);
/* Write the records, silently stopping when we run out of space */
for (rec = record, id = 0; id < BOOTSTAGE_ID_COUNT; id++, rec++) {
if (rec->time_us != 0)
append_data(&ptr, end, rec, sizeof(*rec));
}
/* Write the name strings */
for (rec = record, id = 0; id < BOOTSTAGE_ID_COUNT; id++, rec++) {
if (rec->time_us != 0) {
const char *name;
name = get_record_name(buf, sizeof(buf), rec);
append_data(&ptr, end, name, strlen(name) + 1);
}
}
/* Check for buffer overflow */
if (ptr > end) {
debug("%s: Not enough space for bootstage stash\n", __func__);
return -1;
}
/* Update total data size */
hdr->size = ptr - (char *)base;
printf("Stashed %d records\n", hdr->count);
return 0;
}
int bootstage_unstash(void *base, int size)
{
struct bootstage_hdr *hdr = (struct bootstage_hdr *)base;
struct bootstage_record *rec;
char *ptr = base, *end = ptr + size;
uint rec_size;
int id;
if (size == -1)
end = (char *)(~(uintptr_t)0);
if (hdr + 1 > (struct bootstage_hdr *)end) {
debug("%s: Not enough space for bootstage hdr\n", __func__);
return -1;
}
if (hdr->magic != BOOTSTAGE_MAGIC) {
debug("%s: Invalid bootstage magic\n", __func__);
return -1;
}
if (ptr + hdr->size > end) {
debug("%s: Bootstage data runs past buffer end\n", __func__);
return -1;
}
if (hdr->count * sizeof(*rec) > hdr->size) {
debug("%s: Bootstage has %d records needing %d bytes, but "
"only %d bytes is available\n", __func__, hdr->count,
hdr->count * sizeof(*rec), hdr->size);
return -1;
}
if (hdr->version != BOOTSTAGE_VERSION) {
debug("%s: Bootstage data version %#0x unrecognised\n",
__func__, hdr->version);
return -1;
}
if (next_id + hdr->count > BOOTSTAGE_ID_COUNT) {
debug("%s: Bootstage has %d records, we have space for %d\n"
"- please increase CONFIG_BOOTSTAGE_USER_COUNT\n",
__func__, hdr->count, BOOTSTAGE_ID_COUNT - next_id);
return -1;
}
ptr += sizeof(*hdr);
/* Read the records */
rec_size = hdr->count * sizeof(*record);
memcpy(record + next_id, ptr, rec_size);
/* Read the name strings */
ptr += rec_size;
for (rec = record + next_id, id = 0; id < hdr->count; id++, rec++) {
rec->name = ptr;
/* Assume no data corruption here */
ptr += strlen(ptr) + 1;
}
/* Mark the records as read */
next_id += hdr->count;
printf("Unstashed %d records\n", hdr->count);
return 0;
}

116
common/cmd_bootstage.c Normal file
View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2012, Google Inc. All rights reserved.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#ifndef CONFIG_BOOTSTAGE_STASH
#define CONFIG_BOOTSTAGE_STASH -1UL
#define CONFIG_BOOTSTAGE_STASH_SIZE -1
#endif
static int do_bootstage_report(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
bootstage_report();
return 0;
}
static int get_base_size(int argc, char * const argv[], ulong *basep,
ulong *sizep)
{
char *endp;
*basep = CONFIG_BOOTSTAGE_STASH;
*sizep = CONFIG_BOOTSTAGE_STASH_SIZE;
if (argc < 2)
return 0;
*basep = simple_strtoul(argv[1], &endp, 16);
if (*argv[1] == 0 || *endp != 0)
return -1;
if (argc == 2)
return 0;
*sizep = simple_strtoul(argv[2], &endp, 16);
if (*argv[2] == 0 || *endp != 0)
return -1;
return 0;
}
static int do_bootstage_stash(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
ulong base, size;
int ret;
if (get_base_size(argc, argv, &base, &size))
return CMD_RET_USAGE;
if (base == -1UL) {
printf("No bootstage stash area defined\n");
return 1;
}
if (0 == strcmp(argv[0], "stash"))
ret = bootstage_stash((void *)base, size);
else
ret = bootstage_unstash((void *)base, size);
if (ret)
return 1;
return 0;
}
static cmd_tbl_t cmd_bootstage_sub[] = {
U_BOOT_CMD_MKENT(report, 2, 1, do_bootstage_report, "", ""),
U_BOOT_CMD_MKENT(stash, 4, 0, do_bootstage_stash, "", ""),
U_BOOT_CMD_MKENT(unstash, 4, 0, do_bootstage_stash, "", ""),
};
/*
* Process a bootstage sub-command
*/
static int do_boostage(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
cmd_tbl_t *c;
/* Strip off leading 'bootstage' command argument */
argc--;
argv++;
c = find_cmd_tbl(argv[0], cmd_bootstage_sub,
ARRAY_SIZE(cmd_bootstage_sub));
if (c)
return c->cmd(cmdtp, flag, argc, argv);
else
return CMD_RET_USAGE;
}
U_BOOT_CMD(bootstage, 4, 1, do_boostage,
"Boot stage command",
" - check boot progress and timing\n"
"report - Print a report\n"
"stash [<start> [<size>]] - Stash data into memory\n"
"unstash [<start> [<size>]] - Unstash data from memory"
);

View File

@ -31,6 +31,12 @@
#define CONFIG_BOOTSTAGE_USER_COUNT 20
#endif
/* Flags for each bootstage record */
enum bootstage_flags {
BOOTSTAGEF_ERROR = 1 << 0, /* Error record */
BOOTSTAGEF_ALLOC = 1 << 1, /* Allocate an id */
};
/*
* A list of boot stages that we know about. Each of these indicates the
* state that we are at, and the action that we are about to perform. For
@ -181,6 +187,7 @@ enum bootstage_id {
* rough boot timing information.
*/
BOOTSTAGE_ID_AWAKE,
BOOTSTAGE_ID_START_SPL,
BOOTSTAGE_ID_START_UBOOT_F,
BOOTSTAGE_ID_START_UBOOT_R,
BOOTSTAGE_ID_USB_START,
@ -192,11 +199,15 @@ enum bootstage_id {
BOOTSTAGE_ID_MAIN_LOOP,
BOOTSTAGE_KERNELREAD_START,
BOOTSTAGE_KERNELREAD_STOP,
BOOTSTAGE_ID_BOARD_INIT,
BOOTSTAGE_ID_BOARD_INIT_DONE,
BOOTSTAGE_ID_CPU_AWAKE,
BOOTSTAGE_ID_MAIN_CPU_AWAKE,
BOOTSTAGE_ID_MAIN_CPU_READY,
BOOTSTAGE_ID_ACCUM_LCD,
/* a few spare for the user, from here */
BOOTSTAGE_ID_USER,
BOOTSTAGE_ID_COUNT = BOOTSTAGE_ID_USER + CONFIG_BOOTSTAGE_USER_COUNT,
@ -225,6 +236,17 @@ void show_boot_progress(int val);
#if defined(CONFIG_BOOTSTAGE) && !defined(CONFIG_SPL_BUILD)
/* This is the full bootstage implementation */
/**
* Add a new bootstage record
*
* @param id Bootstage ID to use (ignored if flags & BOOTSTAGEF_ALLOC)
* @param name Name of record, or NULL for none
* @param flags Flags (BOOTSTAGEF_...)
* @param mark Time to record in this record, in microseconds
*/
ulong bootstage_add_record(enum bootstage_id id, const char *name,
int flags, ulong mark);
/*
* Mark a time stamp for the current boot stage.
*/
@ -234,9 +256,64 @@ ulong bootstage_error(enum bootstage_id id);
ulong bootstage_mark_name(enum bootstage_id id, const char *name);
/**
* Mark the start of a bootstage activity. The end will be marked later with
* bootstage_accum() and at that point we accumulate the time taken. Calling
* this function turns the given id into a accumulator rather than and
* absolute mark in time. Accumulators record the total amount of time spent
* in an activty during boot.
*
* @param id Bootstage id to record this timestamp against
* @param name Textual name to display for this id in the report (maybe NULL)
* @return start timestamp in microseconds
*/
uint32_t bootstage_start(enum bootstage_id id, const char *name);
/**
* Mark the end of a bootstage activity
*
* After previously marking the start of an activity with bootstage_start(),
* call this function to mark the end. You can call these functions in pairs
* as many times as you like.
*
* @param id Bootstage id to record this timestamp against
* @return time spent in this iteration of the activity (i.e. the time now
* less the start time recorded in the last bootstage_start() call
* with this id.
*/
uint32_t bootstage_accum(enum bootstage_id id);
/* Print a report about boot time */
void bootstage_report(void);
/**
* Add bootstage information to the device tree
*
* @return 0 if ok, -ve on error
*/
int bootstage_fdt_add_report(void);
/*
* Stash bootstage data into memory
*
* @param base Base address of memory buffer
* @param size Size of memory buffer
* @return 0 if stashed ok, -1 if out of space
*/
int bootstage_stash(void *base, int size);
/**
* Read bootstage data from memory
*
* Bootstage data is read from memory and placed in the bootstage table
* in the user records.
*
* @param base Base address of memory buffer
* @param size Size of memory buffer (-1 if unknown)
* @return 0 if unstashed ok, -1 if bootstage info not found, or out of space
*/
int bootstage_unstash(void *base, int size);
#else
/*
* This is a dummy implementation which just calls show_boot_progress(),
@ -260,7 +337,15 @@ static inline ulong bootstage_mark_name(enum bootstage_id id, const char *name)
return 0;
}
static inline int bootstage_stash(void *base, int size)
{
return 0; /* Pretend to succeed */
}
static inline int bootstage_unstash(void *base, int size)
{
return 0; /* Pretend to succeed */
}
#endif /* CONFIG_BOOTSTAGE */
#endif