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