blob: d081b13fee57051500e40958e8346beb4206d209 [file] [log] [blame]
Evan Lojewskif1e547c2019-03-14 14:34:33 +10301// SPDX-License-Identifier: Apache-2.0
2// Copyright (C) 2018 IBM Corp.
3
Andrew Jeffery035ad762019-03-18 13:02:01 +10304#include "config.h"
5
Evan Lojewskif1e547c2019-03-14 14:34:33 +10306#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
16extern "C" {
Andrew Jeffery035ad762019-03-18 13:02:01 +103017#include "backend.h"
Evan Lojewskif1e547c2019-03-14 14:34:33 +103018#include "common.h"
19#include "lpc.h"
20#include "mboxd.h"
21#include "protocol.h"
Andrew Jeffery035ad762019-03-18 13:02:01 +103022#include "vpnor/backend.h"
Evan Lojewskif1e547c2019-03-14 14:34:33 +103023}
24
Andrew Jefferyfb01e142019-03-18 13:17:08 +103025#include "vpnor/partition.hpp"
Andrew Jefferyde08ca22019-03-18 13:23:46 +103026#include "vpnor/table.hpp"
Evan Lojewskif1e547c2019-03-14 14:34:33 +103027#include "xyz/openbmc_project/Common/error.hpp"
28
Andrew Jeffery035ad762019-03-18 13:02:01 +103029#include <cassert>
Evan Lojewskif1e547c2019-03-14 14:34:33 +103030#include <exception>
Patrick Williams150be912022-06-16 16:46:09 -050031#include <filesystem>
Evan Lojewskif1e547c2019-03-14 14:34:33 +103032#include <memory>
33#include <phosphor-logging/elog-errors.hpp>
34#include <phosphor-logging/log.hpp>
35#include <stdexcept>
36#include <string>
37
Andrew Jefferyf4bc3352019-03-18 12:09:48 +103038#include "vpnor/backend.h"
Evan Lojewskif1e547c2019-03-14 14:34:33 +103039
40namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
Patrick Williams150be912022-06-16 16:46:09 -050041namespace fs = std::filesystem;
Evan Lojewskif1e547c2019-03-14 14:34:33 +103042namespace vpnor = openpower::virtual_pnor;
43
44static constexpr uint32_t VPNOR_ERASE_SIZE = 4 * 1024;
45
Andrew Jeffery035ad762019-03-18 13:02:01 +103046void 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 */
72static int vpnor_init(struct backend* backend,
73 const vpnor_partition_paths* paths)
74{
75 namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
Patrick Williams150be912022-06-16 16:46:09 -050076 namespace fs = std::filesystem;
Andrew Jeffery035ad762019-03-18 13:02:01 +103077 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;
Ninad Palsule2be45632024-11-20 17:28:49 -060088 // 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;
Andrew Jeffery035ad762019-03-18 13:02:01 +103091 priv->vpnor->table =
92 new openpower::virtual_pnor::partition::Table(backend);
93 }
94 catch (vpnor::TocEntryError& e)
95 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -050096 MSG_ERR("vpnor init: %s\n", e.what());
Andrew Jeffery035ad762019-03-18 13:02:01 +103097 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 */
117int 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;
Patrick Williams150be912022-06-16 16:46:09 -0500136 namespace fs = std::filesystem;
Andrew Jeffery035ad762019-03-18 13:02:01 +1030137 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 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500178 MSG_ERR("vpnor part copy: %s\n", e.what());
Andrew Jeffery035ad762019-03-18 13:02:01 +1030179 phosphor::logging::commit<err::InternalFailure>();
180 return -EIO;
181 }
182
183 return 0;
184}
185
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030186int 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 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500198 MSG_ERR("Couldn't find partition path\n");
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030199 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
248cleanup_fd:
249 close(fd);
250
251cleanup_filename:
252 free((void*)filename);
253
254 return rc;
255}
256
257static void vpnor_free(struct backend* backend)
258{
Andrew Jeffery035ad762019-03-18 13:02:01 +1030259 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;
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030270}
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 */
282static 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 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500335 MSG_ERR("vpnor copy: %s\n", e.what());
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030336 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
352static 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 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500392 MSG_ERR("vpnor write: %s\n", e.what());
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030393 return -EINVAL;
394 }
395 catch (const std::exception& e)
396 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500397 MSG_ERR("vpnor write exception: %s\n", e.what());
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030398 phosphor::logging::commit<err::InternalFailure>();
399 return -EIO;
400 }
401 return 0;
402}
403
404static bool vpnor_partition_is_readonly(const pnor_partition& part)
405{
406 return part.data.user.data[1] & PARTITION_READONLY;
407}
408
409static 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 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500426 MSG_ERR("Try to write read only partition (part=%s, offset=0x%x)\n",
Alvin Wang8cef63e2019-10-15 23:23:38 +0800427 part.data.name, offset);
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030428 return -EPERM;
429 }
430 }
431 catch (const openpower::virtual_pnor::UnmappedOffset& e)
432 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500433 MSG_ERR("Try to write unmapped area (offset=0x%lx)\n", e.base);
434
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030435 /*
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 */
453static 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
Andrew Jeffery035ad762019-03-18 13:02:01 +1030460 vpnor_free(backend);
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030461
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
Alvin Wang8cef63e2019-10-15 23:23:38 +0800473/*
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 */
481static 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 {
Ninad Palsuleb8d89b72024-10-23 11:15:21 -0500505 MSG_ERR("Aligned offset is unmapped area (offset=0x%lx)\n", e.base);
506
Alvin Wang8cef63e2019-10-15 23:23:38 +0800507 /*
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
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030516static 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,
Alvin Wang8cef63e2019-10-15 23:23:38 +0800525 .align_offset = vpnor_align_offset,
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030526};
527
528struct backend backend_get_vpnor(void)
529{
Patrick Williams68a24c92023-07-25 12:02:16 -0500530 struct backend be = {nullptr, nullptr, 0, 0, 0};
Evan Lojewskif1e547c2019-03-14 14:34:33 +1030531
532 be.ops = &vpnor_ops;
533
534 return be;
535}
536
537int 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}