blob: 7372c40d61a1c03f84c7e14cd2a882c509e4cc31 [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
238 debug("[gpio] requesting gpios to mux SPI to BMC");
239
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
410 std::string cmd = "flashrom -p linux_mtd:dev=" + devPath.value();
411
412 if (layout == flashLayoutFlat)
413 {
414 cmd += " -w " + path;
415 }
416 else
417 {
418 error("unsupported flash layout");
419
420 co_return 1;
421 }
422
423 debug("[flashrom] running {CMD}", "CMD", cmd);
424
425 const int exitCode = co_await asyncSystem(ctx, cmd);
426
427 std::filesystem::remove(path);
428
429 co_return exitCode;
430}
431
432// NOLINTBEGIN(readability-static-accessed-through-instance)
433sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
434 const uint8_t* image, size_t image_size)
435// NOLINTEND(readability-static-accessed-through-instance)
436{
437 auto devPath = getMTDDevicePath();
438
439 if (!devPath.has_value())
440 {
441 co_return false;
442 }
443
444 int fd = open(devPath.value().c_str(), O_WRONLY);
445 if (fd < 0)
446 {
447 error("Failed to open device: {PATH}", "PATH", devPath.value());
448 co_return false;
449 }
450
451 // Write the image in chunks to avoid blocking for too long.
452 // Also, to provide meaningful progress updates.
453
454 const size_t chunk = static_cast<size_t>(1024 * 1024);
455 ssize_t bytesWritten = 0;
456
457 const int progressStart = 30;
458 const int progressEnd = 90;
459
460 for (size_t offset = 0; offset < image_size; offset += chunk)
461 {
462 const ssize_t written =
463 write(fd, image + offset, std::min(chunk, image_size - offset));
464
465 if (written < 0)
466 {
467 error("Failed to write to device");
468 co_return false;
469 }
470
471 bytesWritten += written;
472
473 setUpdateProgress(
474 progressStart + int((progressEnd - progressStart) *
475 (double(offset) / double(image_size))));
476 }
477
478 close(fd);
479
480 if (static_cast<size_t>(bytesWritten) != image_size)
481 {
482 error("Incomplete write to device");
483 co_return false;
484 }
485
486 debug("Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES", bytesWritten,
487 "PATH", devPath.value());
488
489 co_return true;
490}
491
492std::string SPIDevice::getVersion()
493{
494 std::string version{};
495 try
496 {
497 std::ifstream config(biosVersionPath);
498
499 config >> version;
500 }
501 catch (std::exception& e)
502 {
503 error("Failed to get version with {ERROR}", "ERROR", e.what());
504 version = versionUnknown;
505 }
506
507 if (version.empty())
508 {
509 version = versionUnknown;
510 }
511
512 return version;
513}
514
515// NOLINTNEXTLINE(readability-static-accessed-through-instance)
516auto SPIDevice::processUpdate(std::string versionFileName)
517 -> sdbusplus::async::task<>
518{
519 if (biosVersionFilename != versionFileName)
520 {
521 error(
522 "Update config file name '{NAME}' (!= '{EXPECTED}') is not expected",
523 "NAME", versionFileName, "EXPECTED", biosVersionFilename);
524 co_return;
525 }
526
527 if (softwareCurrent)
528 {
529 softwareCurrent->setVersion(getVersion());
530 }
531
532 co_return;
533}
534
535std::optional<std::string> SPIDevice::getMTDDevicePath() const
536{
537 const std::string spiPath =
538 "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex) +
539 "/spi" + std::to_string(spiControllerIndex) + "." +
540 std::to_string(spiDeviceIndex) + "/mtd/";
541
542 if (!std::filesystem::exists(spiPath))
543 {
544 error("Error: SPI path not found: {PATH}", "PATH", spiPath);
545 return "";
546 }
547
548 for (const auto& entry : std::filesystem::directory_iterator(spiPath))
549 {
550 const std::string mtdName = entry.path().filename().string();
551
552 if (mtdName.starts_with("mtd") && !mtdName.ends_with("ro"))
553 {
554 return "/dev/" + mtdName;
555 }
556 }
557
558 error("Error: No MTD device found for spi {CONTROLLERINDEX}.{DEVICEINDEX}",
559 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", spiDeviceIndex);
560
561 return std::nullopt;
562}