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