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