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 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::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::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 MSG_DBG("Try to write read only partition (part=%s, offset=0x%x)\n", 423 part.data.name, offset); 424 return -EPERM; 425 } 426 } 427 catch (const openpower::virtual_pnor::UnmappedOffset& e) 428 { 429 /* 430 * Writes to unmapped areas are not meaningful, so deny the request. 431 * This removes the ability for a compromised host to abuse unused 432 * space if any data was to be persisted (which it isn't). 433 */ 434 return -EACCES; 435 } 436 437 // Allowed. 438 return 0; 439 } 440 441 /* 442 * vpnor_reset() - Reset the lpc bus mapping 443 * @context: The mbox context pointer 444 * 445 * Return 0 on success otherwise negative error code 446 */ 447 static int vpnor_reset(struct backend* backend, void* buf, uint32_t count) 448 { 449 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv; 450 int rc; 451 452 vpnor_partition_paths paths = priv->paths; 453 454 vpnor_free(backend); 455 456 rc = vpnor_init(backend, &paths); 457 if (rc < 0) 458 return rc; 459 460 rc = vpnor_copy_bootloader_partition(backend, buf, count); 461 if (rc < 0) 462 return rc; 463 464 return reset_lpc_memory; 465 } 466 467 /* 468 * vpnor_align_offset() - Align the offset 469 * @context: The backend context pointer 470 * @offset: The flash offset 471 * @window_size:The window size 472 * 473 * Return: 0 on success otherwise negative error code 474 */ 475 static int vpnor_align_offset(struct backend* backend, uint32_t* offset, 476 uint32_t window_size) 477 { 478 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv; 479 480 /* Adjust the offset to align with the offset of partition base */ 481 try 482 { 483 // Get the base of the partition 484 const pnor_partition& part = priv->vpnor->table->partition(*offset); 485 uint32_t base = part.data.base * VPNOR_ERASE_SIZE; 486 487 // Get the base offset relative to the window_size 488 uint32_t base_offset = base & (window_size - 1); 489 490 // Adjust the offset to align with the base 491 *offset = ((*offset - base_offset) & ~(window_size - 1)) + base_offset; 492 MSG_DBG( 493 "vpnor_align_offset: to @ 0x%.8x(base=0x%.8x base_offset=0x%.8x)\n", 494 *offset, base, base_offset); 495 return 0; 496 } 497 catch (const openpower::virtual_pnor::UnmappedOffset& e) 498 { 499 /* 500 * Writes to unmapped areas are not meaningful, so deny the request. 501 * This removes the ability for a compromised host to abuse unused 502 * space if any data was to be persisted (which it isn't). 503 */ 504 return -EACCES; 505 } 506 } 507 508 static const struct backend_ops vpnor_ops = { 509 .init = vpnor_dev_init, 510 .free = vpnor_free, 511 .copy = vpnor_copy, 512 .set_bytemap = NULL, 513 .erase = NULL, 514 .write = vpnor_write, 515 .validate = vpnor_validate, 516 .reset = vpnor_reset, 517 .align_offset = vpnor_align_offset, 518 }; 519 520 struct backend backend_get_vpnor(void) 521 { 522 struct backend be = {nullptr, nullptr, 0, 0, 0}; 523 524 be.ops = &vpnor_ops; 525 526 return be; 527 } 528 529 int backend_probe_vpnor(struct backend* master, 530 const struct vpnor_partition_paths* paths) 531 { 532 struct backend with; 533 534 assert(master); 535 with = backend_get_vpnor(); 536 537 return backend_init(master, &with, (void*)paths); 538 } 539