blob: a1b1f49e88a5707fd26c5944076ae4d7afd615f4 [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
Alexander Hansenf2c95a02024-11-26 11:16:44 +010090sdbusplus::async::task<bool> SPIDevice::updateDevice(const uint8_t* image,
91 size_t image_size)
Alexander Hansenf2c95a02024-11-26 11:16:44 +010092{
Alexander Hansenf2c95a02024-11-26 11:16:44 +010093 // 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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100101 bool success = co_await HostPower::setState(ctx, stateOff);
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100102 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;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100128}
129
130const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200131const std::string spiNorPath = "/sys/bus/spi/drivers/spi-nor";
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100132
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100133sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100134{
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200135 if (!SPIDevice::isSPIControllerBound())
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100136 {
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200137 debug("binding flash to SMC");
138 std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
139 ofbind << spiDev;
140 ofbind.close();
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100141 }
142
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100143 const int driverBindSleepDuration = 2;
144
145 co_await sdbusplus::async::sleep_for(
146 ctx, std::chrono::seconds(driverBindSleepDuration));
147
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200148 if (!isSPIControllerBound())
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100149 {
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200150 error("failed to bind spi controller");
151 co_return false;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100152 }
153
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200154 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;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100171}
172
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100173sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100174{
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200175 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;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100182 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
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200190bool SPIDevice::isSPIControllerBound()
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100191{
192 std::string path = spiAspeedSMCPath + "/" + spiDev;
193
194 return std::filesystem::exists(path);
195}
196
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200197bool 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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100206static 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
Alexander Hansendb0e88c2025-05-28 13:48:40 +0200228 debug("requesting gpios for mux");
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100229
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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100252sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
253 size_t image_size)
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100254{
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 }
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200288 else if (tool == flashToolFlashcp)
289 {
290 success = co_await SPIDevice::writeSPIFlashWithFlashcp(
291 image, image_size);
292 }
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100293 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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100321sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx,
322 const std::string& cmd)
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100323{
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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100364sdbusplus::async::task<int> SPIDevice::writeSPIFlashWithFlashrom(
365 const uint8_t* image, size_t image_size) const
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100366{
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
Alexander Hansen7fbe7d82025-05-28 17:04:23 +0200399 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);
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100413
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
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200434sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashcp(
435 const uint8_t* image, size_t image_size) const
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200436{
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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100480sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
481 const uint8_t* image, size_t image_size)
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100482{
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
538std::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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100561auto 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
580std::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}