xref: /openbmc/phosphor-bmc-code-mgmt/eeprom-device/eeprom_device.cpp (revision e634411ba7c22d18ae01a0b03ce4f7b881c38fcc)
1 #include "eeprom_device.hpp"
2 
3 #include "common/include/software.hpp"
4 #include "common/include/utils.hpp"
5 
6 #include <gpio_controller.hpp>
7 #include <phosphor-logging/lg2.hpp>
8 #include <sdbusplus/async.hpp>
9 #include <sdbusplus/message.hpp>
10 
11 #include <filesystem>
12 #include <fstream>
13 
14 PHOSPHOR_LOG2_USING;
15 
16 namespace fs = std::filesystem;
17 namespace MatchRules = sdbusplus::bus::match::rules;
18 namespace State = sdbusplus::common::xyz::openbmc_project::state;
19 
getDriverPath(const std::string & chipModel)20 static std::string getDriverPath(const std::string& chipModel)
21 {
22     // Currently, only EEPROM chips with the model AT24 are supported.
23     if (chipModel.find("EEPROM_24C") == std::string::npos)
24     {
25         error("Invalid EEPROM chip model: {CHIP}", "CHIP", chipModel);
26         return "";
27     }
28 
29     std::string path = "/sys/bus/i2c/drivers/at24";
30     return std::filesystem::exists(path) ? path : "";
31 }
32 
getI2CDeviceId(const uint16_t bus,const uint8_t address)33 static std::string getI2CDeviceId(const uint16_t bus, const uint8_t address)
34 {
35     std::ostringstream oss;
36     oss << bus << "-" << std::hex << std::setfill('0') << std::setw(4)
37         << static_cast<int>(address);
38     return oss.str();
39 }
40 
getEEPROMPath(const uint16_t bus,const uint8_t address)41 static std::string getEEPROMPath(const uint16_t bus, const uint8_t address)
42 {
43     std::string devicePath =
44         "/sys/bus/i2c/devices/" + getI2CDeviceId(bus, address) + "/eeprom";
45 
46     if (fs::exists(devicePath) && fs::is_regular_file(devicePath))
47     {
48         debug("Found EEPROM device path: {PATH}", "PATH", devicePath);
49         return devicePath;
50     }
51 
52     return "";
53 }
54 
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)55 EEPROMDevice::EEPROMDevice(
56     sdbusplus::async::context& ctx, const uint16_t bus, const uint8_t address,
57     const std::string& chipModel, const std::vector<std::string>& gpioLines,
58     const std::vector<bool>& gpioPolarities,
59     std::unique_ptr<DeviceVersion> deviceVersion, SoftwareConfig& config,
60     ManagerInf::SoftwareManager* parent) :
61     Device(ctx, config, parent,
62            {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}),
63     bus(bus), address(address), chipModel(chipModel), gpioLines(gpioLines),
64     gpioPolarities(gpioPolarities), deviceVersion(std::move(deviceVersion)),
65     hostPower(ctx)
66 {
67     // Some EEPROM devices require the host to be in a specific state before
68     // retrieving the version. To handle this, set up a match to listen for
69     // property changes on the host state. Once the host reaches the required
70     // condition, the version can be updated accordingly.
71     ctx.spawn(processHostStateChange());
72 
73     debug("Initialized EEPROM device instance on dbus");
74 }
75 
updateDevice(const uint8_t * image,size_t image_size)76 sdbusplus::async::task<bool> EEPROMDevice::updateDevice(const uint8_t* image,
77                                                         size_t image_size)
78 {
79     GPIOGroup muxGPIO(gpioLines, gpioPolarities);
80     std::optional<ScopedBmcMux> guard;
81     if (!gpioLines.empty())
82     {
83         try
84         {
85             guard.emplace(muxGPIO);
86         }
87         catch (const std::exception& e)
88         {
89             error("Failed to mux GPIOs to BMC: {ERROR}", "ERROR", e.what());
90             co_return false;
91         }
92     }
93 
94     setUpdateProgress(20);
95 
96     if (!co_await bindEEPROM())
97     {
98         co_return false;
99     }
100 
101     setUpdateProgress(40);
102 
103     auto success = co_await writeEEPROM(image, image_size);
104 
105     if (success)
106     {
107         debug("Successfully wrote EEPROM");
108         setUpdateProgress(60);
109     }
110     else
111     {
112         error("Failed to write EEPROM");
113     }
114 
115     success = success && co_await unbindEEPROM();
116 
117     if (success)
118     {
119         setUpdateProgress(80);
120     }
121 
122     if (success)
123     {
124         debug("EEPROM device successfully updated");
125         setUpdateProgress(100);
126     }
127     else
128     {
129         error("Failed to update EEPROM device");
130     }
131 
132     co_return success;
133 }
134 
bindEEPROM()135 sdbusplus::async::task<bool> EEPROMDevice::bindEEPROM()
136 {
137     auto i2cDeviceId = getI2CDeviceId(bus, address);
138 
139     debug("Binding {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
140 
141     if (isEEPROMBound())
142     {
143         debug("EEPROM was already bound, unbinding it now");
144         if (!co_await unbindEEPROM())
145         {
146             error("Error unbinding EEPROM");
147             co_return false;
148         }
149     }
150 
151     auto driverPath = getDriverPath(chipModel);
152     if (driverPath.empty())
153     {
154         error("Driver path not found for chip model: {CHIP}", "CHIP",
155               chipModel);
156         co_return false;
157     }
158 
159     auto bindPath = driverPath + "/bind";
160     std::ofstream ofbind(bindPath, std::ofstream::out);
161     if (!ofbind)
162     {
163         error("Failed to open bind file: {PATH}", "PATH", bindPath);
164         co_return false;
165     }
166 
167     ofbind << i2cDeviceId;
168     ofbind.close();
169 
170     // wait for kernel
171     co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
172 
173     auto bound = isEEPROMBound();
174     if (!bound)
175     {
176         error("Failed to bind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
177     }
178 
179     co_return bound;
180 }
unbindEEPROM()181 sdbusplus::async::task<bool> EEPROMDevice::unbindEEPROM()
182 {
183     auto i2cDeviceId = getI2CDeviceId(bus, address);
184 
185     debug("Unbinding EEPROM device {I2CDEVICE}", "I2CDEVICE", i2cDeviceId);
186 
187     auto driverPath = getDriverPath(chipModel);
188     if (driverPath.empty())
189     {
190         error("Failed to unbind EEPROM, driver path not found for chip {CHIP}",
191               "CHIP", chipModel);
192         co_return false;
193     }
194 
195     auto unbindPath = driverPath + "/unbind";
196     std::ofstream ofunbind(unbindPath, std::ofstream::out);
197     if (!ofunbind)
198     {
199         error("Failed to open unbind file: {PATH}", "PATH", unbindPath);
200         co_return false;
201     }
202     ofunbind << i2cDeviceId;
203     ofunbind.close();
204 
205     // wait for kernel
206     co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2));
207 
208     auto bound = isEEPROMBound();
209     if (bound)
210     {
211         error("Failed to unbind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId);
212     }
213 
214     co_return !bound;
215 }
216 
isEEPROMBound()217 bool EEPROMDevice::isEEPROMBound()
218 {
219     auto driverPath = getDriverPath(chipModel);
220 
221     if (driverPath.empty())
222     {
223         error("Failed to check if EEPROM is bound");
224         return false;
225     }
226 
227     auto i2cDeviceId = getI2CDeviceId(bus, address);
228 
229     return std::filesystem::exists(driverPath + "/" + i2cDeviceId);
230 }
231 
writeEEPROM(const uint8_t * image,size_t image_size) const232 sdbusplus::async::task<bool> EEPROMDevice::writeEEPROM(const uint8_t* image,
233                                                        size_t image_size) const
234 {
235     auto eepromPath = getEEPROMPath(bus, address);
236     if (eepromPath.empty())
237     {
238         error("EEPROM file not found for device: {DEVICE}", "DEVICE",
239               getI2CDeviceId(bus, address));
240         co_return -1;
241     }
242     const std::string path =
243         "/tmp/eeprom-image-" +
244         std::to_string(SoftwareInf::Software::getRandomId()) + ".bin";
245 
246     int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
247     if (fd < 0)
248     {
249         error("Failed to open file: {PATH}", "PATH", path);
250         co_return -1;
251     }
252 
253     const ssize_t bytesWritten = write(fd, image, image_size);
254 
255     close(fd);
256 
257     if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size)
258     {
259         error("Failed to write image to file");
260         co_return -1;
261     }
262 
263     debug("Wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path);
264 
265     std::string cmd = "dd if=" + path + " of=" + eepromPath + " bs=1k";
266 
267     debug("Running {CMD}", "CMD", cmd);
268 
269     auto success = co_await asyncSystem(ctx, cmd);
270 
271     std::filesystem::remove(path);
272 
273     co_return success;
274 }
275 
processHostStateChange()276 sdbusplus::async::task<> EEPROMDevice::processHostStateChange()
277 {
278     constexpr int maxRetries = 15;
279     auto requiredHostState = deviceVersion->getHostStateToQueryVersion();
280 
281     if (!requiredHostState)
282     {
283         error("Failed to get required host state");
284         co_return;
285     }
286 
287     while (!ctx.stop_requested())
288     {
289         auto nextResult = co_await hostPower.stateChangedMatch.next<
290             std::string, std::map<std::string, std::variant<std::string>>>();
291 
292         const auto& [interfaceName, changedProperties] = nextResult;
293 
294         auto it = changedProperties.find("CurrentHostState");
295         if (it != changedProperties.end())
296         {
297             const auto& currentHostState = std::get<std::string>(it->second);
298 
299             if (currentHostState ==
300                 State::convertForMessage(*requiredHostState))
301             {
302                 auto isDeviceReady = false;
303                 debug("Host state {STATE} matches to retrieve the version",
304                       "STATE", currentHostState);
305                 for (int i = 0; i < maxRetries; ++i)
306                 {
307                     isDeviceReady = deviceVersion->isDeviceReady();
308                     if (isDeviceReady)
309                     {
310                         debug("Device version is ready");
311                         break;
312                     }
313                     co_await sdbusplus::async::sleep_for(
314                         ctx, std::chrono::seconds(2));
315                 }
316                 std::string version = deviceVersion->getVersion();
317                 if (isDeviceReady && !version.empty())
318                 {
319                     softwareCurrent->setVersion(
320                         version,
321                         SoftwareInf::SoftwareVersion::VersionPurpose::Other);
322                 }
323             }
324         }
325     }
326 
327     co_return;
328 }
329