blob: ca27c28141995ebc33c6f348be1434400d0731a7 [file] [log] [blame]
Kevin Tung994a77f2024-12-23 17:48:56 +08001#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
12PHOSPHOR_LOG2_USING;
13
14namespace fs = std::filesystem;
15namespace MatchRules = sdbusplus::bus::match::rules;
16namespace State = sdbusplus::common::xyz::openbmc_project::state;
17
18static 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
Kevin Tung994a77f2024-12-23 17:48:56 +080091sdbusplus::async::task<int> asyncSystem(sdbusplus::async::context& ctx,
92 const std::string& cmd)
Kevin Tung994a77f2024-12-23 17:48:56 +080093{
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
Alexander Hansen01ba9562025-05-12 16:19:36 +0200122 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 }
Kevin Tung994a77f2024-12-23 17:48:56 +0800129
Alexander Hansen01ba9562025-05-12 16:19:36 +0200130 co_await fdio->next();
Kevin Tung994a77f2024-12-23 17:48:56 +0800131
132 int status;
133 waitpid(pid, &status, 0);
134 close(pipefd[0]);
135
136 co_return WEXITSTATUS(status);
137 }
138}
139
140static 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
153static 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
161static 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
175EEPROMDevice::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
Kevin Tung994a77f2024-12-23 17:48:56 +0800196sdbusplus::async::task<bool> EEPROMDevice::updateDevice(const uint8_t* image,
197 size_t image_size)
Kevin Tung994a77f2024-12-23 17:48:56 +0800198{
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
Kevin Tung994a77f2024-12-23 17:48:56 +0800284sdbusplus::async::task<bool> EEPROMDevice::bindEEPROM()
Kevin Tung994a77f2024-12-23 17:48:56 +0800285{
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}
Kevin Tung994a77f2024-12-23 17:48:56 +0800330sdbusplus::async::task<bool> EEPROMDevice::unbindEEPROM()
Kevin Tung994a77f2024-12-23 17:48:56 +0800331{
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
366bool 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
Kevin Tung994a77f2024-12-23 17:48:56 +0800381sdbusplus::async::task<int> EEPROMDevice::writeEEPROM(const uint8_t* image,
382 size_t image_size) const
Kevin Tung994a77f2024-12-23 17:48:56 +0800383{
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
Kevin Tung994a77f2024-12-23 17:48:56 +0800425sdbusplus::async::task<> EEPROMDevice::processHostStateChange()
Kevin Tung994a77f2024-12-23 17:48:56 +0800426{
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 {
Alexander Hansen01ba9562025-05-12 16:19:36 +0200437 auto nextResult = co_await hostPower.stateChangedMatch.next<
438 std::string, std::map<std::string, std::variant<std::string>>>();
439
440 auto [interfaceName, changedProperties] = nextResult;
Kevin Tung994a77f2024-12-23 17:48:56 +0800441
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}