xref: /openbmc/hiomapd/vpnor/backend.cpp (revision 035ad76b8b45a01cc7fcc184e7e72eed69c0ece5)
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 "pnor_partition.hpp"
26 #include "pnor_partition_table.hpp"
27 #include "xyz/openbmc_project/Common/error.hpp"
28 
29 #include <cassert>
30 #include <exception>
31 #include <experimental/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::experimental::filesystem;
42 namespace vpnor = openpower::virtual_pnor;
43 
44 static constexpr uint32_t VPNOR_ERASE_SIZE = 4 * 1024;
45 
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  */
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::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  */
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::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 
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 
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  */
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 
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 
400 static bool vpnor_partition_is_readonly(const pnor_partition& part)
401 {
402     return part.data.user.data[1] & PARTITION_READONLY;
403 }
404 
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             return -EPERM;
423         }
424     }
425     catch (const openpower::virtual_pnor::UnmappedOffset& e)
426     {
427         /*
428          * Writes to unmapped areas are not meaningful, so deny the request.
429          * This removes the ability for a compromised host to abuse unused
430          * space if any data was to be persisted (which it isn't).
431          */
432         return -EACCES;
433     }
434 
435     // Allowed.
436     return 0;
437 }
438 
439 /*
440  * vpnor_reset() - Reset the lpc bus mapping
441  * @context:     The mbox context pointer
442  *
443  * Return        0 on success otherwise negative error code
444  */
445 static int vpnor_reset(struct backend* backend, void* buf, uint32_t count)
446 {
447     const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv;
448     int rc;
449 
450     vpnor_partition_paths paths = priv->paths;
451 
452     vpnor_free(backend);
453 
454     rc = vpnor_init(backend, &paths);
455     if (rc < 0)
456         return rc;
457 
458     rc = vpnor_copy_bootloader_partition(backend, buf, count);
459     if (rc < 0)
460         return rc;
461 
462     return reset_lpc_memory;
463 }
464 
465 static const struct backend_ops vpnor_ops = {
466     .init = vpnor_dev_init,
467     .free = vpnor_free,
468     .copy = vpnor_copy,
469     .set_bytemap = NULL,
470     .erase = NULL,
471     .write = vpnor_write,
472     .validate = vpnor_validate,
473     .reset = vpnor_reset,
474 };
475 
476 struct backend backend_get_vpnor(void)
477 {
478     struct backend be = {0};
479 
480     be.ops = &vpnor_ops;
481 
482     return be;
483 }
484 
485 int backend_probe_vpnor(struct backend* master,
486                         const struct vpnor_partition_paths* paths)
487 {
488     struct backend with;
489 
490     assert(master);
491     with = backend_get_vpnor();
492 
493     return backend_init(master, &with, (void*)paths);
494 }
495