xref: /openbmc/phosphor-bmc-code-mgmt/bios/spi_device.cpp (revision a2eb951f7384c2b4fa494f90e78295f615c12a56)
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 <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 
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<uint64_t> & 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<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 {
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 
requestMuxGPIOs(const std::vector<std::string> & gpioLines,const std::vector<int> & gpioValues,bool inverted)207 static 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 
229     debug("requesting gpios for mux");
230 
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 
writeSPIFlash(const uint8_t * image,size_t image_size)253 sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(const uint8_t* image,
254                                                       size_t image_size)
255 {
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             {
277                 success = co_await SPIDevice::writeSPIFlashWithFlashrom(
278                     image, image_size);
279                 if (!success)
280                 {
281                     error(
282                         "Error writing to SPI flash {CONTROLLERINDEX}:{DEVICEINDEX}",
283                         "CONTROLLERINDEX", spiControllerIndex, "DEVICEINDEX",
284                         spiDeviceIndex);
285                 }
286             }
287             else if (tool == flashToolFlashcp)
288             {
289                 success = co_await SPIDevice::writeSPIFlashWithFlashcp(
290                     image, image_size);
291             }
292             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 
writeSPIFlashWithFlashrom(const uint8_t * image,size_t image_size) const320 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashrom(
321     const uint8_t* image, size_t image_size) const
322 {
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 
355     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);
369 
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 
383     auto success = co_await asyncSystem(ctx, cmd);
384 
385     std::filesystem::remove(path);
386 
387     co_return success;
388 }
389 
writeSPIFlashWithFlashcp(const uint8_t * image,size_t image_size) const390 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashWithFlashcp(
391     const uint8_t* image, size_t image_size) const
392 {
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 
429     auto success = co_await asyncSystem(ctx, cmd);
430 
431     std::filesystem::remove(path);
432 
433     co_return success;
434 }
435 
writeSPIFlashDefault(const uint8_t * image,size_t image_size)436 sdbusplus::async::task<bool> SPIDevice::writeSPIFlashDefault(
437     const uint8_t* image, size_t image_size)
438 {
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 
getVersion()494 std::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 
processUpdate(std::string versionFileName)517 auto 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 
getMTDDevicePath() const536 std::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 }
564