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