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("vpnor init: %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("vpnor part copy: %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 MSG_ERR("Couldn't find partition path\n"); 196 return -EINVAL; 197 } 198 199 if (backend->flash_size == 0) 200 { 201 filename = get_dev_mtd(); 202 203 MSG_INFO("No flash size provided, using PNOR MTD size\n"); 204 205 if (!filename) 206 { 207 MSG_ERR("Couldn't find the flash /dev/mtd partition\n"); 208 return -errno; 209 } 210 211 MSG_DBG("Opening %s\n", filename); 212 213 fd = open(filename, O_RDWR); 214 if (fd < 0) 215 { 216 MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", filename, 217 strerror(errno)); 218 rc = -errno; 219 goto cleanup_filename; 220 } 221 222 // Read the Flash Info 223 if (ioctl(fd, MEMGETINFO, &mtd_info) == -1) 224 { 225 MSG_ERR("Couldn't get information about MTD: %s\n", 226 strerror(errno)); 227 rc = -errno; 228 goto cleanup_fd; 229 } 230 231 close(fd); 232 free((void*)filename); 233 234 // See comment in flash.c on why 235 // this is needed. 236 backend->flash_size = mtd_info.size; 237 } 238 239 // Hostboot requires a 4K block-size to be used in the FFS flash structure 240 backend->erase_size_shift = log_2(VPNOR_ERASE_SIZE); 241 backend->block_size_shift = backend->erase_size_shift; 242 243 return vpnor_init(backend, paths); 244 245 cleanup_fd: 246 close(fd); 247 248 cleanup_filename: 249 free((void*)filename); 250 251 return rc; 252 } 253 254 static void vpnor_free(struct backend* backend) 255 { 256 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 257 258 if (priv) 259 { 260 if (priv->vpnor) 261 { 262 delete priv->vpnor->table; 263 } 264 delete priv->vpnor; 265 } 266 delete priv; 267 } 268 269 /* 270 * vpnor_copy() - Copy data from the virtual pnor into a provided buffer 271 * @context: The backend context pointer 272 * @offset: The pnor offset to copy from (bytes) 273 * @mem: The buffer to copy into (must be of atleast 'size' bytes) 274 * @size: The number of bytes to copy 275 * Return: Number of bytes copied on success, otherwise negative error 276 * code. vpnor_copy will copy at most 'size' bytes, but it may 277 * copy less. 278 */ 279 static int64_t vpnor_copy(struct backend* backend, uint32_t offset, void* mem, 280 uint32_t size) 281 { 282 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 283 vpnor::partition::Table* table; 284 int rc = size; 285 286 if (!(priv->vpnor && priv->vpnor->table)) 287 { 288 MSG_ERR("Trying to copy data with uninitialised context!\n"); 289 return -EINVAL; 290 } 291 292 table = priv->vpnor->table; 293 294 MSG_DBG("Copy virtual pnor to %p for size 0x%.8x from offset 0x%.8x\n", mem, 295 size, offset); 296 297 /* The virtual PNOR partition table starts at offset 0 in the virtual 298 * pnor image. Check if host asked for an offset that lies within the 299 * partition table. 300 */ 301 size_t sz = table->size(); 302 if (offset < sz) 303 { 304 const pnor_partition_table& toc = table->getHostTable(); 305 rc = std::min(sz - offset, static_cast<size_t>(size)); 306 memcpy(mem, ((uint8_t*)&toc) + offset, rc); 307 return rc; 308 } 309 310 try 311 { 312 vpnor::Request req(backend, offset); 313 rc = req.read(mem, size); 314 } 315 catch (vpnor::UnmappedOffset& e) 316 { 317 /* 318 * Hooo boy. Pretend that this is valid flash so we don't have 319 * discontiguous regions presented to the host. Instead, fill a window 320 * with 0xff so the 'flash' looks erased. Writes to such regions are 321 * dropped on the floor, see the implementation of vpnor_write() below. 322 */ 323 MSG_INFO("Host requested unmapped region of %" PRId32 324 " bytes at offset 0x%" PRIx32 "\n", 325 size, offset); 326 uint32_t span = e.next - e.base; 327 rc = std::min(size, span); 328 memset(mem, 0xff, rc); 329 } 330 catch (std::exception& e) 331 { 332 MSG_ERR("vpnor copy: %s\n", e.what()); 333 phosphor::logging::commit<err::InternalFailure>(); 334 rc = -EIO; 335 } 336 return rc; 337 } 338 339 /* 340 * vpnor_write() - Write to the virtual pnor from a provided buffer 341 * @context: The backend context pointer 342 * @offset: The flash offset to write to (bytes) 343 * @buf: The buffer to write from (must be of atleast size) 344 * @size: The number of bytes to write 345 * 346 * Return: 0 on success otherwise negative error code 347 */ 348 349 static int vpnor_write(struct backend* backend, uint32_t offset, void* buf, 350 uint32_t count) 351 { 352 assert(backend); 353 354 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 355 356 if (!(priv && priv->vpnor && priv->vpnor->table)) 357 { 358 MSG_ERR("Trying to write data with uninitialised context!\n"); 359 return -EINVAL; 360 } 361 362 vpnor::partition::Table* table = priv->vpnor->table; 363 364 try 365 { 366 const struct pnor_partition& part = table->partition(offset); 367 if (part.data.user.data[1] & PARTITION_READONLY) 368 { 369 MSG_ERR("Unreachable: Host attempted to write to read-only " 370 "partition %s\n", 371 part.data.name); 372 return -EPERM; 373 } 374 375 MSG_DBG("Write flash @ 0x%.8x for 0x%.8x from %p\n", offset, count, 376 buf); 377 vpnor::Request req(backend, offset); 378 req.write(buf, count); 379 } 380 catch (vpnor::UnmappedOffset& e) 381 { 382 MSG_ERR("Unreachable: Host attempted to write %" PRIu32 383 " bytes to unmapped offset 0x%" PRIx32 "\n", 384 count, offset); 385 return -EACCES; 386 } 387 catch (const vpnor::OutOfBoundsOffset& e) 388 { 389 MSG_ERR("vpnor write: %s\n", e.what()); 390 return -EINVAL; 391 } 392 catch (const std::exception& e) 393 { 394 MSG_ERR("vpnor write exception: %s\n", e.what()); 395 phosphor::logging::commit<err::InternalFailure>(); 396 return -EIO; 397 } 398 return 0; 399 } 400 401 static bool vpnor_partition_is_readonly(const pnor_partition& part) 402 { 403 return part.data.user.data[1] & PARTITION_READONLY; 404 } 405 406 static int vpnor_validate(struct backend* backend, uint32_t offset, 407 uint32_t size __attribute__((unused)), bool ro) 408 { 409 struct vpnor_data* priv = (struct vpnor_data*)backend->priv; 410 411 /* All reads are allowed */ 412 if (ro) 413 { 414 return 0; 415 } 416 417 /* Only allow write windows on regions mapped by the ToC as writeable */ 418 try 419 { 420 const pnor_partition& part = priv->vpnor->table->partition(offset); 421 if (vpnor_partition_is_readonly(part)) 422 { 423 MSG_ERR("Try to write read only partition (part=%s, offset=0x%x)\n", 424 part.data.name, offset); 425 return -EPERM; 426 } 427 } 428 catch (const openpower::virtual_pnor::UnmappedOffset& e) 429 { 430 MSG_ERR("Try to write unmapped area (offset=0x%lx)\n", e.base); 431 432 /* 433 * Writes to unmapped areas are not meaningful, so deny the request. 434 * This removes the ability for a compromised host to abuse unused 435 * space if any data was to be persisted (which it isn't). 436 */ 437 return -EACCES; 438 } 439 440 // Allowed. 441 return 0; 442 } 443 444 /* 445 * vpnor_reset() - Reset the lpc bus mapping 446 * @context: The mbox context pointer 447 * 448 * Return 0 on success otherwise negative error code 449 */ 450 static int vpnor_reset(struct backend* backend, void* buf, uint32_t count) 451 { 452 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv; 453 int rc; 454 455 vpnor_partition_paths paths = priv->paths; 456 457 vpnor_free(backend); 458 459 rc = vpnor_init(backend, &paths); 460 if (rc < 0) 461 return rc; 462 463 rc = vpnor_copy_bootloader_partition(backend, buf, count); 464 if (rc < 0) 465 return rc; 466 467 return reset_lpc_memory; 468 } 469 470 /* 471 * vpnor_align_offset() - Align the offset 472 * @context: The backend context pointer 473 * @offset: The flash offset 474 * @window_size:The window size 475 * 476 * Return: 0 on success otherwise negative error code 477 */ 478 static int vpnor_align_offset(struct backend* backend, uint32_t* offset, 479 uint32_t window_size) 480 { 481 const struct vpnor_data* priv = (const struct vpnor_data*)backend->priv; 482 483 /* Adjust the offset to align with the offset of partition base */ 484 try 485 { 486 // Get the base of the partition 487 const pnor_partition& part = priv->vpnor->table->partition(*offset); 488 uint32_t base = part.data.base * VPNOR_ERASE_SIZE; 489 490 // Get the base offset relative to the window_size 491 uint32_t base_offset = base & (window_size - 1); 492 493 // Adjust the offset to align with the base 494 *offset = ((*offset - base_offset) & ~(window_size - 1)) + base_offset; 495 MSG_DBG( 496 "vpnor_align_offset: to @ 0x%.8x(base=0x%.8x base_offset=0x%.8x)\n", 497 *offset, base, base_offset); 498 return 0; 499 } 500 catch (const openpower::virtual_pnor::UnmappedOffset& e) 501 { 502 MSG_ERR("Aligned offset is unmapped area (offset=0x%lx)\n", e.base); 503 504 /* 505 * Writes to unmapped areas are not meaningful, so deny the request. 506 * This removes the ability for a compromised host to abuse unused 507 * space if any data was to be persisted (which it isn't). 508 */ 509 return -EACCES; 510 } 511 } 512 513 static const struct backend_ops vpnor_ops = { 514 .init = vpnor_dev_init, 515 .free = vpnor_free, 516 .copy = vpnor_copy, 517 .set_bytemap = NULL, 518 .erase = NULL, 519 .write = vpnor_write, 520 .validate = vpnor_validate, 521 .reset = vpnor_reset, 522 .align_offset = vpnor_align_offset, 523 }; 524 525 struct backend backend_get_vpnor(void) 526 { 527 struct backend be = {nullptr, nullptr, 0, 0, 0}; 528 529 be.ops = &vpnor_ops; 530 531 return be; 532 } 533 534 int backend_probe_vpnor(struct backend* master, 535 const struct vpnor_partition_paths* paths) 536 { 537 struct backend with; 538 539 assert(master); 540 with = backend_get_vpnor(); 541 542 return backend_init(master, &with, (void*)paths); 543 } 544