Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of the coreboot project. |
| 3 | * |
| 4 | * Copyright (C) 2012 The ChromiumOS Authors. All rights reserved. |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; version 2 of the License. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 14 | */ |
| 15 | |
Martin Roth | 32c27c2 | 2017-06-24 14:07:25 -0600 | [diff] [blame] | 16 | #if IS_ENABLED(CONFIG_HAVE_ACPI_RESUME) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 17 | #include <arch/acpi.h> |
David Hendricks | 259e49a | 2014-03-20 20:31:23 -0700 | [diff] [blame] | 18 | #endif |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 19 | #include <arch/early_variables.h> |
Julius Werner | a46ee4d | 2016-08-08 16:18:28 -0700 | [diff] [blame] | 20 | #include <bootstate.h> |
Duncan Laurie | 215f2785 | 2012-10-10 14:34:49 -0700 | [diff] [blame] | 21 | #include <cbmem.h> |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 22 | #include <console/console.h> |
Martin Roth | 32c27c2 | 2017-06-24 14:07:25 -0600 | [diff] [blame] | 23 | #if IS_ENABLED(CONFIG_ARCH_X86) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 24 | #include <pc80/mc146818rtc.h> |
David Hendricks | 259e49a | 2014-03-20 20:31:23 -0700 | [diff] [blame] | 25 | #endif |
Gabe Black | a4c4b1c | 2014-04-30 21:41:23 -0700 | [diff] [blame] | 26 | #include <bcd.h> |
Furquan Shaikh | e19d9af | 2016-07-15 15:54:36 -0700 | [diff] [blame] | 27 | #include <boot_device.h> |
| 28 | #include <commonlib/region.h> |
Aaron Durbin | 0424c95 | 2015-03-28 23:56:22 -0500 | [diff] [blame] | 29 | #include <fmap.h> |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 30 | #include <lib.h> |
Gabe Black | a4c4b1c | 2014-04-30 21:41:23 -0700 | [diff] [blame] | 31 | #include <rtc.h> |
Duncan Laurie | 472ec9c | 2012-06-23 16:13:42 -0700 | [diff] [blame] | 32 | #include <smbios.h> |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 33 | #include <stdint.h> |
| 34 | #include <string.h> |
| 35 | #include <elog.h> |
| 36 | #include "elog_internal.h" |
| 37 | |
Edward O'Callaghan | a3119e5 | 2014-06-18 14:28:03 +1000 | [diff] [blame] | 38 | |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 39 | #if IS_ENABLED(CONFIG_ELOG_DEBUG) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 40 | #define elog_debug(STR...) printk(BIOS_DEBUG, STR) |
| 41 | #else |
| 42 | #define elog_debug(STR...) |
| 43 | #endif |
| 44 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 45 | #define NV_NEEDS_ERASE (~(size_t)0) |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 46 | enum elog_init_state { |
Julius Werner | 5d68622 | 2015-01-27 15:40:47 -0800 | [diff] [blame] | 47 | ELOG_UNINITIALIZED = 0, |
| 48 | ELOG_INITIALIZED, |
| 49 | ELOG_BROKEN, |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 50 | }; |
| 51 | |
| 52 | struct elog_state { |
| 53 | u16 full_threshold; |
| 54 | u16 shrink_size; |
| 55 | |
| 56 | /* |
| 57 | * The non-volatile storage chases the mirrored copy. When nv_last_write |
| 58 | * is less than the mirrored last write the non-volatile storage needs |
| 59 | * to be updated. |
| 60 | */ |
| 61 | size_t mirror_last_write; |
| 62 | size_t nv_last_write; |
| 63 | |
| 64 | struct region_device nv_dev; |
| 65 | /* Device that mirrors the eventlog in memory. */ |
| 66 | struct mem_region_device mirror_dev; |
| 67 | |
| 68 | enum elog_init_state elog_initialized; |
| 69 | }; |
| 70 | |
| 71 | static struct elog_state g_elog_state CAR_GLOBAL; |
Aaron Durbin | 10a070b | 2013-06-28 12:30:53 -0700 | [diff] [blame] | 72 | |
Karthikeyan Ramasubramanian | 5ac4b85 | 2018-11-08 11:57:51 -0700 | [diff] [blame] | 73 | #define ELOG_SIZE (4 * KiB) |
| 74 | static uint8_t elog_mirror_buf[ELOG_SIZE] CAR_GLOBAL; |
| 75 | |
| 76 | static void *get_elog_mirror_buffer(void) |
| 77 | { |
| 78 | return car_get_var_ptr(elog_mirror_buf); |
| 79 | } |
| 80 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 81 | static inline struct region_device *mirror_dev_get(void) |
| 82 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 83 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 84 | |
| 85 | return &es->mirror_dev.rdev; |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 86 | } |
| 87 | |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 88 | static size_t elog_events_start(void) |
| 89 | { |
| 90 | /* Events are added directly after the header. */ |
| 91 | return sizeof(struct elog_header); |
| 92 | } |
| 93 | |
| 94 | static size_t elog_events_total_space(void) |
| 95 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 96 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 97 | |
| 98 | return region_device_sz(&es->nv_dev) - elog_events_start(); |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 99 | } |
| 100 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 101 | static struct event_header *elog_get_event_buffer(size_t offset, size_t size) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 102 | { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 103 | return rdev_mmap(mirror_dev_get(), offset, size); |
| 104 | } |
| 105 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 106 | static struct event_header *elog_get_next_event_buffer(size_t size) |
| 107 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 108 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 109 | |
| 110 | elog_debug("ELOG: new event at offset 0x%zx\n", es->mirror_last_write); |
| 111 | return elog_get_event_buffer(es->mirror_last_write, size); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 112 | } |
| 113 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 114 | static void elog_put_event_buffer(struct event_header *event) |
| 115 | { |
| 116 | rdev_munmap(mirror_dev_get(), event); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 117 | } |
| 118 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 119 | static size_t elog_mirror_reset_last_write(void) |
| 120 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 121 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 122 | /* Return previous write value. */ |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 123 | size_t prev = es->mirror_last_write; |
| 124 | |
| 125 | es->mirror_last_write = 0; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 126 | return prev; |
| 127 | } |
| 128 | |
| 129 | static void elog_mirror_increment_last_write(size_t size) |
| 130 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 131 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 132 | |
| 133 | es->mirror_last_write += size; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | static void elog_nv_reset_last_write(void) |
| 137 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 138 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 139 | |
| 140 | es->nv_last_write = 0; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | static void elog_nv_increment_last_write(size_t size) |
| 144 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 145 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 146 | |
| 147 | es->nv_last_write += size; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 148 | } |
| 149 | |
| 150 | static void elog_nv_needs_possible_erase(void) |
| 151 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 152 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 153 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 154 | /* If last write is 0 it means it is already erased. */ |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 155 | if (es->nv_last_write != 0) |
| 156 | es->nv_last_write = NV_NEEDS_ERASE; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | static bool elog_should_shrink(void) |
| 160 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 161 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 162 | |
| 163 | return es->mirror_last_write >= es->full_threshold; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 164 | } |
| 165 | |
| 166 | static bool elog_nv_needs_erase(void) |
| 167 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 168 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 169 | |
| 170 | return es->nv_last_write == NV_NEEDS_ERASE; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | static bool elog_nv_needs_update(void) |
| 174 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 175 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 176 | |
| 177 | return es->nv_last_write != es->mirror_last_write; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 178 | } |
| 179 | |
| 180 | static size_t elog_nv_region_to_update(size_t *offset) |
| 181 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 182 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 183 | |
| 184 | *offset = es->nv_last_write; |
| 185 | return es->mirror_last_write - es->nv_last_write; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 186 | } |
| 187 | |
| 188 | /* |
| 189 | * When parsing state from the NV one needs to adjust both the NV and mirror |
| 190 | * write state. Therefore, provide helper functions which adjust both |
| 191 | * at the same time. |
| 192 | */ |
| 193 | static void elog_tandem_reset_last_write(void) |
| 194 | { |
| 195 | elog_mirror_reset_last_write(); |
| 196 | elog_nv_reset_last_write(); |
| 197 | } |
| 198 | |
| 199 | static void elog_tandem_increment_last_write(size_t size) |
| 200 | { |
| 201 | elog_mirror_increment_last_write(size); |
| 202 | elog_nv_increment_last_write(size); |
| 203 | } |
| 204 | |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 205 | static void elog_debug_dump_buffer(const char *msg) |
| 206 | { |
| 207 | struct region_device *rdev; |
| 208 | void *buffer; |
| 209 | |
| 210 | if (!IS_ENABLED(CONFIG_ELOG_DEBUG)) |
| 211 | return; |
| 212 | |
| 213 | elog_debug(msg); |
| 214 | |
| 215 | rdev = mirror_dev_get(); |
| 216 | |
| 217 | buffer = rdev_mmap_full(rdev); |
| 218 | |
| 219 | if (buffer == NULL) |
| 220 | return; |
| 221 | |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 222 | hexdump(buffer, region_device_sz(rdev)); |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 223 | |
| 224 | rdev_munmap(rdev, buffer); |
| 225 | } |
| 226 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 227 | /* |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 228 | * Update the checksum at the last byte |
| 229 | */ |
| 230 | static void elog_update_checksum(struct event_header *event, u8 checksum) |
| 231 | { |
| 232 | u8 *event_data = (u8*)event; |
| 233 | event_data[event->length - 1] = checksum; |
| 234 | } |
| 235 | |
| 236 | /* |
| 237 | * Simple byte checksum for events |
| 238 | */ |
| 239 | static u8 elog_checksum_event(struct event_header *event) |
| 240 | { |
| 241 | u8 index, checksum = 0; |
| 242 | u8 *data = (u8*)event; |
| 243 | |
| 244 | for (index = 0; index < event->length; index++) |
| 245 | checksum += data[index]; |
| 246 | return checksum; |
| 247 | } |
| 248 | |
| 249 | /* |
Aaron Durbin | 422c440 | 2016-08-04 10:21:06 -0500 | [diff] [blame] | 250 | * Check if mirrored buffer is filled with ELOG_TYPE_EOL byte from the |
| 251 | * provided offset to the end of the mirrored buffer. |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 252 | */ |
Aaron Durbin | 422c440 | 2016-08-04 10:21:06 -0500 | [diff] [blame] | 253 | static int elog_is_buffer_clear(size_t offset) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 254 | { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 255 | size_t i; |
| 256 | const struct region_device *rdev = mirror_dev_get(); |
| 257 | size_t size = region_device_sz(rdev) - offset; |
| 258 | uint8_t *buffer = rdev_mmap(rdev, offset, size); |
| 259 | int ret = 1; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 260 | |
Aaron Durbin | 422c440 | 2016-08-04 10:21:06 -0500 | [diff] [blame] | 261 | elog_debug("elog_is_buffer_clear(offset=%zu size=%zu)\n", offset, size); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 262 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 263 | if (buffer == NULL) |
| 264 | return 0; |
| 265 | |
| 266 | for (i = 0; i < size; i++) { |
| 267 | if (buffer[i] != ELOG_TYPE_EOL) { |
| 268 | ret = 0; |
| 269 | break; |
| 270 | } |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 271 | } |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 272 | rdev_munmap(rdev, buffer); |
| 273 | return ret; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 274 | } |
| 275 | |
| 276 | /* |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 277 | * Verify if the mirrored elog structure is valid. |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 278 | * Returns 1 if the header is valid, 0 otherwise |
| 279 | */ |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 280 | static int elog_is_header_valid(void) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 281 | { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 282 | struct elog_header *header; |
| 283 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 284 | elog_debug("elog_is_header_valid()\n"); |
| 285 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 286 | header = rdev_mmap(mirror_dev_get(), 0, sizeof(*header)); |
| 287 | |
| 288 | if (header == NULL) { |
| 289 | printk(BIOS_ERR, "ELOG: could not map header.\n"); |
| 290 | return 0; |
| 291 | } |
| 292 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 293 | if (header->magic != ELOG_SIGNATURE) { |
| 294 | printk(BIOS_ERR, "ELOG: header magic 0x%X != 0x%X\n", |
| 295 | header->magic, ELOG_SIGNATURE); |
| 296 | return 0; |
| 297 | } |
| 298 | if (header->version != ELOG_VERSION) { |
| 299 | printk(BIOS_ERR, "ELOG: header version %u != %u\n", |
| 300 | header->version, ELOG_VERSION); |
| 301 | return 0; |
| 302 | } |
| 303 | if (header->header_size != sizeof(*header)) { |
Denis 'GNUtoo' Carikli | bc2c9ef | 2013-06-26 20:04:49 +0200 | [diff] [blame] | 304 | printk(BIOS_ERR, "ELOG: header size mismatch %u != %zu\n", |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 305 | header->header_size, sizeof(*header)); |
| 306 | return 0; |
| 307 | } |
| 308 | return 1; |
| 309 | } |
| 310 | |
| 311 | /* |
| 312 | * Validate the event header and data. |
| 313 | */ |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 314 | static size_t elog_is_event_valid(size_t offset) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 315 | { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 316 | uint8_t checksum; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 317 | struct event_header *event; |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 318 | uint8_t len; |
| 319 | const size_t len_offset = offsetof(struct event_header, length); |
| 320 | const size_t size = sizeof(len); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 321 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 322 | /* Read and validate length. */ |
| 323 | if (rdev_readat(mirror_dev_get(), &len, offset + len_offset, size) < 0) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 324 | return 0; |
| 325 | |
| 326 | /* Event length must be at least header size + checksum */ |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 327 | if (len < (sizeof(*event) + sizeof(checksum))) |
| 328 | return 0; |
| 329 | |
| 330 | if (len > MAX_EVENT_SIZE) |
| 331 | return 0; |
| 332 | |
| 333 | event = elog_get_event_buffer(offset, len); |
| 334 | if (!event) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 335 | return 0; |
| 336 | |
| 337 | /* If event checksum is invalid the area is corrupt */ |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 338 | checksum = elog_checksum_event(event); |
| 339 | elog_put_event_buffer(event); |
| 340 | |
| 341 | if (checksum != 0) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 342 | return 0; |
| 343 | |
| 344 | /* Event is valid */ |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 345 | return len; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 346 | } |
| 347 | |
| 348 | /* |
Aaron Durbin | 052a995 | 2016-08-03 22:21:29 -0500 | [diff] [blame] | 349 | * Write 'size' bytes of data from provided 'offset' in the mirrored elog to |
| 350 | * the flash backing store. This will not erase the flash and it assumes the |
| 351 | * flash area has been erased appropriately. |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 352 | */ |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 353 | static void elog_nv_write(size_t offset, size_t size) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 354 | { |
Aaron Durbin | 052a995 | 2016-08-03 22:21:29 -0500 | [diff] [blame] | 355 | void *address; |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 356 | const struct region_device *rdev = mirror_dev_get(); |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 357 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 358 | |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 359 | if (!size) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 360 | return; |
| 361 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 362 | address = rdev_mmap(rdev, offset, size); |
Aaron Durbin | 052a995 | 2016-08-03 22:21:29 -0500 | [diff] [blame] | 363 | |
John E. Kabat Jr | d45011c | 2017-09-29 11:23:35 -0600 | [diff] [blame] | 364 | elog_debug("%s(address=0x%p offset=0x%08zx size=%zu)\n", __func__, |
| 365 | address, offset, size); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 366 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 367 | if (address == NULL) |
| 368 | return; |
| 369 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 370 | /* Write the data to flash */ |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 371 | if (rdev_writeat(&es->nv_dev, address, offset, size) != size) |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 372 | printk(BIOS_ERR, "ELOG: NV Write failed at 0x%zx, size 0x%zx\n", |
| 373 | offset, size); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 374 | |
| 375 | rdev_munmap(rdev, address); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 376 | } |
| 377 | |
| 378 | /* |
| 379 | * Erase the first block specified in the address. |
| 380 | * Only handles flash area within a single flash block. |
| 381 | */ |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 382 | static void elog_nv_erase(void) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 383 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 384 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 385 | size_t size = region_device_sz(&es->nv_dev); |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 386 | elog_debug("%s()\n", __func__); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 387 | |
| 388 | /* Erase the sectors in this region */ |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 389 | if (rdev_eraseat(&es->nv_dev, 0, size) != size) |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 390 | printk(BIOS_ERR, "ELOG: erase failure.\n"); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 391 | } |
| 392 | |
| 393 | /* |
Gabe Black | 0bf1feb | 2013-04-26 03:34:00 -0700 | [diff] [blame] | 394 | * Scan the event area and validate each entry and update the ELOG state. |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 395 | */ |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 396 | static int elog_update_event_buffer_state(void) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 397 | { |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 398 | size_t offset = elog_events_start(); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 399 | |
| 400 | elog_debug("elog_update_event_buffer_state()\n"); |
| 401 | |
| 402 | /* Go through each event and validate it */ |
| 403 | while (1) { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 404 | uint8_t type; |
| 405 | const size_t type_offset = offsetof(struct event_header, type); |
| 406 | size_t len; |
| 407 | const size_t size = sizeof(type); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 408 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 409 | if (rdev_readat(mirror_dev_get(), &type, |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 410 | offset + type_offset, size) < 0) { |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 411 | return -1; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 412 | } |
| 413 | |
| 414 | /* The end of the event marker has been found */ |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 415 | if (type == ELOG_TYPE_EOL) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 416 | break; |
| 417 | |
| 418 | /* Validate the event */ |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 419 | len = elog_is_event_valid(offset); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 420 | |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 421 | if (!len) { |
| 422 | printk(BIOS_ERR, "ELOG: Invalid event @ offset 0x%zx\n", |
| 423 | offset); |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 424 | return -1; |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 425 | } |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 426 | |
| 427 | /* Move to the next event */ |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 428 | elog_tandem_increment_last_write(len); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 429 | offset += len; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 430 | } |
| 431 | |
| 432 | /* Ensure the remaining buffer is empty */ |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 433 | if (!elog_is_buffer_clear(offset)) { |
| 434 | printk(BIOS_ERR, "ELOG: buffer not cleared from 0x%zx\n", |
| 435 | offset); |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 436 | return -1; |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 437 | } |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 438 | |
| 439 | return 0; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 440 | } |
| 441 | |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 442 | static int elog_scan_flash(void) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 443 | { |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 444 | elog_debug("elog_scan_flash()\n"); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 445 | void *mirror_buffer; |
| 446 | const struct region_device *rdev = mirror_dev_get(); |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 447 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 448 | size_t size = region_device_sz(&es->nv_dev); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 449 | |
Duncan Laurie | 215f2785 | 2012-10-10 14:34:49 -0700 | [diff] [blame] | 450 | /* Fill memory buffer by reading from SPI */ |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 451 | mirror_buffer = rdev_mmap_full(rdev); |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 452 | if (rdev_readat(&es->nv_dev, mirror_buffer, 0, size) != size) { |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 453 | rdev_munmap(rdev, mirror_buffer); |
| 454 | printk(BIOS_ERR, "ELOG: NV read failure.\n"); |
| 455 | return -1; |
| 456 | } |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 457 | rdev_munmap(rdev, mirror_buffer); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 458 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 459 | /* No writes have been done yet. */ |
| 460 | elog_tandem_reset_last_write(); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 461 | |
Gabe Black | 455c68e | 2013-04-24 17:54:37 -0700 | [diff] [blame] | 462 | /* Check if the area is empty or not */ |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 463 | if (elog_is_buffer_clear(0)) { |
| 464 | printk(BIOS_ERR, "ELOG: NV Buffer Cleared.\n"); |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 465 | return -1; |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 466 | } |
Gabe Black | 455c68e | 2013-04-24 17:54:37 -0700 | [diff] [blame] | 467 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 468 | /* Indicate that header possibly written. */ |
| 469 | elog_tandem_increment_last_write(elog_events_start()); |
| 470 | |
Gabe Black | 455c68e | 2013-04-24 17:54:37 -0700 | [diff] [blame] | 471 | /* Validate the header */ |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 472 | if (!elog_is_header_valid()) { |
| 473 | printk(BIOS_ERR, "ELOG: NV Buffer Invalid.\n"); |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 474 | return -1; |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 475 | } |
Gabe Black | 455c68e | 2013-04-24 17:54:37 -0700 | [diff] [blame] | 476 | |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 477 | return elog_update_event_buffer_state(); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 478 | } |
| 479 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 480 | static void elog_write_header_in_mirror(void) |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 481 | { |
| 482 | static const struct elog_header header = { |
| 483 | .magic = ELOG_SIGNATURE, |
| 484 | .version = ELOG_VERSION, |
| 485 | .header_size = sizeof(struct elog_header), |
| 486 | .reserved = { |
| 487 | [0] = ELOG_TYPE_EOL, |
| 488 | [1] = ELOG_TYPE_EOL, |
| 489 | }, |
| 490 | }; |
| 491 | |
| 492 | rdev_writeat(mirror_dev_get(), &header, 0, sizeof(header)); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 493 | elog_mirror_increment_last_write(elog_events_start()); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 494 | } |
| 495 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 496 | static void elog_move_events_to_front(size_t offset, size_t size) |
| 497 | { |
| 498 | void *src; |
| 499 | void *dest; |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 500 | size_t start_offset = elog_events_start(); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 501 | const struct region_device *rdev = mirror_dev_get(); |
| 502 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 503 | src = rdev_mmap(rdev, offset, size); |
| 504 | dest = rdev_mmap(rdev, start_offset, size); |
| 505 | |
| 506 | if (src == NULL || dest == NULL) { |
| 507 | printk(BIOS_ERR, "ELOG: failure moving events!\n"); |
| 508 | rdev_munmap(rdev, dest); |
| 509 | rdev_munmap(rdev, src); |
| 510 | return; |
| 511 | } |
| 512 | |
| 513 | /* Move the events to the front. */ |
| 514 | memmove(dest, src, size); |
| 515 | rdev_munmap(rdev, dest); |
| 516 | rdev_munmap(rdev, src); |
| 517 | |
| 518 | /* Mark EOL for previously used buffer until the end. */ |
| 519 | offset = start_offset + size; |
| 520 | size = region_device_sz(rdev) - offset; |
| 521 | dest = rdev_mmap(rdev, offset, size); |
| 522 | if (dest == NULL) { |
| 523 | printk(BIOS_ERR, "ELOG: failure filling EOL!\n"); |
| 524 | return; |
| 525 | } |
| 526 | memset(dest, ELOG_TYPE_EOL, size); |
| 527 | rdev_munmap(rdev, dest); |
| 528 | } |
| 529 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 530 | /* Perform the shrink and move events returning the size of bytes shrunk. */ |
| 531 | static size_t elog_do_shrink(size_t requested_size, size_t last_write) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 532 | { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 533 | const struct region_device *rdev = mirror_dev_get(); |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 534 | size_t offset = elog_events_start(); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 535 | size_t remaining_size; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 536 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 537 | while (1) { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 538 | const size_t type_offset = offsetof(struct event_header, type); |
| 539 | const size_t len_offset = offsetof(struct event_header, length); |
| 540 | const size_t size = sizeof(uint8_t); |
| 541 | uint8_t type; |
| 542 | uint8_t len; |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 543 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 544 | /* Next event has exceeded constraints */ |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 545 | if (offset > requested_size) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 546 | break; |
| 547 | |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 548 | if (rdev_readat(rdev, &type, offset + type_offset, size) < 0) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 549 | break; |
| 550 | |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 551 | /* Reached the end of the area */ |
| 552 | if (type == ELOG_TYPE_EOL) |
| 553 | break; |
| 554 | |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 555 | if (rdev_readat(rdev, &len, offset + len_offset, size) < 0) |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 556 | break; |
| 557 | |
| 558 | offset += len; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 559 | } |
| 560 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 561 | /* |
| 562 | * Move the events and update the last write. The last write before |
| 563 | * shrinking was captured prior to resetting the counter to determine |
| 564 | * actual size we're keeping. |
| 565 | */ |
| 566 | remaining_size = last_write - offset; |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 567 | elog_debug("ELOG: shrinking offset: 0x%zx remaining_size: 0x%zx\n", |
| 568 | offset, remaining_size); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 569 | elog_move_events_to_front(offset, remaining_size); |
| 570 | elog_mirror_increment_last_write(remaining_size); |
Gabe Black | bfae4aa | 2013-04-24 03:16:21 -0700 | [diff] [blame] | 571 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 572 | /* Return the amount of data removed. */ |
| 573 | return offset - elog_events_start(); |
| 574 | } |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 575 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 576 | /* |
| 577 | * Shrink the log, deleting old entries and moving the |
| 578 | * remaining ones to the front of the log. |
| 579 | */ |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 580 | static int elog_shrink_by_size(size_t requested_size) |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 581 | { |
| 582 | size_t shrunk_size; |
| 583 | size_t captured_last_write; |
| 584 | size_t total_event_space = elog_events_total_space(); |
| 585 | |
| 586 | elog_debug("%s()\n", __func__); |
| 587 | |
| 588 | /* Indicate possible erase required. */ |
| 589 | elog_nv_needs_possible_erase(); |
| 590 | |
| 591 | /* Capture the last write to determine data size in buffer to shrink. */ |
| 592 | captured_last_write = elog_mirror_reset_last_write(); |
| 593 | |
| 594 | /* Prepare new header. */ |
| 595 | elog_write_header_in_mirror(); |
| 596 | |
| 597 | /* Determine if any actual shrinking is required. */ |
| 598 | if (requested_size >= total_event_space) |
| 599 | shrunk_size = total_event_space; |
| 600 | else |
| 601 | shrunk_size = elog_do_shrink(requested_size, |
| 602 | captured_last_write); |
Duncan Laurie | 032be82 | 2013-05-23 07:23:09 -0700 | [diff] [blame] | 603 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 604 | /* Add clear event */ |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 605 | return elog_add_event_word(ELOG_TYPE_LOG_CLEAR, shrunk_size); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 606 | } |
| 607 | |
| 608 | static int elog_prepare_empty(void) |
| 609 | { |
| 610 | elog_debug("elog_prepare_empty()\n"); |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 611 | return elog_shrink_by_size(elog_events_total_space()); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 612 | } |
| 613 | |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 614 | static int elog_shrink(void) |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 615 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 616 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 617 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 618 | if (elog_should_shrink()) |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 619 | return elog_shrink_by_size(es->shrink_size); |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 620 | return 0; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 621 | } |
| 622 | |
Furquan Shaikh | 7f4221c | 2014-11-12 16:19:37 -0800 | [diff] [blame] | 623 | /* |
| 624 | * Convert a flash offset into a memory mapped flash address |
| 625 | */ |
Furquan Shaikh | e19d9af | 2016-07-15 15:54:36 -0700 | [diff] [blame] | 626 | static inline u8 *elog_flash_offset_to_address(void) |
Furquan Shaikh | 7f4221c | 2014-11-12 16:19:37 -0800 | [diff] [blame] | 627 | { |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 628 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 629 | |
Aaron Durbin | 16c173f | 2016-08-11 14:04:10 -0500 | [diff] [blame] | 630 | /* Only support memory-mapped devices. */ |
| 631 | if (!IS_ENABLED(CONFIG_BOOT_DEVICE_MEMORY_MAPPED)) |
Furquan Shaikh | e19d9af | 2016-07-15 15:54:36 -0700 | [diff] [blame] | 632 | return NULL; |
Furquan Shaikh | 7f4221c | 2014-11-12 16:19:37 -0800 | [diff] [blame] | 633 | |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 634 | if (!region_device_sz(&es->nv_dev)) |
Furquan Shaikh | 7f4221c | 2014-11-12 16:19:37 -0800 | [diff] [blame] | 635 | return NULL; |
| 636 | |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 637 | /* Get a view into the read-only boot device. */ |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 638 | return rdev_mmap(boot_device_ro(), region_device_offset(&es->nv_dev), |
| 639 | region_device_sz(&es->nv_dev)); |
Furquan Shaikh | 7f4221c | 2014-11-12 16:19:37 -0800 | [diff] [blame] | 640 | } |
| 641 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 642 | /* |
Duncan Laurie | 472ec9c | 2012-06-23 16:13:42 -0700 | [diff] [blame] | 643 | * Fill out SMBIOS Type 15 table entry so the |
| 644 | * event log can be discovered at runtime. |
| 645 | */ |
| 646 | int elog_smbios_write_type15(unsigned long *current, int handle) |
| 647 | { |
| 648 | struct smbios_type15 *t = (struct smbios_type15 *)*current; |
| 649 | int len = sizeof(struct smbios_type15); |
Aaron Durbin | 18fedb3 | 2016-08-05 21:45:54 -0500 | [diff] [blame] | 650 | uintptr_t log_address; |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 651 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 652 | size_t elog_size = region_device_sz(&es->nv_dev); |
Duncan Laurie | 472ec9c | 2012-06-23 16:13:42 -0700 | [diff] [blame] | 653 | |
Aaron Durbin | 18fedb3 | 2016-08-05 21:45:54 -0500 | [diff] [blame] | 654 | if (IS_ENABLED(CONFIG_ELOG_CBMEM)) { |
| 655 | /* Save event log buffer into CBMEM for the OS to read */ |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 656 | void *cbmem = cbmem_add(CBMEM_ID_ELOG, elog_size); |
Aaron Durbin | 18fedb3 | 2016-08-05 21:45:54 -0500 | [diff] [blame] | 657 | if (cbmem) |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 658 | rdev_readat(mirror_dev_get(), cbmem, 0, elog_size); |
Aaron Durbin | 18fedb3 | 2016-08-05 21:45:54 -0500 | [diff] [blame] | 659 | log_address = (uintptr_t)cbmem; |
| 660 | } else { |
| 661 | log_address = (uintptr_t)elog_flash_offset_to_address(); |
| 662 | } |
| 663 | |
| 664 | if (!log_address) { |
| 665 | printk(BIOS_WARNING, "SMBIOS type 15 log address invalid.\n"); |
Duncan Laurie | 215f2785 | 2012-10-10 14:34:49 -0700 | [diff] [blame] | 666 | return 0; |
Aaron Durbin | 18fedb3 | 2016-08-05 21:45:54 -0500 | [diff] [blame] | 667 | } |
Duncan Laurie | 215f2785 | 2012-10-10 14:34:49 -0700 | [diff] [blame] | 668 | |
Duncan Laurie | 472ec9c | 2012-06-23 16:13:42 -0700 | [diff] [blame] | 669 | memset(t, 0, len); |
| 670 | t->type = SMBIOS_EVENT_LOG; |
| 671 | t->length = len - 2; |
| 672 | t->handle = handle; |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 673 | t->area_length = elog_size - 1; |
Duncan Laurie | 472ec9c | 2012-06-23 16:13:42 -0700 | [diff] [blame] | 674 | t->header_offset = 0; |
| 675 | t->data_offset = sizeof(struct elog_header); |
| 676 | t->access_method = SMBIOS_EVENTLOG_ACCESS_METHOD_MMIO32; |
| 677 | t->log_status = SMBIOS_EVENTLOG_STATUS_VALID; |
| 678 | t->change_token = 0; |
Aaron Durbin | 18fedb3 | 2016-08-05 21:45:54 -0500 | [diff] [blame] | 679 | t->address = log_address; |
Duncan Laurie | 472ec9c | 2012-06-23 16:13:42 -0700 | [diff] [blame] | 680 | t->header_format = ELOG_HEADER_TYPE_OEM; |
| 681 | t->log_type_descriptors = 0; |
| 682 | t->log_type_descriptor_length = 2; |
| 683 | |
| 684 | *current += len; |
| 685 | return len; |
| 686 | } |
| 687 | |
| 688 | /* |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 689 | * Clear the entire event log |
| 690 | */ |
| 691 | int elog_clear(void) |
| 692 | { |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 693 | elog_debug("elog_clear()\n"); |
| 694 | |
Gabe Black | 8f4baec | 2013-04-25 17:21:58 -0700 | [diff] [blame] | 695 | /* Make sure ELOG structures are initialized */ |
| 696 | if (elog_init() < 0) |
| 697 | return -1; |
| 698 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 699 | return elog_prepare_empty(); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 700 | } |
| 701 | |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 702 | static int elog_find_flash(void) |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 703 | { |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 704 | size_t total_size; |
Aaron Durbin | f41a9a6 | 2016-08-04 15:59:35 -0500 | [diff] [blame] | 705 | size_t reserved_space = ELOG_MIN_AVAILABLE_ENTRIES * MAX_EVENT_SIZE; |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 706 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
| 707 | struct region_device *rdev = &es->nv_dev; |
Aaron Durbin | 5d208ff | 2016-07-22 14:30:55 -0500 | [diff] [blame] | 708 | |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 709 | elog_debug("%s()\n", __func__); |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 710 | |
Aaron Durbin | 5d208ff | 2016-07-22 14:30:55 -0500 | [diff] [blame] | 711 | /* Find the ELOG base and size in FMAP */ |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 712 | if (fmap_locate_area_as_rdev_rw("RW_ELOG", rdev) < 0) { |
Aaron Durbin | 5d208ff | 2016-07-22 14:30:55 -0500 | [diff] [blame] | 713 | printk(BIOS_WARNING, "ELOG: Unable to find RW_ELOG in FMAP\n"); |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 714 | return -1; |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 715 | } |
Aaron Durbin | 5d208ff | 2016-07-22 14:30:55 -0500 | [diff] [blame] | 716 | |
Karthikeyan Ramasubramanian | 5ac4b85 | 2018-11-08 11:57:51 -0700 | [diff] [blame] | 717 | if (region_device_sz(rdev) < ELOG_SIZE) { |
| 718 | printk(BIOS_WARNING, "ELOG: Needs a minimum size of %dKiB: %zu\n", |
| 719 | ELOG_SIZE / KiB, region_device_sz(rdev)); |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 720 | return -1; |
| 721 | } |
| 722 | |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 723 | printk(BIOS_INFO, "ELOG: NV offset 0x%zx size 0x%zx\n", |
| 724 | region_device_offset(rdev), region_device_sz(rdev)); |
| 725 | |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 726 | /* Keep 4KiB max size until large malloc()s have been fixed. */ |
Karthikeyan Ramasubramanian | 5ac4b85 | 2018-11-08 11:57:51 -0700 | [diff] [blame] | 727 | total_size = MIN(ELOG_SIZE, region_device_sz(rdev)); |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 728 | rdev_chain(rdev, rdev, 0, total_size); |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 729 | |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 730 | es->full_threshold = total_size - reserved_space; |
| 731 | es->shrink_size = total_size * ELOG_SHRINK_PERCENTAGE / 100; |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 732 | |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 733 | if (reserved_space > es->shrink_size) { |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 734 | printk(BIOS_ERR, "ELOG: SHRINK_PERCENTAGE too small\n"); |
| 735 | return -1; |
| 736 | } |
| 737 | |
| 738 | return 0; |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 739 | } |
| 740 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 741 | static int elog_sync_to_nv(void) |
| 742 | { |
| 743 | size_t offset; |
| 744 | size_t size; |
| 745 | bool erase_needed; |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 746 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 747 | |
| 748 | /* Determine if any updates are required. */ |
| 749 | if (!elog_nv_needs_update()) |
| 750 | return 0; |
| 751 | |
| 752 | erase_needed = elog_nv_needs_erase(); |
| 753 | |
| 754 | /* Erase if necessary. */ |
| 755 | if (erase_needed) { |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 756 | elog_nv_erase(); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 757 | elog_nv_reset_last_write(); |
| 758 | } |
| 759 | |
| 760 | size = elog_nv_region_to_update(&offset); |
| 761 | |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 762 | elog_nv_write(offset, size); |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 763 | elog_nv_increment_last_write(size); |
| 764 | |
| 765 | /* |
| 766 | * If erase wasn't performed then don't rescan. Assume the appended |
| 767 | * write was successful. |
| 768 | */ |
| 769 | if (!erase_needed) |
| 770 | return 0; |
| 771 | |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 772 | elog_debug_dump_buffer("ELOG: in-memory mirror:\n"); |
| 773 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 774 | /* Mark broken if the scan failed after a sync. */ |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 775 | if (elog_scan_flash() < 0) { |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 776 | printk(BIOS_ERR, "ELOG: Sync back from NV storage failed.\n"); |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 777 | elog_debug_dump_buffer("ELOG: Buffer from NV:\n"); |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 778 | es->elog_initialized = ELOG_BROKEN; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 779 | return -1; |
| 780 | } |
| 781 | |
| 782 | return 0; |
| 783 | } |
| 784 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 785 | /* |
Daniel Kurtz | 7c67069 | 2018-05-24 18:24:07 -0600 | [diff] [blame] | 786 | * Do not log boot count events in S3 resume or SMM. |
| 787 | */ |
| 788 | static bool elog_do_add_boot_count(void) |
| 789 | { |
| 790 | if (ENV_SMM) |
| 791 | return false; |
| 792 | |
| 793 | #if IS_ENABLED(CONFIG_HAVE_ACPI_RESUME) |
| 794 | return !acpi_is_wakeup_s3(); |
| 795 | #else |
| 796 | return true; |
| 797 | #endif |
| 798 | } |
| 799 | |
Karthikeyan Ramasubramanian | cca1f37 | 2018-11-06 13:00:27 -0700 | [diff] [blame] | 800 | static void ramstage_elog_add_boot_count(void) |
| 801 | { |
| 802 | if (elog_do_add_boot_count()) { |
| 803 | elog_add_event_dword(ELOG_TYPE_BOOT, boot_count_read()); |
| 804 | |
| 805 | #if IS_ENABLED(CONFIG_ARCH_X86) |
| 806 | /* Check and log POST codes from previous boot */ |
| 807 | if (IS_ENABLED(CONFIG_CMOS_POST)) |
| 808 | cmos_post_log(); |
| 809 | #endif |
| 810 | } |
| 811 | } |
| 812 | |
Daniel Kurtz | 7c67069 | 2018-05-24 18:24:07 -0600 | [diff] [blame] | 813 | /* |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 814 | * Event log main entry point |
| 815 | */ |
| 816 | int elog_init(void) |
| 817 | { |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 818 | void *mirror_buffer; |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 819 | size_t elog_size; |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 820 | struct elog_state *es = car_get_var_ptr(&g_elog_state); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 821 | |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 822 | switch (es->elog_initialized) { |
Julius Werner | 5d68622 | 2015-01-27 15:40:47 -0800 | [diff] [blame] | 823 | case ELOG_UNINITIALIZED: |
| 824 | break; |
| 825 | case ELOG_INITIALIZED: |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 826 | return 0; |
Julius Werner | 5d68622 | 2015-01-27 15:40:47 -0800 | [diff] [blame] | 827 | case ELOG_BROKEN: |
| 828 | return -1; |
| 829 | } |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 830 | es->elog_initialized = ELOG_BROKEN; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 831 | |
| 832 | elog_debug("elog_init()\n"); |
| 833 | |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 834 | /* Set up the backing store */ |
Aaron Durbin | 367f2b9 | 2016-08-05 15:41:37 -0500 | [diff] [blame] | 835 | if (elog_find_flash() < 0) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 836 | return -1; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 837 | |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 838 | elog_size = region_device_sz(&es->nv_dev); |
Karthikeyan Ramasubramanian | 5ac4b85 | 2018-11-08 11:57:51 -0700 | [diff] [blame] | 839 | mirror_buffer = get_elog_mirror_buffer(); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 840 | if (!mirror_buffer) { |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 841 | printk(BIOS_ERR, "ELOG: Unable to allocate backing store\n"); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 842 | return -1; |
| 843 | } |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 844 | mem_region_device_rw_init(&es->mirror_dev, mirror_buffer, elog_size); |
Gabe Black | 84a93d1 | 2013-04-24 23:31:41 -0700 | [diff] [blame] | 845 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 846 | /* |
| 847 | * Mark as initialized to allow elog_init() to be called and deemed |
| 848 | * successful in the prepare/shrink path which adds events. |
| 849 | */ |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 850 | es->elog_initialized = ELOG_INITIALIZED; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 851 | |
Aaron Durbin | 9d130a9 | 2016-08-05 15:24:33 -0500 | [diff] [blame] | 852 | /* Load the log from flash and prepare the flash if necessary. */ |
Aaron Durbin | 1297443 | 2016-08-09 17:01:27 -0500 | [diff] [blame] | 853 | if (elog_scan_flash() < 0 && elog_prepare_empty() < 0) { |
| 854 | printk(BIOS_ERR, "ELOG: Unable to prepare flash\n"); |
| 855 | return -1; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 856 | } |
| 857 | |
Aaron Durbin | d10f9d5 | 2016-08-10 11:37:14 -0500 | [diff] [blame] | 858 | printk(BIOS_INFO, "ELOG: area is %zu bytes, full threshold %d," |
Karthikeyan Ramasubramanian | 07bc08c | 2018-11-06 12:59:18 -0700 | [diff] [blame] | 859 | " shrink size %d\n", region_device_sz(&es->nv_dev), |
| 860 | es->full_threshold, es->shrink_size); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 861 | |
Karthikeyan Ramasubramanian | cca1f37 | 2018-11-06 13:00:27 -0700 | [diff] [blame] | 862 | if (ENV_RAMSTAGE) |
| 863 | ramstage_elog_add_boot_count(); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 864 | return 0; |
| 865 | } |
| 866 | |
| 867 | /* |
| 868 | * Populate timestamp in event header with current time |
| 869 | */ |
| 870 | static void elog_fill_timestamp(struct event_header *event) |
| 871 | { |
Patrick Georgi | 6e5f22e | 2015-04-22 13:28:21 +0200 | [diff] [blame] | 872 | #if IS_ENABLED(CONFIG_RTC) |
Gabe Black | a4c4b1c | 2014-04-30 21:41:23 -0700 | [diff] [blame] | 873 | struct rtc_time time; |
| 874 | |
| 875 | rtc_get(&time); |
| 876 | event->second = bin2bcd(time.sec); |
| 877 | event->minute = bin2bcd(time.min); |
| 878 | event->hour = bin2bcd(time.hour); |
| 879 | event->day = bin2bcd(time.mday); |
| 880 | event->month = bin2bcd(time.mon); |
Aaron Durbin | c41c6a4 | 2015-11-06 15:20:23 -0600 | [diff] [blame] | 881 | event->year = bin2bcd(time.year % 100); |
Patrick Georgi | 6e5f22e | 2015-04-22 13:28:21 +0200 | [diff] [blame] | 882 | |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 883 | /* Basic sanity check of expected ranges */ |
| 884 | if (event->month > 0x12 || event->day > 0x31 || event->hour > 0x23 || |
Patrick Georgi | 6e5f22e | 2015-04-22 13:28:21 +0200 | [diff] [blame] | 885 | event->minute > 0x59 || event->second > 0x59) |
| 886 | #endif |
| 887 | { |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 888 | event->year = 0; |
| 889 | event->month = 0; |
| 890 | event->day = 0; |
| 891 | event->hour = 0; |
| 892 | event->minute = 0; |
| 893 | event->second = 0; |
| 894 | } |
| 895 | } |
| 896 | |
| 897 | /* |
Gabe Black | 331eb08 | 2013-04-24 04:11:40 -0700 | [diff] [blame] | 898 | * Add an event to the log |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 899 | */ |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 900 | int elog_add_event_raw(u8 event_type, void *data, u8 data_size) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 901 | { |
| 902 | struct event_header *event; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 903 | u8 event_size; |
| 904 | |
Gabe Black | 331eb08 | 2013-04-24 04:11:40 -0700 | [diff] [blame] | 905 | elog_debug("elog_add_event_raw(type=%X)\n", event_type); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 906 | |
| 907 | /* Make sure ELOG structures are initialized */ |
| 908 | if (elog_init() < 0) |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 909 | return -1; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 910 | |
| 911 | /* Header + Data + Checksum */ |
| 912 | event_size = sizeof(*event) + data_size + 1; |
| 913 | if (event_size > MAX_EVENT_SIZE) { |
| 914 | printk(BIOS_ERR, "ELOG: Event(%X) data size too " |
| 915 | "big (%d)\n", event_type, event_size); |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 916 | return -1; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 917 | } |
| 918 | |
| 919 | /* Make sure event data can fit */ |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 920 | event = elog_get_next_event_buffer(event_size); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 921 | if (event == NULL) { |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 922 | printk(BIOS_ERR, "ELOG: Event(%X) does not fit\n", |
| 923 | event_type); |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 924 | return -1; |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 925 | } |
| 926 | |
| 927 | /* Fill out event data */ |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 928 | event->type = event_type; |
| 929 | event->length = event_size; |
| 930 | elog_fill_timestamp(event); |
| 931 | |
| 932 | if (data_size) |
| 933 | memcpy(&event[1], data, data_size); |
| 934 | |
| 935 | /* Zero the checksum byte and then compute checksum */ |
| 936 | elog_update_checksum(event, 0); |
| 937 | elog_update_checksum(event, -(elog_checksum_event(event))); |
Aaron Durbin | 281c994 | 2016-08-04 14:25:59 -0500 | [diff] [blame] | 938 | elog_put_event_buffer(event); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 939 | |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 940 | elog_mirror_increment_last_write(event_size); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 941 | |
Julius Werner | 4d9fafa | 2017-04-12 13:24:23 -0700 | [diff] [blame] | 942 | printk(BIOS_INFO, "ELOG: Event(%X) added with size %d ", |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 943 | event_type, event_size); |
Julius Werner | 4d9fafa | 2017-04-12 13:24:23 -0700 | [diff] [blame] | 944 | if (event->day != 0) { |
| 945 | printk(BIOS_INFO, "at 20%02x-%02x-%02x %02x:%02x:%02x UTC\n", |
| 946 | event->year, event->month, event->day, |
| 947 | event->hour, event->minute, event->second); |
| 948 | } else { |
| 949 | printk(BIOS_INFO, "(timestamp unavailable)\n"); |
| 950 | } |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 951 | |
| 952 | /* Shrink the log if we are getting too full */ |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 953 | if (elog_shrink() < 0) |
| 954 | return -1; |
Aaron Durbin | 0284f62 | 2016-08-05 14:33:53 -0500 | [diff] [blame] | 955 | |
| 956 | /* Ensure the updates hit the non-volatile storage. */ |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 957 | return elog_sync_to_nv(); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 958 | } |
| 959 | |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 960 | int elog_add_event(u8 event_type) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 961 | { |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 962 | return elog_add_event_raw(event_type, NULL, 0); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 963 | } |
| 964 | |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 965 | int elog_add_event_byte(u8 event_type, u8 data) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 966 | { |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 967 | return elog_add_event_raw(event_type, &data, sizeof(data)); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 968 | } |
| 969 | |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 970 | int elog_add_event_word(u8 event_type, u16 data) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 971 | { |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 972 | return elog_add_event_raw(event_type, &data, sizeof(data)); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 973 | } |
| 974 | |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 975 | int elog_add_event_dword(u8 event_type, u32 data) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 976 | { |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 977 | return elog_add_event_raw(event_type, &data, sizeof(data)); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 978 | } |
| 979 | |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 980 | int elog_add_event_wake(u8 source, u32 instance) |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 981 | { |
| 982 | struct elog_event_data_wake wake = { |
| 983 | .source = source, |
| 984 | .instance = instance |
| 985 | }; |
Aaron Durbin | eec1c28 | 2016-08-06 10:02:37 -0500 | [diff] [blame] | 986 | return elog_add_event_raw(ELOG_TYPE_WAKE_SOURCE, &wake, sizeof(wake)); |
Duncan Laurie | 7d2b81c | 2012-06-23 16:08:47 -0700 | [diff] [blame] | 987 | } |
Julius Werner | a46ee4d | 2016-08-08 16:18:28 -0700 | [diff] [blame] | 988 | |
Richard Spiegel | e680990 | 2018-08-20 13:51:30 -0700 | [diff] [blame] | 989 | int elog_add_extended_event(u8 type, u32 complement) |
| 990 | { |
| 991 | struct elog_event_extended_event event = { |
| 992 | .event_type = type, |
| 993 | .event_complement = complement |
| 994 | }; |
| 995 | return elog_add_event_raw(ELOG_TYPE_EXTENDED_EVENT, |
| 996 | &event, |
| 997 | sizeof(event)); |
| 998 | } |
| 999 | |
Julius Werner | a46ee4d | 2016-08-08 16:18:28 -0700 | [diff] [blame] | 1000 | /* Make sure elog_init() runs at least once to log System Boot event. */ |
| 1001 | static void elog_bs_init(void *unused) { elog_init(); } |
| 1002 | BOOT_STATE_INIT_ENTRY(BS_POST_DEVICE, BS_ON_ENTRY, elog_bs_init, NULL); |