blob: fbe65f3cd0218a596765efd373d0ab144a2d0dcd [file] [log] [blame]
Kevin Tung994a77f2024-12-23 17:48:56 +08001#include "eeprom_device.hpp"
2
3#include "common/include/software.hpp"
Kevin Tunga2eb9512025-05-05 18:28:56 +08004#include "common/include/utils.hpp"
Kevin Tung994a77f2024-12-23 17:48:56 +08005
6#include <phosphor-logging/lg2.hpp>
7#include <sdbusplus/async.hpp>
8#include <sdbusplus/message.hpp>
9
10#include <filesystem>
11#include <fstream>
12
13PHOSPHOR_LOG2_USING;
14
15namespace fs = std::filesystem;
16namespace MatchRules = sdbusplus::bus::match::rules;
17namespace State = sdbusplus::common::xyz::openbmc_project::state;
18
19static 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
Kevin Tung994a77f2024-12-23 17:48:56 +080092static 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
105static 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
113static 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
127EEPROMDevice::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
Kevin Tung994a77f2024-12-23 17:48:56 +0800148sdbusplus::async::task<bool> EEPROMDevice::updateDevice(const uint8_t* image,
149 size_t image_size)
Kevin Tung994a77f2024-12-23 17:48:56 +0800150{
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
Kevin Tunga2eb9512025-05-05 18:28:56 +0800175 auto success = co_await writeEEPROM(image, image_size);
Kevin Tung994a77f2024-12-23 17:48:56 +0800176
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
Kevin Tung994a77f2024-12-23 17:48:56 +0800230sdbusplus::async::task<bool> EEPROMDevice::bindEEPROM()
Kevin Tung994a77f2024-12-23 17:48:56 +0800231{
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}
Kevin Tung994a77f2024-12-23 17:48:56 +0800276sdbusplus::async::task<bool> EEPROMDevice::unbindEEPROM()
Kevin Tung994a77f2024-12-23 17:48:56 +0800277{
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
312bool 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
Kevin Tunga2eb9512025-05-05 18:28:56 +0800327sdbusplus::async::task<bool> EEPROMDevice::writeEEPROM(const uint8_t* image,
328 size_t image_size) const
Kevin Tung994a77f2024-12-23 17:48:56 +0800329{
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
Kevin Tunga2eb9512025-05-05 18:28:56 +0800364 auto success = co_await asyncSystem(ctx, cmd);
Kevin Tung994a77f2024-12-23 17:48:56 +0800365
366 std::filesystem::remove(path);
367
Kevin Tunga2eb9512025-05-05 18:28:56 +0800368 co_return success;
Kevin Tung994a77f2024-12-23 17:48:56 +0800369}
370
Kevin Tung994a77f2024-12-23 17:48:56 +0800371sdbusplus::async::task<> EEPROMDevice::processHostStateChange()
Kevin Tung994a77f2024-12-23 17:48:56 +0800372{
373 auto requiredHostState = deviceVersion->getHostStateToQueryVersion();
374
375 if (!requiredHostState)
376 {
377 error("Failed to get required host state");
378 co_return;
379 }
380
381 while (!ctx.stop_requested())
382 {
Alexander Hansen01ba9562025-05-12 16:19:36 +0200383 auto nextResult = co_await hostPower.stateChangedMatch.next<
384 std::string, std::map<std::string, std::variant<std::string>>>();
385
386 auto [interfaceName, changedProperties] = nextResult;
Kevin Tung994a77f2024-12-23 17:48:56 +0800387
388 auto it = changedProperties.find("CurrentHostState");
389 if (it != changedProperties.end())
390 {
391 const auto& currentHostState = std::get<std::string>(it->second);
392
393 if (currentHostState ==
394 State::convertForMessage(*requiredHostState))
395 {
396 debug("Host state {STATE} matches to retrieve the version",
397 "STATE", currentHostState);
398 std::string version = deviceVersion->getVersion();
399 if (!version.empty())
400 {
401 softwareCurrent->setVersion(version);
402 }
403 }
404 }
405 }
406
407 co_return;
408}