xref: /openbmc/phosphor-bmc-code-mgmt/bios/spi_device.cpp (revision 994a77ff25aeb28b2cce7142d081af767f9eb542)
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 SPIDevice::SPIDevice(sdbusplus::async::context& ctx,
28                      uint64_t spiControllerIndex, uint64_t spiDeviceIndex,
29                      bool dryRun, const std::vector<std::string>& gpioLinesIn,
30                      const std::vector<uint64_t>& gpioValuesIn,
31                      SoftwareConfig& config, SoftwareManager* parent,
32                      enum FlashLayout layout, enum FlashTool tool,
33                      const std::string& versionDirPath) :
34     Device(ctx, config, parent,
35            {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}),
36     NotifyWatchIntf(ctx, versionDirPath), dryRun(dryRun),
37     gpioLines(gpioLinesIn),
38     gpioValues(gpioValuesIn.begin(), gpioValuesIn.end()),
39     spiControllerIndex(spiControllerIndex), spiDeviceIndex(spiDeviceIndex),
40     layout(layout), tool(tool)
41 {
42     // To probe the driver for our spi flash, we need the memory-mapped address
43     // of the spi peripheral. These values are specific to aspeed BMC.
44     // https://github.com/torvalds/linux/blob/master/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
45     std::map<uint32_t, std::string> spiDevAddr = {
46         {0, "1e620000.spi"},
47         {1, "1e630000.spi"},
48         {2, "1e631000.spi"},
49     };
50 
51     if (spiControllerIndex >= spiDevAddr.size())
52     {
53         throw std::invalid_argument("SPI controller index out of bounds");
54     }
55 
56     spiDev = spiDevAddr[spiControllerIndex];
57 
58     ctx.spawn(readNotifyAsync());
59 
60     debug(
61         "SPI Device {NAME} at {CONTROLLERINDEX}:{DEVICEINDEX} initialized successfully",
62         "NAME", config.configName, "CONTROLLERINDEX", spiControllerIndex,
63         "DEVICEINDEX", spiDeviceIndex);
64 }
65 
66 // NOLINTBEGIN(readability-static-accessed-through-instance)
67 sdbusplus::async::task<bool> SPIDevice::updateDevice(const uint8_t* image,
68                                                      size_t image_size)
69 // NOLINTEND(readability-static-accessed-through-instance)
70 {
71     // NOLINTBEGIN(readability-static-accessed-through-instance)
72     // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
73     auto prevPowerstate = co_await HostPower::getState(ctx);
74 
75     if (prevPowerstate != stateOn && prevPowerstate != stateOff)
76     {
77         co_return false;
78     }
79 
80     // NOLINTBEGIN(readability-static-accessed-through-instance)
81     bool success = co_await HostPower::setState(ctx, stateOff);
82     // NOLINTEND(readability-static-accessed-through-instance)
83     if (!success)
84     {
85         error("error changing host power state");
86         co_return false;
87     }
88     setUpdateProgress(10);
89 
90     success = co_await writeSPIFlash(image, image_size);
91 
92     if (success)
93     {
94         setUpdateProgress(100);
95     }
96 
97     // restore the previous powerstate
98     const bool powerstate_restore =
99         co_await HostPower::setState(ctx, prevPowerstate);
100     if (!powerstate_restore)
101     {
102         error("error changing host power state");
103         co_return false;
104     }
105 
106     // return value here is only describing if we successfully wrote to the
107     // SPI flash. Restoring powerstate can still fail.
108     co_return success;
109     // NOLINTEND(readability-static-accessed-through-instance)
110 }
111 
112 const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
113 
114 // NOLINTBEGIN(readability-static-accessed-through-instance)
115 sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
116 // NOLINTEND(readability-static-accessed-through-instance)
117 {
118     debug("binding flash to SMC");
119 
120     if (SPIDevice::isSPIFlashBound())
121     {
122         debug("flash was already bound, unbinding it now");
123         bool success = co_await SPIDevice::unbindSPIFlash();
124 
125         if (!success)
126         {
127             error("error unbinding spi flash");
128             co_return false;
129         }
130     }
131 
132     std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
133     ofbind << spiDev;
134     ofbind.close();
135 
136     const int driverBindSleepDuration = 2;
137 
138     co_await sdbusplus::async::sleep_for(
139         ctx, std::chrono::seconds(driverBindSleepDuration));
140 
141     const bool isBound = isSPIFlashBound();
142 
143     if (!isBound)
144     {
145         error("failed to bind spi device");
146     }
147 
148     co_return isBound;
149 }
150 
151 // NOLINTBEGIN(readability-static-accessed-through-instance)
152 sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
153 // NOLINTEND(readability-static-accessed-through-instance)
154 {
155     debug("unbinding flash from SMC");
156     std::ofstream ofunbind(spiAspeedSMCPath + "/unbind", std::ofstream::out);
157     ofunbind << spiDev;
158     ofunbind.close();
159 
160     // wait for kernel
161     co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
162 
163     co_return !isSPIFlashBound();
164 }
165 
166 bool SPIDevice::isSPIFlashBound()
167 {
168     std::string path = spiAspeedSMCPath + "/" + spiDev;
169 
170     return std::filesystem::exists(path);
171 }
172 
173 static std::unique_ptr<::gpiod::line_bulk> requestMuxGPIOs(
174     const std::vector<std::string>& gpioLines,
175     const std::vector<int>& gpioValues, bool inverted)
176 {
177     std::vector<::gpiod::line> lines;
178 
179     for (const std::string& lineName : gpioLines)
180     {
181         const ::gpiod::line line = ::gpiod::find_line(lineName);
182 
183         if (line.is_used())
184         {
185             error("gpio line {LINE} was still used", "LINE", lineName);
186             return nullptr;
187         }
188 
189         lines.push_back(line);
190     }
191 
192     ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT,
193                                  0};
194 
195     debug("[gpio] requesting gpios to mux SPI to BMC");
196 
197     auto lineBulk = std::make_unique<::gpiod::line_bulk>(lines);
198 
199     if (inverted)
200     {
201         std::vector<int> valuesInverted;
202         valuesInverted.reserve(gpioValues.size());
203 
204         for (int value : gpioValues)
205         {
206             valuesInverted.push_back(value ? 0 : 1);
207         }
208 
209         lineBulk->request(config, valuesInverted);
210     }
211     else
212     {
213         lineBulk->request(config, gpioValues);
214     }
215 
216     return lineBulk;
217 }
218 
219 // NOLINTBEGIN(readability-static-accessed-through-instance)
220 sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
221                                                       size_t image_size)
222 // NOLINTEND(readability-static-accessed-through-instance)
223 {
224     debug("[gpio] requesting gpios to mux SPI to BMC");
225 
226     std::unique_ptr<::gpiod::line_bulk> lineBulk =
227         requestMuxGPIOs(gpioLines, gpioValues, false);
228 
229     if (!lineBulk)
230     {
231         co_return false;
232     }
233 
234     bool success = co_await SPIDevice::bindSPIFlash();
235     if (success)
236     {
237         if (dryRun)
238         {
239             info("dry run, NOT writing to the chip");
240         }
241         else
242         {
243             if (tool == flashToolFlashrom)
244             {
245                 const int status =
246                     co_await SPIDevice::writeSPIFlashWithFlashrom(image,
247                                                                   image_size);
248                 if (status != 0)
249                 {
250                     error(
251                         "Error writing to SPI flash {CONTROLLERINDEX}:{DEVICEINDEX}, exit code {EXITCODE}",
252                         "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX",
253                         spiDeviceIndex, "EXITCODE", status);
254                 }
255                 success = (status == 0);
256             }
257             else
258             {
259                 success =
260                     co_await SPIDevice::writeSPIFlashDefault(image, image_size);
261             }
262         }
263 
264         success = success && co_await SPIDevice::unbindSPIFlash();
265     }
266 
267     lineBulk->release();
268 
269     // switch bios flash back to host via mux / GPIO
270     // (not assume there is a pull to the default value)
271     debug("[gpio] requesting gpios to mux SPI to Host");
272 
273     lineBulk = requestMuxGPIOs(gpioLines, gpioValues, true);
274 
275     if (!lineBulk)
276     {
277         co_return success;
278     }
279 
280     lineBulk->release();
281 
282     co_return success;
283 }
284 
285 // NOLINTBEGIN(readability-static-accessed-through-instance)
286 sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx,
287                                         const std::string& cmd)
288 // NOLINTEND(readability-static-accessed-through-instance)
289 {
290     int pipefd[2];
291     if (pipe(pipefd) == -1)
292     {
293         perror("pipe");
294         co_return -1;
295     }
296 
297     pid_t pid = fork();
298     if (pid == -1)
299     {
300         perror("fork");
301         close(pipefd[0]);
302         close(pipefd[1]);
303         co_return -1;
304     }
305     else if (pid == 0)
306     {
307         close(pipefd[0]);
308         int exitCode = std::system(cmd.c_str());
309 
310         ssize_t status = write(pipefd[1], &exitCode, sizeof(exitCode));
311         close(pipefd[1]);
312         exit((status == sizeof(exitCode)) ? 0 : 1);
313     }
314     else
315     {
316         close(pipefd[1]);
317 
318         sdbusplus::async::fdio pipe_fdio(ctx, pipefd[0]);
319 
320         co_await pipe_fdio.next();
321 
322         int status;
323         waitpid(pid, &status, 0);
324         close(pipefd[0]);
325 
326         co_return WEXITSTATUS(status);
327     }
328 }
329 
330 // NOLINTBEGIN(readability-static-accessed-through-instance)
331 sdbusplus::async::task<int> SPIDevice::writeSPIFlashWithFlashrom(
332     const uint8_t* image, size_t image_size) const
333 // NOLINTEND(readability-static-accessed-through-instance)
334 {
335     // randomize the name to enable parallel updates
336     const std::string path = "/tmp/spi-device-image-" +
337                              std::to_string(Software::getRandomId()) + ".bin";
338 
339     int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
340     if (fd < 0)
341     {
342         error("Failed to open file: {PATH}", "PATH", path);
343         co_return 1;
344     }
345 
346     const ssize_t bytesWritten = write(fd, image, image_size);
347 
348     close(fd);
349 
350     setUpdateProgress(30);
351 
352     if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
353     {
354         error("Failed to write image to file");
355         co_return 1;
356     }
357 
358     debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
359 
360     auto devPath = getMTDDevicePath();
361 
362     if (!devPath.has_value())
363     {
364         co_return 1;
365     }
366 
367     std::string cmd = "flashrom -p linux_mtd:dev=" + devPath.value();
368 
369     if (layout == flashLayoutFlat)
370     {
371         cmd += " -w " + path;
372     }
373     else
374     {
375         error("unsupported flash layout");
376 
377         co_return 1;
378     }
379 
380     debug("[flashrom] running {CMD}", "CMD", cmd);
381 
382     const int exitCode = co_await asyncSystem(ctx, cmd);
383 
384     std::filesystem::remove(path);
385 
386     co_return exitCode;
387 }
388 
389 // NOLINTBEGIN(readability-static-accessed-through-instance)
390 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
391     const uint8_t* image, size_t image_size)
392 // NOLINTEND(readability-static-accessed-through-instance)
393 {
394     auto devPath = getMTDDevicePath();
395 
396     if (!devPath.has_value())
397     {
398         co_return false;
399     }
400 
401     int fd = open(devPath.value().c_str(), O_WRONLY);
402     if (fd < 0)
403     {
404         error("Failed to open device: {PATH}", "PATH", devPath.value());
405         co_return false;
406     }
407 
408     // Write the image in chunks to avoid blocking for too long.
409     // Also, to provide meaningful progress updates.
410 
411     const size_t chunk = static_cast<size_t>(1024 * 1024);
412     ssize_t bytesWritten = 0;
413 
414     const int progressStart = 30;
415     const int progressEnd = 90;
416 
417     for (size_t offset = 0; offset < image_size; offset += chunk)
418     {
419         const ssize_t written =
420             write(fd, image + offset, std::min(chunk, image_size - offset));
421 
422         if (written < 0)
423         {
424             error("Failed to write to device");
425             co_return false;
426         }
427 
428         bytesWritten += written;
429 
430         setUpdateProgress(
431             progressStart + int((progressEnd - progressStart) *
432                                 (double(offset) / double(image_size))));
433     }
434 
435     close(fd);
436 
437     if (static_cast<size_t>(bytesWritten) != image_size)
438     {
439         error("Incomplete write to device");
440         co_return false;
441     }
442 
443     debug("Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES", bytesWritten,
444           "PATH", devPath.value());
445 
446     co_return true;
447 }
448 
449 std::string SPIDevice::getVersion()
450 {
451     std::string version{};
452     try
453     {
454         std::ifstream config(biosVersionPath);
455 
456         config >> version;
457     }
458     catch (std::exception& e)
459     {
460         error("Failed to get version with {ERROR}", "ERROR", e.what());
461         version = versionUnknown;
462     }
463 
464     if (version.empty())
465     {
466         version = versionUnknown;
467     }
468 
469     return version;
470 }
471 
472 // NOLINTNEXTLINE(readability-static-accessed-through-instance)
473 auto SPIDevice::processUpdate(std::string versionFileName)
474     -> sdbusplus::async::task<>
475 {
476     if (biosVersionFilename != versionFileName)
477     {
478         error(
479             "Update config file name '{NAME}' (!= '{EXPECTED}') is not expected",
480             "NAME", versionFileName, "EXPECTED", biosVersionFilename);
481         co_return;
482     }
483 
484     if (softwareCurrent)
485     {
486         softwareCurrent->setVersion(getVersion());
487     }
488 
489     co_return;
490 }
491 
492 std::optional<std::string> SPIDevice::getMTDDevicePath() const
493 {
494     const std::string spiPath =
495         "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex) +
496         "/spi" + std::to_string(spiControllerIndex) + "." +
497         std::to_string(spiDeviceIndex) + "/mtd/";
498 
499     if (!std::filesystem::exists(spiPath))
500     {
501         error("Error: SPI path not found: {PATH}", "PATH", spiPath);
502         return "";
503     }
504 
505     for (const auto& entry : std::filesystem::directory_iterator(spiPath))
506     {
507         const std::string mtdName = entry.path().filename().string();
508 
509         if (mtdName.starts_with("mtd") && !mtdName.ends_with("ro"))
510         {
511             return "/dev/" + mtdName;
512         }
513     }
514 
515     error("Error: No MTD device found for spi {CONTROLLERINDEX}.{DEVICEINDEX}",
516           "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", spiDeviceIndex);
517 
518     return std::nullopt;
519 }
520