xref: /openbmc/hiomapd/vpnor/backend.cpp (revision 2be4563d3f2fbfa67d18a070d387e1653f94d42f)
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