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