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