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 // Table object may throw error hence initialize table pointer 89 // to null so that no one try to free the junk pointer. 90 priv->vpnor->table = NULL; 91 priv->vpnor->table = 92 new openpower::virtual_pnor::partition::Table(backend); 93 } 94 catch (vpnor::TocEntryError& e) 95 { 96 MSG_ERR("vpnor init: %s\n", e.what()); 97 try 98 { 99 phosphor::logging::commit<err::InternalFailure>(); 100 } 101 catch (const std::exception& e) 102 { 103 MSG_ERR("Failed to commit InternalFailure: %s\n", e.what()); 104 } 105 return -EINVAL; 106 } 107 108 return 0; 109 } 110 111 /** @brief Copy bootloader partition (alongwith TOC) to LPC memory 112 * 113 * @param[in] backend - The backend context pointer 114 * 115 * @returns 0 on success, negative error code on failure 116 */ 117 int vpnor_copy_bootloader_partition(const struct backend* backend, void* buf, 118 uint32_t count) 119 { 120 // The hostboot bootloader has certain size/offset assumptions, so 121 // we need a special partition table here. 122 // It assumes the PNOR is 64M, the TOC size is 32K, the erase block is 123 // 4K, the page size is 4K. 124 // It also assumes the TOC is at the 'end of pnor - toc size - 1 page size' 125 // offset, and first looks for the TOC here, before proceeding to move up 126 // page by page looking for the TOC. So it is optimal to place the TOC at 127 // this offset. 128 constexpr size_t eraseSize = 0x1000; 129 constexpr size_t pageSize = 0x1000; 130 constexpr size_t pnorSize = 0x4000000; 131 constexpr size_t tocMaxSize = 0x8000; 132 constexpr size_t tocStart = pnorSize - tocMaxSize - pageSize; 133 constexpr auto blPartitionName = "HBB"; 134 135 namespace err = sdbusplus::xyz::openbmc_project::Common::Error; 136 namespace fs = std::filesystem; 137 namespace vpnor = openpower::virtual_pnor; 138 139 try 140 { 141 vpnor_partition_table vtbl{}; 142 struct vpnor_data priv; 143 struct backend local = *backend; 144 145 priv.vpnor = &vtbl; 146 priv.paths = ((struct vpnor_data*)backend->priv)->paths; 147 local.priv = &priv; 148 local.block_size_shift = log_2(eraseSize); 149 150 openpower::virtual_pnor::partition::Table blTable(&local); 151 152 vtbl.table = &blTable; 153 154 size_t tocOffset = 0; 155 156 const pnor_partition& partition = blTable.partition(blPartitionName); 157 size_t hbbOffset = partition.data.base * eraseSize; 158 uint32_t hbbSize = partition.data.actual; 159 160 if (count < tocStart + blTable.capacity() || 161 count < hbbOffset + hbbSize) 162 { 163 MSG_ERR("Reserved memory too small for dumb bootstrap\n"); 164 return -EINVAL; 165 } 166 167 uint8_t* buf8 = static_cast<uint8_t*>(buf); 168 backend_copy(&local, tocOffset, buf8 + tocStart, blTable.capacity()); 169 backend_copy(&local, hbbOffset, buf8 + hbbOffset, hbbSize); 170 } 171 catch (err::InternalFailure& e) 172 { 173 phosphor::logging::commit<err::InternalFailure>(); 174 return -EIO; 175 } 176 catch (vpnor::ReasonedError& e) 177 { 178 MSG_ERR("vpnor part copy: %s\n", e.what()); 179 phosphor::logging::commit<err::InternalFailure>(); 180 return -EIO; 181 } 182 183 return 0; 184 } 185 186 int vpnor_dev_init(struct backend* backend, void* data) 187 { 188 vpnor_partition_paths* paths = (vpnor_partition_paths*)data; 189 struct mtd_info_user mtd_info; 190 const char* filename = NULL; 191 int fd; 192 int rc = 0; 193 194 if (!(fs::is_directory(fs::status(paths->ro_loc)) && 195 fs::is_directory(fs::status(paths->rw_loc)) && 196 fs::is_directory(fs::status(paths->prsv_loc)))) 197 { 198 MSG_ERR("Couldn't find partition path\n"); 199 return -EINVAL; 200 } 201 202 if (backend->flash_size == 0) 203 { 204 filename = get_dev_mtd(); 205 206 MSG_INFO("No flash size provided, using PNOR MTD size\n"); 207 208 if (!filename) 209 { 210 MSG_ERR("Couldn't find the flash /dev/mtd partition\n"); 211 return -errno; 212 } 213 214 MSG_DBG("Opening %s\n", filename); 215 216 fd = open(filename, O_RDWR); 217 if (fd < 0) 218 { 219 MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", filename, 220 strerror(errno)); 221 rc = -errno; 222 goto cleanup_filename; 223 } 224 225 // Read the Flash Info 226 if (ioctl(fd, MEMGETINFO, &mtd_info) == -1) 227 { 228 MSG_ERR("Couldn't get information about MTD: %s\n", 229 strerror(errno)); 230 rc = -errno; 231 goto cleanup_fd; 232 } 233 234 close(fd); 235 free((void*)filename); 236 237 // See comment in flash.c on why 238 // this is needed. 239 backend->flash_size = mtd_info.size; 240 } 241 242 // Hostboot requires a 4K block-size to be used in the FFS flash structure 243 backend->erase_size_shift = log_2(VPNOR_ERASE_SIZE); 244 backend->block_size_shift = backend->erase_size_shift; 245 246 return vpnor_init(backend, paths); 247 248 cleanup_fd: 249 close(fd); 250 251 cleanup_filename: 252 free((void*)filename); 253 254 return rc; 255 } 256 257 static void vpnor_free(struct backend* backend) 258 { 259 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 260 261 if (priv) 262 { 263 if (priv->vpnor) 264 { 265 delete priv->vpnor->table; 266 } 267 delete priv->vpnor; 268 } 269 delete priv; 270 } 271 272 /* 273 * vpnor_copy() - Copy data from the virtual pnor into a provided buffer 274 * @context: The backend context pointer 275 * @offset: The pnor offset to copy from (bytes) 276 * @mem: The buffer to copy into (must be of atleast 'size' bytes) 277 * @size: The number of bytes to copy 278 * Return: Number of bytes copied on success, otherwise negative error 279 * code. vpnor_copy will copy at most 'size' bytes, but it may 280 * copy less. 281 */ 282 static int64_t vpnor_copy(struct backend* backend, uint32_t offset, void* mem, 283 uint32_t size) 284 { 285 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 286 vpnor::partition::Table* table; 287 int rc = size; 288 289 if (!(priv->vpnor && priv->vpnor->table)) 290 { 291 MSG_ERR("Trying to copy data with uninitialised context!\n"); 292 return -EINVAL; 293 } 294 295 table = priv->vpnor->table; 296 297 MSG_DBG("Copy virtual pnor to %p for size 0x%.8x from offset 0x%.8x\n", mem, 298 size, offset); 299 300 /* The virtual PNOR partition table starts at offset 0 in the virtual 301 * pnor image. Check if host asked for an offset that lies within the 302 * partition table. 303 */ 304 size_t sz = table->size(); 305 if (offset < sz) 306 { 307 const pnor_partition_table& toc = table->getHostTable(); 308 rc = std::min(sz - offset, static_cast<size_t>(size)); 309 memcpy(mem, ((uint8_t*)&toc) + offset, rc); 310 return rc; 311 } 312 313 try 314 { 315 vpnor::Request req(backend, offset); 316 rc = req.read(mem, size); 317 } 318 catch (vpnor::UnmappedOffset& e) 319 { 320 /* 321 * Hooo boy. Pretend that this is valid flash so we don't have 322 * discontiguous regions presented to the host. Instead, fill a window 323 * with 0xff so the 'flash' looks erased. Writes to such regions are 324 * dropped on the floor, see the implementation of vpnor_write() below. 325 */ 326 MSG_INFO("Host requested unmapped region of %" PRId32 327 " bytes at offset 0x%" PRIx32 "\n", 328 size, offset); 329 uint32_t span = e.next - e.base; 330 rc = std::min(size, span); 331 memset(mem, 0xff, rc); 332 } 333 catch (std::exception& e) 334 { 335 MSG_ERR("vpnor copy: %s\n", e.what()); 336 phosphor::logging::commit<err::InternalFailure>(); 337 rc = -EIO; 338 } 339 return rc; 340 } 341 342 /* 343 * vpnor_write() - Write to the virtual pnor from a provided buffer 344 * @context: The backend context pointer 345 * @offset: The flash offset to write to (bytes) 346 * @buf: The buffer to write from (must be of atleast size) 347 * @size: The number of bytes to write 348 * 349 * Return: 0 on success otherwise negative error code 350 */ 351 352 static int vpnor_write(struct backend* backend, uint32_t offset, void* buf, 353 uint32_t count) 354 { 355 assert(backend); 356 357 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 358 359 if (!(priv && priv->vpnor && priv->vpnor->table)) 360 { 361 MSG_ERR("Trying to write data with uninitialised context!\n"); 362 return -EINVAL; 363 } 364 365 vpnor::partition::Table* table = priv->vpnor->table; 366 367 try 368 { 369 const struct pnor_partition& part = table->partition(offset); 370 if (part.data.user.data[1] & PARTITION_READONLY) 371 { 372 MSG_ERR("Unreachable: Host attempted to write to read-only " 373 "partition %s\n", 374 part.data.name); 375 return -EPERM; 376 } 377 378 MSG_DBG("Write flash @ 0x%.8x for 0x%.8x from %p\n", offset, count, 379 buf); 380 vpnor::Request req(backend, offset); 381 req.write(buf, count); 382 } 383 catch (vpnor::UnmappedOffset& e) 384 { 385 MSG_ERR("Unreachable: Host attempted to write %" PRIu32 386 " bytes to unmapped offset 0x%" PRIx32 "\n", 387 count, offset); 388 return -EACCES; 389 } 390 catch (const vpnor::OutOfBoundsOffset& e) 391 { 392 MSG_ERR("vpnor write: %s\n", e.what()); 393 return -EINVAL; 394 } 395 catch (const std::exception& e) 396 { 397 MSG_ERR("vpnor write exception: %s\n", e.what()); 398 phosphor::logging::commit<err::InternalFailure>(); 399 return -EIO; 400 } 401 return 0; 402 } 403 404 static bool vpnor_partition_is_readonly(const pnor_partition& part) 405 { 406 return part.data.user.data[1] & PARTITION_READONLY; 407 } 408 409 static int vpnor_validate(struct backend* backend, uint32_t offset, 410 uint32_t size __attribute__((unused)), bool ro) 411 { 412 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 413 414 /* All reads are allowed */ 415 if (ro) 416 { 417 return 0; 418 } 419 420 /* Only allow write windows on regions mapped by the ToC as writeable */ 421 try 422 { 423 const pnor_partition& part = priv->vpnor->table->partition(offset); 424 if (vpnor_partition_is_readonly(part)) 425 { 426 MSG_ERR("Try to write read only partition (part=%s, offset=0x%x)\n", 427 part.data.name, offset); 428 return -EPERM; 429 } 430 } 431 catch (const openpower::virtual_pnor::UnmappedOffset& e) 432 { 433 MSG_ERR("Try to write unmapped area (offset=0x%lx)\n", e.base); 434 435 /* 436 * Writes to unmapped areas are not meaningful, so deny the request. 437 * This removes the ability for a compromised host to abuse unused 438 * space if any data was to be persisted (which it isn't). 439 */ 440 return -EACCES; 441 } 442 443 // Allowed. 444 return 0; 445 } 446 447 /* 448 * vpnor_reset() - Reset the lpc bus mapping 449 * @context: The mbox context pointer 450 * 451 * Return 0 on success otherwise negative error code 452 */ 453 static int vpnor_reset(struct backend* backend, void* buf, uint32_t count) 454 { 455 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv; 456 int rc; 457 458 vpnor_partition_paths paths = priv->paths; 459 460 vpnor_free(backend); 461 462 rc = vpnor_init(backend, &paths); 463 if (rc < 0) 464 return rc; 465 466 rc = vpnor_copy_bootloader_partition(backend, buf, count); 467 if (rc < 0) 468 return rc; 469 470 return reset_lpc_memory; 471 } 472 473 /* 474 * vpnor_align_offset() - Align the offset 475 * @context: The backend context pointer 476 * @offset: The flash offset 477 * @window_size:The window size 478 * 479 * Return: 0 on success otherwise negative error code 480 */ 481 static int vpnor_align_offset(struct backend* backend, uint32_t* offset, 482 uint32_t window_size) 483 { 484 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv; 485 486 /* Adjust the offset to align with the offset of partition base */ 487 try 488 { 489 // Get the base of the partition 490 const pnor_partition& part = priv->vpnor->table->partition(*offset); 491 uint32_t base = part.data.base * VPNOR_ERASE_SIZE; 492 493 // Get the base offset relative to the window_size 494 uint32_t base_offset = base & (window_size - 1); 495 496 // Adjust the offset to align with the base 497 *offset = ((*offset - base_offset) & ~(window_size - 1)) + base_offset; 498 MSG_DBG( 499 "vpnor_align_offset: to @ 0x%.8x(base=0x%.8x base_offset=0x%.8x)\n", 500 *offset, base, base_offset); 501 return 0; 502 } 503 catch (const openpower::virtual_pnor::UnmappedOffset& e) 504 { 505 MSG_ERR("Aligned offset is unmapped area (offset=0x%lx)\n", e.base); 506 507 /* 508 * Writes to unmapped areas are not meaningful, so deny the request. 509 * This removes the ability for a compromised host to abuse unused 510 * space if any data was to be persisted (which it isn't). 511 */ 512 return -EACCES; 513 } 514 } 515 516 static const struct backend_ops vpnor_ops = { 517 .init = vpnor_dev_init, 518 .free = vpnor_free, 519 .copy = vpnor_copy, 520 .set_bytemap = NULL, 521 .erase = NULL, 522 .write = vpnor_write, 523 .validate = vpnor_validate, 524 .reset = vpnor_reset, 525 .align_offset = vpnor_align_offset, 526 }; 527 528 struct backend backend_get_vpnor(void) 529 { 530 struct backend be = {nullptr, nullptr, 0, 0, 0}; 531 532 be.ops = &vpnor_ops; 533 534 return be; 535 } 536 537 int backend_probe_vpnor(struct backend* master, 538 const struct vpnor_partition_paths* paths) 539 { 540 struct backend with; 541 542 assert(master); 543 with = backend_get_vpnor(); 544 545 return backend_init(master, &with, (void*)paths); 546 } 547