xref: /openbmc/phosphor-bmc-code-mgmt/eeprom-device/eeprom_device.cpp (revision 01ba956256e45b953c78591319a5f25ff4ba618d)
1 #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 
12 PHOSPHOR_LOG2_USING;
13 
14 namespace fs = std::filesystem;
15 namespace MatchRules = sdbusplus::bus::match::rules;
16 namespace State = sdbusplus::common::xyz::openbmc_project::state;
17 
18 static 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)
92 sdbusplus::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         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         }
131 
132         co_await fdio->next();
133 
134         int status;
135         waitpid(pid, &status, 0);
136         close(pipefd[0]);
137 
138         co_return WEXITSTATUS(status);
139     }
140 }
141 
142 static 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 
155 static 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 
163 static 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 
177 EEPROMDevice::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)
199 sdbusplus::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)
289 sdbusplus::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)
337 sdbusplus::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 
374 bool 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)
390 sdbusplus::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)
436 sdbusplus::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     {
449         auto nextResult = co_await hostPower.stateChangedMatch.next<
450             std::string, std::map<std::string, std::variant<std::string>>>();
451 
452         auto [interfaceName, changedProperties] = nextResult;
453 
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 }
475