blob: f68446f879b618f9f7b78fb7a38d3386b9e04c04 [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";
137
138// NOLINTBEGIN(readability-static-accessed-through-instance)
139sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
140// NOLINTEND(readability-static-accessed-through-instance)
141{
142 debug("binding flash to SMC");
143
144 if (SPIDevice::isSPIFlashBound())
145 {
146 debug("flash was already bound, unbinding it now");
147 bool success = co_await SPIDevice::unbindSPIFlash();
148
149 if (!success)
150 {
151 error("error unbinding spi flash");
152 co_return false;
153 }
154 }
155
156 std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
157 ofbind << spiDev;
158 ofbind.close();
159
160 const int driverBindSleepDuration = 2;
161
162 co_await sdbusplus::async::sleep_for(
163 ctx, std::chrono::seconds(driverBindSleepDuration));
164
165 const bool isBound = isSPIFlashBound();
166
167 if (!isBound)
168 {
169 error("failed to bind spi device");
170 }
171
172 co_return isBound;
173}
174
175// NOLINTBEGIN(readability-static-accessed-through-instance)
176sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
177// NOLINTEND(readability-static-accessed-through-instance)
178{
179 debug("unbinding flash from SMC");
180 std::ofstream ofunbind(spiAspeedSMCPath + "/unbind", std::ofstream::out);
181 ofunbind << spiDev;
182 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
190bool SPIDevice::isSPIFlashBound()
191{
192 std::string path = spiAspeedSMCPath + "/" + spiDev;
193
194 return std::filesystem::exists(path);
195}
196
197static std::unique_ptr<::gpiod::line_bulk> requestMuxGPIOs(
198 const std::vector<std::string>& gpioLines,
199 const std::vector<int>& gpioValues, bool inverted)
200{
201 std::vector<::gpiod::line> lines;
202
203 for (const std::string& lineName : gpioLines)
204 {
205 const ::gpiod::line line = ::gpiod::find_line(lineName);
206
207 if (line.is_used())
208 {
209 error("gpio line {LINE} was still used", "LINE", lineName);
210 return nullptr;
211 }
212
213 lines.push_back(line);
214 }
215
216 ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT,
217 0};
218
219 debug("[gpio] requesting gpios to mux SPI to BMC");
220
221 auto lineBulk = std::make_unique<::gpiod::line_bulk>(lines);
222
223 if (inverted)
224 {
225 std::vector<int> valuesInverted;
226 valuesInverted.reserve(gpioValues.size());
227
228 for (int value : gpioValues)
229 {
230 valuesInverted.push_back(value ? 0 : 1);
231 }
232
233 lineBulk->request(config, valuesInverted);
234 }
235 else
236 {
237 lineBulk->request(config, gpioValues);
238 }
239
240 return lineBulk;
241}
242
243// NOLINTBEGIN(readability-static-accessed-through-instance)
244sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
245 size_t image_size)
246// NOLINTEND(readability-static-accessed-through-instance)
247{
248 debug("[gpio] requesting gpios to mux SPI to BMC");
249
250 std::unique_ptr<::gpiod::line_bulk> lineBulk =
251 requestMuxGPIOs(gpioLines, gpioValues, false);
252
253 if (!lineBulk)
254 {
255 co_return false;
256 }
257
258 bool success = co_await SPIDevice::bindSPIFlash();
259 if (success)
260 {
261 if (dryRun)
262 {
263 info("dry run, NOT writing to the chip");
264 }
265 else
266 {
267 if (tool == flashToolFlashrom)
268 {
269 const int status =
270 co_await SPIDevice::writeSPIFlashWithFlashrom(image,
271 image_size);
272 if (status != 0)
273 {
274 error(
275 "Error writing to SPI flash {CONTROLLERINDEX}:{DEVICEINDEX}, exit code {EXITCODE}",
276 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX",
277 spiDeviceIndex, "EXITCODE", status);
278 }
279 success = (status == 0);
280 }
281 else
282 {
283 success =
284 co_await SPIDevice::writeSPIFlashDefault(image, image_size);
285 }
286 }
287
288 success = success && co_await SPIDevice::unbindSPIFlash();
289 }
290
291 lineBulk->release();
292
293 // switch bios flash back to host via mux / GPIO
294 // (not assume there is a pull to the default value)
295 debug("[gpio] requesting gpios to mux SPI to Host");
296
297 lineBulk = requestMuxGPIOs(gpioLines, gpioValues, true);
298
299 if (!lineBulk)
300 {
301 co_return success;
302 }
303
304 lineBulk->release();
305
306 co_return success;
307}
308
309// NOLINTBEGIN(readability-static-accessed-through-instance)
310sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx,
311 const std::string& cmd)
312// NOLINTEND(readability-static-accessed-through-instance)
313{
314 int pipefd[2];
315 if (pipe(pipefd) == -1)
316 {
317 perror("pipe");
318 co_return -1;
319 }
320
321 pid_t pid = fork();
322 if (pid == -1)
323 {
324 perror("fork");
325 close(pipefd[0]);
326 close(pipefd[1]);
327 co_return -1;
328 }
329 else if (pid == 0)
330 {
331 close(pipefd[0]);
332 int exitCode = std::system(cmd.c_str());
333
334 ssize_t status = write(pipefd[1], &exitCode, sizeof(exitCode));
335 close(pipefd[1]);
336 exit((status == sizeof(exitCode)) ? 0 : 1);
337 }
338 else
339 {
340 close(pipefd[1]);
341
342 sdbusplus::async::fdio pipe_fdio(ctx, pipefd[0]);
343
344 co_await pipe_fdio.next();
345
346 int status;
347 waitpid(pid, &status, 0);
348 close(pipefd[0]);
349
350 co_return WEXITSTATUS(status);
351 }
352}
353
354// NOLINTBEGIN(readability-static-accessed-through-instance)
355sdbusplus::async::task<int> SPIDevice::writeSPIFlashWithFlashrom(
356 const uint8_t* image, size_t image_size) const
357// NOLINTEND(readability-static-accessed-through-instance)
358{
359 // randomize the name to enable parallel updates
360 const std::string path = "/tmp/spi-device-image-" +
361 std::to_string(Software::getRandomId()) + ".bin";
362
363 int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
364 if (fd < 0)
365 {
366 error("Failed to open file: {PATH}", "PATH", path);
367 co_return 1;
368 }
369
370 const ssize_t bytesWritten = write(fd, image, image_size);
371
372 close(fd);
373
374 setUpdateProgress(30);
375
376 if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
377 {
378 error("Failed to write image to file");
379 co_return 1;
380 }
381
382 debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
383
384 auto devPath = getMTDDevicePath();
385
386 if (!devPath.has_value())
387 {
388 co_return 1;
389 }
390
391 std::string cmd = "flashrom -p linux_mtd:dev=" + devPath.value();
392
393 if (layout == flashLayoutFlat)
394 {
395 cmd += " -w " + path;
396 }
397 else
398 {
399 error("unsupported flash layout");
400
401 co_return 1;
402 }
403
404 debug("[flashrom] running {CMD}", "CMD", cmd);
405
406 const int exitCode = co_await asyncSystem(ctx, cmd);
407
408 std::filesystem::remove(path);
409
410 co_return exitCode;
411}
412
413// NOLINTBEGIN(readability-static-accessed-through-instance)
414sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
415 const uint8_t* image, size_t image_size)
416// NOLINTEND(readability-static-accessed-through-instance)
417{
418 auto devPath = getMTDDevicePath();
419
420 if (!devPath.has_value())
421 {
422 co_return false;
423 }
424
425 int fd = open(devPath.value().c_str(), O_WRONLY);
426 if (fd < 0)
427 {
428 error("Failed to open device: {PATH}", "PATH", devPath.value());
429 co_return false;
430 }
431
432 // Write the image in chunks to avoid blocking for too long.
433 // Also, to provide meaningful progress updates.
434
435 const size_t chunk = static_cast<size_t>(1024 * 1024);
436 ssize_t bytesWritten = 0;
437
438 const int progressStart = 30;
439 const int progressEnd = 90;
440
441 for (size_t offset = 0; offset < image_size; offset += chunk)
442 {
443 const ssize_t written =
444 write(fd, image + offset, std::min(chunk, image_size - offset));
445
446 if (written < 0)
447 {
448 error("Failed to write to device");
449 co_return false;
450 }
451
452 bytesWritten += written;
453
454 setUpdateProgress(
455 progressStart + int((progressEnd - progressStart) *
456 (double(offset) / double(image_size))));
457 }
458
459 close(fd);
460
461 if (static_cast<size_t>(bytesWritten) != image_size)
462 {
463 error("Incomplete write to device");
464 co_return false;
465 }
466
467 debug("Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES", bytesWritten,
468 "PATH", devPath.value());
469
470 co_return true;
471}
472
473std::string SPIDevice::getVersion()
474{
475 std::string version{};
476 try
477 {
478 std::ifstream config(biosVersionPath);
479
480 config >> version;
481 }
482 catch (std::exception& e)
483 {
484 error("Failed to get version with {ERROR}", "ERROR", e.what());
485 version = versionUnknown;
486 }
487
488 if (version.empty())
489 {
490 version = versionUnknown;
491 }
492
493 return version;
494}
495
496// NOLINTNEXTLINE(readability-static-accessed-through-instance)
497auto SPIDevice::processUpdate(std::string versionFileName)
498 -> sdbusplus::async::task<>
499{
500 if (biosVersionFilename != versionFileName)
501 {
502 error(
503 "Update config file name '{NAME}' (!= '{EXPECTED}') is not expected",
504 "NAME", versionFileName, "EXPECTED", biosVersionFilename);
505 co_return;
506 }
507
508 if (softwareCurrent)
509 {
510 softwareCurrent->setVersion(getVersion());
511 }
512
513 co_return;
514}
515
516std::optional<std::string> SPIDevice::getMTDDevicePath() const
517{
518 const std::string spiPath =
519 "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex) +
520 "/spi" + std::to_string(spiControllerIndex) + "." +
521 std::to_string(spiDeviceIndex) + "/mtd/";
522
523 if (!std::filesystem::exists(spiPath))
524 {
525 error("Error: SPI path not found: {PATH}", "PATH", spiPath);
526 return "";
527 }
528
529 for (const auto& entry : std::filesystem::directory_iterator(spiPath))
530 {
531 const std::string mtdName = entry.path().filename().string();
532
533 if (mtdName.starts_with("mtd") && !mtdName.ends_with("ro"))
534 {
535 return "/dev/" + mtdName;
536 }
537 }
538
539 error("Error: No MTD device found for spi {CONTROLLERINDEX}.{DEVICEINDEX}",
540 "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", spiDeviceIndex);
541
542 return std::nullopt;
543}