/* 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; }