blob: fbf6fe92f7a2637b43c4fb000ce1a7d29543c640 [file] [log] [blame]
Alexander Hansenf2c95a02024-11-26 11:16:44 +01001#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
20PHOSPHOR_LOG2_USING;
21
22using namespace std::literals;
23using namespace phosphor::software;
24using namespace phosphor::software::manager;
25using namespace phosphor::software::host_power;
26
Alexander Hansendfb60672025-05-07 14:22:45 +020027static 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
Alexander Hansenf2c95a02024-11-26 11:16:44 +010058SPIDevice::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{
Alexander Hansendfb60672025-05-07 14:22:45 +020073 auto optAddr = getSPIDevAddr(spiControllerIndex);
Alexander Hansenf2c95a02024-11-26 11:16:44 +010074
Alexander Hansendfb60672025-05-07 14:22:45 +020075 if (!optAddr.has_value())
Alexander Hansenf2c95a02024-11-26 11:16:44 +010076 {
Alexander Hansendfb60672025-05-07 14:22:45 +020077 throw std::invalid_argument("SPI controller index not found");
Alexander Hansenf2c95a02024-11-26 11:16:44 +010078 }
79
Alexander Hansendfb60672025-05-07 14:22:45 +020080 spiDev = optAddr.value();
Alexander Hansenf2c95a02024-11-26 11:16:44 +010081
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// NOLINTBEGIN(readability-static-accessed-through-instance)
91sdbusplus::async::task<bool> SPIDevice::updateDevice(const uint8_t* image,
92 size_t image_size)
93// NOLINTEND(readability-static-accessed-through-instance)
94{
95 // NOLINTBEGIN(readability-static-accessed-through-instance)
96 // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
97 auto prevPowerstate = co_await HostPower::getState(ctx);
98
99 if (prevPowerstate != stateOn && prevPowerstate != stateOff)
100 {
101 co_return false;
102 }
103
104 // NOLINTBEGIN(readability-static-accessed-through-instance)
105 bool success = co_await HostPower::setState(ctx, stateOff);
106 // NOLINTEND(readability-static-accessed-through-instance)
107 if (!success)
108 {
109 error("error changing host power state");
110 co_return false;
111 }
112 setUpdateProgress(10);
113
114 success = co_await writeSPIFlash(image, image_size);
115
116 if (success)
117 {
118 setUpdateProgress(100);
119 }
120
121 // restore the previous powerstate
122 const bool powerstate_restore =
123 co_await HostPower::setState(ctx, prevPowerstate);
124 if (!powerstate_restore)
125 {
126 error("error changing host power state");
127 co_return false;
128 }
129
130 // return value here is only describing if we successfully wrote to the
131 // SPI flash. Restoring powerstate can still fail.
132 co_return success;
133 // NOLINTEND(readability-static-accessed-through-instance)
134}
135
136const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200137const std::string spiNorPath = "/sys/bus/spi/drivers/spi-nor";
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100138
139// NOLINTBEGIN(readability-static-accessed-through-instance)
140sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
141// NOLINTEND(readability-static-accessed-through-instance)
142{
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200143 if (!SPIDevice::isSPIControllerBound())
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100144 {
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200145 debug("binding flash to SMC");
146 std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
147 ofbind << spiDev;
148 ofbind.close();
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100149 }
150
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100151 const int driverBindSleepDuration = 2;
152
153 co_await sdbusplus::async::sleep_for(
154 ctx, std::chrono::seconds(driverBindSleepDuration));
155
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200156 if (!isSPIControllerBound())
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100157 {
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200158 error("failed to bind spi controller");
159 co_return false;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100160 }
161
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200162 const std::string name =
163 std::format("spi{}.{}", spiControllerIndex, spiDeviceIndex);
164
165 std::ofstream ofbindSPINor(spiNorPath + "/bind", std::ofstream::out);
166 ofbindSPINor << name;
167 ofbindSPINor.close();
168
169 co_await sdbusplus::async::sleep_for(
170 ctx, std::chrono::seconds(driverBindSleepDuration));
171
172 if (!isSPIFlashBound())
173 {
174 error("failed to bind spi flash (spi-nor driver)");
175 co_return false;
176 }
177
178 co_return true;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100179}
180
181// NOLINTBEGIN(readability-static-accessed-through-instance)
182sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
183// NOLINTEND(readability-static-accessed-through-instance)
184{
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200185 debug("unbinding flash");
186
187 const std::string name =
188 std::format("spi{}.{}", spiControllerIndex, spiDeviceIndex);
189
190 std::ofstream ofunbind(spiNorPath + "/unbind", std::ofstream::out);
191 ofunbind << name;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100192 ofunbind.close();
193
194 // wait for kernel
195 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
196
197 co_return !isSPIFlashBound();
198}
199
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200200bool SPIDevice::isSPIControllerBound()
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100201{
202 std::string path = spiAspeedSMCPath + "/" + spiDev;
203
204 return std::filesystem::exists(path);
205}
206
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200207bool SPIDevice::isSPIFlashBound()
208{
209 const std::string name =
210 std::format("spi{}.{}", spiControllerIndex, spiDeviceIndex);
211 std::string path = spiNorPath + "/" + name;
212
213 return std::filesystem::exists(path);
214}
215
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100216static std::unique_ptr<::gpiod::line_bulk> requestMuxGPIOs(
217 const std::vector<std::string>& gpioLines,
218 const std::vector<int>& gpioValues, bool inverted)
219{
220 std::vector<::gpiod::line> lines;
221
222 for (const std::string& lineName : gpioLines)
223 {
224 const ::gpiod::line line = ::gpiod::find_line(lineName);
225
226 if (line.is_used())
227 {
228 error("gpio line {LINE} was still used", "LINE", lineName);
229 return nullptr;
230 }
231
232 lines.push_back(line);
233 }
234
235 ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT,
236 0};
237
Alexander Hansendb0e88c2025-05-28 13:48:40 +0200238 debug("requesting gpios for mux");
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100239
240 auto lineBulk = std::make_unique<::gpiod::line_bulk>(lines);
241
242 if (inverted)
243 {
244 std::vector<int> valuesInverted;
245 valuesInverted.reserve(gpioValues.size());
246
247 for (int value : gpioValues)
248 {
249 valuesInverted.push_back(value ? 0 : 1);
250 }
251
252 lineBulk->request(config, valuesInverted);
253 }
254 else
255 {
256 lineBulk->request(config, gpioValues);
257 }
258
259 return lineBulk;
260}
261
262// NOLINTBEGIN(readability-static-accessed-through-instance)
263sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
264 size_t image_size)
265// NOLINTEND(readability-static-accessed-through-instance)
266{
267 debug("[gpio] requesting gpios to mux SPI to BMC");
268
269 std::unique_ptr<::gpiod::line_bulk> lineBulk =
270 requestMuxGPIOs(gpioLines, gpioValues, false);
271
272 if (!lineBulk)
273 {
274 co_return false;
275 }
276
277 bool success = co_await SPIDevice::bindSPIFlash();
278 if (success)
279 {
280 if (dryRun)
281 {
282 info("dry run, NOT writing to the chip");
283 }
284 else
285 {
286 if (tool == flashToolFlashrom)
287 {
288 const int status =
289 co_await SPIDevice::writeSPIFlashWithFlashrom(image,
290 image_size);
291 if (status != 0)
292 {
293 error(
294 "Error writing to SPI flash {CONTROLLERINDEX}:{DEVICEINDEX}, exit code {EXITCODE}",
295 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX",
296 spiDeviceIndex, "EXITCODE", status);
297 }
298 success = (status == 0);
299 }
300 else
301 {
302 success =
303 co_await SPIDevice::writeSPIFlashDefault(image, image_size);
304 }
305 }
306
307 success = success && co_await SPIDevice::unbindSPIFlash();
308 }
309
310 lineBulk->release();
311
312 // switch bios flash back to host via mux / GPIO
313 // (not assume there is a pull to the default value)
314 debug("[gpio] requesting gpios to mux SPI to Host");
315
316 lineBulk = requestMuxGPIOs(gpioLines, gpioValues, true);
317
318 if (!lineBulk)
319 {
320 co_return success;
321 }
322
323 lineBulk->release();
324
325 co_return success;
326}
327
328// NOLINTBEGIN(readability-static-accessed-through-instance)
329sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx,
330 const std::string& cmd)
331// NOLINTEND(readability-static-accessed-through-instance)
332{
333 int pipefd[2];
334 if (pipe(pipefd) == -1)
335 {
336 perror("pipe");
337 co_return -1;
338 }
339
340 pid_t pid = fork();
341 if (pid == -1)
342 {
343 perror("fork");
344 close(pipefd[0]);
345 close(pipefd[1]);
346 co_return -1;
347 }
348 else if (pid == 0)
349 {
350 close(pipefd[0]);
351 int exitCode = std::system(cmd.c_str());
352
353 ssize_t status = write(pipefd[1], &exitCode, sizeof(exitCode));
354 close(pipefd[1]);
355 exit((status == sizeof(exitCode)) ? 0 : 1);
356 }
357 else
358 {
359 close(pipefd[1]);
360
361 sdbusplus::async::fdio pipe_fdio(ctx, pipefd[0]);
362
363 co_await pipe_fdio.next();
364
365 int status;
366 waitpid(pid, &status, 0);
367 close(pipefd[0]);
368
369 co_return WEXITSTATUS(status);
370 }
371}
372
373// NOLINTBEGIN(readability-static-accessed-through-instance)
374sdbusplus::async::task<int> SPIDevice::writeSPIFlashWithFlashrom(
375 const uint8_t* image, size_t image_size) const
376// NOLINTEND(readability-static-accessed-through-instance)
377{
378 // randomize the name to enable parallel updates
379 const std::string path = "/tmp/spi-device-image-" +
380 std::to_string(Software::getRandomId()) + ".bin";
381
382 int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
383 if (fd < 0)
384 {
385 error("Failed to open file: {PATH}", "PATH", path);
386 co_return 1;
387 }
388
389 const ssize_t bytesWritten = write(fd, image, image_size);
390
391 close(fd);
392
393 setUpdateProgress(30);
394
395 if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
396 {
397 error("Failed to write image to file");
398 co_return 1;
399 }
400
401 debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
402
403 auto devPath = getMTDDevicePath();
404
405 if (!devPath.has_value())
406 {
407 co_return 1;
408 }
409
Alexander Hansen7fbe7d82025-05-28 17:04:23 +0200410 size_t devNum = 0;
411
412 try
413 {
414 devNum = std::stoi(devPath.value().substr(8));
415 }
416 catch (std::exception& e)
417 {
418 error("could not parse mtd device number from {STR}: {ERROR}", "STR",
419 devPath.value(), "ERROR", e);
420 co_return 1;
421 }
422
423 std::string cmd = "flashrom -p linux_mtd:dev=" + std::to_string(devNum);
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100424
425 if (layout == flashLayoutFlat)
426 {
427 cmd += " -w " + path;
428 }
429 else
430 {
431 error("unsupported flash layout");
432
433 co_return 1;
434 }
435
436 debug("[flashrom] running {CMD}", "CMD", cmd);
437
438 const int exitCode = co_await asyncSystem(ctx, cmd);
439
440 std::filesystem::remove(path);
441
442 co_return exitCode;
443}
444
445// NOLINTBEGIN(readability-static-accessed-through-instance)
446sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
447 const uint8_t* image, size_t image_size)
448// NOLINTEND(readability-static-accessed-through-instance)
449{
450 auto devPath = getMTDDevicePath();
451
452 if (!devPath.has_value())
453 {
454 co_return false;
455 }
456
457 int fd = open(devPath.value().c_str(), O_WRONLY);
458 if (fd < 0)
459 {
460 error("Failed to open device: {PATH}", "PATH", devPath.value());
461 co_return false;
462 }
463
464 // Write the image in chunks to avoid blocking for too long.
465 // Also, to provide meaningful progress updates.
466
467 const size_t chunk = static_cast<size_t>(1024 * 1024);
468 ssize_t bytesWritten = 0;
469
470 const int progressStart = 30;
471 const int progressEnd = 90;
472
473 for (size_t offset = 0; offset < image_size; offset += chunk)
474 {
475 const ssize_t written =
476 write(fd, image + offset, std::min(chunk, image_size - offset));
477
478 if (written < 0)
479 {
480 error("Failed to write to device");
481 co_return false;
482 }
483
484 bytesWritten += written;
485
486 setUpdateProgress(
487 progressStart + int((progressEnd - progressStart) *
488 (double(offset) / double(image_size))));
489 }
490
491 close(fd);
492
493 if (static_cast<size_t>(bytesWritten) != image_size)
494 {
495 error("Incomplete write to device");
496 co_return false;
497 }
498
499 debug("Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES", bytesWritten,
500 "PATH", devPath.value());
501
502 co_return true;
503}
504
505std::string SPIDevice::getVersion()
506{
507 std::string version{};
508 try
509 {
510 std::ifstream config(biosVersionPath);
511
512 config >> version;
513 }
514 catch (std::exception& e)
515 {
516 error("Failed to get version with {ERROR}", "ERROR", e.what());
517 version = versionUnknown;
518 }
519
520 if (version.empty())
521 {
522 version = versionUnknown;
523 }
524
525 return version;
526}
527
528// NOLINTNEXTLINE(readability-static-accessed-through-instance)
529auto SPIDevice::processUpdate(std::string versionFileName)
530 -> sdbusplus::async::task<>
531{
532 if (biosVersionFilename != versionFileName)
533 {
534 error(
535 "Update config file name '{NAME}' (!= '{EXPECTED}') is not expected",
536 "NAME", versionFileName, "EXPECTED", biosVersionFilename);
537 co_return;
538 }
539
540 if (softwareCurrent)
541 {
542 softwareCurrent->setVersion(getVersion());
543 }
544
545 co_return;
546}
547
548std::optional<std::string> SPIDevice::getMTDDevicePath() const
549{
550 const std::string spiPath =
551 "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex) +
552 "/spi" + std::to_string(spiControllerIndex) + "." +
553 std::to_string(spiDeviceIndex) + "/mtd/";
554
555 if (!std::filesystem::exists(spiPath))
556 {
557 error("Error: SPI path not found: {PATH}", "PATH", spiPath);
558 return "";
559 }
560
561 for (const auto& entry : std::filesystem::directory_iterator(spiPath))
562 {
563 const std::string mtdName = entry.path().filename().string();
564
565 if (mtdName.starts_with("mtd") && !mtdName.ends_with("ro"))
566 {
567 return "/dev/" + mtdName;
568 }
569 }
570
571 error("Error: No MTD device found for spi {CONTROLLERINDEX}.{DEVICEINDEX}",
572 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", spiDeviceIndex);
573
574 return std::nullopt;
575}