xref: /openbmc/hiomapd/vpnor/backend.cpp (revision 68a24c9e)
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("%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("%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         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 
244 cleanup_fd:
245     close(fd);
246 
247 cleanup_filename:
248     free((void*)filename);
249 
250     return rc;
251 }
252 
vpnor_free(struct backend * backend)253 static void vpnor_free(struct backend* backend)
254 {
255     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;
266 }
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  */
vpnor_copy(struct backend * backend,uint32_t offset,void * mem,uint32_t size)278 static 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 
vpnor_write(struct backend * backend,uint32_t offset,void * buf,uint32_t count)348 static 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 
vpnor_partition_is_readonly(const pnor_partition & part)400 static bool vpnor_partition_is_readonly(const pnor_partition& part)
401 {
402     return part.data.user.data[1] & PARTITION_READONLY;
403 }
404 
vpnor_validate(struct backend * backend,uint32_t offset,uint32_t size,bool ro)405 static 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         {
422             MSG_DBG("Try to write read only partition (part=%s, offset=0x%x)\n",
423                     part.data.name, offset);
424             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  */
vpnor_reset(struct backend * backend,void * buf,uint32_t count)447 static 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 
454     vpnor_free(backend);
455 
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 
467 /*
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  */
vpnor_align_offset(struct backend * backend,uint32_t * offset,uint32_t window_size)475 static 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 
508 static 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,
517     .align_offset = vpnor_align_offset,
518 };
519 
backend_get_vpnor(void)520 struct backend backend_get_vpnor(void)
521 {
522     struct backend be = {nullptr, nullptr, 0, 0, 0};
523 
524     be.ops = &vpnor_ops;
525 
526     return be;
527 }
528 
backend_probe_vpnor(struct backend * master,const struct vpnor_partition_paths * paths)529 int 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 }
539