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