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