1 #include "spi_device.hpp" 2 3 #include "common/include/NotifyWatch.hpp" 4 #include "common/include/device.hpp" 5 #include "common/include/host_power.hpp" 6 #include "common/include/software_manager.hpp" 7 8 #include <gpiod.hpp> 9 #include <phosphor-logging/lg2.hpp> 10 #include <sdbusplus/async.hpp> 11 #include <sdbusplus/async/context.hpp> 12 #include <xyz/openbmc_project/Association/Definitions/server.hpp> 13 #include <xyz/openbmc_project/ObjectMapper/client.hpp> 14 #include <xyz/openbmc_project/State/Host/client.hpp> 15 16 #include <cstddef> 17 #include <fstream> 18 #include <random> 19 20 PHOSPHOR_LOG2_USING; 21 22 using namespace std::literals; 23 using namespace phosphor::software; 24 using namespace phosphor::software::manager; 25 using namespace phosphor::software::host_power; 26 27 static std::optional<std::string> getSPIDevAddr(uint64_t spiControllerIndex) 28 { 29 const fs::path spi_path = 30 "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex); 31 32 if (!fs::exists(spi_path)) 33 { 34 error("SPI controller index not found at {PATH}", "PATH", 35 spi_path.string()); 36 return std::nullopt; 37 } 38 39 fs::path target = fs::read_symlink(spi_path); 40 41 // The path looks like 42 // ../../devices/platform/ahb/1e630000.spi/spi_master/spi1 We want to 43 // extract e.g. '1e630000.spi' 44 45 for (const auto& part : target) 46 { 47 std::string part_str = part.string(); 48 if (part_str.find(".spi") != std::string::npos) 49 { 50 debug("Found SPI Address {ADDR}", "ADDR", part_str); 51 return part_str; 52 } 53 } 54 55 return std::nullopt; 56 } 57 58 SPIDevice::SPIDevice(sdbusplus::async::context& ctx, 59 uint64_t spiControllerIndex, uint64_t spiDeviceIndex, 60 bool dryRun, const std::vector<std::string>& gpioLinesIn, 61 const std::vector<uint64_t>& gpioValuesIn, 62 SoftwareConfig& config, SoftwareManager* parent, 63 enum FlashLayout layout, enum FlashTool tool, 64 const std::string& versionDirPath) : 65 Device(ctx, config, parent, 66 {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}), 67 NotifyWatchIntf(ctx, versionDirPath), dryRun(dryRun), 68 gpioLines(gpioLinesIn), 69 gpioValues(gpioValuesIn.begin(), gpioValuesIn.end()), 70 spiControllerIndex(spiControllerIndex), spiDeviceIndex(spiDeviceIndex), 71 layout(layout), tool(tool) 72 { 73 auto optAddr = getSPIDevAddr(spiControllerIndex); 74 75 if (!optAddr.has_value()) 76 { 77 throw std::invalid_argument("SPI controller index not found"); 78 } 79 80 spiDev = optAddr.value(); 81 82 ctx.spawn(readNotifyAsync()); 83 84 debug( 85 "SPI Device {NAME} at {CONTROLLERINDEX}:{DEVICEINDEX} initialized successfully", 86 "NAME", config.configName, "CONTROLLERINDEX", spiControllerIndex, 87 "DEVICEINDEX", spiDeviceIndex); 88 } 89 90 sdbusplus::async::task<bool> SPIDevice::updateDevice(const uint8_t* image, 91 size_t image_size) 92 { 93 // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch) 94 auto prevPowerstate = co_await HostPower::getState(ctx); 95 96 if (prevPowerstate != stateOn && prevPowerstate != stateOff) 97 { 98 co_return false; 99 } 100 101 bool success = co_await HostPower::setState(ctx, stateOff); 102 if (!success) 103 { 104 error("error changing host power state"); 105 co_return false; 106 } 107 setUpdateProgress(10); 108 109 success = co_await writeSPIFlash(image, image_size); 110 111 if (success) 112 { 113 setUpdateProgress(100); 114 } 115 116 // restore the previous powerstate 117 const bool powerstate_restore = 118 co_await HostPower::setState(ctx, prevPowerstate); 119 if (!powerstate_restore) 120 { 121 error("error changing host power state"); 122 co_return false; 123 } 124 125 // return value here is only describing if we successfully wrote to the 126 // SPI flash. Restoring powerstate can still fail. 127 co_return success; 128 } 129 130 const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc"; 131 const std::string spiNorPath = "/sys/bus/spi/drivers/spi-nor"; 132 133 sdbusplus::async::task<bool> SPIDevice::bindSPIFlash() 134 { 135 if (!SPIDevice::isSPIControllerBound()) 136 { 137 debug("binding flash to SMC"); 138 std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out); 139 ofbind << spiDev; 140 ofbind.close(); 141 } 142 143 const int driverBindSleepDuration = 2; 144 145 co_await sdbusplus::async::sleep_for( 146 ctx, std::chrono::seconds(driverBindSleepDuration)); 147 148 if (!isSPIControllerBound()) 149 { 150 error("failed to bind spi controller"); 151 co_return false; 152 } 153 154 const std::string name = 155 std::format("spi{}.{}", spiControllerIndex, spiDeviceIndex); 156 157 std::ofstream ofbindSPINor(spiNorPath + "/bind", std::ofstream::out); 158 ofbindSPINor << name; 159 ofbindSPINor.close(); 160 161 co_await sdbusplus::async::sleep_for( 162 ctx, std::chrono::seconds(driverBindSleepDuration)); 163 164 if (!isSPIFlashBound()) 165 { 166 error("failed to bind spi flash (spi-nor driver)"); 167 co_return false; 168 } 169 170 co_return true; 171 } 172 173 sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash() 174 { 175 debug("unbinding flash"); 176 177 const std::string name = 178 std::format("spi{}.{}", spiControllerIndex, spiDeviceIndex); 179 180 std::ofstream ofunbind(spiNorPath + "/unbind", std::ofstream::out); 181 ofunbind << name; 182 ofunbind.close(); 183 184 // wait for kernel 185 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2)); 186 187 co_return !isSPIFlashBound(); 188 } 189 190 bool SPIDevice::isSPIControllerBound() 191 { 192 std::string path = spiAspeedSMCPath + "/" + spiDev; 193 194 return std::filesystem::exists(path); 195 } 196 197 bool SPIDevice::isSPIFlashBound() 198 { 199 const std::string name = 200 std::format("spi{}.{}", spiControllerIndex, spiDeviceIndex); 201 std::string path = spiNorPath + "/" + name; 202 203 return std::filesystem::exists(path); 204 } 205 206 static std::unique_ptr<::gpiod::line_bulk> requestMuxGPIOs( 207 const std::vector<std::string>& gpioLines, 208 const std::vector<int>& gpioValues, bool inverted) 209 { 210 std::vector<::gpiod::line> lines; 211 212 for (const std::string& lineName : gpioLines) 213 { 214 const ::gpiod::line line = ::gpiod::find_line(lineName); 215 216 if (line.is_used()) 217 { 218 error("gpio line {LINE} was still used", "LINE", lineName); 219 return nullptr; 220 } 221 222 lines.push_back(line); 223 } 224 225 ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT, 226 0}; 227 228 debug("requesting gpios for mux"); 229 230 auto lineBulk = std::make_unique<::gpiod::line_bulk>(lines); 231 232 if (inverted) 233 { 234 std::vector<int> valuesInverted; 235 valuesInverted.reserve(gpioValues.size()); 236 237 for (int value : gpioValues) 238 { 239 valuesInverted.push_back(value ? 0 : 1); 240 } 241 242 lineBulk->request(config, valuesInverted); 243 } 244 else 245 { 246 lineBulk->request(config, gpioValues); 247 } 248 249 return lineBulk; 250 } 251 252 sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image, 253 size_t image_size) 254 { 255 debug("[gpio] requesting gpios to mux SPI to BMC"); 256 257 std::unique_ptr<::gpiod::line_bulk> lineBulk = 258 requestMuxGPIOs(gpioLines, gpioValues, false); 259 260 if (!lineBulk) 261 { 262 co_return false; 263 } 264 265 bool success = co_await SPIDevice::bindSPIFlash(); 266 if (success) 267 { 268 if (dryRun) 269 { 270 info("dry run, NOT writing to the chip"); 271 } 272 else 273 { 274 if (tool == flashToolFlashrom) 275 { 276 const int status = 277 co_await SPIDevice::writeSPIFlashWithFlashrom(image, 278 image_size); 279 if (status != 0) 280 { 281 error( 282 "Error writing to SPI flash {CONTROLLERINDEX}:{DEVICEINDEX}, exit code {EXITCODE}", 283 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", 284 spiDeviceIndex, "EXITCODE", status); 285 } 286 success = (status == 0); 287 } 288 else if (tool == flashToolFlashcp) 289 { 290 success = co_await SPIDevice::writeSPIFlashWithFlashcp( 291 image, image_size); 292 } 293 else 294 { 295 success = 296 co_await SPIDevice::writeSPIFlashDefault(image, image_size); 297 } 298 } 299 300 success = success && co_await SPIDevice::unbindSPIFlash(); 301 } 302 303 lineBulk->release(); 304 305 // switch bios flash back to host via mux / GPIO 306 // (not assume there is a pull to the default value) 307 debug("[gpio] requesting gpios to mux SPI to Host"); 308 309 lineBulk = requestMuxGPIOs(gpioLines, gpioValues, true); 310 311 if (!lineBulk) 312 { 313 co_return success; 314 } 315 316 lineBulk->release(); 317 318 co_return success; 319 } 320 321 sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx, 322 const std::string& cmd) 323 { 324 int pipefd[2]; 325 if (pipe(pipefd) == -1) 326 { 327 perror("pipe"); 328 co_return -1; 329 } 330 331 pid_t pid = fork(); 332 if (pid == -1) 333 { 334 perror("fork"); 335 close(pipefd[0]); 336 close(pipefd[1]); 337 co_return -1; 338 } 339 else if (pid == 0) 340 { 341 close(pipefd[0]); 342 int exitCode = std::system(cmd.c_str()); 343 344 ssize_t status = write(pipefd[1], &exitCode, sizeof(exitCode)); 345 close(pipefd[1]); 346 exit((status == sizeof(exitCode)) ? 0 : 1); 347 } 348 else 349 { 350 close(pipefd[1]); 351 352 sdbusplus::async::fdio pipe_fdio(ctx, pipefd[0]); 353 354 co_await pipe_fdio.next(); 355 356 int status; 357 waitpid(pid, &status, 0); 358 close(pipefd[0]); 359 360 co_return WEXITSTATUS(status); 361 } 362 } 363 364 sdbusplus::async::task<int> SPIDevice::writeSPIFlashWithFlashrom( 365 const uint8_t* image, size_t image_size) const 366 { 367 // randomize the name to enable parallel updates 368 const std::string path = "/tmp/spi-device-image-" + 369 std::to_string(Software::getRandomId()) + ".bin"; 370 371 int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644); 372 if (fd < 0) 373 { 374 error("Failed to open file: {PATH}", "PATH", path); 375 co_return 1; 376 } 377 378 const ssize_t bytesWritten = write(fd, image, image_size); 379 380 close(fd); 381 382 setUpdateProgress(30); 383 384 if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size) 385 { 386 error("Failed to write image to file"); 387 co_return 1; 388 } 389 390 debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path); 391 392 auto devPath = getMTDDevicePath(); 393 394 if (!devPath.has_value()) 395 { 396 co_return 1; 397 } 398 399 size_t devNum = 0; 400 401 try 402 { 403 devNum = std::stoi(devPath.value().substr(8)); 404 } 405 catch (std::exception& e) 406 { 407 error("could not parse mtd device number from {STR}: {ERROR}", "STR", 408 devPath.value(), "ERROR", e); 409 co_return 1; 410 } 411 412 std::string cmd = "flashrom -p linux_mtd:dev=" + std::to_string(devNum); 413 414 if (layout == flashLayoutFlat) 415 { 416 cmd += " -w " + path; 417 } 418 else 419 { 420 error("unsupported flash layout"); 421 422 co_return 1; 423 } 424 425 debug("[flashrom] running {CMD}", "CMD", cmd); 426 427 const int exitCode = co_await asyncSystem(ctx, cmd); 428 429 std::filesystem::remove(path); 430 431 co_return exitCode; 432 } 433 434 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashcp( 435 const uint8_t* image, size_t image_size) const 436 { 437 // randomize the name to enable parallel updates 438 const std::string path = "/tmp/spi-device-image-" + 439 std::to_string(Software::getRandomId()) + ".bin"; 440 441 int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644); 442 if (fd < 0) 443 { 444 error("Failed to open file: {PATH}", "PATH", path); 445 co_return 1; 446 } 447 448 const ssize_t bytesWritten = write(fd, image, image_size); 449 450 close(fd); 451 452 setUpdateProgress(30); 453 454 if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size) 455 { 456 error("Failed to write image to file"); 457 co_return 1; 458 } 459 460 debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path); 461 462 auto devPath = getMTDDevicePath(); 463 464 if (!devPath.has_value()) 465 { 466 co_return 1; 467 } 468 469 std::string cmd = std::format("flashcp -v {} {}", path, devPath.value()); 470 471 debug("running {CMD}", "CMD", cmd); 472 473 const int exitCode = co_await asyncSystem(ctx, cmd); 474 475 std::filesystem::remove(path); 476 477 co_return exitCode == 0; 478 } 479 480 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault( 481 const uint8_t* image, size_t image_size) 482 { 483 auto devPath = getMTDDevicePath(); 484 485 if (!devPath.has_value()) 486 { 487 co_return false; 488 } 489 490 int fd = open(devPath.value().c_str(), O_WRONLY); 491 if (fd < 0) 492 { 493 error("Failed to open device: {PATH}", "PATH", devPath.value()); 494 co_return false; 495 } 496 497 // Write the image in chunks to avoid blocking for too long. 498 // Also, to provide meaningful progress updates. 499 500 const size_t chunk = static_cast<size_t>(1024 * 1024); 501 ssize_t bytesWritten = 0; 502 503 const int progressStart = 30; 504 const int progressEnd = 90; 505 506 for (size_t offset = 0; offset < image_size; offset += chunk) 507 { 508 const ssize_t written = 509 write(fd, image + offset, std::min(chunk, image_size - offset)); 510 511 if (written < 0) 512 { 513 error("Failed to write to device"); 514 co_return false; 515 } 516 517 bytesWritten += written; 518 519 setUpdateProgress( 520 progressStart + int((progressEnd - progressStart) * 521 (double(offset) / double(image_size)))); 522 } 523 524 close(fd); 525 526 if (static_cast<size_t>(bytesWritten) != image_size) 527 { 528 error("Incomplete write to device"); 529 co_return false; 530 } 531 532 debug("Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES", bytesWritten, 533 "PATH", devPath.value()); 534 535 co_return true; 536 } 537 538 std::string SPIDevice::getVersion() 539 { 540 std::string version{}; 541 try 542 { 543 std::ifstream config(biosVersionPath); 544 545 config >> version; 546 } 547 catch (std::exception& e) 548 { 549 error("Failed to get version with {ERROR}", "ERROR", e.what()); 550 version = versionUnknown; 551 } 552 553 if (version.empty()) 554 { 555 version = versionUnknown; 556 } 557 558 return version; 559 } 560 561 auto SPIDevice::processUpdate(std::string versionFileName) 562 -> sdbusplus::async::task<> 563 { 564 if (biosVersionFilename != versionFileName) 565 { 566 error( 567 "Update config file name '{NAME}' (!= '{EXPECTED}') is not expected", 568 "NAME", versionFileName, "EXPECTED", biosVersionFilename); 569 co_return; 570 } 571 572 if (softwareCurrent) 573 { 574 softwareCurrent->setVersion(getVersion()); 575 } 576 577 co_return; 578 } 579 580 std::optional<std::string> SPIDevice::getMTDDevicePath() const 581 { 582 const std::string spiPath = 583 "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex) + 584 "/spi" + std::to_string(spiControllerIndex) + "." + 585 std::to_string(spiDeviceIndex) + "/mtd/"; 586 587 if (!std::filesystem::exists(spiPath)) 588 { 589 error("Error: SPI path not found: {PATH}", "PATH", spiPath); 590 return ""; 591 } 592 593 for (const auto& entry : std::filesystem::directory_iterator(spiPath)) 594 { 595 const std::string mtdName = entry.path().filename().string(); 596 597 if (mtdName.starts_with("mtd") && !mtdName.ends_with("ro")) 598 { 599 return "/dev/" + mtdName; 600 } 601 } 602 603 error("Error: No MTD device found for spi {CONTROLLERINDEX}.{DEVICEINDEX}", 604 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", spiDeviceIndex); 605 606 return std::nullopt; 607 } 608