blob: 1af1e665801aa885e795354d9dd373b62776198a [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"
Kevin Tunga2eb9512025-05-05 18:28:56 +08007#include "common/include/utils.hpp"
Alexander Hansenf2c95a02024-11-26 11:16:44 +01008
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
21PHOSPHOR_LOG2_USING;
22
23using namespace std::literals;
24using namespace phosphor::software;
25using namespace phosphor::software::manager;
26using namespace phosphor::software::host_power;
27
Alexander Hansendfb60672025-05-07 14:22:45 +020028static 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
Alexander Hansenf2c95a02024-11-26 11:16:44 +010059SPIDevice::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{
Alexander Hansendfb60672025-05-07 14:22:45 +020074 auto optAddr = getSPIDevAddr(spiControllerIndex);
Alexander Hansenf2c95a02024-11-26 11:16:44 +010075
Alexander Hansendfb60672025-05-07 14:22:45 +020076 if (!optAddr.has_value())
Alexander Hansenf2c95a02024-11-26 11:16:44 +010077 {
Alexander Hansendfb60672025-05-07 14:22:45 +020078 throw std::invalid_argument("SPI controller index not found");
Alexander Hansenf2c95a02024-11-26 11:16:44 +010079 }
80
Alexander Hansendfb60672025-05-07 14:22:45 +020081 spiDev = optAddr.value();
Alexander Hansenf2c95a02024-11-26 11:16:44 +010082
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
Alexander Hansenf2c95a02024-11-26 11:16:44 +010091sdbusplus::async::task<bool> SPIDevice::updateDevice(const uint8_t* image,
92 size_t image_size)
Alexander Hansenf2c95a02024-11-26 11:16:44 +010093{
Alexander Hansenf2c95a02024-11-26 11:16:44 +010094 // 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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100102 bool success = co_await HostPower::setState(ctx, stateOff);
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100103 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;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100129}
130
131const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200132const std::string spiNorPath = "/sys/bus/spi/drivers/spi-nor";
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100133
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100134sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100135{
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200136 if (!SPIDevice::isSPIControllerBound())
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100137 {
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200138 debug("binding flash to SMC");
139 std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
140 ofbind << spiDev;
141 ofbind.close();
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100142 }
143
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100144 const int driverBindSleepDuration = 2;
145
146 co_await sdbusplus::async::sleep_for(
147 ctx, std::chrono::seconds(driverBindSleepDuration));
148
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200149 if (!isSPIControllerBound())
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100150 {
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200151 error("failed to bind spi controller");
152 co_return false;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100153 }
154
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200155 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;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100172}
173
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100174sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100175{
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200176 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;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100183 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
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200191bool SPIDevice::isSPIControllerBound()
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100192{
193 std::string path = spiAspeedSMCPath + "/" + spiDev;
194
195 return std::filesystem::exists(path);
196}
197
Alexander Hansenac4fdd02025-05-20 12:54:50 +0200198bool 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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100207static 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
Alexander Hansendb0e88c2025-05-28 13:48:40 +0200229 debug("requesting gpios for mux");
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100230
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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100253sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
254 size_t image_size)
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100255{
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 {
Kevin Tunga2eb9512025-05-05 18:28:56 +0800277 success = co_await SPIDevice::writeSPIFlashWithFlashrom(
278 image, image_size);
279 if (!success)
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100280 {
281 error(
Kevin Tunga2eb9512025-05-05 18:28:56 +0800282 "Error writing to SPI flash {CONTROLLERINDEX}:{DEVICEINDEX}",
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100283 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX",
Kevin Tunga2eb9512025-05-05 18:28:56 +0800284 spiDeviceIndex);
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100285 }
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100286 }
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200287 else if (tool == flashToolFlashcp)
288 {
289 success = co_await SPIDevice::writeSPIFlashWithFlashcp(
290 image, image_size);
291 }
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100292 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
Kevin Tunga2eb9512025-05-05 18:28:56 +0800320sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashrom(
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100321 const uint8_t* image, size_t image_size) const
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100322{
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
Alexander Hansen7fbe7d82025-05-28 17:04:23 +0200355 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);
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100369
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
Kevin Tunga2eb9512025-05-05 18:28:56 +0800383 auto success = co_await asyncSystem(ctx, cmd);
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100384
385 std::filesystem::remove(path);
386
Kevin Tunga2eb9512025-05-05 18:28:56 +0800387 co_return success;
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100388}
389
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200390sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashcp(
391 const uint8_t* image, size_t image_size) const
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200392{
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
Kevin Tunga2eb9512025-05-05 18:28:56 +0800429 auto success = co_await asyncSystem(ctx, cmd);
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200430
431 std::filesystem::remove(path);
432
Kevin Tunga2eb9512025-05-05 18:28:56 +0800433 co_return success;
Alexander Hansen5db0c6b2025-05-30 11:21:30 +0200434}
435
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100436sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
437 const uint8_t* image, size_t image_size)
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100438{
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
494std::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
Alexander Hansenf2c95a02024-11-26 11:16:44 +0100517auto 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 }
532
533 co_return;
534}
535
536std::optional<std::string> SPIDevice::getMTDDevicePath() const
537{
538 const std::string spiPath =
539 "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex) +
540 "/spi" + std::to_string(spiControllerIndex) + "." +
541 std::to_string(spiDeviceIndex) + "/mtd/";
542
543 if (!std::filesystem::exists(spiPath))
544 {
545 error("Error: SPI path not found: {PATH}", "PATH", spiPath);
546 return "";
547 }
548
549 for (const auto& entry : std::filesystem::directory_iterator(spiPath))
550 {
551 const std::string mtdName = entry.path().filename().string();
552
553 if (mtdName.starts_with("mtd") && !mtdName.ends_with("ro"))
554 {
555 return "/dev/" + mtdName;
556 }
557 }
558
559 error("Error: No MTD device found for spi {CONTROLLERINDEX}.{DEVICEINDEX}",
560 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", spiDeviceIndex);
561
562 return std::nullopt;
563}