coreboot
coreboot is an Open Source project aimed at replacing the proprietary BIOS found in most computers.
vbnv_flash.c
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 
3 #include <commonlib/region.h>
4 #include <console/console.h>
5 #include <fmap.h>
6 #include <string.h>
7 #include <vb2_api.h>
9 #include <security/vboot/vbnv.h>
11 
12 #define BLOB_SIZE VB2_NVDATA_SIZE
13 
15  /* VBNV flash is initialized */
17 
18  /* Offset of the current nvdata in flash */
20 
21  /* Offset of the topmost nvdata blob in flash */
23 
24  /* Region to store and retrieve the VBNV contents. */
25  struct region_device vbnv_dev;
26 
27  /* Cache of the current nvdata */
29 };
30 static struct vbnv_flash_ctx vbnv_flash;
31 
32 /*
33  * This code assumes that flash is erased to 1-bits, and write operations can
34  * only change 1-bits to 0-bits. So if the new contents only change 1-bits to
35  * 0-bits, we can reuse the current blob.
36  */
37 static inline uint8_t erase_value(void)
38 {
39  return 0xff;
40 }
41 
42 static inline int can_overwrite(uint8_t current, uint8_t new)
43 {
44  return (current & new) == new;
45 }
46 
47 static int init_vbnv(void)
48 {
49  struct vbnv_flash_ctx *ctx = &vbnv_flash;
50  struct region_device *rdev = &ctx->vbnv_dev;
52  uint8_t empty_blob[BLOB_SIZE];
53  int used_below, empty_above;
54  int offset;
55  int i;
56 
57  if (fmap_locate_area_as_rdev_rw("RW_NVRAM", rdev) ||
59  printk(BIOS_ERR, "%s: failed to locate NVRAM\n", __func__);
60  return 1;
61  }
62 
63  /* Prepare an empty blob to compare against. */
64  for (i = 0; i < BLOB_SIZE; i++)
65  empty_blob[i] = erase_value();
66 
68 
69  /* Binary search for the border between used and empty */
70  used_below = 0;
71  empty_above = region_device_sz(rdev) / BLOB_SIZE;
72 
73  while (used_below + 1 < empty_above) {
74  int guess = (used_below + empty_above) / 2;
75  if (rdev_readat(rdev, buf, guess * BLOB_SIZE, BLOB_SIZE) < 0) {
76  printk(BIOS_ERR, "failed to read nvdata\n");
77  return 1;
78  }
79  if (!memcmp(buf, empty_blob, BLOB_SIZE))
80  empty_above = guess;
81  else
82  used_below = guess;
83  }
84 
85  /*
86  * Offset points to the last non-empty blob. Or if all blobs are empty
87  * (nvram is totally erased), point to the first blob.
88  */
89  offset = used_below * BLOB_SIZE;
90 
91  /* reread the nvdata and write it to the cache */
92  if (rdev_readat(rdev, ctx->cache, offset, BLOB_SIZE) < 0) {
93  printk(BIOS_ERR, "failed to read nvdata\n");
94  return 1;
95  }
96 
97  ctx->blob_offset = offset;
98  ctx->initialized = 1;
99 
100  return 0;
101 }
102 
103 static int erase_nvram(void)
104 {
105  struct vbnv_flash_ctx *ctx = &vbnv_flash;
106  const struct region_device *rdev = &ctx->vbnv_dev;
107 
108  if (rdev_eraseat(rdev, 0, region_device_sz(rdev)) < 0) {
109  printk(BIOS_ERR, "failed to erase nvram\n");
110  return 1;
111  }
112 
113  printk(BIOS_INFO, "nvram is cleared\n");
114  return 0;
115 }
116 
117 void read_vbnv_flash(uint8_t *vbnv_copy)
118 {
119  struct vbnv_flash_ctx *ctx = &vbnv_flash;
120 
121  if (!ctx->initialized)
122  if (init_vbnv())
123  return; /* error */
124 
125  memcpy(vbnv_copy, ctx->cache, BLOB_SIZE);
126 }
127 
128 void save_vbnv_flash(const uint8_t *vbnv_copy)
129 {
130  struct vbnv_flash_ctx *ctx = &vbnv_flash;
131  int new_offset;
132  int i;
133  const struct region_device *rdev = &ctx->vbnv_dev;
134 
135  if (!ctx->initialized)
136  if (init_vbnv())
137  return; /* error */
138 
139  /* Bail out if there have been no changes. */
140  if (!memcmp(vbnv_copy, ctx->cache, BLOB_SIZE))
141  return;
142 
143  new_offset = ctx->blob_offset;
144 
145  /* See if we can overwrite the current blob with the new one */
146  for (i = 0; i < BLOB_SIZE; i++) {
147  if (!can_overwrite(ctx->cache[i], vbnv_copy[i])) {
148  /* unable to overwrite. need to use the next blob */
149  new_offset += BLOB_SIZE;
150  if (new_offset > ctx->top_offset) {
151  if (erase_nvram())
152  return; /* error */
153  new_offset = 0;
154  }
155  break;
156  }
157  }
158 
159  if (rdev_writeat(rdev, vbnv_copy, new_offset, BLOB_SIZE) == BLOB_SIZE) {
160  /* write was successful. safely move pointer forward */
161  ctx->blob_offset = new_offset;
162  memcpy(ctx->cache, vbnv_copy, BLOB_SIZE);
163  } else {
164  printk(BIOS_ERR, "failed to save nvdata\n");
165  }
166 }
void * memcpy(void *dest, const void *src, size_t n)
Definition: memcpy.c:7
#define printk(level,...)
Definition: stdlib.h:16
static struct region_device rdev
Definition: flashconsole.c:14
static size_t offset
Definition: flashconsole.c:16
int fmap_locate_area_as_rdev_rw(const char *name, struct region_device *area)
Definition: fmap.c:154
#define BIOS_INFO
BIOS_INFO - Expected events.
Definition: loglevel.h:113
#define BIOS_ERR
BIOS_ERR - System in incomplete state.
Definition: loglevel.h:72
static uint8_t * buf
Definition: uart.c:7
ssize_t rdev_eraseat(const struct region_device *rd, size_t offset, size_t size)
Definition: region.c:114
static size_t region_device_sz(const struct region_device *rdev)
Definition: region.h:132
ssize_t rdev_writeat(const struct region_device *rd, const void *b, size_t offset, size_t size)
Definition: region.c:94
ssize_t rdev_readat(const struct region_device *rd, void *b, size_t offset, size_t size)
Definition: region.c:77
unsigned char uint8_t
Definition: stdint.h:8
int memcmp(const void *s1, const void *s2, size_t n)
Definition: memcmp.c:3
struct region_device vbnv_dev
Definition: vbnv_flash.c:25
uint8_t cache[BLOB_SIZE]
Definition: vbnv_flash.c:28
#define BLOB_SIZE
Definition: vbnv_flash.c:12
static uint8_t erase_value(void)
Definition: vbnv_flash.c:37
static int init_vbnv(void)
Definition: vbnv_flash.c:47
void read_vbnv_flash(uint8_t *vbnv_copy)
Definition: vbnv_flash.c:117
static int erase_nvram(void)
Definition: vbnv_flash.c:103
static int can_overwrite(uint8_t current, uint8_t new)
Definition: vbnv_flash.c:42
static struct vbnv_flash_ctx vbnv_flash
Definition: vbnv_flash.c:30
void save_vbnv_flash(const uint8_t *vbnv_copy)
Definition: vbnv_flash.c:128