305 lines
7.2 KiB
C
305 lines
7.2 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <console/console.h>
|
|
#include <cbmem.h>
|
|
#include <symbols.h>
|
|
#include <timer.h>
|
|
#include <timestamp.h>
|
|
#include <smp/node.h>
|
|
|
|
#define MAX_TIMESTAMPS 192
|
|
|
|
/* This points to the active timestamp_table and can change within a stage
|
|
as CBMEM comes available. */
|
|
static struct timestamp_table *glob_ts_table;
|
|
|
|
static void timestamp_cache_init(struct timestamp_table *ts_cache,
|
|
uint64_t base)
|
|
{
|
|
ts_cache->num_entries = 0;
|
|
ts_cache->base_time = base;
|
|
ts_cache->max_entries = (REGION_SIZE(timestamp) -
|
|
offsetof(struct timestamp_table, entries))
|
|
/ sizeof(struct timestamp_entry);
|
|
}
|
|
|
|
static struct timestamp_table *timestamp_cache_get(void)
|
|
{
|
|
struct timestamp_table *ts_cache = NULL;
|
|
|
|
if (!ENV_ROMSTAGE_OR_BEFORE)
|
|
return NULL;
|
|
|
|
if (REGION_SIZE(timestamp) < sizeof(*ts_cache)) {
|
|
BUG();
|
|
} else {
|
|
ts_cache = (void *)_timestamp;
|
|
}
|
|
|
|
return ts_cache;
|
|
}
|
|
|
|
static struct timestamp_table *timestamp_alloc_cbmem_table(void)
|
|
{
|
|
struct timestamp_table *tst;
|
|
|
|
tst = cbmem_add(CBMEM_ID_TIMESTAMP,
|
|
sizeof(struct timestamp_table) +
|
|
MAX_TIMESTAMPS * sizeof(struct timestamp_entry));
|
|
|
|
if (!tst)
|
|
return NULL;
|
|
|
|
tst->base_time = 0;
|
|
tst->max_entries = MAX_TIMESTAMPS;
|
|
tst->num_entries = 0;
|
|
|
|
return tst;
|
|
}
|
|
|
|
/* Determine if one should proceed into timestamp code. This is for protecting
|
|
* systems that have multiple processors running in romstage -- namely AMD
|
|
* based x86 platforms. */
|
|
static int timestamp_should_run(void)
|
|
{
|
|
/*
|
|
* Only check boot_cpu() in other stages than
|
|
* ENV_PAYLOAD_LOADER on x86.
|
|
*/
|
|
if ((!ENV_PAYLOAD_LOADER && ENV_X86) && !boot_cpu())
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static struct timestamp_table *timestamp_table_get(void)
|
|
{
|
|
if (glob_ts_table)
|
|
return glob_ts_table;
|
|
|
|
glob_ts_table = timestamp_cache_get();
|
|
|
|
return glob_ts_table;
|
|
}
|
|
|
|
static void timestamp_table_set(struct timestamp_table *ts)
|
|
{
|
|
glob_ts_table = ts;
|
|
}
|
|
|
|
static const char *timestamp_name(enum timestamp_id id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(timestamp_ids); i++) {
|
|
if (timestamp_ids[i].id == id)
|
|
return timestamp_ids[i].name;
|
|
}
|
|
|
|
return "Unknown timestamp ID";
|
|
}
|
|
|
|
static void timestamp_add_table_entry(struct timestamp_table *ts_table,
|
|
enum timestamp_id id, uint64_t ts_time)
|
|
{
|
|
struct timestamp_entry *tse;
|
|
|
|
if (ts_table->num_entries >= ts_table->max_entries)
|
|
return;
|
|
|
|
tse = &ts_table->entries[ts_table->num_entries++];
|
|
tse->entry_id = id;
|
|
tse->entry_stamp = ts_time;
|
|
|
|
if (ts_table->num_entries == ts_table->max_entries)
|
|
printk(BIOS_ERR, "ERROR: Timestamp table full\n");
|
|
}
|
|
|
|
void timestamp_add(enum timestamp_id id, uint64_t ts_time)
|
|
{
|
|
struct timestamp_table *ts_table;
|
|
|
|
if (!timestamp_should_run())
|
|
return;
|
|
|
|
ts_table = timestamp_table_get();
|
|
|
|
if (!ts_table) {
|
|
printk(BIOS_ERR, "ERROR: No timestamp table found\n");
|
|
return;
|
|
}
|
|
|
|
ts_time -= ts_table->base_time;
|
|
timestamp_add_table_entry(ts_table, id, ts_time);
|
|
|
|
if (CONFIG(TIMESTAMPS_ON_CONSOLE))
|
|
printk(BIOS_INFO, "Timestamp - %s: %llu\n", timestamp_name(id), ts_time);
|
|
}
|
|
|
|
void timestamp_add_now(enum timestamp_id id)
|
|
{
|
|
timestamp_add(id, timestamp_get());
|
|
}
|
|
|
|
void timestamp_init(uint64_t base)
|
|
{
|
|
struct timestamp_table *ts_cache;
|
|
|
|
assert(ENV_ROMSTAGE_OR_BEFORE);
|
|
|
|
if (!timestamp_should_run())
|
|
return;
|
|
|
|
ts_cache = timestamp_cache_get();
|
|
|
|
if (!ts_cache) {
|
|
printk(BIOS_ERR, "ERROR: No timestamp cache to init\n");
|
|
return;
|
|
}
|
|
|
|
timestamp_cache_init(ts_cache, base);
|
|
timestamp_table_set(ts_cache);
|
|
}
|
|
|
|
static void timestamp_sync_cache_to_cbmem(struct timestamp_table *ts_cbmem_table)
|
|
{
|
|
uint32_t i;
|
|
struct timestamp_table *ts_cache_table;
|
|
|
|
ts_cache_table = timestamp_table_get();
|
|
if (!ts_cache_table) {
|
|
printk(BIOS_ERR, "ERROR: No timestamp cache found\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* There's no need to worry about the base_time fields being out of
|
|
* sync because only the following configuration is used/supported:
|
|
*
|
|
* Timestamps get initialized before ramstage, which implies
|
|
* CBMEM initialization in romstage.
|
|
* This requires the board to define a TIMESTAMP() region in its
|
|
* memlayout.ld (default on x86). The base_time from timestamp_init()
|
|
* (usually called from bootblock.c on most non-x86 boards) persists
|
|
* in that region until it gets synced to CBMEM in romstage.
|
|
* In ramstage, the BSS cache's base_time will be 0 until the second
|
|
* sync, which will adjust the timestamps in there to the correct
|
|
* base_time (from CBMEM) with the timestamp_add_table_entry() below.
|
|
*
|
|
* If you try to initialize timestamps before ramstage but don't define
|
|
* a TIMESTAMP region, all operations will fail (safely), and coreboot
|
|
* will behave as if timestamps collection was disabled.
|
|
*/
|
|
|
|
/* Inherit cache base_time. */
|
|
ts_cbmem_table->base_time = ts_cache_table->base_time;
|
|
|
|
for (i = 0; i < ts_cache_table->num_entries; i++) {
|
|
struct timestamp_entry *tse = &ts_cache_table->entries[i];
|
|
timestamp_add_table_entry(ts_cbmem_table, tse->entry_id,
|
|
tse->entry_stamp);
|
|
}
|
|
|
|
/* Cache no longer required. */
|
|
ts_cache_table->num_entries = 0;
|
|
}
|
|
|
|
static void timestamp_reinit(int is_recovery)
|
|
{
|
|
struct timestamp_table *ts_cbmem_table;
|
|
|
|
if (!timestamp_should_run())
|
|
return;
|
|
|
|
/* First time into romstage we make a clean new table. For platforms that travel
|
|
through this path on resume, ARCH_X86 S3, timestamps are also reset. */
|
|
if (ENV_ROMSTAGE) {
|
|
ts_cbmem_table = timestamp_alloc_cbmem_table();
|
|
} else {
|
|
/* Find existing table in cbmem. */
|
|
ts_cbmem_table = cbmem_find(CBMEM_ID_TIMESTAMP);
|
|
}
|
|
|
|
if (ts_cbmem_table == NULL) {
|
|
printk(BIOS_ERR, "ERROR: No timestamp table allocated\n");
|
|
timestamp_table_set(NULL);
|
|
return;
|
|
}
|
|
|
|
if (ENV_ROMSTAGE)
|
|
timestamp_sync_cache_to_cbmem(ts_cbmem_table);
|
|
|
|
/* Seed the timestamp tick frequency in ENV_PAYLOAD_LOADER. */
|
|
if (ENV_PAYLOAD_LOADER)
|
|
ts_cbmem_table->tick_freq_mhz = timestamp_tick_freq_mhz();
|
|
|
|
timestamp_table_set(ts_cbmem_table);
|
|
}
|
|
|
|
void timestamp_rescale_table(uint16_t N, uint16_t M)
|
|
{
|
|
uint32_t i;
|
|
struct timestamp_table *ts_table;
|
|
|
|
if (!timestamp_should_run())
|
|
return;
|
|
|
|
if (N == 0 || M == 0)
|
|
return;
|
|
|
|
ts_table = timestamp_table_get();
|
|
|
|
/* No timestamp table found */
|
|
if (ts_table == NULL) {
|
|
printk(BIOS_ERR, "ERROR: No timestamp table found\n");
|
|
return;
|
|
}
|
|
|
|
ts_table->base_time /= M;
|
|
ts_table->base_time *= N;
|
|
for (i = 0; i < ts_table->num_entries; i++) {
|
|
struct timestamp_entry *tse = &ts_table->entries[i];
|
|
tse->entry_stamp /= M;
|
|
tse->entry_stamp *= N;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the time in microseconds since boot (or more precise: since timestamp
|
|
* table was initialized).
|
|
*/
|
|
uint32_t get_us_since_boot(void)
|
|
{
|
|
struct timestamp_table *ts = timestamp_table_get();
|
|
|
|
if (ts == NULL || ts->tick_freq_mhz == 0)
|
|
return 0;
|
|
return (timestamp_get() - ts->base_time) / ts->tick_freq_mhz;
|
|
}
|
|
|
|
ROMSTAGE_CBMEM_INIT_HOOK(timestamp_reinit)
|
|
POSTCAR_CBMEM_INIT_HOOK(timestamp_reinit)
|
|
RAMSTAGE_CBMEM_INIT_HOOK(timestamp_reinit)
|
|
|
|
/* Provide default timestamp implementation using monotonic timer. */
|
|
uint64_t __weak timestamp_get(void)
|
|
{
|
|
struct mono_time t1, t2;
|
|
|
|
if (!CONFIG(HAVE_MONOTONIC_TIMER))
|
|
return 0;
|
|
|
|
mono_time_set_usecs(&t1, 0);
|
|
timer_monotonic_get(&t2);
|
|
|
|
return mono_time_diff_microseconds(&t1, &t2);
|
|
}
|
|
|
|
/* Like timestamp_get() above this matches up with microsecond granularity. */
|
|
int __weak timestamp_tick_freq_mhz(void)
|
|
{
|
|
return 1;
|
|
}
|