1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright (C) 2018 IBM Corp.
3
4 #include "config.h"
5
6 #include <fcntl.h>
7 #include <stdint.h>
8 #include <stdlib.h>
9 #include <sys/ioctl.h>
10 #include <sys/mman.h>
11 #include <syslog.h>
12 #include <unistd.h>
13
14 #include <algorithm>
15
16 extern "C" {
17 #include "backend.h"
18 #include "common.h"
19 #include "lpc.h"
20 #include "mboxd.h"
21 #include "protocol.h"
22 #include "vpnor/backend.h"
23 }
24
25 #include "vpnor/partition.hpp"
26 #include "vpnor/table.hpp"
27 #include "xyz/openbmc_project/Common/error.hpp"
28
29 #include <cassert>
30 #include <exception>
31 #include <filesystem>
32 #include <memory>
33 #include <phosphor-logging/elog-errors.hpp>
34 #include <phosphor-logging/log.hpp>
35 #include <stdexcept>
36 #include <string>
37
38 #include "vpnor/backend.h"
39
40 namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
41 namespace fs = std::filesystem;
42 namespace vpnor = openpower::virtual_pnor;
43
44 static constexpr uint32_t VPNOR_ERASE_SIZE = 4 * 1024;
45
vpnor_default_paths(vpnor_partition_paths * paths)46 void vpnor_default_paths(vpnor_partition_paths* paths)
47 {
48 strncpy(paths->ro_loc, PARTITION_FILES_RO_LOC, PATH_MAX);
49 paths->ro_loc[PATH_MAX - 1] = '\0';
50 strncpy(paths->rw_loc, PARTITION_FILES_RW_LOC, PATH_MAX);
51 paths->rw_loc[PATH_MAX - 1] = '\0';
52 strncpy(paths->prsv_loc, PARTITION_FILES_PRSV_LOC, PATH_MAX);
53 paths->prsv_loc[PATH_MAX - 1] = '\0';
54 strncpy(paths->patch_loc, PARTITION_FILES_PATCH_LOC, PATH_MAX);
55 paths->prsv_loc[PATH_MAX - 1] = '\0';
56 }
57
58 /** @brief Create a virtual PNOR partition table.
59 *
60 * @param[in] backend - The backend context pointer
61 * @param[in] paths - A paths object pointer to initialise vpnor
62 *
63 * This API should be called before calling any other APIs below. If a table
64 * already exists, this function will not do anything further. This function
65 * will not do anything if the context is NULL.
66 *
67 * The content of the paths object is copied out, ownership is retained by the
68 * caller.
69 *
70 * Returns 0 if the call succeeds, else a negative error code.
71 */
vpnor_init(struct backend * backend,const vpnor_partition_paths * paths)72 static int vpnor_init(struct backend* backend,
73 const vpnor_partition_paths* paths)
74 {
75 namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
76 namespace fs = std::filesystem;
77 namespace vpnor = openpower::virtual_pnor;
78
79 vpnor_data* priv = new vpnor_data;
80 assert(priv);
81
82 priv->paths = *paths;
83 backend->priv = priv;
84
85 try
86 {
87 priv->vpnor = new vpnor_partition_table;
88 // Table object may throw error hence initialize table pointer
89 // to null so that no one try to free the junk pointer.
90 priv->vpnor->table = NULL;
91 priv->vpnor->table =
92 new openpower::virtual_pnor::partition::Table(backend);
93 }
94 catch (vpnor::TocEntryError& e)
95 {
96 MSG_ERR("vpnor init: %s\n", e.what());
97 try
98 {
99 phosphor::logging::commit<err::InternalFailure>();
100 }
101 catch (const std::exception& e)
102 {
103 MSG_ERR("Failed to commit InternalFailure: %s\n", e.what());
104 }
105 return -EINVAL;
106 }
107
108 return 0;
109 }
110
111 /** @brief Copy bootloader partition (alongwith TOC) to LPC memory
112 *
113 * @param[in] backend - The backend context pointer
114 *
115 * @returns 0 on success, negative error code on failure
116 */
vpnor_copy_bootloader_partition(const struct backend * backend,void * buf,uint32_t count)117 int vpnor_copy_bootloader_partition(const struct backend* backend, void* buf,
118 uint32_t count)
119 {
120 // The hostboot bootloader has certain size/offset assumptions, so
121 // we need a special partition table here.
122 // It assumes the PNOR is 64M, the TOC size is 32K, the erase block is
123 // 4K, the page size is 4K.
124 // It also assumes the TOC is at the 'end of pnor - toc size - 1 page size'
125 // offset, and first looks for the TOC here, before proceeding to move up
126 // page by page looking for the TOC. So it is optimal to place the TOC at
127 // this offset.
128 constexpr size_t eraseSize = 0x1000;
129 constexpr size_t pageSize = 0x1000;
130 constexpr size_t pnorSize = 0x4000000;
131 constexpr size_t tocMaxSize = 0x8000;
132 constexpr size_t tocStart = pnorSize - tocMaxSize - pageSize;
133 constexpr auto blPartitionName = "HBB";
134
135 namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
136 namespace fs = std::filesystem;
137 namespace vpnor = openpower::virtual_pnor;
138
139 try
140 {
141 vpnor_partition_table vtbl{};
142 struct vpnor_data priv;
143 struct backend local = *backend;
144
145 priv.vpnor = &vtbl;
146 priv.paths = ((struct vpnor_data*)backend->priv)->paths;
147 local.priv = &priv;
148 local.block_size_shift = log_2(eraseSize);
149
150 openpower::virtual_pnor::partition::Table blTable(&local);
151
152 vtbl.table = &blTable;
153
154 size_t tocOffset = 0;
155
156 const pnor_partition& partition = blTable.partition(blPartitionName);
157 size_t hbbOffset = partition.data.base * eraseSize;
158 uint32_t hbbSize = partition.data.actual;
159
160 if (count < tocStart + blTable.capacity() ||
161 count < hbbOffset + hbbSize)
162 {
163 MSG_ERR("Reserved memory too small for dumb bootstrap\n");
164 return -EINVAL;
165 }
166
167 uint8_t* buf8 = static_cast<uint8_t*>(buf);
168 backend_copy(&local, tocOffset, buf8 + tocStart, blTable.capacity());
169 backend_copy(&local, hbbOffset, buf8 + hbbOffset, hbbSize);
170 }
171 catch (err::InternalFailure& e)
172 {
173 phosphor::logging::commit<err::InternalFailure>();
174 return -EIO;
175 }
176 catch (vpnor::ReasonedError& e)
177 {
178 MSG_ERR("vpnor part copy: %s\n", e.what());
179 phosphor::logging::commit<err::InternalFailure>();
180 return -EIO;
181 }
182
183 return 0;
184 }
185
vpnor_dev_init(struct backend * backend,void * data)186 int vpnor_dev_init(struct backend* backend, void* data)
187 {
188 vpnor_partition_paths* paths = (vpnor_partition_paths*)data;
189 struct mtd_info_user mtd_info;
190 const char* filename = NULL;
191 int fd;
192 int rc = 0;
193
194 if (!(fs::is_directory(fs::status(paths->ro_loc)) &&
195 fs::is_directory(fs::status(paths->rw_loc)) &&
196 fs::is_directory(fs::status(paths->prsv_loc))))
197 {
198 MSG_ERR("Couldn't find partition path\n");
199 return -EINVAL;
200 }
201
202 if (backend->flash_size == 0)
203 {
204 filename = get_dev_mtd();
205
206 MSG_INFO("No flash size provided, using PNOR MTD size\n");
207
208 if (!filename)
209 {
210 MSG_ERR("Couldn't find the flash /dev/mtd partition\n");
211 return -errno;
212 }
213
214 MSG_DBG("Opening %s\n", filename);
215
216 fd = open(filename, O_RDWR);
217 if (fd < 0)
218 {
219 MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", filename,
220 strerror(errno));
221 rc = -errno;
222 goto cleanup_filename;
223 }
224
225 // Read the Flash Info
226 if (ioctl(fd, MEMGETINFO, &mtd_info) == -1)
227 {
228 MSG_ERR("Couldn't get information about MTD: %s\n",
229 strerror(errno));
230 rc = -errno;
231 goto cleanup_fd;
232 }
233
234 close(fd);
235 free((void*)filename);
236
237 // See comment in flash.c on why
238 // this is needed.
239 backend->flash_size = mtd_info.size;
240 }
241
242 // Hostboot requires a 4K block-size to be used in the FFS flash structure
243 backend->erase_size_shift = log_2(VPNOR_ERASE_SIZE);
244 backend->block_size_shift = backend->erase_size_shift;
245
246 return vpnor_init(backend, paths);
247
248 cleanup_fd:
249 close(fd);
250
251 cleanup_filename:
252 free((void*)filename);
253
254 return rc;
255 }
256
vpnor_free(struct backend * backend)257 static void vpnor_free(struct backend* backend)
258 {
259 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
260
261 if (priv)
262 {
263 if (priv->vpnor)
264 {
265 delete priv->vpnor->table;
266 }
267 delete priv->vpnor;
268 }
269 delete priv;
270 }
271
272 /*
273 * vpnor_copy() - Copy data from the virtual pnor into a provided buffer
274 * @context: The backend context pointer
275 * @offset: The pnor offset to copy from (bytes)
276 * @mem: The buffer to copy into (must be of atleast 'size' bytes)
277 * @size: The number of bytes to copy
278 * Return: Number of bytes copied on success, otherwise negative error
279 * code. vpnor_copy will copy at most 'size' bytes, but it may
280 * copy less.
281 */
vpnor_copy(struct backend * backend,uint32_t offset,void * mem,uint32_t size)282 static int64_t vpnor_copy(struct backend* backend, uint32_t offset, void* mem,
283 uint32_t size)
284 {
285 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
286 vpnor::partition::Table* table;
287 int rc = size;
288
289 if (!(priv->vpnor && priv->vpnor->table))
290 {
291 MSG_ERR("Trying to copy data with uninitialised context!\n");
292 return -EINVAL;
293 }
294
295 table = priv->vpnor->table;
296
297 MSG_DBG("Copy virtual pnor to %p for size 0x%.8x from offset 0x%.8x\n", mem,
298 size, offset);
299
300 /* The virtual PNOR partition table starts at offset 0 in the virtual
301 * pnor image. Check if host asked for an offset that lies within the
302 * partition table.
303 */
304 size_t sz = table->size();
305 if (offset < sz)
306 {
307 const pnor_partition_table& toc = table->getHostTable();
308 rc = std::min(sz - offset, static_cast<size_t>(size));
309 memcpy(mem, ((uint8_t*)&toc) + offset, rc);
310 return rc;
311 }
312
313 try
314 {
315 vpnor::Request req(backend, offset);
316 rc = req.read(mem, size);
317 }
318 catch (vpnor::UnmappedOffset& e)
319 {
320 /*
321 * Hooo boy. Pretend that this is valid flash so we don't have
322 * discontiguous regions presented to the host. Instead, fill a window
323 * with 0xff so the 'flash' looks erased. Writes to such regions are
324 * dropped on the floor, see the implementation of vpnor_write() below.
325 */
326 MSG_INFO("Host requested unmapped region of %" PRId32
327 " bytes at offset 0x%" PRIx32 "\n",
328 size, offset);
329 uint32_t span = e.next - e.base;
330 rc = std::min(size, span);
331 memset(mem, 0xff, rc);
332 }
333 catch (std::exception& e)
334 {
335 MSG_ERR("vpnor copy: %s\n", e.what());
336 phosphor::logging::commit<err::InternalFailure>();
337 rc = -EIO;
338 }
339 return rc;
340 }
341
342 /*
343 * vpnor_write() - Write to the virtual pnor from a provided buffer
344 * @context: The backend context pointer
345 * @offset: The flash offset to write to (bytes)
346 * @buf: The buffer to write from (must be of atleast size)
347 * @size: The number of bytes to write
348 *
349 * Return: 0 on success otherwise negative error code
350 */
351
vpnor_write(struct backend * backend,uint32_t offset,void * buf,uint32_t count)352 static int vpnor_write(struct backend* backend, uint32_t offset, void* buf,
353 uint32_t count)
354 {
355 assert(backend);
356
357 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
358
359 if (!(priv && priv->vpnor && priv->vpnor->table))
360 {
361 MSG_ERR("Trying to write data with uninitialised context!\n");
362 return -EINVAL;
363 }
364
365 vpnor::partition::Table* table = priv->vpnor->table;
366
367 try
368 {
369 const struct pnor_partition& part = table->partition(offset);
370 if (part.data.user.data[1] & PARTITION_READONLY)
371 {
372 MSG_ERR("Unreachable: Host attempted to write to read-only "
373 "partition %s\n",
374 part.data.name);
375 return -EPERM;
376 }
377
378 MSG_DBG("Write flash @ 0x%.8x for 0x%.8x from %p\n", offset, count,
379 buf);
380 vpnor::Request req(backend, offset);
381 req.write(buf, count);
382 }
383 catch (vpnor::UnmappedOffset& e)
384 {
385 MSG_ERR("Unreachable: Host attempted to write %" PRIu32
386 " bytes to unmapped offset 0x%" PRIx32 "\n",
387 count, offset);
388 return -EACCES;
389 }
390 catch (const vpnor::OutOfBoundsOffset& e)
391 {
392 MSG_ERR("vpnor write: %s\n", e.what());
393 return -EINVAL;
394 }
395 catch (const std::exception& e)
396 {
397 MSG_ERR("vpnor write exception: %s\n", e.what());
398 phosphor::logging::commit<err::InternalFailure>();
399 return -EIO;
400 }
401 return 0;
402 }
403
vpnor_partition_is_readonly(const pnor_partition & part)404 static bool vpnor_partition_is_readonly(const pnor_partition& part)
405 {
406 return part.data.user.data[1] & PARTITION_READONLY;
407 }
408
vpnor_validate(struct backend * backend,uint32_t offset,uint32_t size,bool ro)409 static int vpnor_validate(struct backend* backend, uint32_t offset,
410 uint32_t size __attribute__((unused)), bool ro)
411 {
412 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
413
414 /* All reads are allowed */
415 if (ro)
416 {
417 return 0;
418 }
419
420 /* Only allow write windows on regions mapped by the ToC as writeable */
421 try
422 {
423 const pnor_partition& part = priv->vpnor->table->partition(offset);
424 if (vpnor_partition_is_readonly(part))
425 {
426 MSG_ERR("Try to write read only partition (part=%s, offset=0x%x)\n",
427 part.data.name, offset);
428 return -EPERM;
429 }
430 }
431 catch (const openpower::virtual_pnor::UnmappedOffset& e)
432 {
433 MSG_ERR("Try to write unmapped area (offset=0x%lx)\n", e.base);
434
435 /*
436 * Writes to unmapped areas are not meaningful, so deny the request.
437 * This removes the ability for a compromised host to abuse unused
438 * space if any data was to be persisted (which it isn't).
439 */
440 return -EACCES;
441 }
442
443 // Allowed.
444 return 0;
445 }
446
447 /*
448 * vpnor_reset() - Reset the lpc bus mapping
449 * @context: The mbox context pointer
450 *
451 * Return 0 on success otherwise negative error code
452 */
vpnor_reset(struct backend * backend,void * buf,uint32_t count)453 static int vpnor_reset(struct backend* backend, void* buf, uint32_t count)
454 {
455 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv;
456 int rc;
457
458 vpnor_partition_paths paths = priv->paths;
459
460 vpnor_free(backend);
461
462 rc = vpnor_init(backend, &paths);
463 if (rc < 0)
464 return rc;
465
466 rc = vpnor_copy_bootloader_partition(backend, buf, count);
467 if (rc < 0)
468 return rc;
469
470 return reset_lpc_memory;
471 }
472
473 /*
474 * vpnor_align_offset() - Align the offset
475 * @context: The backend context pointer
476 * @offset: The flash offset
477 * @window_size:The window size
478 *
479 * Return: 0 on success otherwise negative error code
480 */
vpnor_align_offset(struct backend * backend,uint32_t * offset,uint32_t window_size)481 static int vpnor_align_offset(struct backend* backend, uint32_t* offset,
482 uint32_t window_size)
483 {
484 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv;
485
486 /* Adjust the offset to align with the offset of partition base */
487 try
488 {
489 // Get the base of the partition
490 const pnor_partition& part = priv->vpnor->table->partition(*offset);
491 uint32_t base = part.data.base * VPNOR_ERASE_SIZE;
492
493 // Get the base offset relative to the window_size
494 uint32_t base_offset = base & (window_size - 1);
495
496 // Adjust the offset to align with the base
497 *offset = ((*offset - base_offset) & ~(window_size - 1)) + base_offset;
498 MSG_DBG(
499 "vpnor_align_offset: to @ 0x%.8x(base=0x%.8x base_offset=0x%.8x)\n",
500 *offset, base, base_offset);
501 return 0;
502 }
503 catch (const openpower::virtual_pnor::UnmappedOffset& e)
504 {
505 MSG_ERR("Aligned offset is unmapped area (offset=0x%lx)\n", e.base);
506
507 /*
508 * Writes to unmapped areas are not meaningful, so deny the request.
509 * This removes the ability for a compromised host to abuse unused
510 * space if any data was to be persisted (which it isn't).
511 */
512 return -EACCES;
513 }
514 }
515
516 static const struct backend_ops vpnor_ops = {
517 .init = vpnor_dev_init,
518 .free = vpnor_free,
519 .copy = vpnor_copy,
520 .set_bytemap = NULL,
521 .erase = NULL,
522 .write = vpnor_write,
523 .validate = vpnor_validate,
524 .reset = vpnor_reset,
525 .align_offset = vpnor_align_offset,
526 };
527
backend_get_vpnor(void)528 struct backend backend_get_vpnor(void)
529 {
530 struct backend be = {nullptr, nullptr, 0, 0, 0};
531
532 be.ops = &vpnor_ops;
533
534 return be;
535 }
536
backend_probe_vpnor(struct backend * master,const struct vpnor_partition_paths * paths)537 int backend_probe_vpnor(struct backend* master,
538 const struct vpnor_partition_paths* paths)
539 {
540 struct backend with;
541
542 assert(master);
543 with = backend_get_vpnor();
544
545 return backend_init(master, &with, (void*)paths);
546 }
547