xref: /openbmc/phosphor-bmc-code-mgmt/bios/spi_device.cpp (revision bd5081f0b99a27dde8101576524b2c02a4a580b6)
1 #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 
20 PHOSPHOR_LOG2_USING;
21 
22 using namespace std::literals;
23 using namespace phosphor::software;
24 using namespace phosphor::software::manager;
25 using namespace phosphor::software::host_power;
26 
27 static 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 
58 SPIDevice::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 {
73     auto optAddr = getSPIDevAddr(spiControllerIndex);
74 
75     if (!optAddr.has_value())
76     {
77         throw std::invalid_argument("SPI controller index not found");
78     }
79 
80     spiDev = optAddr.value();
81 
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 sdbusplus::async::task<bool> SPIDevice::updateDevice(const uint8_t* image,
91                                                      size_t image_size)
92 {
93     // 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 
101     bool success = co_await HostPower::setState(ctx, stateOff);
102     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;
128 }
129 
130 const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
131 const std::string spiNorPath = "/sys/bus/spi/drivers/spi-nor";
132 
133 sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
134 {
135     if (!SPIDevice::isSPIControllerBound())
136     {
137         debug("binding flash to SMC");
138         std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
139         ofbind << spiDev;
140         ofbind.close();
141     }
142 
143     const int driverBindSleepDuration = 2;
144 
145     co_await sdbusplus::async::sleep_for(
146         ctx, std::chrono::seconds(driverBindSleepDuration));
147 
148     if (!isSPIControllerBound())
149     {
150         error("failed to bind spi controller");
151         co_return false;
152     }
153 
154     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;
171 }
172 
173 sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
174 {
175     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;
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 
190 bool SPIDevice::isSPIControllerBound()
191 {
192     std::string path = spiAspeedSMCPath + "/" + spiDev;
193 
194     return std::filesystem::exists(path);
195 }
196 
197 bool 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 
206 static 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 
228     debug("requesting gpios for mux");
229 
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 
252 sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
253                                                       size_t image_size)
254 {
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             }
288             else if (tool == flashToolFlashcp)
289             {
290                 success = co_await SPIDevice::writeSPIFlashWithFlashcp(
291                     image, image_size);
292             }
293             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 
321 sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx,
322                                         const std::string& cmd)
323 {
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 
364 sdbusplus::async::task<int> SPIDevice::writeSPIFlashWithFlashrom(
365     const uint8_t* image, size_t image_size) const
366 {
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 
399     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);
413 
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 
434 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashcp(
435     const uint8_t* image, size_t image_size) const
436 {
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 
480 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
481     const uint8_t* image, size_t image_size)
482 {
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 
538 std::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 
561 auto 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 
580 std::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 }
608