blob: 277c84e1f0f01bd008ae792865b77bdfda4afc5f [file] [log] [blame]
Kevin Tung994a77f2024-12-23 17:48:56 +08001#include "eeprom_device.hpp"
2
3#include "common/include/software.hpp"
4
5#include <phosphor-logging/lg2.hpp>
6#include <sdbusplus/async.hpp>
7#include <sdbusplus/message.hpp>
8
9#include <filesystem>
10#include <fstream>
11
12PHOSPHOR_LOG2_USING;
13
14namespace fs = std::filesystem;
15namespace MatchRules = sdbusplus::bus::match::rules;
16namespace State = sdbusplus::common::xyz::openbmc_project::state;
17
18static std::vector<std::unique_ptr<::gpiod::line_bulk>> requestMuxGPIOs(
19 const std::vector<std::string>& gpioLines,
20 const std::vector<bool>& gpioPolarities, bool inverted)
21{
22 std::map<std::string, std::vector<std::string>> groupLineNames;
23 std::map<std::string, std::vector<int>> groupValues;
24
25 for (size_t i = 0; i < gpioLines.size(); ++i)
26 {
27 auto line = ::gpiod::find_line(gpioLines[i]);
28
29 if (!line)
30 {
31 error("Failed to find GPIO line: {LINE}", "LINE", gpioLines[i]);
32 return {};
33 }
34
35 if (line.is_used())
36 {
37 error("GPIO line {LINE} was still used", "LINE", gpioLines[i]);
38 return {};
39 }
40
41 std::string chipName = line.get_chip().name();
42 groupLineNames[chipName].push_back(gpioLines[i]);
43 groupValues[chipName].push_back(gpioPolarities[i] ^ inverted ? 1 : 0);
44 }
45
46 std::vector<std::unique_ptr<::gpiod::line_bulk>> lineBulks;
47 ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT,
48 0};
49
50 for (auto& [chipName, lineNames] : groupLineNames)
51 {
52 ::gpiod::chip chip(chipName);
53 std::vector<::gpiod::line> lines;
54
55 for (size_t i = 0; i < lineNames.size(); ++i)
56 {
57 const auto& name = lineNames[i];
58 auto line = chip.find_line(name);
59
60 if (!line)
61 {
62 error("Failed to get {LINE} from chip {CHIP}", "LINE", name,
63 "CHIP", chipName);
64 return {};
65 }
66
67 debug("Requesting chip {CHIP}, GPIO line {LINE} to {VALUE}", "CHIP",
68 chip.name(), "LINE", line.name(), "VALUE",
69 groupValues[chipName][i]);
70
71 lines.push_back(std::move(line));
72 }
73
74 auto lineBulk = std::make_unique<::gpiod::line_bulk>(lines);
75
76 if (!lineBulk)
77 {
78 error("Failed to create line bulk for chip={CHIP}", "CHIP",
79 chipName);
80 return {};
81 }
82
83 lineBulk->request(config, groupValues[chipName]);
84
85 lineBulks.push_back(std::move(lineBulk));
86 }
87
88 return lineBulks;
89}
90
91// NOLINTBEGIN(readability-static-accessed-through-instance)
92sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx,
93 const std::string& cmd)
94// NOLINTEND(readability-static-accessed-through-instance)
95{
96 int pipefd[2];
97 if (pipe(pipefd) == -1)
98 {
99 perror("pipe");
100 co_return -1;
101 }
102
103 pid_t pid = fork();
104 if (pid == -1)
105 {
106 perror("fork");
107 close(pipefd[0]);
108 close(pipefd[1]);
109 co_return -1;
110 }
111 else if (pid == 0)
112 {
113 close(pipefd[0]);
114 int exitCode = std::system(cmd.c_str());
115
116 ssize_t status = write(pipefd[1], &exitCode, sizeof(exitCode));
117 close(pipefd[1]);
118 exit((status == sizeof(exitCode)) ? 0 : 1);
119 }
120 else
121 {
122 close(pipefd[1]);
123
Alexander Hansen01ba9562025-05-12 16:19:36 +0200124 auto fdio = std::make_unique<sdbusplus::async::fdio>(ctx, pipefd[0]);
125 if (!fdio)
126 {
127 perror("fdio creation failed");
128 close(pipefd[0]);
129 co_return -1;
130 }
Kevin Tung994a77f2024-12-23 17:48:56 +0800131
Alexander Hansen01ba9562025-05-12 16:19:36 +0200132 co_await fdio->next();
Kevin Tung994a77f2024-12-23 17:48:56 +0800133
134 int status;
135 waitpid(pid, &status, 0);
136 close(pipefd[0]);
137
138 co_return WEXITSTATUS(status);
139 }
140}
141
142static std::string getDriverPath(const std::string& chipModel)
143{
144 // Currently, only EEPROM chips with the model AT24 are supported.
145 if (chipModel.find("EEPROM_24C") == std::string::npos)
146 {
147 error("Invalid EEPROM chip model: {CHIP}", "CHIP", chipModel);
148 return "";
149 }
150
151 std::string path = "/sys/bus/i2c/drivers/at24";
152 return std::filesystem::exists(path) ? path : "";
153}
154
155static std::string getI2CDeviceId(const uint16_t bus, const uint8_t address)
156{
157 std::ostringstream oss;
158 oss << bus << "-" << std::hex << std::setfill('0') << std::setw(4)
159 << static_cast<int>(address);
160 return oss.str();
161}
162
163static std::string getEEPROMPath(const uint16_t bus, const uint8_t address)
164{
165 std::string devicePath =
166 "/sys/bus/i2c/devices/" + getI2CDeviceId(bus, address) + "/eeprom";
167
168 if (fs::exists(devicePath) && fs::is_regular_file(devicePath))
169 {
170 debug("Found EEPROM device path: {PATH}", "PATH", devicePath);
171 return devicePath;
172 }
173
174 return "";
175}
176
177EEPROMDevice::EEPROMDevice(
178 sdbusplus::async::context& ctx, const uint16_t bus, const uint8_t address,
179 const std::string& chipModel, const std::vector<std::string>& gpioLines,
180 const std::vector<bool>& gpioPolarities,
181 std::unique_ptr<DeviceVersion> deviceVersion, SoftwareConfig& config,
182 ManagerInf::SoftwareManager* parent) :
183 Device(ctx, config, parent,
184 {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}),
185 bus(bus), address(address), chipModel(chipModel), gpioLines(gpioLines),
186 gpioPolarities(gpioPolarities), deviceVersion(std::move(deviceVersion)),
187 hostPower(ctx)
188{
189 // Some EEPROM devices require the host to be in a specific state before
190 // retrieving the version. To handle this, set up a match to listen for
191 // property changes on the host state. Once the host reaches the required
192 // condition, the version can be updated accordingly.
193 ctx.spawn(processHostStateChange());
194
195 debug("Initialized EEPROM device instance on dbus");
196}
197
198// NOLINTBEGIN(readability-static-accessed-through-instance)
199sdbusplus::async::task<bool> EEPROMDevice::updateDevice(const uint8_t* image,
200 size_t image_size)
201// NOLINTEND(readability-static-accessed-through-instance)
202{
203 std::vector<std::unique_ptr<::gpiod::line_bulk>> lineBulks;
204
205 if (!gpioLines.empty())
206 {
207 debug("Requesting GPIOs to mux EEPROM to BMC");
208
209 lineBulks = requestMuxGPIOs(gpioLines, gpioPolarities, false);
210
211 if (lineBulks.empty())
212 {
213 error("Failed to mux EEPROM to BMC");
214 co_return false;
215 }
216 }
217
218 setUpdateProgress(20);
219
220 if (!co_await bindEEPROM())
221 {
222 co_return false;
223 }
224
225 setUpdateProgress(40);
226
227 const int rc = co_await writeEEPROM(image, image_size);
228 if (rc != 0)
229 {
230 error("Error writing to EEPROM, exit code {CODE}", "CODE", rc);
231 }
232
233 bool success = (rc == 0);
234
235 if (success)
236 {
237 debug("Successfully wrote EEPROM");
238 setUpdateProgress(60);
239 }
240 else
241 {
242 error("Failed to write EEPROM");
243 }
244
245 success = success && co_await unbindEEPROM();
246
247 if (success)
248 {
249 setUpdateProgress(80);
250 }
251
252 if (!gpioLines.empty())
253 {
254 for (auto& lineBulk : lineBulks)
255 {
256 lineBulk->release();
257 }
258
259 debug("Requesting GPIOs to mux EEPROM back to device");
260
261 lineBulks = requestMuxGPIOs(gpioLines, gpioPolarities, true);
262
263 if (lineBulks.empty())
264 {
265 error("Failed to mux EEPROM back to device");
266 co_return false;
267 }
268
269 for (auto& lineBulk : lineBulks)
270 {
271 lineBulk->release();
272 }
273 }
274
275 if (success)
276 {
277 debug("EEPROM device successfully updated");
278 setUpdateProgress(100);
279 }
280 else
281 {
282 error("Failed to update EEPROM device");
283 }
284
285 co_return success;
286}
287
288// NOLINTBEGIN(readability-static-accessed-through-instance)
289sdbusplus::async::task<bool> EEPROMDevice::bindEEPROM()
290// NOLINTEND(readability-static-accessed-through-instance)
291{
292 auto i2cDeviceId = getI2CDeviceId(bus, address);
293
294 debug("Binding {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
295
296 if (isEEPROMBound())
297 {
298 debug("EEPROM was already bound, unbinding it now");
299 if (!co_await unbindEEPROM())
300 {
301 error("Error unbinding EEPROM");
302 co_return false;
303 }
304 }
305
306 auto driverPath = getDriverPath(chipModel);
307 if (driverPath.empty())
308 {
309 error("Driver path not found for chip model: {CHIP}", "CHIP",
310 chipModel);
311 co_return false;
312 }
313
314 auto bindPath = driverPath + "/bind";
315 std::ofstream ofbind(bindPath, std::ofstream::out);
316 if (!ofbind)
317 {
318 error("Failed to open bind file: {PATH}", "PATH", bindPath);
319 co_return false;
320 }
321
322 ofbind << i2cDeviceId;
323 ofbind.close();
324
325 // wait for kernel
326 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
327
328 auto bound = isEEPROMBound();
329 if (!bound)
330 {
331 error("Failed to bind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
332 }
333
334 co_return bound;
335}
336// NOLINTBEGIN(readability-static-accessed-through-instance)
337sdbusplus::async::task<bool> EEPROMDevice::unbindEEPROM()
338// NOLINTEND(readability-static-accessed-through-instance)
339{
340 auto i2cDeviceId = getI2CDeviceId(bus, address);
341
342 debug("Unbinding EEPROM device {I2CDEVICE}", "I2CDEVICE", i2cDeviceId);
343
344 auto driverPath = getDriverPath(chipModel);
345 if (driverPath.empty())
346 {
347 error("Failed to unbind EEPROM, driver path not found for chip {CHIP}",
348 "CHIP", chipModel);
349 co_return false;
350 }
351
352 auto unbindPath = driverPath + "/unbind";
353 std::ofstream ofunbind(unbindPath, std::ofstream::out);
354 if (!ofunbind)
355 {
356 error("Failed to open unbind file: {PATH}", "PATH", unbindPath);
357 co_return false;
358 }
359 ofunbind << i2cDeviceId;
360 ofunbind.close();
361
362 // wait for kernel
363 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
364
365 auto bound = isEEPROMBound();
366 if (bound)
367 {
368 error("Failed to unbind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
369 }
370
371 co_return !bound;
372}
373
374bool EEPROMDevice::isEEPROMBound()
375{
376 auto driverPath = getDriverPath(chipModel);
377
378 if (driverPath.empty())
379 {
380 error("Failed to check if EEPROM is bound");
381 return false;
382 }
383
384 auto i2cDeviceId = getI2CDeviceId(bus, address);
385
386 return std::filesystem::exists(driverPath + "/" + i2cDeviceId);
387}
388
389// NOLINTBEGIN(readability-static-accessed-through-instance)
390sdbusplus::async::task<int> EEPROMDevice::writeEEPROM(const uint8_t* image,
391 size_t image_size) const
392// NOLINTEND(readability-static-accessed-through-instance)
393{
394 auto eepromPath = getEEPROMPath(bus, address);
395 if (eepromPath.empty())
396 {
397 error("EEPROM file not found for device: {DEVICE}", "DEVICE",
398 getI2CDeviceId(bus, address));
399 co_return -1;
400 }
401 const std::string path =
402 "/tmp/eeprom-image-" +
403 std::to_string(SoftwareInf::Software::getRandomId()) + ".bin";
404
405 int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
406 if (fd < 0)
407 {
408 error("Failed to open file: {PATH}", "PATH", path);
409 co_return -1;
410 }
411
412 const ssize_t bytesWritten = write(fd, image, image_size);
413
414 close(fd);
415
416 if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
417 {
418 error("Failed to write image to file");
419 co_return -1;
420 }
421
422 debug("Wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
423
424 std::string cmd = "dd if=" + path + " of=" + eepromPath + " bs=1k";
425
426 debug("Running {CMD}", "CMD", cmd);
427
428 const int exitCode = co_await asyncSystem(ctx, cmd);
429
430 std::filesystem::remove(path);
431
432 co_return exitCode;
433}
434
435// NOLINTBEGIN(readability-static-accessed-through-instance)
436sdbusplus::async::task<> EEPROMDevice::processHostStateChange()
437// NOLINTEND(readability-static-accessed-through-instance)
438{
439 auto requiredHostState = deviceVersion->getHostStateToQueryVersion();
440
441 if (!requiredHostState)
442 {
443 error("Failed to get required host state");
444 co_return;
445 }
446
447 while (!ctx.stop_requested())
448 {
Alexander Hansen01ba9562025-05-12 16:19:36 +0200449 auto nextResult = co_await hostPower.stateChangedMatch.next<
450 std::string, std::map<std::string, std::variant<std::string>>>();
451
452 auto [interfaceName, changedProperties] = nextResult;
Kevin Tung994a77f2024-12-23 17:48:56 +0800453
454 auto it = changedProperties.find("CurrentHostState");
455 if (it != changedProperties.end())
456 {
457 const auto& currentHostState = std::get<std::string>(it->second);
458
459 if (currentHostState ==
460 State::convertForMessage(*requiredHostState))
461 {
462 debug("Host state {STATE} matches to retrieve the version",
463 "STATE", currentHostState);
464 std::string version = deviceVersion->getVersion();
465 if (!version.empty())
466 {
467 softwareCurrent->setVersion(version);
468 }
469 }
470 }
471 }
472
473 co_return;
474}