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