blob: 86f59d9baec630980504bff8b932b26c240f26b9 [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
124 sdbusplus::async::fdio pipe_fdio(ctx, pipefd[0]);
125
126 co_await pipe_fdio.next();
127
128 int status;
129 waitpid(pid, &status, 0);
130 close(pipefd[0]);
131
132 co_return WEXITSTATUS(status);
133 }
134}
135
136static std::string getDriverPath(const std::string& chipModel)
137{
138 // Currently, only EEPROM chips with the model AT24 are supported.
139 if (chipModel.find("EEPROM_24C") == std::string::npos)
140 {
141 error("Invalid EEPROM chip model: {CHIP}", "CHIP", chipModel);
142 return "";
143 }
144
145 std::string path = "/sys/bus/i2c/drivers/at24";
146 return std::filesystem::exists(path) ? path : "";
147}
148
149static std::string getI2CDeviceId(const uint16_t bus, const uint8_t address)
150{
151 std::ostringstream oss;
152 oss << bus << "-" << std::hex << std::setfill('0') << std::setw(4)
153 << static_cast<int>(address);
154 return oss.str();
155}
156
157static std::string getEEPROMPath(const uint16_t bus, const uint8_t address)
158{
159 std::string devicePath =
160 "/sys/bus/i2c/devices/" + getI2CDeviceId(bus, address) + "/eeprom";
161
162 if (fs::exists(devicePath) && fs::is_regular_file(devicePath))
163 {
164 debug("Found EEPROM device path: {PATH}", "PATH", devicePath);
165 return devicePath;
166 }
167
168 return "";
169}
170
171EEPROMDevice::EEPROMDevice(
172 sdbusplus::async::context& ctx, const uint16_t bus, const uint8_t address,
173 const std::string& chipModel, const std::vector<std::string>& gpioLines,
174 const std::vector<bool>& gpioPolarities,
175 std::unique_ptr<DeviceVersion> deviceVersion, SoftwareConfig& config,
176 ManagerInf::SoftwareManager* parent) :
177 Device(ctx, config, parent,
178 {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}),
179 bus(bus), address(address), chipModel(chipModel), gpioLines(gpioLines),
180 gpioPolarities(gpioPolarities), deviceVersion(std::move(deviceVersion)),
181 hostPower(ctx)
182{
183 // Some EEPROM devices require the host to be in a specific state before
184 // retrieving the version. To handle this, set up a match to listen for
185 // property changes on the host state. Once the host reaches the required
186 // condition, the version can be updated accordingly.
187 ctx.spawn(processHostStateChange());
188
189 debug("Initialized EEPROM device instance on dbus");
190}
191
192// NOLINTBEGIN(readability-static-accessed-through-instance)
193sdbusplus::async::task<bool> EEPROMDevice::updateDevice(const uint8_t* image,
194 size_t image_size)
195// NOLINTEND(readability-static-accessed-through-instance)
196{
197 std::vector<std::unique_ptr<::gpiod::line_bulk>> lineBulks;
198
199 if (!gpioLines.empty())
200 {
201 debug("Requesting GPIOs to mux EEPROM to BMC");
202
203 lineBulks = requestMuxGPIOs(gpioLines, gpioPolarities, false);
204
205 if (lineBulks.empty())
206 {
207 error("Failed to mux EEPROM to BMC");
208 co_return false;
209 }
210 }
211
212 setUpdateProgress(20);
213
214 if (!co_await bindEEPROM())
215 {
216 co_return false;
217 }
218
219 setUpdateProgress(40);
220
221 const int rc = co_await writeEEPROM(image, image_size);
222 if (rc != 0)
223 {
224 error("Error writing to EEPROM, exit code {CODE}", "CODE", rc);
225 }
226
227 bool success = (rc == 0);
228
229 if (success)
230 {
231 debug("Successfully wrote EEPROM");
232 setUpdateProgress(60);
233 }
234 else
235 {
236 error("Failed to write EEPROM");
237 }
238
239 success = success && co_await unbindEEPROM();
240
241 if (success)
242 {
243 setUpdateProgress(80);
244 }
245
246 if (!gpioLines.empty())
247 {
248 for (auto& lineBulk : lineBulks)
249 {
250 lineBulk->release();
251 }
252
253 debug("Requesting GPIOs to mux EEPROM back to device");
254
255 lineBulks = requestMuxGPIOs(gpioLines, gpioPolarities, true);
256
257 if (lineBulks.empty())
258 {
259 error("Failed to mux EEPROM back to device");
260 co_return false;
261 }
262
263 for (auto& lineBulk : lineBulks)
264 {
265 lineBulk->release();
266 }
267 }
268
269 if (success)
270 {
271 debug("EEPROM device successfully updated");
272 setUpdateProgress(100);
273 }
274 else
275 {
276 error("Failed to update EEPROM device");
277 }
278
279 co_return success;
280}
281
282// NOLINTBEGIN(readability-static-accessed-through-instance)
283sdbusplus::async::task<bool> EEPROMDevice::bindEEPROM()
284// NOLINTEND(readability-static-accessed-through-instance)
285{
286 auto i2cDeviceId = getI2CDeviceId(bus, address);
287
288 debug("Binding {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
289
290 if (isEEPROMBound())
291 {
292 debug("EEPROM was already bound, unbinding it now");
293 if (!co_await unbindEEPROM())
294 {
295 error("Error unbinding EEPROM");
296 co_return false;
297 }
298 }
299
300 auto driverPath = getDriverPath(chipModel);
301 if (driverPath.empty())
302 {
303 error("Driver path not found for chip model: {CHIP}", "CHIP",
304 chipModel);
305 co_return false;
306 }
307
308 auto bindPath = driverPath + "/bind";
309 std::ofstream ofbind(bindPath, std::ofstream::out);
310 if (!ofbind)
311 {
312 error("Failed to open bind file: {PATH}", "PATH", bindPath);
313 co_return false;
314 }
315
316 ofbind << i2cDeviceId;
317 ofbind.close();
318
319 // wait for kernel
320 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
321
322 auto bound = isEEPROMBound();
323 if (!bound)
324 {
325 error("Failed to bind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
326 }
327
328 co_return bound;
329}
330// NOLINTBEGIN(readability-static-accessed-through-instance)
331sdbusplus::async::task<bool> EEPROMDevice::unbindEEPROM()
332// NOLINTEND(readability-static-accessed-through-instance)
333{
334 auto i2cDeviceId = getI2CDeviceId(bus, address);
335
336 debug("Unbinding EEPROM device {I2CDEVICE}", "I2CDEVICE", i2cDeviceId);
337
338 auto driverPath = getDriverPath(chipModel);
339 if (driverPath.empty())
340 {
341 error("Failed to unbind EEPROM, driver path not found for chip {CHIP}",
342 "CHIP", chipModel);
343 co_return false;
344 }
345
346 auto unbindPath = driverPath + "/unbind";
347 std::ofstream ofunbind(unbindPath, std::ofstream::out);
348 if (!ofunbind)
349 {
350 error("Failed to open unbind file: {PATH}", "PATH", unbindPath);
351 co_return false;
352 }
353 ofunbind << i2cDeviceId;
354 ofunbind.close();
355
356 // wait for kernel
357 co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
358
359 auto bound = isEEPROMBound();
360 if (bound)
361 {
362 error("Failed to unbind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
363 }
364
365 co_return !bound;
366}
367
368bool EEPROMDevice::isEEPROMBound()
369{
370 auto driverPath = getDriverPath(chipModel);
371
372 if (driverPath.empty())
373 {
374 error("Failed to check if EEPROM is bound");
375 return false;
376 }
377
378 auto i2cDeviceId = getI2CDeviceId(bus, address);
379
380 return std::filesystem::exists(driverPath + "/" + i2cDeviceId);
381}
382
383// NOLINTBEGIN(readability-static-accessed-through-instance)
384sdbusplus::async::task<int> EEPROMDevice::writeEEPROM(const uint8_t* image,
385 size_t image_size) const
386// NOLINTEND(readability-static-accessed-through-instance)
387{
388 auto eepromPath = getEEPROMPath(bus, address);
389 if (eepromPath.empty())
390 {
391 error("EEPROM file not found for device: {DEVICE}", "DEVICE",
392 getI2CDeviceId(bus, address));
393 co_return -1;
394 }
395 const std::string path =
396 "/tmp/eeprom-image-" +
397 std::to_string(SoftwareInf::Software::getRandomId()) + ".bin";
398
399 int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
400 if (fd < 0)
401 {
402 error("Failed to open file: {PATH}", "PATH", path);
403 co_return -1;
404 }
405
406 const ssize_t bytesWritten = write(fd, image, image_size);
407
408 close(fd);
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 std::string cmd = "dd if=" + path + " of=" + eepromPath + " bs=1k";
419
420 debug("Running {CMD}", "CMD", cmd);
421
422 const int exitCode = co_await asyncSystem(ctx, cmd);
423
424 std::filesystem::remove(path);
425
426 co_return exitCode;
427}
428
429// NOLINTBEGIN(readability-static-accessed-through-instance)
430sdbusplus::async::task<> EEPROMDevice::processHostStateChange()
431// NOLINTEND(readability-static-accessed-through-instance)
432{
433 auto requiredHostState = deviceVersion->getHostStateToQueryVersion();
434
435 if (!requiredHostState)
436 {
437 error("Failed to get required host state");
438 co_return;
439 }
440
441 while (!ctx.stop_requested())
442 {
443 auto [interfaceName, changedProperties] =
444 co_await hostPower.stateChangedMatch
445 .next<std::string,
446 std::map<std::string, std::variant<std::string>>>();
447
448 auto it = changedProperties.find("CurrentHostState");
449 if (it != changedProperties.end())
450 {
451 const auto& currentHostState = std::get<std::string>(it->second);
452
453 if (currentHostState ==
454 State::convertForMessage(*requiredHostState))
455 {
456 debug("Host state {STATE} matches to retrieve the version",
457 "STATE", currentHostState);
458 std::string version = deviceVersion->getVersion();
459 if (!version.empty())
460 {
461 softwareCurrent->setVersion(version);
462 }
463 }
464 }
465 }
466
467 co_return;
468}