blob: 93cb9a3e3841e0544f047f00fe38f8cbfc1d13ab [file] [log] [blame]
Alexander Hansenf2c95a02024-11-26 11:16:44 +01001#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
20PHOSPHOR_LOG2_USING;
21
22using namespace std::literals;
23using namespace phosphor::software;
24using namespace phosphor::software::manager;
25using namespace phosphor::software::host_power;
26
27SPIDevice::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)
67sdbusplus::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
112const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
113
114// NOLINTBEGIN(readability-static-accessed-through-instance)
115sdbusplus::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)
152sdbusplus::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
166bool SPIDevice::isSPIFlashBound()
167{
168 std::string path = spiAspeedSMCPath + "/" + spiDev;
169
170 return std::filesystem::exists(path);
171}
172
173static 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)
220sdbusplus::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)
286sdbusplus::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)
331sdbusplus::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)
390sdbusplus::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
449std::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)
473auto 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
492std::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}