blob: 46fe1dc0bd8697abe30834cafaa233e02f4a6127 [file] [log] [blame]
Alexander Hansen1ba6e1c2024-11-26 11:16:44 +01001#include "spi_device.hpp"
2
3#include "common/include/device.hpp"
4#include "common/include/software_manager.hpp"
5
6#include <gpiod.hpp>
7#include <phosphor-logging/lg2.hpp>
8#include <sdbusplus/async.hpp>
9#include <sdbusplus/async/context.hpp>
10
11#include <fstream>
12
13using namespace std::literals;
14
15SPIDevice::SPIDevice(
16 sdbusplus::async::context& ctx, const std::string& spiDevName, bool dryRun,
17 bool hasME, const std::vector<std::string>& gpioLines,
18 const std::vector<uint8_t>& gpioValues, DeviceConfig& config,
19 SoftwareManager* parent, bool layoutFlat, bool toolFlashrom, bool debug) :
20 Device(ctx, dryRun, config, parent), hasManagementEngine(hasME),
21 gpioLines(gpioLines), gpioValues(gpioValues), spiDev(spiDevName),
22 layoutFlat(layoutFlat), toolFlashrom(toolFlashrom), debug(debug)
23{
24 lg2::debug("initialized SPI Device instance on dbus");
25}
26
27// NOLINTBEGIN
28sdbusplus::async::task<std::string> SPIDevice::getInventoryItemObjectPath()
29// NOLINTEND
30{
31 // TODO: we currently do not know how to access the object path of the
32 // inventory item here
33 co_return "";
34}
35
36// NOLINTBEGIN
37sdbusplus::async::task<bool> SPIDevice::updateDevice(
38 const uint8_t* image, size_t image_size,
39 std::unique_ptr<SoftwareActivationProgress>& activationProgress)
40// NOLINTEND
41{
42 // NOLINTBEGIN
43 bool success =
44 co_await this->writeSPIFlash(image, image_size, activationProgress);
45 // NOLINTEND
46 co_return success;
47}
48
49constexpr const char* IPMB_SERVICE = "xyz.openbmc_project.Ipmi.Channel.Ipmb";
50constexpr const char* IPMB_PATH = "/xyz/openbmc_project/Ipmi/Channel/Ipmb";
51constexpr const char* IPMB_INTF = "org.openbmc.Ipmb";
52
53// NOLINTBEGIN
54sdbusplus::async::task<> SPIDevice::setManagementEngineRecoveryMode()
55// NOLINTEND
56{
57 lg2::info("[ME] setting Management Engine to recovery mode");
58 auto m = ctx.get_bus().new_method_call(IPMB_SERVICE, IPMB_PATH, IPMB_INTF,
59 "sendRequest");
60
61 // me address, 0x2e oen, 0x00 - lun, 0xdf - force recovery
62 uint8_t cmd_recover[] = {0x1, 0x2e, 0x0, 0xdf};
63 for (unsigned int i = 0; i < sizeof(cmd_recover); i++)
64 {
65 m.append(cmd_recover[i]);
66 }
67 std::vector<uint8_t> remainder = {0x04, 0x57, 0x01, 0x00, 0x01};
68 m.append(remainder);
69
70 m.call();
71
72 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(5));
73
74 co_return;
75}
76
77// NOLINTBEGIN
78sdbusplus::async::task<> SPIDevice::resetManagementEngine()
79// NOLINTEND
80{
81 lg2::info("[ME] resetting Management Engine");
82 auto m = ctx.get_bus().new_method_call(IPMB_SERVICE, IPMB_PATH, IPMB_INTF,
83 "sendRequest");
84
85 // me address, 0x6 App Fn, 0x00 - lun, 0x2 - cold reset
86 uint8_t cmd_recover[] = {0x1, 0x6, 0x0, 0x2};
87 for (unsigned int i = 0; i < sizeof(cmd_recover); i++)
88 {
89 m.append(cmd_recover[i]);
90 }
91 std::vector<uint8_t> remainder;
92 m.append(remainder);
93
94 m.call();
95
96 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(5));
97
98 co_return;
99}
100
101const std::string spiAspeedSMCPath = "/sys/bus/platform/drivers/spi-aspeed-smc";
102
103// NOLINTBEGIN
104sdbusplus::async::task<bool> SPIDevice::bindSPIFlash()
105// NOLINTEND
106{
107 lg2::info("[SPI] binding flash to SMC");
108 std::ofstream ofbind(spiAspeedSMCPath + "/bind", std::ofstream::out);
109 ofbind << this->spiDev;
110 ofbind.close();
111
112 // wait for kernel
113 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
114
115 co_return isSPIFlashBound();
116}
117
118// NOLINTBEGIN
119sdbusplus::async::task<bool> SPIDevice::unbindSPIFlash()
120// NOLINTEND
121{
122 lg2::info("[SPI] unbinding flash from SMC");
123 std::ofstream ofunbind(spiAspeedSMCPath + "/unbind", std::ofstream::out);
124 ofunbind << this->spiDev;
125 ofunbind.close();
126
127 // wait for kernel
128 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
129
130 co_return !isSPIFlashBound();
131}
132
133bool SPIDevice::isSPIFlashBound()
134{
135 std::string path = spiAspeedSMCPath + "/" + this->spiDev;
136 lg2::debug("[SPI] checking {PATH}", "PATH", path);
137
138 return std::filesystem::exists(path);
139}
140
141// NOLINTBEGIN
142sdbusplus::async::task<bool> SPIDevice::writeSPIFlash(
143 const uint8_t* image, size_t image_size,
144 const std::unique_ptr<SoftwareActivationProgress>& activationProgress)
145// NOLINTEND
146{
147 auto currentPowerstateOpt = co_await parent->getHostPowerstate();
148
149 if (!currentPowerstateOpt.has_value())
150 {
151 co_return false;
152 }
153
154 const bool prevPowerstate = currentPowerstateOpt.value();
155
156 // NOLINTBEGIN
157 bool success = co_await parent->setHostPowerstate(false);
158 // NOLINTEND
159 if (!success)
160 {
161 lg2::error("error changing host power state");
162 co_return false;
163 }
164 activationProgress->progress(10);
165
166 if (hasManagementEngine)
167 {
168 co_await setManagementEngineRecoveryMode();
169 }
170 activationProgress->progress(20);
171
172 success = co_await writeSPIFlashHostOff(image, image_size);
173
174 if (success)
175 {
176 activationProgress->progress(70);
177 }
178
179 if (hasManagementEngine)
180 {
181 co_await resetManagementEngine();
182 }
183
184 if (success)
185 {
186 activationProgress->progress(90);
187 }
188
189 // restore the previous powerstate
190 const bool powerstate_restore =
191 co_await parent->setHostPowerstate(prevPowerstate);
192 if (!powerstate_restore)
193 {
194 lg2::error("error changing host power state");
195 co_return false;
196 }
197
198 // return value here is only describing if we successfully wrote to the
199 // SPI flash. Restoring powerstate can still fail.
200 co_return success;
201}
202
203// NOLINTBEGIN
204sdbusplus::async::task<bool>
205 SPIDevice::writeSPIFlashHostOff(const uint8_t* image, size_t image_size)
206// NOLINTEND
207{
208 gpiod::chip chip;
209 try
210 {
211 // TODO: make it work for multiple chips
212 chip = gpiod::chip("/dev/gpiochip0");
213 }
214 catch (std::exception& e)
215 {
216 lg2::error(e.what());
217 co_return false;
218 }
219
220 std::vector<unsigned int> offsets;
221
222 for (const std::string& lineName : gpioLines)
223 {
224 const ::gpiod::line line = ::gpiod::find_line(lineName);
225
226 if (line.is_used())
227 {
228 lg2::error("gpio line {LINE} was still used", "LINE", lineName);
229 co_return false;
230 }
231 offsets.push_back(line.offset());
232 }
233
234 auto lines = chip.get_lines(offsets);
235
236 ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT,
237 0};
238 std::vector<int> values;
239 std::vector<int> valuesInverted;
240 values.reserve(gpioValues.size());
241
242 for (uint8_t value : gpioValues)
243 {
244 values.push_back(value);
245 valuesInverted.push_back(value ? 0 : 1);
246 }
247
248 lg2::debug("[gpio] requesting gpios to mux SPI to BMC");
249 lines.request(config, values);
250
251 co_await writeSPIFlashHostOffGPIOSet(image, image_size);
252
253 lines.release();
254
255 // switch bios flash back to host via mux / GPIO
256 // (not assume there is a pull to the default value)
257 lg2::debug("[gpio] requesting gpios to mux SPI to Host");
258 lines.request(config, valuesInverted);
259
260 lines.release();
261
262 co_return true;
263}
264
265// NOLINTBEGIN
266sdbusplus::async::task<bool> SPIDevice::writeSPIFlashHostOffGPIOSet(
267 const uint8_t* image, size_t image_size)
268// NOLINTEND
269{
270 bool success = true;
271
272 if (SPIDevice::isSPIFlashBound())
273 {
274 lg2::debug("[SPI] flash was already bound, unbinding it now");
275 success = co_await SPIDevice::unbindSPIFlash();
276
277 if (!success)
278 {
279 lg2::error("[SPI] error unbinding spi flash");
280 co_return false;
281 }
282 }
283
284 success = co_await SPIDevice::bindSPIFlash();
285
286 if (!success)
287 {
288 lg2::error("[SPI] failed to bind spi device");
289 co_await SPIDevice::unbindSPIFlash();
290 co_return false;
291 }
292
293 if (dryRun)
294 {
295 lg2::info("[SPI] dry run, NOT writing to the chip");
296 }
297 else
298 {
299 if (this->toolFlashrom)
300 {
301 co_await SPIDevice::writeSPIFlashFlashromHostOffGPIOSetDeviceBound(
302 image, image_size);
303 }
304 else
305 {
306 co_await SPIDevice::writeSPIFlashHostOffGPIOSetDeviceBound(
307 image, image_size);
308 }
309 }
310
311 success = co_await SPIDevice::unbindSPIFlash();
312
313 co_return success;
314}
315
316// NOLINTBEGIN
317sdbusplus::async::task<bool>
318 SPIDevice::writeSPIFlashFlashromHostOffGPIOSetDeviceBound(
319 const uint8_t* image, size_t image_size)
320// NOLINTEND
321{
322 // TODO: randomize the name to enable parallel updates
323 std::string path = "/tmp/spi-device-image.bin";
324 int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
325 if (fd < 0)
326 {
327 lg2::error("[SPI] Failed to open file: {PATH}", "PATH", path);
328 co_return false;
329 }
330
331 const ssize_t bytesWritten = write(fd, image, image_size);
332
333 close(fd);
334
335 if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
336 {
337 lg2::error("[SPI] Failed to write image to file");
338 co_return false;
339 }
340
341 // TODO: do not hardcode the mtd device
342 std::string cmd = "flashrom -p linux_mtd:dev=6 ";
343
344 if (this->layoutFlat)
345 {
346 cmd += "-w " + path;
347 }
348 else
349 {
350 cmd += "--ifd -i fd -i bios -i me -w " + path;
351 }
352
353 lg2::info("[flashrom] running {CMD}", "CMD", cmd);
354
355 const int exitCode = std::system(cmd.c_str());
356
357 if (exitCode != 0)
358 {
359 lg2::error("[SPI] error running flaashrom");
360 }
361
362 // in debug mode we do not delete the raw component image
363 if (!this->debug)
364 {
365 std::filesystem::remove(path);
366 }
367
368 co_return exitCode;
369}
370
371// NOLINTBEGIN
372sdbusplus::async::task<bool> SPIDevice::writeSPIFlashHostOffGPIOSetDeviceBound(
373 const uint8_t* image, size_t image_size)
374// NOLINTEND
375{
376 // TODO: not hardcode the mtd device
377 std::string devPath = "/dev/mtd6";
378 int fd = open(devPath.c_str(), O_WRONLY);
379 if (fd < 0)
380 {
381 lg2::error("[SPI] Failed to open device: {PATH}", "PATH", devPath);
382 co_return false;
383 }
384
385 ssize_t bytesWritten = write(fd, image, image_size);
386
387 close(fd);
388
389 if (bytesWritten < 0)
390 {
391 lg2::error("[SPI] Failed to write to device");
392 co_return false;
393 }
394
395 if (static_cast<size_t>(bytesWritten) != image_size)
396 {
397 lg2::error("[SPI] Incomplete write to device");
398 co_return false;
399 }
400
401 lg2::info("[SPI] Successfully wrote {NBYTES} bytes to {PATH}", "NBYTES",
402 bytesWritten, "PATH", devPath);
403
404 co_return true;
405}