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 priv->vpnor->table =
89 new openpower::virtual_pnor::partition::Table(backend);
90 }
91 catch (vpnor::TocEntryError& e)
92 {
93 MSG_ERR("vpnor init: %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 */
vpnor_copy_bootloader_partition(const struct backend * backend,void * buf,uint32_t count)114 int 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::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("vpnor part copy: %s\n", e.what());
176 phosphor::logging::commit<err::InternalFailure>();
177 return -EIO;
178 }
179
180 return 0;
181 }
182
vpnor_dev_init(struct backend * backend,void * data)183 int 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 MSG_ERR("Couldn't find partition path\n");
196 return -EINVAL;
197 }
198
199 if (backend->flash_size == 0)
200 {
201 filename = get_dev_mtd();
202
203 MSG_INFO("No flash size provided, using PNOR MTD size\n");
204
205 if (!filename)
206 {
207 MSG_ERR("Couldn't find the flash /dev/mtd partition\n");
208 return -errno;
209 }
210
211 MSG_DBG("Opening %s\n", filename);
212
213 fd = open(filename, O_RDWR);
214 if (fd < 0)
215 {
216 MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", filename,
217 strerror(errno));
218 rc = -errno;
219 goto cleanup_filename;
220 }
221
222 // Read the Flash Info
223 if (ioctl(fd, MEMGETINFO, &mtd_info) == -1)
224 {
225 MSG_ERR("Couldn't get information about MTD: %s\n",
226 strerror(errno));
227 rc = -errno;
228 goto cleanup_fd;
229 }
230
231 close(fd);
232 free((void*)filename);
233
234 // See comment in flash.c on why
235 // this is needed.
236 backend->flash_size = mtd_info.size;
237 }
238
239 // Hostboot requires a 4K block-size to be used in the FFS flash structure
240 backend->erase_size_shift = log_2(VPNOR_ERASE_SIZE);
241 backend->block_size_shift = backend->erase_size_shift;
242
243 return vpnor_init(backend, paths);
244
245 cleanup_fd:
246 close(fd);
247
248 cleanup_filename:
249 free((void*)filename);
250
251 return rc;
252 }
253
vpnor_free(struct backend * backend)254 static void vpnor_free(struct backend* backend)
255 {
256 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
257
258 if (priv)
259 {
260 if (priv->vpnor)
261 {
262 delete priv->vpnor->table;
263 }
264 delete priv->vpnor;
265 }
266 delete priv;
267 }
268
269 /*
270 * vpnor_copy() - Copy data from the virtual pnor into a provided buffer
271 * @context: The backend context pointer
272 * @offset: The pnor offset to copy from (bytes)
273 * @mem: The buffer to copy into (must be of atleast 'size' bytes)
274 * @size: The number of bytes to copy
275 * Return: Number of bytes copied on success, otherwise negative error
276 * code. vpnor_copy will copy at most 'size' bytes, but it may
277 * copy less.
278 */
vpnor_copy(struct backend * backend,uint32_t offset,void * mem,uint32_t size)279 static int64_t vpnor_copy(struct backend* backend, uint32_t offset, void* mem,
280 uint32_t size)
281 {
282 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
283 vpnor::partition::Table* table;
284 int rc = size;
285
286 if (!(priv->vpnor && priv->vpnor->table))
287 {
288 MSG_ERR("Trying to copy data with uninitialised context!\n");
289 return -EINVAL;
290 }
291
292 table = priv->vpnor->table;
293
294 MSG_DBG("Copy virtual pnor to %p for size 0x%.8x from offset 0x%.8x\n", mem,
295 size, offset);
296
297 /* The virtual PNOR partition table starts at offset 0 in the virtual
298 * pnor image. Check if host asked for an offset that lies within the
299 * partition table.
300 */
301 size_t sz = table->size();
302 if (offset < sz)
303 {
304 const pnor_partition_table& toc = table->getHostTable();
305 rc = std::min(sz - offset, static_cast<size_t>(size));
306 memcpy(mem, ((uint8_t*)&toc) + offset, rc);
307 return rc;
308 }
309
310 try
311 {
312 vpnor::Request req(backend, offset);
313 rc = req.read(mem, size);
314 }
315 catch (vpnor::UnmappedOffset& e)
316 {
317 /*
318 * Hooo boy. Pretend that this is valid flash so we don't have
319 * discontiguous regions presented to the host. Instead, fill a window
320 * with 0xff so the 'flash' looks erased. Writes to such regions are
321 * dropped on the floor, see the implementation of vpnor_write() below.
322 */
323 MSG_INFO("Host requested unmapped region of %" PRId32
324 " bytes at offset 0x%" PRIx32 "\n",
325 size, offset);
326 uint32_t span = e.next - e.base;
327 rc = std::min(size, span);
328 memset(mem, 0xff, rc);
329 }
330 catch (std::exception& e)
331 {
332 MSG_ERR("vpnor copy: %s\n", e.what());
333 phosphor::logging::commit<err::InternalFailure>();
334 rc = -EIO;
335 }
336 return rc;
337 }
338
339 /*
340 * vpnor_write() - Write to the virtual pnor from a provided buffer
341 * @context: The backend context pointer
342 * @offset: The flash offset to write to (bytes)
343 * @buf: The buffer to write from (must be of atleast size)
344 * @size: The number of bytes to write
345 *
346 * Return: 0 on success otherwise negative error code
347 */
348
vpnor_write(struct backend * backend,uint32_t offset,void * buf,uint32_t count)349 static int vpnor_write(struct backend* backend, uint32_t offset, void* buf,
350 uint32_t count)
351 {
352 assert(backend);
353
354 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
355
356 if (!(priv && priv->vpnor && priv->vpnor->table))
357 {
358 MSG_ERR("Trying to write data with uninitialised context!\n");
359 return -EINVAL;
360 }
361
362 vpnor::partition::Table* table = priv->vpnor->table;
363
364 try
365 {
366 const struct pnor_partition& part = table->partition(offset);
367 if (part.data.user.data[1] & PARTITION_READONLY)
368 {
369 MSG_ERR("Unreachable: Host attempted to write to read-only "
370 "partition %s\n",
371 part.data.name);
372 return -EPERM;
373 }
374
375 MSG_DBG("Write flash @ 0x%.8x for 0x%.8x from %p\n", offset, count,
376 buf);
377 vpnor::Request req(backend, offset);
378 req.write(buf, count);
379 }
380 catch (vpnor::UnmappedOffset& e)
381 {
382 MSG_ERR("Unreachable: Host attempted to write %" PRIu32
383 " bytes to unmapped offset 0x%" PRIx32 "\n",
384 count, offset);
385 return -EACCES;
386 }
387 catch (const vpnor::OutOfBoundsOffset& e)
388 {
389 MSG_ERR("vpnor write: %s\n", e.what());
390 return -EINVAL;
391 }
392 catch (const std::exception& e)
393 {
394 MSG_ERR("vpnor write exception: %s\n", e.what());
395 phosphor::logging::commit<err::InternalFailure>();
396 return -EIO;
397 }
398 return 0;
399 }
400
vpnor_partition_is_readonly(const pnor_partition & part)401 static bool vpnor_partition_is_readonly(const pnor_partition& part)
402 {
403 return part.data.user.data[1] & PARTITION_READONLY;
404 }
405
vpnor_validate(struct backend * backend,uint32_t offset,uint32_t size,bool ro)406 static int vpnor_validate(struct backend* backend, uint32_t offset,
407 uint32_t size __attribute__((unused)), bool ro)
408 {
409 struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
410
411 /* All reads are allowed */
412 if (ro)
413 {
414 return 0;
415 }
416
417 /* Only allow write windows on regions mapped by the ToC as writeable */
418 try
419 {
420 const pnor_partition& part = priv->vpnor->table->partition(offset);
421 if (vpnor_partition_is_readonly(part))
422 {
423 MSG_ERR("Try to write read only partition (part=%s, offset=0x%x)\n",
424 part.data.name, offset);
425 return -EPERM;
426 }
427 }
428 catch (const openpower::virtual_pnor::UnmappedOffset& e)
429 {
430 MSG_ERR("Try to write unmapped area (offset=0x%lx)\n", e.base);
431
432 /*
433 * Writes to unmapped areas are not meaningful, so deny the request.
434 * This removes the ability for a compromised host to abuse unused
435 * space if any data was to be persisted (which it isn't).
436 */
437 return -EACCES;
438 }
439
440 // Allowed.
441 return 0;
442 }
443
444 /*
445 * vpnor_reset() - Reset the lpc bus mapping
446 * @context: The mbox context pointer
447 *
448 * Return 0 on success otherwise negative error code
449 */
vpnor_reset(struct backend * backend,void * buf,uint32_t count)450 static int vpnor_reset(struct backend* backend, void* buf, uint32_t count)
451 {
452 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv;
453 int rc;
454
455 vpnor_partition_paths paths = priv->paths;
456
457 vpnor_free(backend);
458
459 rc = vpnor_init(backend, &paths);
460 if (rc < 0)
461 return rc;
462
463 rc = vpnor_copy_bootloader_partition(backend, buf, count);
464 if (rc < 0)
465 return rc;
466
467 return reset_lpc_memory;
468 }
469
470 /*
471 * vpnor_align_offset() - Align the offset
472 * @context: The backend context pointer
473 * @offset: The flash offset
474 * @window_size:The window size
475 *
476 * Return: 0 on success otherwise negative error code
477 */
vpnor_align_offset(struct backend * backend,uint32_t * offset,uint32_t window_size)478 static int vpnor_align_offset(struct backend* backend, uint32_t* offset,
479 uint32_t window_size)
480 {
481 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv;
482
483 /* Adjust the offset to align with the offset of partition base */
484 try
485 {
486 // Get the base of the partition
487 const pnor_partition& part = priv->vpnor->table->partition(*offset);
488 uint32_t base = part.data.base * VPNOR_ERASE_SIZE;
489
490 // Get the base offset relative to the window_size
491 uint32_t base_offset = base & (window_size - 1);
492
493 // Adjust the offset to align with the base
494 *offset = ((*offset - base_offset) & ~(window_size - 1)) + base_offset;
495 MSG_DBG(
496 "vpnor_align_offset: to @ 0x%.8x(base=0x%.8x base_offset=0x%.8x)\n",
497 *offset, base, base_offset);
498 return 0;
499 }
500 catch (const openpower::virtual_pnor::UnmappedOffset& e)
501 {
502 MSG_ERR("Aligned offset is unmapped area (offset=0x%lx)\n", e.base);
503
504 /*
505 * Writes to unmapped areas are not meaningful, so deny the request.
506 * This removes the ability for a compromised host to abuse unused
507 * space if any data was to be persisted (which it isn't).
508 */
509 return -EACCES;
510 }
511 }
512
513 static const struct backend_ops vpnor_ops = {
514 .init = vpnor_dev_init,
515 .free = vpnor_free,
516 .copy = vpnor_copy,
517 .set_bytemap = NULL,
518 .erase = NULL,
519 .write = vpnor_write,
520 .validate = vpnor_validate,
521 .reset = vpnor_reset,
522 .align_offset = vpnor_align_offset,
523 };
524
backend_get_vpnor(void)525 struct backend backend_get_vpnor(void)
526 {
527 struct backend be = {nullptr, nullptr, 0, 0, 0};
528
529 be.ops = &vpnor_ops;
530
531 return be;
532 }
533
backend_probe_vpnor(struct backend * master,const struct vpnor_partition_paths * paths)534 int backend_probe_vpnor(struct backend* master,
535 const struct vpnor_partition_paths* paths)
536 {
537 struct backend with;
538
539 assert(master);
540 with = backend_get_vpnor();
541
542 return backend_init(master, &with, (void*)paths);
543 }
544