/* * This file is part of the coreboot project. * * Copyright (C) 2011 The ChromiumOS Authors. All rights reserved. * Copyright (C) 2017 Siemens AG * * 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; version 2 of the License. * * 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. */ #include <assert.h> #include <stddef.h> #include <stdint.h> #include <console/console.h> #include <cbmem.h> #include <symbols.h> #include <timer.h> #include <timestamp.h> #include <arch/early_variables.h> #include <rules.h> #include <smp/node.h> #define MAX_TIMESTAMPS 192 /* When changing this number, adjust TIMESTAMP() size ASSERT() in memlayout.h */ #define MAX_BSS_TIMESTAMP_CACHE 16 struct __packed timestamp_cache { uint32_t cache_state; struct timestamp_table table; /* The struct timestamp_table has a 0 length array as its last field. * The following 'entries' array serves as the storage space for the * cache when allocated in the BSS. */ struct timestamp_entry entries[MAX_BSS_TIMESTAMP_CACHE]; }; DECLARE_OPTIONAL_REGION(timestamp); #if defined(__PRE_RAM__) #define USE_TIMESTAMP_REGION (_timestamp_size > 0) #else #define USE_TIMESTAMP_REGION 0 #endif /* The cache location will sit in BSS when in ramstage. */ #define TIMESTAMP_CACHE_IN_BSS ENV_RAMSTAGE #define HAS_CBMEM (ENV_ROMSTAGE || ENV_RAMSTAGE || ENV_POSTCAR) /* Storage of cache entries during ramstage prior to cbmem coming online. */ static struct timestamp_cache timestamp_cache; enum { TIMESTAMP_CACHE_UNINITIALIZED = 0, TIMESTAMP_CACHE_INITIALIZED, TIMESTAMP_CACHE_NOT_NEEDED, }; static void timestamp_cache_init(struct timestamp_cache *ts_cache, uint64_t base) { ts_cache->table.num_entries = 0; ts_cache->table.max_entries = MAX_BSS_TIMESTAMP_CACHE; ts_cache->table.base_time = base; ts_cache->cache_state = TIMESTAMP_CACHE_INITIALIZED; if (USE_TIMESTAMP_REGION) ts_cache->table.max_entries = (_timestamp_size - offsetof(struct timestamp_cache, entries)) / sizeof(struct timestamp_entry); } static struct timestamp_cache *timestamp_cache_get(void) { struct timestamp_cache *ts_cache = NULL; if (TIMESTAMP_CACHE_IN_BSS) { ts_cache = ×tamp_cache; } else if (USE_TIMESTAMP_REGION) { if (_timestamp_size < sizeof(*ts_cache)) BUG(); ts_cache = car_get_var_ptr((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 ramstage on x86. */ if ((!ENV_RAMSTAGE && IS_ENABLED(CONFIG_ARCH_X86)) && !boot_cpu()) return 0; return 1; } static struct timestamp_table *timestamp_table_get(void) { MAYBE_STATIC struct timestamp_table *ts_table = NULL; struct timestamp_cache *ts_cache; if (!timestamp_should_run()) return NULL; if (ts_table != NULL) return ts_table; ts_cache = timestamp_cache_get(); if (ts_cache == NULL) { if (HAS_CBMEM) ts_table = cbmem_find(CBMEM_ID_TIMESTAMP); return ts_table; } /* Cache is required. */ if (ts_cache->cache_state != TIMESTAMP_CACHE_NOT_NEEDED) return &ts_cache->table; /* Cache shouldn't be used but there's no backing store. */ if (!HAS_CBMEM) return NULL; ts_table = cbmem_find(CBMEM_ID_TIMESTAMP); return ts_table; } 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 - ts_table->base_time; if (IS_ENABLED(CONFIG_TIMESTAMPS_ON_CONSOLE)) printk(BIOS_SPEW, "Timestamp - %s: %" PRIu64 "\n", timestamp_name(id), 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; ts_table = timestamp_table_get(); if (!ts_table) { printk(BIOS_ERR, "ERROR: No timestamp table found\n"); return; } timestamp_add_table_entry(ts_table, id, ts_time); } void timestamp_add_now(enum timestamp_id id) { timestamp_add(id, timestamp_get()); } void timestamp_init(uint64_t base) { struct timestamp_cache *ts_cache; if (!timestamp_should_run()) return; ts_cache = timestamp_cache_get(); if (!ts_cache) { printk(BIOS_ERR, "ERROR: No timestamp cache to init\n"); return; } /* In the EARLY_CBMEM_INIT case timestamps could have already been * recovered. In those circumstances honor the cache which sits in BSS * as it has already been initialized. */ if (ENV_RAMSTAGE && IS_ENABLED(CONFIG_EARLY_CBMEM_INIT) && ts_cache->cache_state != TIMESTAMP_CACHE_UNINITIALIZED) return; timestamp_cache_init(ts_cache, base); } static void timestamp_sync_cache_to_cbmem(int is_recovery) { uint32_t i; struct timestamp_cache *ts_cache; struct timestamp_table *ts_cache_table; struct timestamp_table *ts_cbmem_table = NULL; if (!timestamp_should_run()) return; ts_cache = timestamp_cache_get(); /* No timestamp cache found */ if (ts_cache == NULL) { printk(BIOS_ERR, "ERROR: No timestamp cache found\n"); return; } ts_cache_table = &ts_cache->table; /* cbmem is being recovered. */ if (is_recovery) { /* x86 resume path expects timestamps to be reset. */ if (IS_ENABLED(CONFIG_ARCH_ROMSTAGE_X86_32) && ENV_ROMSTAGE) ts_cbmem_table = timestamp_alloc_cbmem_table(); else { /* Find existing table in cbmem. */ ts_cbmem_table = cbmem_find(CBMEM_ID_TIMESTAMP); /* No existing timestamp table. */ if (ts_cbmem_table == NULL) ts_cbmem_table = timestamp_alloc_cbmem_table(); } } else /* First time sync. Add new table. */ ts_cbmem_table = timestamp_alloc_cbmem_table(); if (ts_cbmem_table == NULL) { printk(BIOS_ERR, "ERROR: No timestamp table allocated\n"); return; } /* * There's no need to worry about the base_time fields being out of * sync because only the following configurations are used/supported: * * 1. Timestamps get initialized before ramstage, which implies * CONFIG_EARLY_CBMEM_INIT and 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. * * 2. Timestamps only get initialized in ramstage *and* * CONFIG_LATE_CBMEM_INIT is set. main() will call timestamp_init() * very early (before any timestamps get logged) to set a base_time * in the BSS cache, which will later get synced over to CBMEM. * * 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 only get initialized in ramstage. * * If CONFIG_EARLY_CBMEM_INIT is set but timestamps only get * initialized in ramstage, the base_time from timestamp_init() will * get ignored and all timestamps will be 0-based. */ 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); } /* Freshly added cbmem table has base_time 0. Inherit cache base_time */ if (ts_cbmem_table->base_time == 0) ts_cbmem_table->base_time = ts_cache_table->base_time; /* Seed the timestamp tick frequency in ramstage. */ if (ENV_RAMSTAGE) ts_cbmem_table->tick_freq_mhz = timestamp_tick_freq_mhz(); /* Cache no longer required. */ ts_cache_table->num_entries = 0; ts_cache->cache_state = TIMESTAMP_CACHE_NOT_NEEDED; } 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_sync_cache_to_cbmem) RAMSTAGE_CBMEM_INIT_HOOK(timestamp_sync_cache_to_cbmem) /* Provide default timestamp implementation using monotonic timer. */ uint64_t __weak timestamp_get(void) { struct mono_time t1, t2; if (!IS_ENABLED(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; }