xref: /openbmc/phosphor-bmc-code-mgmt/bios/spi_device.cpp (revision e634411ba7c22d18ae01a0b03ce4f7b881c38fcc)
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 #include "common/include/utils.hpp"
8 
9 #include <gpio_controller.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 
21 PHOSPHOR_LOG2_USING;
22 
23 using namespace std::literals;
24 using namespace phosphor::software;
25 using namespace phosphor::software::manager;
26 using namespace phosphor::software::host_power;
27 
getSPIDevAddr(uint64_t spiControllerIndex)28 static 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 
SPIDevice(sdbusplus::async::context & ctx,uint64_t spiControllerIndex,uint64_t spiDeviceIndex,bool dryRun,const std::vector<std::string> & gpioLinesIn,const std::vector<bool> & gpioValuesIn,SoftwareConfig & config,SoftwareManager * parent,enum FlashLayout layout,enum FlashTool tool,const std::string & versionDirPath)59 SPIDevice::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<bool>& 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 {
74     auto optAddr = getSPIDevAddr(spiControllerIndex);
75 
76     if (!optAddr.has_value())
77     {
78         throw std::invalid_argument("SPI controller index not found");
79     }
80 
81     spiDev = optAddr.value();
82 
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 
updateDevice(const uint8_t * image,size_t image_size)91 sdbusplus::async::task<bool> SPIDevice::updateDevice(const uint8_t* image,
92                                                      size_t image_size)
93 {
94     // 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 
102     bool success = co_await HostPower::setState(ctx, stateOff);
103     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;
129 }
130 
131 const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
132 const std::string spiNorPath = "/sys/bus/spi/drivers/spi-nor";
133 
bindSPIFlash()134 sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
135 {
136     if (!SPIDevice::isSPIControllerBound())
137     {
138         debug("binding flash to SMC");
139         std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
140         ofbind << spiDev;
141         ofbind.close();
142     }
143 
144     const int driverBindSleepDuration = 2;
145 
146     co_await sdbusplus::async::sleep_for(
147         ctx, std::chrono::seconds(driverBindSleepDuration));
148 
149     if (!isSPIControllerBound())
150     {
151         error("failed to bind spi controller");
152         co_return false;
153     }
154 
155     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;
172 }
173 
unbindSPIFlash()174 sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
175 {
176     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;
183     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 
isSPIControllerBound()191 bool SPIDevice::isSPIControllerBound()
192 {
193     std::string path = spiAspeedSMCPath + "/" + spiDev;
194 
195     return std::filesystem::exists(path);
196 }
197 
isSPIFlashBound()198 bool 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 
writeSPIFlash(const uint8_t * image,size_t image_size)207 sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
208                                                       size_t image_size)
209 {
210     debug("[gpio] requesting gpios to mux SPI to BMC");
211 
212     GPIOGroup muxGPIO(gpioLines, gpioValues);
213     std::optional<ScopedBmcMux> guard;
214     if (!gpioLines.empty())
215     {
216         try
217         {
218             guard.emplace(muxGPIO);
219         }
220         catch (const std::exception& e)
221         {
222             error("Failed to mux GPIOs to BMC: {ERROR}", "ERROR", e.what());
223             co_return false;
224         }
225     }
226     else
227     {
228         error("No GPIO lines configured for SPI muxing");
229         co_return false;
230     }
231 
232     bool success = co_await SPIDevice::bindSPIFlash();
233     if (success)
234     {
235         if (dryRun)
236         {
237             info("dry run, NOT writing to the chip");
238         }
239         else
240         {
241             if (tool == flashToolFlashrom)
242             {
243                 success = co_await SPIDevice::writeSPIFlashWithFlashrom(
244                     image, image_size);
245                 if (!success)
246                 {
247                     error(
248                         "Error writing to SPI flash {CONTROLLERINDEX}:{DEVICEINDEX}",
249                         "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX",
250                         spiDeviceIndex);
251                 }
252             }
253             else if (tool == flashToolFlashcp)
254             {
255                 success = co_await SPIDevice::writeSPIFlashWithFlashcp(
256                     image, image_size);
257             }
258             else
259             {
260                 success =
261                     co_await SPIDevice::writeSPIFlashDefault(image, image_size);
262             }
263         }
264 
265         success = success && co_await SPIDevice::unbindSPIFlash();
266     }
267 
268     lg2::info("Successfully updated SPI flash");
269     co_return success;
270 }
271 
writeSPIFlashWithFlashrom(const uint8_t * image,size_t image_size) const272 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashrom(
273     const uint8_t* image, size_t image_size) const
274 {
275     // randomize the name to enable parallel updates
276     const std::string path = "/tmp/spi-device-image-" +
277                              std::to_string(Software::getRandomId()) + ".bin";
278 
279     int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
280     if (fd < 0)
281     {
282         error("Failed to open file: {PATH}", "PATH", path);
283         co_return 1;
284     }
285 
286     const ssize_t bytesWritten = write(fd, image, image_size);
287 
288     close(fd);
289 
290     setUpdateProgress(30);
291 
292     if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
293     {
294         error("Failed to write image to file");
295         co_return 1;
296     }
297 
298     debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
299 
300     auto devPath = getMTDDevicePath();
301 
302     if (!devPath.has_value())
303     {
304         co_return 1;
305     }
306 
307     size_t devNum = 0;
308 
309     try
310     {
311         devNum = std::stoi(devPath.value().substr(8));
312     }
313     catch (std::exception& e)
314     {
315         error("could not parse mtd device number from {STR}: {ERROR}", "STR",
316               devPath.value(), "ERROR", e);
317         co_return 1;
318     }
319 
320     std::string cmd = "flashrom -p linux_mtd:dev=" + std::to_string(devNum);
321 
322     if (layout == flashLayoutFlat)
323     {
324         cmd += " -w " + path;
325     }
326     else
327     {
328         error("unsupported flash layout");
329 
330         co_return 1;
331     }
332 
333     debug("[flashrom] running {CMD}", "CMD", cmd);
334 
335     auto success = co_await asyncSystem(ctx, cmd);
336 
337     std::filesystem::remove(path);
338 
339     co_return success;
340 }
341 
writeSPIFlashWithFlashcp(const uint8_t * image,size_t image_size) const342 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashcp(
343     const uint8_t* image, size_t image_size) const
344 {
345     // randomize the name to enable parallel updates
346     const std::string path = "/tmp/spi-device-image-" +
347                              std::to_string(Software::getRandomId()) + ".bin";
348 
349     int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
350     if (fd < 0)
351     {
352         error("Failed to open file: {PATH}", "PATH", path);
353         co_return 1;
354     }
355 
356     const ssize_t bytesWritten = write(fd, image, image_size);
357 
358     close(fd);
359 
360     setUpdateProgress(30);
361 
362     if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
363     {
364         error("Failed to write image to file");
365         co_return 1;
366     }
367 
368     debug("wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
369 
370     auto devPath = getMTDDevicePath();
371 
372     if (!devPath.has_value())
373     {
374         co_return 1;
375     }
376 
377     std::string cmd = std::format("flashcp -v {} {}", path, devPath.value());
378 
379     debug("running {CMD}", "CMD", cmd);
380 
381     auto success = co_await asyncSystem(ctx, cmd);
382 
383     std::filesystem::remove(path);
384 
385     co_return success;
386 }
387 
writeSPIFlashDefault(const uint8_t * image,size_t image_size)388 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
389     const uint8_t* image, size_t image_size)
390 {
391     auto devPath = getMTDDevicePath();
392 
393     if (!devPath.has_value())
394     {
395         co_return false;
396     }
397 
398     int fd = open(devPath.value().c_str(), O_WRONLY);
399     if (fd < 0)
400     {
401         error("Failed to open device: {PATH}", "PATH", devPath.value());
402         co_return false;
403     }
404 
405     // Write the image in chunks to avoid blocking for too long.
406     // Also, to provide meaningful progress updates.
407 
408     const size_t chunk = static_cast<size_t>(1024 * 1024);
409     ssize_t bytesWritten = 0;
410 
411     const int progressStart = 30;
412     const int progressEnd = 90;
413 
414     for (size_t offset = 0; offset < image_size; offset += chunk)
415     {
416         const ssize_t written =
417             write(fd, image + offset, std::min(chunk, image_size - offset));
418 
419         if (written < 0)
420         {
421             error("Failed to write to device");
422             co_return false;
423         }
424 
425         bytesWritten += written;
426 
427         setUpdateProgress(
428             progressStart + int((progressEnd - progressStart) *
429                                 (double(offset) / double(image_size))));
430     }
431 
432     close(fd);
433 
434     if (static_cast<size_t>(bytesWritten) != image_size)
435     {
436         error("Incomplete write to device");
437         co_return false;
438     }
439 
440     debug("Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES", bytesWritten,
441           "PATH", devPath.value());
442 
443     co_return true;
444 }
445 
getVersion()446 std::string SPIDevice::getVersion()
447 {
448     std::string version{};
449     try
450     {
451         std::ifstream config(biosVersionPath);
452 
453         config >> version;
454     }
455     catch (std::exception& e)
456     {
457         error("Failed to get version with {ERROR}", "ERROR", e.what());
458         version = versionUnknown;
459     }
460 
461     if (version.empty())
462     {
463         version = versionUnknown;
464     }
465 
466     return version;
467 }
468 
processUpdate(std::string versionFileName)469 auto SPIDevice::processUpdate(std::string versionFileName)
470     -> sdbusplus::async::task<>
471 {
472     if (biosVersionFilename != versionFileName)
473     {
474         error(
475             "Update config file name '{NAME}' (!= '{EXPECTED}') is not expected",
476             "NAME", versionFileName, "EXPECTED", biosVersionFilename);
477         co_return;
478     }
479 
480     if (softwareCurrent)
481     {
482         softwareCurrent->setVersion(getVersion(),
483                                     SoftwareVersion::VersionPurpose::Host);
484     }
485 
486     co_return;
487 }
488 
getMTDDevicePath() const489 std::optional<std::string> SPIDevice::getMTDDevicePath() const
490 {
491     const std::string spiPath =
492         "/sys/class/spi_master/spi" + std::to_string(spiControllerIndex) +
493         "/spi" + std::to_string(spiControllerIndex) + "." +
494         std::to_string(spiDeviceIndex) + "/mtd/";
495 
496     if (!std::filesystem::exists(spiPath))
497     {
498         error("Error: SPI path not found: {PATH}", "PATH", spiPath);
499         return "";
500     }
501 
502     for (const auto& entry : std::filesystem::directory_iterator(spiPath))
503     {
504         const std::string mtdName = entry.path().filename().string();
505 
506         if (mtdName.starts_with("mtd") && !mtdName.ends_with("ro"))
507         {
508             return "/dev/" + mtdName;
509         }
510     }
511 
512     error("Error: No MTD device found for spi {CONTROLLERINDEX}.{DEVICEINDEX}",
513           "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX", spiDeviceIndex);
514 
515     return std::nullopt;
516 }
517