xref: /openbmc/phosphor-bmc-code-mgmt/eeprom-device/eeprom_device.cpp (revision 994a77ff25aeb28b2cce7142d081af767f9eb542)
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         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 
136 static 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 
149 static 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 
157 static 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 
171 EEPROMDevice::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)
193 sdbusplus::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)
283 sdbusplus::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)
331 sdbusplus::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 
368 bool 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)
384 sdbusplus::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)
430 sdbusplus::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 }
469