1994a77ffSKevin Tung #include "eeprom_device.hpp" 2994a77ffSKevin Tung 3994a77ffSKevin Tung #include "common/include/software.hpp" 4*a2eb951fSKevin Tung #include "common/include/utils.hpp" 5994a77ffSKevin Tung 6994a77ffSKevin Tung #include <phosphor-logging/lg2.hpp> 7994a77ffSKevin Tung #include <sdbusplus/async.hpp> 8994a77ffSKevin Tung #include <sdbusplus/message.hpp> 9994a77ffSKevin Tung 10994a77ffSKevin Tung #include <filesystem> 11994a77ffSKevin Tung #include <fstream> 12994a77ffSKevin Tung 13994a77ffSKevin Tung PHOSPHOR_LOG2_USING; 14994a77ffSKevin Tung 15994a77ffSKevin Tung namespace fs = std::filesystem; 16994a77ffSKevin Tung namespace MatchRules = sdbusplus::bus::match::rules; 17994a77ffSKevin Tung namespace State = sdbusplus::common::xyz::openbmc_project::state; requestMuxGPIOs(const std::vector<std::string> & gpioLines,const std::vector<bool> & gpioPolarities,bool inverted)18994a77ffSKevin Tung 19994a77ffSKevin Tung static std::vector<std::unique_ptr<::gpiod::line_bulk>> requestMuxGPIOs( 20994a77ffSKevin Tung const std::vector<std::string>& gpioLines, 21994a77ffSKevin Tung const std::vector<bool>& gpioPolarities, bool inverted) 22994a77ffSKevin Tung { 23994a77ffSKevin Tung std::map<std::string, std::vector<std::string>> groupLineNames; 24994a77ffSKevin Tung std::map<std::string, std::vector<int>> groupValues; 25994a77ffSKevin Tung 26994a77ffSKevin Tung for (size_t i = 0; i < gpioLines.size(); ++i) 27994a77ffSKevin Tung { 28994a77ffSKevin Tung auto line = ::gpiod::find_line(gpioLines[i]); 29994a77ffSKevin Tung 30994a77ffSKevin Tung if (!line) 31994a77ffSKevin Tung { 32994a77ffSKevin Tung error("Failed to find GPIO line: {LINE}", "LINE", gpioLines[i]); 33994a77ffSKevin Tung return {}; 34994a77ffSKevin Tung } 35994a77ffSKevin Tung 36994a77ffSKevin Tung if (line.is_used()) 37994a77ffSKevin Tung { 38994a77ffSKevin Tung error("GPIO line {LINE} was still used", "LINE", gpioLines[i]); 39994a77ffSKevin Tung return {}; 40994a77ffSKevin Tung } 41994a77ffSKevin Tung 42994a77ffSKevin Tung std::string chipName = line.get_chip().name(); 43994a77ffSKevin Tung groupLineNames[chipName].push_back(gpioLines[i]); 44994a77ffSKevin Tung groupValues[chipName].push_back(gpioPolarities[i] ^ inverted ? 1 : 0); 45994a77ffSKevin Tung } 46994a77ffSKevin Tung 47994a77ffSKevin Tung std::vector<std::unique_ptr<::gpiod::line_bulk>> lineBulks; 48994a77ffSKevin Tung ::gpiod::line_request config{"", ::gpiod::line_request::DIRECTION_OUTPUT, 49994a77ffSKevin Tung 0}; 50994a77ffSKevin Tung 51994a77ffSKevin Tung for (auto& [chipName, lineNames] : groupLineNames) 52994a77ffSKevin Tung { 53994a77ffSKevin Tung ::gpiod::chip chip(chipName); 54994a77ffSKevin Tung std::vector<::gpiod::line> lines; 55994a77ffSKevin Tung 56994a77ffSKevin Tung for (size_t i = 0; i < lineNames.size(); ++i) 57994a77ffSKevin Tung { 58994a77ffSKevin Tung const auto& name = lineNames[i]; 59994a77ffSKevin Tung auto line = chip.find_line(name); 60994a77ffSKevin Tung 61994a77ffSKevin Tung if (!line) 62994a77ffSKevin Tung { 63994a77ffSKevin Tung error("Failed to get {LINE} from chip {CHIP}", "LINE", name, 64994a77ffSKevin Tung "CHIP", chipName); 65994a77ffSKevin Tung return {}; 66994a77ffSKevin Tung } 67994a77ffSKevin Tung 68994a77ffSKevin Tung debug("Requesting chip {CHIP}, GPIO line {LINE} to {VALUE}", "CHIP", 69994a77ffSKevin Tung chip.name(), "LINE", line.name(), "VALUE", 70994a77ffSKevin Tung groupValues[chipName][i]); 71994a77ffSKevin Tung 72994a77ffSKevin Tung lines.push_back(std::move(line)); 73994a77ffSKevin Tung } 74994a77ffSKevin Tung 75994a77ffSKevin Tung auto lineBulk = std::make_unique<::gpiod::line_bulk>(lines); 76994a77ffSKevin Tung 77994a77ffSKevin Tung if (!lineBulk) 78994a77ffSKevin Tung { 79994a77ffSKevin Tung error("Failed to create line bulk for chip={CHIP}", "CHIP", 80994a77ffSKevin Tung chipName); 81994a77ffSKevin Tung return {}; 82994a77ffSKevin Tung } 83994a77ffSKevin Tung 84994a77ffSKevin Tung lineBulk->request(config, groupValues[chipName]); 85994a77ffSKevin Tung 86994a77ffSKevin Tung lineBulks.push_back(std::move(lineBulk)); 87994a77ffSKevin Tung } 88994a77ffSKevin Tung 89994a77ffSKevin Tung return lineBulks; 90994a77ffSKevin Tung } asyncSystem(sdbusplus::async::context & ctx,const std::string & cmd)91994a77ffSKevin Tung 92994a77ffSKevin Tung static std::string getDriverPath(const std::string& chipModel) 93994a77ffSKevin Tung { 94994a77ffSKevin Tung // Currently, only EEPROM chips with the model AT24 are supported. 95994a77ffSKevin Tung if (chipModel.find("EEPROM_24C") == std::string::npos) 96994a77ffSKevin Tung { 97994a77ffSKevin Tung error("Invalid EEPROM chip model: {CHIP}", "CHIP", chipModel); 98994a77ffSKevin Tung return ""; 99994a77ffSKevin Tung } 100994a77ffSKevin Tung 101994a77ffSKevin Tung std::string path = "/sys/bus/i2c/drivers/at24"; 102994a77ffSKevin Tung return std::filesystem::exists(path) ? path : ""; 103994a77ffSKevin Tung } 104994a77ffSKevin Tung 105994a77ffSKevin Tung static std::string getI2CDeviceId(const uint16_t bus, const uint8_t address) 106994a77ffSKevin Tung { 107994a77ffSKevin Tung std::ostringstream oss; 108994a77ffSKevin Tung oss << bus << "-" << std::hex << std::setfill('0') << std::setw(4) 109994a77ffSKevin Tung << static_cast<int>(address); 110994a77ffSKevin Tung return oss.str(); 111994a77ffSKevin Tung } 112994a77ffSKevin Tung 113994a77ffSKevin Tung static std::string getEEPROMPath(const uint16_t bus, const uint8_t address) 114994a77ffSKevin Tung { 115994a77ffSKevin Tung std::string devicePath = 116994a77ffSKevin Tung "/sys/bus/i2c/devices/" + getI2CDeviceId(bus, address) + "/eeprom"; 117994a77ffSKevin Tung 118994a77ffSKevin Tung if (fs::exists(devicePath) && fs::is_regular_file(devicePath)) 119994a77ffSKevin Tung { 120994a77ffSKevin Tung debug("Found EEPROM device path: {PATH}", "PATH", devicePath); 121994a77ffSKevin Tung return devicePath; 122994a77ffSKevin Tung } 123994a77ffSKevin Tung 124994a77ffSKevin Tung return ""; 125994a77ffSKevin Tung } 126994a77ffSKevin Tung 127994a77ffSKevin Tung EEPROMDevice::EEPROMDevice( 128994a77ffSKevin Tung sdbusplus::async::context& ctx, const uint16_t bus, const uint8_t address, 129994a77ffSKevin Tung const std::string& chipModel, const std::vector<std::string>& gpioLines, 130994a77ffSKevin Tung const std::vector<bool>& gpioPolarities, 131994a77ffSKevin Tung std::unique_ptr<DeviceVersion> deviceVersion, SoftwareConfig& config, 132994a77ffSKevin Tung ManagerInf::SoftwareManager* parent) : 133994a77ffSKevin Tung Device(ctx, config, parent, 134994a77ffSKevin Tung {RequestedApplyTimes::Immediate, RequestedApplyTimes::OnReset}), 135994a77ffSKevin Tung bus(bus), address(address), chipModel(chipModel), gpioLines(gpioLines), 136994a77ffSKevin Tung gpioPolarities(gpioPolarities), deviceVersion(std::move(deviceVersion)), 137994a77ffSKevin Tung hostPower(ctx) 138994a77ffSKevin Tung { 139994a77ffSKevin Tung // Some EEPROM devices require the host to be in a specific state before getDriverPath(const std::string & chipModel)140994a77ffSKevin Tung // retrieving the version. To handle this, set up a match to listen for 141994a77ffSKevin Tung // property changes on the host state. Once the host reaches the required 142994a77ffSKevin Tung // condition, the version can be updated accordingly. 143994a77ffSKevin Tung ctx.spawn(processHostStateChange()); 144994a77ffSKevin Tung 145994a77ffSKevin Tung debug("Initialized EEPROM device instance on dbus"); 146994a77ffSKevin Tung } 147994a77ffSKevin Tung 148994a77ffSKevin Tung sdbusplus::async::task<bool> EEPROMDevice::updateDevice(const uint8_t* image, 149994a77ffSKevin Tung size_t image_size) 150994a77ffSKevin Tung { 151994a77ffSKevin Tung std::vector<std::unique_ptr<::gpiod::line_bulk>> lineBulks; 152994a77ffSKevin Tung getI2CDeviceId(const uint16_t bus,const uint8_t address)153994a77ffSKevin Tung if (!gpioLines.empty()) 154994a77ffSKevin Tung { 155994a77ffSKevin Tung debug("Requesting GPIOs to mux EEPROM to BMC"); 156994a77ffSKevin Tung 157994a77ffSKevin Tung lineBulks = requestMuxGPIOs(gpioLines, gpioPolarities, false); 158994a77ffSKevin Tung 159994a77ffSKevin Tung if (lineBulks.empty()) 160994a77ffSKevin Tung { 161994a77ffSKevin Tung error("Failed to mux EEPROM to BMC"); 162994a77ffSKevin Tung co_return false; 163994a77ffSKevin Tung } 164994a77ffSKevin Tung } 165994a77ffSKevin Tung 166994a77ffSKevin Tung setUpdateProgress(20); 167994a77ffSKevin Tung 168994a77ffSKevin Tung if (!co_await bindEEPROM()) 169994a77ffSKevin Tung { 170994a77ffSKevin Tung co_return false; 171994a77ffSKevin Tung } 172994a77ffSKevin Tung 173994a77ffSKevin Tung setUpdateProgress(40); 174994a77ffSKevin Tung 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)175*a2eb951fSKevin Tung auto success = co_await writeEEPROM(image, image_size); 176994a77ffSKevin Tung 177994a77ffSKevin Tung if (success) 178994a77ffSKevin Tung { 179994a77ffSKevin Tung debug("Successfully wrote EEPROM"); 180994a77ffSKevin Tung setUpdateProgress(60); 181994a77ffSKevin Tung } 182994a77ffSKevin Tung else 183994a77ffSKevin Tung { 184994a77ffSKevin Tung error("Failed to write EEPROM"); 185994a77ffSKevin Tung } 186994a77ffSKevin Tung 187994a77ffSKevin Tung success = success && co_await unbindEEPROM(); 188994a77ffSKevin Tung 189994a77ffSKevin Tung if (success) 190994a77ffSKevin Tung { 191994a77ffSKevin Tung setUpdateProgress(80); 192994a77ffSKevin Tung } 193994a77ffSKevin Tung 194994a77ffSKevin Tung if (!gpioLines.empty()) 195994a77ffSKevin Tung { updateDevice(const uint8_t * image,size_t image_size)196994a77ffSKevin Tung for (auto& lineBulk : lineBulks) 197994a77ffSKevin Tung { 198994a77ffSKevin Tung lineBulk->release(); 199994a77ffSKevin Tung } 200994a77ffSKevin Tung 201994a77ffSKevin Tung debug("Requesting GPIOs to mux EEPROM back to device"); 202994a77ffSKevin Tung 203994a77ffSKevin Tung lineBulks = requestMuxGPIOs(gpioLines, gpioPolarities, true); 204994a77ffSKevin Tung 205994a77ffSKevin Tung if (lineBulks.empty()) 206994a77ffSKevin Tung { 207994a77ffSKevin Tung error("Failed to mux EEPROM back to device"); 208994a77ffSKevin Tung co_return false; 209994a77ffSKevin Tung } 210994a77ffSKevin Tung 211994a77ffSKevin Tung for (auto& lineBulk : lineBulks) 212994a77ffSKevin Tung { 213994a77ffSKevin Tung lineBulk->release(); 214994a77ffSKevin Tung } 215994a77ffSKevin Tung } 216994a77ffSKevin Tung 217994a77ffSKevin Tung if (success) 218994a77ffSKevin Tung { 219994a77ffSKevin Tung debug("EEPROM device successfully updated"); 220994a77ffSKevin Tung setUpdateProgress(100); 221994a77ffSKevin Tung } 222994a77ffSKevin Tung else 223994a77ffSKevin Tung { 224994a77ffSKevin Tung error("Failed to update EEPROM device"); 225994a77ffSKevin Tung } 226994a77ffSKevin Tung 227994a77ffSKevin Tung co_return success; 228994a77ffSKevin Tung } 229994a77ffSKevin Tung 230994a77ffSKevin Tung sdbusplus::async::task<bool> EEPROMDevice::bindEEPROM() 231994a77ffSKevin Tung { 232994a77ffSKevin Tung auto i2cDeviceId = getI2CDeviceId(bus, address); 233994a77ffSKevin Tung 234994a77ffSKevin Tung debug("Binding {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId); 235994a77ffSKevin Tung 236994a77ffSKevin Tung if (isEEPROMBound()) 237994a77ffSKevin Tung { 238994a77ffSKevin Tung debug("EEPROM was already bound, unbinding it now"); 239994a77ffSKevin Tung if (!co_await unbindEEPROM()) 240994a77ffSKevin Tung { 241994a77ffSKevin Tung error("Error unbinding EEPROM"); 242994a77ffSKevin Tung co_return false; 243994a77ffSKevin Tung } 244994a77ffSKevin Tung } 245994a77ffSKevin Tung 246994a77ffSKevin Tung auto driverPath = getDriverPath(chipModel); 247994a77ffSKevin Tung if (driverPath.empty()) 248994a77ffSKevin Tung { 249994a77ffSKevin Tung error("Driver path not found for chip model: {CHIP}", "CHIP", 250994a77ffSKevin Tung chipModel); 251994a77ffSKevin Tung co_return false; 252994a77ffSKevin Tung } 253994a77ffSKevin Tung 254994a77ffSKevin Tung auto bindPath = driverPath + "/bind"; 255994a77ffSKevin Tung std::ofstream ofbind(bindPath, std::ofstream::out); 256994a77ffSKevin Tung if (!ofbind) 257994a77ffSKevin Tung { 258994a77ffSKevin Tung error("Failed to open bind file: {PATH}", "PATH", bindPath); 259994a77ffSKevin Tung co_return false; 260994a77ffSKevin Tung } 261994a77ffSKevin Tung 262994a77ffSKevin Tung ofbind << i2cDeviceId; 263994a77ffSKevin Tung ofbind.close(); 264994a77ffSKevin Tung 265994a77ffSKevin Tung // wait for kernel 266994a77ffSKevin Tung co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2)); 267994a77ffSKevin Tung 268994a77ffSKevin Tung auto bound = isEEPROMBound(); 269994a77ffSKevin Tung if (!bound) 270994a77ffSKevin Tung { 271994a77ffSKevin Tung error("Failed to bind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId); 272994a77ffSKevin Tung } 273994a77ffSKevin Tung 274994a77ffSKevin Tung co_return bound; 275994a77ffSKevin Tung } 276994a77ffSKevin Tung sdbusplus::async::task<bool> EEPROMDevice::unbindEEPROM() 277994a77ffSKevin Tung { 278994a77ffSKevin Tung auto i2cDeviceId = getI2CDeviceId(bus, address); 279994a77ffSKevin Tung 280994a77ffSKevin Tung debug("Unbinding EEPROM device {I2CDEVICE}", "I2CDEVICE", i2cDeviceId); 281994a77ffSKevin Tung 282994a77ffSKevin Tung auto driverPath = getDriverPath(chipModel); 283994a77ffSKevin Tung if (driverPath.empty()) bindEEPROM()284994a77ffSKevin Tung { 285994a77ffSKevin Tung error("Failed to unbind EEPROM, driver path not found for chip {CHIP}", 286994a77ffSKevin Tung "CHIP", chipModel); 287994a77ffSKevin Tung co_return false; 288994a77ffSKevin Tung } 289994a77ffSKevin Tung 290994a77ffSKevin Tung auto unbindPath = driverPath + "/unbind"; 291994a77ffSKevin Tung std::ofstream ofunbind(unbindPath, std::ofstream::out); 292994a77ffSKevin Tung if (!ofunbind) 293994a77ffSKevin Tung { 294994a77ffSKevin Tung error("Failed to open unbind file: {PATH}", "PATH", unbindPath); 295994a77ffSKevin Tung co_return false; 296994a77ffSKevin Tung } 297994a77ffSKevin Tung ofunbind << i2cDeviceId; 298994a77ffSKevin Tung ofunbind.close(); 299994a77ffSKevin Tung 300994a77ffSKevin Tung // wait for kernel 301994a77ffSKevin Tung co_await sdbusplus::async::sleep_for(ctx, std::chrono::seconds(2)); 302994a77ffSKevin Tung 303994a77ffSKevin Tung auto bound = isEEPROMBound(); 304994a77ffSKevin Tung if (bound) 305994a77ffSKevin Tung { 306994a77ffSKevin Tung error("Failed to unbind {I2CDEVICE} EEPROM", "I2CDEVICE", i2cDeviceId); 307994a77ffSKevin Tung } 308994a77ffSKevin Tung 309994a77ffSKevin Tung co_return !bound; 310994a77ffSKevin Tung } 311994a77ffSKevin Tung 312994a77ffSKevin Tung bool EEPROMDevice::isEEPROMBound() 313994a77ffSKevin Tung { 314994a77ffSKevin Tung auto driverPath = getDriverPath(chipModel); 315994a77ffSKevin Tung 316994a77ffSKevin Tung if (driverPath.empty()) 317994a77ffSKevin Tung { 318994a77ffSKevin Tung error("Failed to check if EEPROM is bound"); 319994a77ffSKevin Tung return false; 320994a77ffSKevin Tung } 321994a77ffSKevin Tung 322994a77ffSKevin Tung auto i2cDeviceId = getI2CDeviceId(bus, address); 323994a77ffSKevin Tung 324994a77ffSKevin Tung return std::filesystem::exists(driverPath + "/" + i2cDeviceId); 325994a77ffSKevin Tung } 326994a77ffSKevin Tung 327*a2eb951fSKevin Tung sdbusplus::async::task<bool> EEPROMDevice::writeEEPROM(const uint8_t* image, 328994a77ffSKevin Tung size_t image_size) const 329994a77ffSKevin Tung { unbindEEPROM()330994a77ffSKevin Tung auto eepromPath = getEEPROMPath(bus, address); 331994a77ffSKevin Tung if (eepromPath.empty()) 332994a77ffSKevin Tung { 333994a77ffSKevin Tung error("EEPROM file not found for device: {DEVICE}", "DEVICE", 334994a77ffSKevin Tung getI2CDeviceId(bus, address)); 335994a77ffSKevin Tung co_return -1; 336994a77ffSKevin Tung } 337994a77ffSKevin Tung const std::string path = 338994a77ffSKevin Tung "/tmp/eeprom-image-" + 339994a77ffSKevin Tung std::to_string(SoftwareInf::Software::getRandomId()) + ".bin"; 340994a77ffSKevin Tung 341994a77ffSKevin Tung int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644); 342994a77ffSKevin Tung if (fd < 0) 343994a77ffSKevin Tung { 344994a77ffSKevin Tung error("Failed to open file: {PATH}", "PATH", path); 345994a77ffSKevin Tung co_return -1; 346994a77ffSKevin Tung } 347994a77ffSKevin Tung 348994a77ffSKevin Tung const ssize_t bytesWritten = write(fd, image, image_size); 349994a77ffSKevin Tung 350994a77ffSKevin Tung close(fd); 351994a77ffSKevin Tung 352994a77ffSKevin Tung if (bytesWritten < 0 || static_cast<size_t>(bytesWritten) != image_size) 353994a77ffSKevin Tung { 354994a77ffSKevin Tung error("Failed to write image to file"); 355994a77ffSKevin Tung co_return -1; 356994a77ffSKevin Tung } 357994a77ffSKevin Tung 358994a77ffSKevin Tung debug("Wrote {SIZE} bytes to {PATH}", "SIZE", bytesWritten, "PATH", path); 359994a77ffSKevin Tung 360994a77ffSKevin Tung std::string cmd = "dd if=" + path + " of=" + eepromPath + " bs=1k"; 361994a77ffSKevin Tung 362994a77ffSKevin Tung debug("Running {CMD}", "CMD", cmd); 363994a77ffSKevin Tung 364*a2eb951fSKevin Tung auto success = co_await asyncSystem(ctx, cmd); 365994a77ffSKevin Tung isEEPROMBound()366994a77ffSKevin Tung std::filesystem::remove(path); 367994a77ffSKevin Tung 368*a2eb951fSKevin Tung co_return success; 369994a77ffSKevin Tung } 370994a77ffSKevin Tung 371994a77ffSKevin Tung sdbusplus::async::task<> EEPROMDevice::processHostStateChange() 372994a77ffSKevin Tung { 373994a77ffSKevin Tung auto requiredHostState = deviceVersion->getHostStateToQueryVersion(); 374994a77ffSKevin Tung 375994a77ffSKevin Tung if (!requiredHostState) 376994a77ffSKevin Tung { 377994a77ffSKevin Tung error("Failed to get required host state"); 378994a77ffSKevin Tung co_return; 379994a77ffSKevin Tung } 380994a77ffSKevin Tung writeEEPROM(const uint8_t * image,size_t image_size) const381994a77ffSKevin Tung while (!ctx.stop_requested()) 382994a77ffSKevin Tung { 38301ba9562SAlexander Hansen auto nextResult = co_await hostPower.stateChangedMatch.next< 38401ba9562SAlexander Hansen std::string, std::map<std::string, std::variant<std::string>>>(); 38501ba9562SAlexander Hansen 38601ba9562SAlexander Hansen auto [interfaceName, changedProperties] = nextResult; 387994a77ffSKevin Tung 388994a77ffSKevin Tung auto it = changedProperties.find("CurrentHostState"); 389994a77ffSKevin Tung if (it != changedProperties.end()) 390994a77ffSKevin Tung { 391994a77ffSKevin Tung const auto& currentHostState = std::get<std::string>(it->second); 392994a77ffSKevin Tung 393994a77ffSKevin Tung if (currentHostState == 394994a77ffSKevin Tung State::convertForMessage(*requiredHostState)) 395994a77ffSKevin Tung { 396994a77ffSKevin Tung debug("Host state {STATE} matches to retrieve the version", 397994a77ffSKevin Tung "STATE", currentHostState); 398994a77ffSKevin Tung std::string version = deviceVersion->getVersion(); 399994a77ffSKevin Tung if (!version.empty()) 400994a77ffSKevin Tung { 401994a77ffSKevin Tung softwareCurrent->setVersion(version); 402994a77ffSKevin Tung } 403994a77ffSKevin Tung } 404994a77ffSKevin Tung } 405994a77ffSKevin Tung } 406994a77ffSKevin Tung 407994a77ffSKevin Tung co_return; 408994a77ffSKevin Tung } 409