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