xref: /openbmc/hiomapd/vpnor/backend.cpp (revision a042978b)
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright (C) 2018 IBM Corp.
3 
4 #include <fcntl.h>
5 #include <stdint.h>
6 #include <stdlib.h>
7 #include <sys/ioctl.h>
8 #include <sys/mman.h>
9 #include <syslog.h>
10 #include <unistd.h>
11 
12 #include <algorithm>
13 
14 extern "C" {
15 #include "common.h"
16 #include "lpc.h"
17 #include "mboxd.h"
18 #include "protocol.h"
19 }
20 
21 #include "config.h"
22 
23 #include "pnor_partition.hpp"
24 #include "pnor_partition_table.hpp"
25 #include "xyz/openbmc_project/Common/error.hpp"
26 
27 #include <exception>
28 #include <memory>
29 #include <phosphor-logging/elog-errors.hpp>
30 #include <phosphor-logging/log.hpp>
31 #include <stdexcept>
32 #include <string>
33 
34 #include "mboxd_pnor_partition_table.h"
35 
36 namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
37 namespace fs = std::experimental::filesystem;
38 namespace vpnor = openpower::virtual_pnor;
39 
40 static constexpr uint32_t VPNOR_ERASE_SIZE = 4 * 1024;
41 
42 int vpnor_dev_init(struct backend* backend, void* data)
43 {
44     vpnor_partition_paths* paths = (vpnor_partition_paths*)data;
45     struct mtd_info_user mtd_info;
46     const char* filename = NULL;
47     int fd;
48     int rc = 0;
49 
50     if (!(fs::is_directory(fs::status(paths->ro_loc)) &&
51           fs::is_directory(fs::status(paths->rw_loc)) &&
52           fs::is_directory(fs::status(paths->prsv_loc))))
53     {
54         return -EINVAL;
55     }
56 
57     if (backend->flash_size == 0)
58     {
59         filename = get_dev_mtd();
60 
61         MSG_INFO("No flash size provided, using PNOR MTD size\n");
62 
63         if (!filename)
64         {
65             MSG_ERR("Couldn't find the flash /dev/mtd partition\n");
66             return -errno;
67         }
68 
69         MSG_DBG("Opening %s\n", filename);
70 
71         fd = open(filename, O_RDWR);
72         if (fd < 0)
73         {
74             MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", filename,
75                     strerror(errno));
76             rc = -errno;
77             goto cleanup_filename;
78         }
79 
80         // Read the Flash Info
81         if (ioctl(fd, MEMGETINFO, &mtd_info) == -1)
82         {
83             MSG_ERR("Couldn't get information about MTD: %s\n",
84                     strerror(errno));
85             rc = -errno;
86             goto cleanup_fd;
87         }
88 
89         close(fd);
90         free((void*)filename);
91 
92         // See comment in flash.c on why
93         // this is needed.
94         backend->flash_size = mtd_info.size;
95     }
96 
97     // Hostboot requires a 4K block-size to be used in the FFS flash structure
98     backend->erase_size_shift = log_2(VPNOR_ERASE_SIZE);
99     backend->block_size_shift = backend->erase_size_shift;
100 
101     return vpnor_init(backend, paths);
102 
103 cleanup_fd:
104     close(fd);
105 
106 cleanup_filename:
107     free((void*)filename);
108 
109     return rc;
110 }
111 
112 static void vpnor_free(struct backend* backend)
113 {
114     vpnor_destroy(backend);
115 }
116 
117 /*
118  * vpnor_copy() - Copy data from the virtual pnor into a provided buffer
119  * @context:    The backend context pointer
120  * @offset:     The pnor offset to copy from (bytes)
121  * @mem:        The buffer to copy into (must be of atleast 'size' bytes)
122  * @size:       The number of bytes to copy
123  * Return:      Number of bytes copied on success, otherwise negative error
124  *              code. vpnor_copy will copy at most 'size' bytes, but it may
125  *              copy less.
126  */
127 static int64_t vpnor_copy(struct backend* backend, uint32_t offset, void* mem,
128                           uint32_t size)
129 {
130     struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
131     vpnor::partition::Table* table;
132     int rc = size;
133 
134     if (!(priv->vpnor && priv->vpnor->table))
135     {
136         MSG_ERR("Trying to copy data with uninitialised context!\n");
137         return -EINVAL;
138     }
139 
140     table = priv->vpnor->table;
141 
142     MSG_DBG("Copy virtual pnor to %p for size 0x%.8x from offset 0x%.8x\n", mem,
143             size, offset);
144 
145     /* The virtual PNOR partition table starts at offset 0 in the virtual
146      * pnor image. Check if host asked for an offset that lies within the
147      * partition table.
148      */
149     size_t sz = table->size();
150     if (offset < sz)
151     {
152         const pnor_partition_table& toc = table->getHostTable();
153         rc = std::min(sz - offset, static_cast<size_t>(size));
154         memcpy(mem, ((uint8_t*)&toc) + offset, rc);
155         return rc;
156     }
157 
158     try
159     {
160         vpnor::Request req(backend, offset);
161         rc = req.read(mem, size);
162     }
163     catch (vpnor::UnmappedOffset& e)
164     {
165         /*
166          * Hooo boy. Pretend that this is valid flash so we don't have
167          * discontiguous regions presented to the host. Instead, fill a window
168          * with 0xff so the 'flash' looks erased. Writes to such regions are
169          * dropped on the floor, see the implementation of vpnor_write() below.
170          */
171         MSG_INFO("Host requested unmapped region of %" PRId32
172                  " bytes at offset 0x%" PRIx32 "\n",
173                  size, offset);
174         uint32_t span = e.next - e.base;
175         rc = std::min(size, span);
176         memset(mem, 0xff, rc);
177     }
178     catch (std::exception& e)
179     {
180         MSG_ERR("%s\n", e.what());
181         phosphor::logging::commit<err::InternalFailure>();
182         rc = -EIO;
183     }
184     return rc;
185 }
186 
187 /*
188  * vpnor_write() - Write to the virtual pnor from a provided buffer
189  * @context: The backend context pointer
190  * @offset:  The flash offset to write to (bytes)
191  * @buf:     The buffer to write from (must be of atleast size)
192  * @size:    The number of bytes to write
193  *
194  * Return:  0 on success otherwise negative error code
195  */
196 
197 static int vpnor_write(struct backend* backend, uint32_t offset, void* buf,
198                        uint32_t count)
199 {
200     assert(backend);
201 
202     struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
203 
204     if (!(priv && priv->vpnor && priv->vpnor->table))
205     {
206         MSG_ERR("Trying to write data with uninitialised context!\n");
207         return -EINVAL;
208     }
209 
210     vpnor::partition::Table* table = priv->vpnor->table;
211 
212     try
213     {
214         const struct pnor_partition& part = table->partition(offset);
215         if (part.data.user.data[1] & PARTITION_READONLY)
216         {
217             MSG_ERR("Unreachable: Host attempted to write to read-only "
218                     "partition %s\n",
219                     part.data.name);
220             return -EPERM;
221         }
222 
223         MSG_DBG("Write flash @ 0x%.8x for 0x%.8x from %p\n", offset, count,
224                 buf);
225         vpnor::Request req(backend, offset);
226         req.write(buf, count);
227     }
228     catch (vpnor::UnmappedOffset& e)
229     {
230         MSG_ERR("Unreachable: Host attempted to write %" PRIu32
231                 " bytes to unmapped offset 0x%" PRIx32 "\n",
232                 count, offset);
233         return -EACCES;
234     }
235     catch (const vpnor::OutOfBoundsOffset& e)
236     {
237         MSG_ERR("%s\n", e.what());
238         return -EINVAL;
239     }
240     catch (const std::exception& e)
241     {
242         MSG_ERR("%s\n", e.what());
243         phosphor::logging::commit<err::InternalFailure>();
244         return -EIO;
245     }
246     return 0;
247 }
248 
249 static bool vpnor_partition_is_readonly(const pnor_partition& part)
250 {
251     return part.data.user.data[1] & PARTITION_READONLY;
252 }
253 
254 static int vpnor_validate(struct backend* backend, uint32_t offset,
255                           uint32_t size __attribute__((unused)), bool ro)
256 {
257     struct vpnor_data* priv = (struct vpnor_data*)backend->priv;
258 
259     /* All reads are allowed */
260     if (ro)
261     {
262         return 0;
263     }
264 
265     /* Only allow write windows on regions mapped by the ToC as writeable */
266     try
267     {
268         const pnor_partition& part = priv->vpnor->table->partition(offset);
269         if (vpnor_partition_is_readonly(part))
270         {
271             return -EPERM;
272         }
273     }
274     catch (const openpower::virtual_pnor::UnmappedOffset& e)
275     {
276         /*
277          * Writes to unmapped areas are not meaningful, so deny the request.
278          * This removes the ability for a compromised host to abuse unused
279          * space if any data was to be persisted (which it isn't).
280          */
281         return -EACCES;
282     }
283 
284     // Allowed.
285     return 0;
286 }
287 
288 /*
289  * vpnor_reset() - Reset the lpc bus mapping
290  * @context:     The mbox context pointer
291  *
292  * Return        0 on success otherwise negative error code
293  */
294 static int vpnor_reset(struct backend* backend, void* buf, uint32_t count)
295 {
296     const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv;
297     int rc;
298 
299     vpnor_partition_paths paths = priv->paths;
300 
301     vpnor_destroy(backend);
302 
303     rc = vpnor_init(backend, &paths);
304     if (rc < 0)
305         return rc;
306 
307     rc = vpnor_copy_bootloader_partition(backend, buf, count);
308     if (rc < 0)
309         return rc;
310 
311     return reset_lpc_memory;
312 }
313 
314 static const struct backend_ops vpnor_ops = {
315     .init = vpnor_dev_init,
316     .free = vpnor_free,
317     .copy = vpnor_copy,
318     .set_bytemap = NULL,
319     .erase = NULL,
320     .write = vpnor_write,
321     .validate = vpnor_validate,
322     .reset = vpnor_reset,
323 };
324 
325 struct backend backend_get_vpnor(void)
326 {
327     struct backend be = {0};
328 
329     be.ops = &vpnor_ops;
330 
331     return be;
332 }
333 
334 int backend_probe_vpnor(struct backend* master,
335                         const struct vpnor_partition_paths* paths)
336 {
337     struct backend with;
338 
339     assert(master);
340     with = backend_get_vpnor();
341 
342     return backend_init(master, &with, (void*)paths);
343 }
344