Daisuke Nojiri | 477dd18 | 2014-10-08 11:32:23 -0700 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of the coreboot project. |
| 3 | * |
| 4 | * Copyright (C) 2014 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. |
Daisuke Nojiri | 477dd18 | 2014-10-08 11:32:23 -0700 | [diff] [blame] | 14 | */ |
| 15 | |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 16 | #include <arch/early_variables.h> |
Daisuke Nojiri | 914a21e | 2015-09-04 14:32:49 -0700 | [diff] [blame] | 17 | #include <assert.h> |
Furquan Shaikh | 2a12e2e | 2016-07-25 11:48:03 -0700 | [diff] [blame] | 18 | #include <commonlib/region.h> |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 19 | #include <console/console.h> |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 20 | #include <string.h> |
Randall Spangler | 144c228 | 2014-12-03 17:35:53 -0800 | [diff] [blame] | 21 | #include <vb2_api.h> |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 22 | #include <vboot_nvstorage.h> |
Furquan Shaikh | 2a12e2e | 2016-07-25 11:48:03 -0700 | [diff] [blame] | 23 | #include <vboot/vboot_common.h> |
| 24 | #include <vboot/vbnv.h> |
| 25 | #include <vboot/vbnv_layout.h> |
Daisuke Nojiri | 477dd18 | 2014-10-08 11:32:23 -0700 | [diff] [blame] | 26 | |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 27 | #define BLOB_SIZE VB2_NVDATA_SIZE |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 28 | |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 29 | struct vbnv_flash_ctx { |
| 30 | /* VBNV flash is initialized */ |
| 31 | int initialized; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 32 | |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 33 | /* Offset of the current nvdata in flash */ |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 34 | int blob_offset; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 35 | |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 36 | /* Offset of the topmost nvdata blob in flash */ |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 37 | int top_offset; |
Vadim Bendebury | 9e381a1 | 2015-01-10 23:11:43 -0800 | [diff] [blame] | 38 | |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 39 | /* Region to store and retrieve the VBNV contents. */ |
| 40 | struct region_device vbnv_dev; |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 41 | |
| 42 | /* Cache of the current nvdata */ |
| 43 | uint8_t cache[BLOB_SIZE]; |
| 44 | }; |
| 45 | static struct vbnv_flash_ctx vbnv_flash CAR_GLOBAL; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 46 | |
| 47 | /* |
| 48 | * This code assumes that flash is erased to 1-bits, and write operations can |
| 49 | * only change 1-bits to 0-bits. So if the new contents only change 1-bits to |
| 50 | * 0-bits, we can reuse the current blob. |
| 51 | */ |
| 52 | static inline uint8_t erase_value(void) |
| 53 | { |
| 54 | return 0xff; |
| 55 | } |
| 56 | |
| 57 | static inline int can_overwrite(uint8_t current, uint8_t new) |
| 58 | { |
| 59 | return (current & new) == new; |
| 60 | } |
| 61 | |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 62 | static int init_vbnv(void) |
| 63 | { |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 64 | struct vbnv_flash_ctx *ctx = car_get_var_ptr(&vbnv_flash); |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 65 | struct region_device *rdev = &ctx->vbnv_dev; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 66 | uint8_t buf[BLOB_SIZE]; |
| 67 | uint8_t empty_blob[BLOB_SIZE]; |
Randall Spangler | aa1d314 | 2016-08-26 16:01:16 -0700 | [diff] [blame] | 68 | int used_below, empty_above; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 69 | int offset; |
| 70 | int i; |
| 71 | |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 72 | if (vboot_named_region_device_rw("RW_NVRAM", rdev) || |
| 73 | region_device_sz(rdev) < BLOB_SIZE) { |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 74 | printk(BIOS_ERR, "%s: failed to locate NVRAM\n", __func__); |
| 75 | return 1; |
| 76 | } |
| 77 | |
| 78 | /* Prepare an empty blob to compare against. */ |
| 79 | for (i = 0; i < BLOB_SIZE; i++) |
| 80 | empty_blob[i] = erase_value(); |
| 81 | |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 82 | ctx->top_offset = region_device_sz(rdev) - BLOB_SIZE; |
Vadim Bendebury | 9e381a1 | 2015-01-10 23:11:43 -0800 | [diff] [blame] | 83 | |
Randall Spangler | aa1d314 | 2016-08-26 16:01:16 -0700 | [diff] [blame] | 84 | /* Binary search for the border between used and empty */ |
| 85 | used_below = 0; |
| 86 | empty_above = region_device_sz(rdev) / BLOB_SIZE; |
| 87 | |
| 88 | while (used_below + 1 < empty_above) { |
| 89 | int guess = (used_below + empty_above) / 2; |
| 90 | if (rdev_readat(rdev, buf, guess * BLOB_SIZE, BLOB_SIZE) < 0) { |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 91 | printk(BIOS_ERR, "failed to read nvdata\n"); |
| 92 | return 1; |
| 93 | } |
| 94 | if (!memcmp(buf, empty_blob, BLOB_SIZE)) |
Randall Spangler | aa1d314 | 2016-08-26 16:01:16 -0700 | [diff] [blame] | 95 | empty_above = guess; |
| 96 | else |
| 97 | used_below = guess; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 98 | } |
| 99 | |
Randall Spangler | aa1d314 | 2016-08-26 16:01:16 -0700 | [diff] [blame] | 100 | /* |
| 101 | * Offset points to the last non-empty blob. Or if all blobs are empty |
| 102 | * (nvram is totally erased), point to the first blob. |
| 103 | */ |
| 104 | offset = used_below * BLOB_SIZE; |
| 105 | |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 106 | /* reread the nvdata and write it to the cache */ |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 107 | if (rdev_readat(rdev, ctx->cache, offset, BLOB_SIZE) < 0) { |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 108 | printk(BIOS_ERR, "failed to read nvdata\n"); |
| 109 | return 1; |
| 110 | } |
| 111 | |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 112 | ctx->blob_offset = offset; |
| 113 | ctx->initialized = 1; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 114 | |
| 115 | return 0; |
| 116 | } |
| 117 | |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 118 | static int erase_nvram(void) |
| 119 | { |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 120 | struct vbnv_flash_ctx *ctx = car_get_var_ptr(&vbnv_flash); |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 121 | const struct region_device *rdev = &ctx->vbnv_dev; |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 122 | |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 123 | if (rdev_eraseat(rdev, 0, region_device_sz(rdev)) < 0) { |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 124 | printk(BIOS_ERR, "failed to erase nvram\n"); |
| 125 | return 1; |
| 126 | } |
| 127 | |
| 128 | printk(BIOS_INFO, "nvram is cleared\n"); |
| 129 | return 0; |
| 130 | } |
| 131 | |
Duncan Laurie | 88b28ad | 2016-01-25 17:13:27 -0800 | [diff] [blame] | 132 | void read_vbnv_flash(uint8_t *vbnv_copy) |
Daisuke Nojiri | 477dd18 | 2014-10-08 11:32:23 -0700 | [diff] [blame] | 133 | { |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 134 | struct vbnv_flash_ctx *ctx = car_get_var_ptr(&vbnv_flash); |
| 135 | |
| 136 | if (!ctx->initialized) |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 137 | if (init_vbnv()) |
| 138 | return; /* error */ |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 139 | |
| 140 | memcpy(vbnv_copy, ctx->cache, BLOB_SIZE); |
Daisuke Nojiri | 477dd18 | 2014-10-08 11:32:23 -0700 | [diff] [blame] | 141 | } |
| 142 | |
Duncan Laurie | 88b28ad | 2016-01-25 17:13:27 -0800 | [diff] [blame] | 143 | void save_vbnv_flash(const uint8_t *vbnv_copy) |
Daisuke Nojiri | 477dd18 | 2014-10-08 11:32:23 -0700 | [diff] [blame] | 144 | { |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 145 | struct vbnv_flash_ctx *ctx = car_get_var_ptr(&vbnv_flash); |
Vadim Bendebury | 9e381a1 | 2015-01-10 23:11:43 -0800 | [diff] [blame] | 146 | int new_offset; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 147 | int i; |
Aaron Durbin | b18a666 | 2016-08-12 12:48:58 -0500 | [diff] [blame] | 148 | const struct region_device *rdev = &ctx->vbnv_dev; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 149 | |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 150 | if (!ctx->initialized) |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 151 | if (init_vbnv()) |
| 152 | return; /* error */ |
| 153 | |
| 154 | /* Bail out if there have been no changes. */ |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 155 | if (!memcmp(vbnv_copy, ctx->cache, BLOB_SIZE)) |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 156 | return; |
| 157 | |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 158 | new_offset = ctx->blob_offset; |
Vadim Bendebury | 9e381a1 | 2015-01-10 23:11:43 -0800 | [diff] [blame] | 159 | |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 160 | /* See if we can overwrite the current blob with the new one */ |
| 161 | for (i = 0; i < BLOB_SIZE; i++) { |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 162 | if (!can_overwrite(ctx->cache[i], vbnv_copy[i])) { |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 163 | /* unable to overwrite. need to use the next blob */ |
| 164 | new_offset += BLOB_SIZE; |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 165 | if (new_offset > ctx->top_offset) { |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 166 | if (erase_nvram()) |
| 167 | return; /* error */ |
Aaron Durbin | 4e50cdd | 2015-05-15 23:25:46 -0500 | [diff] [blame] | 168 | new_offset = 0; |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 169 | } |
| 170 | break; |
| 171 | } |
| 172 | } |
| 173 | |
Aaron Durbin | 1b5581c | 2016-08-23 13:22:17 -0500 | [diff] [blame] | 174 | if (rdev_writeat(rdev, vbnv_copy, new_offset, BLOB_SIZE) == BLOB_SIZE) { |
Vadim Bendebury | 9e381a1 | 2015-01-10 23:11:43 -0800 | [diff] [blame] | 175 | /* write was successful. safely move pointer forward */ |
Duncan Laurie | 0165038 | 2016-01-26 16:22:53 -0800 | [diff] [blame] | 176 | ctx->blob_offset = new_offset; |
| 177 | memcpy(ctx->cache, vbnv_copy, BLOB_SIZE); |
Vadim Bendebury | 9e381a1 | 2015-01-10 23:11:43 -0800 | [diff] [blame] | 178 | } else { |
| 179 | printk(BIOS_ERR, "failed to save nvdata\n"); |
Daisuke Nojiri | 2b59a83 | 2014-10-29 11:18:11 -0700 | [diff] [blame] | 180 | } |
Daisuke Nojiri | 477dd18 | 2014-10-08 11:32:23 -0700 | [diff] [blame] | 181 | } |