185 lines
5.4 KiB
C
185 lines
5.4 KiB
C
/* Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "event_log.h"
|
|
#include "hooks.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
/* Event log FIFO */
|
|
#define UNIT_SIZE sizeof(struct event_log_entry)
|
|
#define UNIT_COUNT (CONFIG_EVENT_LOG_SIZE/UNIT_SIZE)
|
|
#define UNIT_COUNT_MASK (UNIT_COUNT - 1)
|
|
static struct event_log_entry __bss_slow log_events[UNIT_COUNT];
|
|
BUILD_ASSERT(POWER_OF_TWO(UNIT_COUNT));
|
|
|
|
/*
|
|
* The FIFO pointers are defined as following :
|
|
* "log_head" is the next available event to dequeue.
|
|
* "log_tail" is marking the end of the FIFO content (after last committed
|
|
* event)
|
|
* "log_tail_next" is the next available spot to enqueue events.
|
|
* The pointers are not wrapped until they are used, so we don't need an extra
|
|
* entry to disambiguate between full and empty FIFO.
|
|
*
|
|
* For concurrency, several tasks might try to enqueue events in parallel with
|
|
* log_add_event(). Only one task is dequeuing events (host commands, VDM,
|
|
* TPM command handler). When the FIFO is full, log_add_event() will discard
|
|
* the oldest events, so "log_head" is incremented/decremented in a critical
|
|
* section since it is accessed from both log_add_event() and
|
|
* log_dequeue_event(). log_tail_next is also protected as several writers can
|
|
* race to add an event to the queue.
|
|
* When a writer is done adding its event, it is updating log_tail,
|
|
* so the event can be consumed by log_dequeue_event().
|
|
*/
|
|
static size_t log_head;
|
|
static size_t log_tail;
|
|
static size_t log_tail_next;
|
|
|
|
/* Size of one FIFO entry */
|
|
#define ENTRY_SIZE(payload_sz) (1+DIV_ROUND_UP((payload_sz), UNIT_SIZE))
|
|
|
|
void log_add_event(uint8_t type, uint8_t size, uint16_t data,
|
|
void *payload, uint32_t timestamp)
|
|
{
|
|
struct event_log_entry *r;
|
|
size_t payload_size = EVENT_LOG_SIZE(size);
|
|
size_t total_size = ENTRY_SIZE(payload_size);
|
|
size_t current_tail, first;
|
|
|
|
/* --- critical section : reserve queue space --- */
|
|
interrupt_disable();
|
|
current_tail = log_tail_next;
|
|
log_tail_next = current_tail + total_size;
|
|
interrupt_enable();
|
|
/* --- end of critical section --- */
|
|
|
|
/* Out of space : discard the oldest entry */
|
|
while ((UNIT_COUNT - (current_tail - log_head)) < total_size) {
|
|
struct event_log_entry *oldest;
|
|
/* --- critical section : atomically free-up space --- */
|
|
interrupt_disable();
|
|
oldest = log_events + (log_head & UNIT_COUNT_MASK);
|
|
log_head += ENTRY_SIZE(EVENT_LOG_SIZE(oldest->size));
|
|
interrupt_enable();
|
|
/* --- end of critical section --- */
|
|
}
|
|
|
|
r = log_events + (current_tail & UNIT_COUNT_MASK);
|
|
|
|
r->timestamp = timestamp;
|
|
r->type = type;
|
|
r->size = size;
|
|
r->data = data;
|
|
/* copy the payload into the FIFO */
|
|
first = MIN(total_size - 1, (UNIT_COUNT -
|
|
(current_tail & UNIT_COUNT_MASK)) - 1);
|
|
if (first)
|
|
memcpy(r->payload, payload, first * UNIT_SIZE);
|
|
if (first < total_size - 1)
|
|
memcpy(log_events, ((uint8_t *)payload) + first * UNIT_SIZE,
|
|
(total_size - first) * UNIT_SIZE);
|
|
/* mark the entry available in the queue if nobody is behind us */
|
|
if (current_tail == log_tail)
|
|
log_tail = log_tail_next;
|
|
}
|
|
|
|
int log_dequeue_event(struct event_log_entry *r)
|
|
{
|
|
uint32_t now = get_time().val >> EVENT_LOG_TIMESTAMP_SHIFT;
|
|
unsigned int total_size, first;
|
|
struct event_log_entry *entry;
|
|
size_t current_head;
|
|
|
|
retry:
|
|
current_head = log_head;
|
|
/* The log FIFO is empty */
|
|
if (log_tail == current_head) {
|
|
memset(r, 0, UNIT_SIZE);
|
|
r->type = EVENT_LOG_NO_ENTRY;
|
|
return UNIT_SIZE;
|
|
}
|
|
|
|
entry = log_events + (current_head & UNIT_COUNT_MASK);
|
|
total_size = ENTRY_SIZE(EVENT_LOG_SIZE(entry->size));
|
|
first = MIN(total_size, UNIT_COUNT - (current_head & UNIT_COUNT_MASK));
|
|
memcpy(r, entry, first * UNIT_SIZE);
|
|
if (first < total_size)
|
|
memcpy(r + first, log_events, (total_size-first) * UNIT_SIZE);
|
|
|
|
/* --- critical section : remove the entry from the queue --- */
|
|
interrupt_disable();
|
|
if (log_head != current_head) { /* our entry was thrown away */
|
|
interrupt_enable();
|
|
goto retry;
|
|
}
|
|
log_head += total_size;
|
|
interrupt_enable();
|
|
/* --- end of critical section --- */
|
|
|
|
/* fixup the timestamp : number of milliseconds in the past */
|
|
r->timestamp = now - r->timestamp;
|
|
|
|
return total_size * UNIT_SIZE;
|
|
}
|
|
|
|
#ifdef CONFIG_CMD_DLOG
|
|
/*
|
|
* Display TPM event logs.
|
|
*/
|
|
static int command_dlog(int argc, char **argv)
|
|
{
|
|
size_t log_cur;
|
|
const uint8_t * const log_events_end =
|
|
(uint8_t *)&log_events[UNIT_COUNT];
|
|
|
|
if (argc > 1) {
|
|
if (!strcasecmp(argv[1], "clear")) {
|
|
interrupt_disable();
|
|
log_head = log_tail = log_tail_next = 0;
|
|
interrupt_enable();
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
/* Too many parameters */
|
|
return EC_ERROR_PARAM1;
|
|
}
|
|
|
|
ccprintf(" TIMESTAMP | TYPE | DATA | SIZE | PAYLOAD\n");
|
|
log_cur = log_head;
|
|
while (log_cur != log_tail) {
|
|
struct event_log_entry *r;
|
|
uint8_t *payload;
|
|
uint32_t payload_bytes;
|
|
|
|
r = &log_events[log_cur & UNIT_COUNT_MASK];
|
|
payload_bytes = EVENT_LOG_SIZE(r->size);
|
|
log_cur += ENTRY_SIZE(payload_bytes);
|
|
|
|
ccprintf("%10d %4d 0x%04X %4d ", r->timestamp, r->type,
|
|
r->data, payload_bytes);
|
|
|
|
/* display payload if exists */
|
|
payload = r->payload;
|
|
while (payload_bytes--) {
|
|
if (payload >= log_events_end)
|
|
payload = (uint8_t *)&log_events[0];
|
|
|
|
ccprintf("%02X", *payload);
|
|
payload++;
|
|
}
|
|
ccprintf("\n");
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(dlog,
|
|
command_dlog,
|
|
"[clear]",
|
|
"Display/clear TPM event logs");
|
|
#endif
|