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