blob: ccda4ec5e3c744fe1251a18a77d9aa20ee2d9837 [file] [log] [blame]
Evan Lojewskif1e547c2019-03-14 14:34:33 +10301// 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
14extern "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
36namespace err = sdbusplus::xyz::openbmc_project::Common::Error;
37namespace fs = std::experimental::filesystem;
38namespace vpnor = openpower::virtual_pnor;
39
40static constexpr uint32_t VPNOR_ERASE_SIZE = 4 * 1024;
41
42int 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
103cleanup_fd:
104 close(fd);
105
106cleanup_filename:
107 free((void*)filename);
108
109 return rc;
110}
111
112static 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 */
127static 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
197static 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
249static bool vpnor_partition_is_readonly(const pnor_partition& part)
250{
251 return part.data.user.data[1] & PARTITION_READONLY;
252}
253
254static 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 */
294static 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
314static 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
325struct backend backend_get_vpnor(void)
326{
327 struct backend be = {0};
328
329 be.ops = &vpnor_ops;
330
331 return be;
332}
333
334int 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}