1 #include "config.h" 2 3 #include "item_updater_static.hpp" 4 5 #include "activation_static.hpp" 6 #include "version.hpp" 7 8 #include <array> 9 #include <cstring> 10 #include <filesystem> 11 #include <fstream> 12 #include <phosphor-logging/elog-errors.hpp> 13 #include <phosphor-logging/log.hpp> 14 #include <sstream> 15 #include <string> 16 #include <tuple> 17 #include <xyz/openbmc_project/Common/error.hpp> 18 19 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 20 using namespace phosphor::logging; 21 using sdbusplus::exception::SdBusError; 22 23 // When you see server:: you know we're referencing our base class 24 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 25 namespace fs = std::filesystem; 26 27 namespace utils 28 { 29 30 template <typename... Ts> 31 std::string concat_string(Ts const&... ts) 32 { 33 std::stringstream s; 34 ((s << ts << " "), ...) << std::endl; 35 return s.str(); 36 } 37 38 // Helper function to run pflash command 39 // Returns return code and the stdout 40 template <typename... Ts> 41 std::pair<int, std::string> pflash(Ts const&... ts) 42 { 43 std::array<char, 512> buffer; 44 std::string cmd = concat_string("pflash", ts...); 45 std::stringstream result; 46 int rc; 47 FILE* pipe = popen(cmd.c_str(), "r"); 48 if (!pipe) 49 { 50 throw std::runtime_error("popen() failed!"); 51 } 52 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) 53 { 54 result << buffer.data(); 55 } 56 rc = pclose(pipe); 57 return {rc, result.str()}; 58 } 59 60 std::string getPNORVersion() 61 { 62 // A signed version partition will have an extra 4K header starting with 63 // the magic number 17082011 in big endian: 64 // https://github.com/open-power/skiboot/blob/master/libstb/container.h#L47 65 66 constexpr uint8_t MAGIC[] = {0x17, 0x08, 0x20, 0x11}; 67 constexpr auto MAGIC_SIZE = sizeof(MAGIC); 68 static_assert(MAGIC_SIZE == 4); 69 70 auto tmp = fs::temp_directory_path(); 71 std::string tmpDir(tmp / "versionXXXXXX"); 72 if (!mkdtemp(tmpDir.data())) 73 { 74 log<level::ERR>("Failed to create temp dir"); 75 return {}; 76 } 77 78 fs::path versionFile = tmpDir; 79 versionFile /= "version"; 80 81 auto [rc, r] = 82 pflash("-P VERSION -r", versionFile.string(), "2>&1 > /dev/null"); 83 if (rc != 0) 84 { 85 log<level::ERR>("Failed to read VERSION", entry("RETURNCODE=%d", rc)); 86 return {}; 87 } 88 89 std::ifstream f(versionFile.c_str(), std::ios::in | std::ios::binary); 90 uint8_t magic[MAGIC_SIZE]; 91 std::string version; 92 93 f.read(reinterpret_cast<char*>(magic), MAGIC_SIZE); 94 f.seekg(0, std::ios::beg); 95 if (std::memcmp(magic, MAGIC, MAGIC_SIZE) == 0) 96 { 97 // Skip the first 4K header 98 f.ignore(4096); 99 } 100 101 getline(f, version, '\0'); 102 f.close(); 103 104 // Clear the temp dir 105 std::error_code ec; 106 fs::remove_all(tmpDir, ec); 107 if (ec) 108 { 109 log<level::ERR>("Failed to remove temp dir", 110 entry("DIR=%s", tmpDir.c_str()), 111 entry("ERR=%s", ec.message().c_str())); 112 } 113 114 return version; 115 } 116 117 inline void pnorClear(const std::string& part, bool shouldEcc = true) 118 { 119 int rc; 120 std::tie(rc, std::ignore) = 121 pflash("-P", part, shouldEcc ? "-c" : "-e", "-f >/dev/null"); 122 if (rc != 0) 123 { 124 log<level::ERR>("Failed to clear partition", 125 entry("PART=%s", part.c_str()), 126 entry("RETURNCODE=%d", rc)); 127 } 128 else 129 { 130 log<level::INFO>("Clear partition successfully", 131 entry("PART=%s", part.c_str())); 132 } 133 } 134 135 } // namespace utils 136 137 namespace openpower 138 { 139 namespace software 140 { 141 namespace updater 142 { 143 // TODO: Change paths once openbmc/openbmc#1663 is completed. 144 constexpr auto MBOXD_INTERFACE = "org.openbmc.mboxd"; 145 constexpr auto MBOXD_PATH = "/org/openbmc/mboxd"; 146 147 std::unique_ptr<Activation> ItemUpdaterStatic::createActivationObject( 148 const std::string& path, const std::string& versionId, 149 const std::string& extVersion, 150 sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations 151 activationStatus, 152 AssociationList& assocs) 153 { 154 return std::make_unique<ActivationStatic>( 155 bus, path, *this, versionId, extVersion, activationStatus, assocs); 156 } 157 158 std::unique_ptr<Version> ItemUpdaterStatic::createVersionObject( 159 const std::string& objPath, const std::string& versionId, 160 const std::string& versionString, 161 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose 162 versionPurpose, 163 const std::string& filePath) 164 { 165 auto version = std::make_unique<Version>( 166 bus, objPath, *this, versionId, versionString, versionPurpose, filePath, 167 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1)); 168 version->deleteObject = std::make_unique<Delete>(bus, objPath, *version); 169 return version; 170 } 171 172 bool ItemUpdaterStatic::validateImage(const std::string& path) 173 { 174 // There is no need to validate static layout pnor 175 return true; 176 } 177 178 void ItemUpdaterStatic::processPNORImage() 179 { 180 auto fullVersion = utils::getPNORVersion(); 181 182 const auto& [version, extendedVersion] = Version::getVersions(fullVersion); 183 auto id = Version::getId(version); 184 185 auto activationState = server::Activation::Activations::Active; 186 if (version.empty()) 187 { 188 log<level::ERR>("Failed to read version", 189 entry("VERSION=%s", fullVersion.c_str())); 190 activationState = server::Activation::Activations::Invalid; 191 } 192 193 if (extendedVersion.empty()) 194 { 195 log<level::ERR>("Failed to read extendedVersion", 196 entry("VERSION=%s", fullVersion.c_str())); 197 activationState = server::Activation::Activations::Invalid; 198 } 199 200 auto purpose = server::Version::VersionPurpose::Host; 201 auto path = fs::path(SOFTWARE_OBJPATH) / id; 202 AssociationList associations = {}; 203 204 if (activationState == server::Activation::Activations::Active) 205 { 206 // Create an association to the host inventory item 207 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 208 ACTIVATION_REV_ASSOCIATION, 209 HOST_INVENTORY_PATH)); 210 211 // Create an active association since this image is active 212 createActiveAssociation(path); 213 } 214 215 // Create Activation instance for this version. 216 activations.insert(std::make_pair( 217 id, std::make_unique<ActivationStatic>(bus, path, *this, id, 218 extendedVersion, activationState, 219 associations))); 220 221 // If Active, create RedundancyPriority instance for this version. 222 if (activationState == server::Activation::Activations::Active) 223 { 224 // For now only one PNOR is supported with static layout 225 activations.find(id)->second->redundancyPriority = 226 std::make_unique<RedundancyPriority>( 227 bus, path, *(activations.find(id)->second), 0); 228 } 229 230 // Create Version instance for this version. 231 auto versionPtr = std::make_unique<Version>( 232 bus, path, *this, id, version, purpose, "", 233 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1)); 234 versionPtr->deleteObject = std::make_unique<Delete>(bus, path, *versionPtr); 235 versions.insert(std::make_pair(id, std::move(versionPtr))); 236 237 if (!id.empty()) 238 { 239 updateFunctionalAssociation(id); 240 } 241 } 242 243 void ItemUpdaterStatic::reset() 244 { 245 // The pair contains the partition name and if it should use ECC clear 246 using PartClear = std::pair<const char*, bool>; 247 constexpr std::array<PartClear, 11> partitions = {{ 248 {"HBEL", true}, 249 {"GUARD", true}, 250 {"NVRAM", false}, 251 {"DJVPD", true}, 252 {"MVPD", true}, 253 {"CVPD", true}, 254 {"FIRDATA", true}, 255 {"BMC_INV", false}, 256 {"ATTR_TMP", false}, 257 {"ATTR_PERM", true}, 258 {"HB_VOLATILE", true}, 259 }}; 260 261 std::vector<uint8_t> mboxdArgs; 262 263 // Suspend mboxd - no args required. 264 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, 265 MBOXD_INTERFACE, "cmd"); 266 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 267 268 try 269 { 270 bus.call_noreply(dbusCall); 271 } 272 catch (const SdBusError& e) 273 { 274 log<level::ERR>("Error in mboxd suspend call", 275 entry("ERROR=%s", e.what())); 276 elog<InternalFailure>(); 277 } 278 for (auto p : partitions) 279 { 280 utils::pnorClear(p.first, p.second); 281 } 282 283 // Resume mboxd with arg 1, indicating that the flash was modified. 284 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE, 285 "cmd"); 286 mboxdArgs.push_back(1); 287 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 288 289 try 290 { 291 bus.call_noreply(dbusCall); 292 } 293 catch (const SdBusError& e) 294 { 295 log<level::ERR>("Error in mboxd resume call", 296 entry("ERROR=%s", e.what())); 297 elog<InternalFailure>(); 298 } 299 } 300 301 bool ItemUpdaterStatic::isVersionFunctional(const std::string& versionId) 302 { 303 return versionId == functionalVersionId; 304 } 305 306 void ItemUpdaterStatic::freePriority(uint8_t value, 307 const std::string& versionId) 308 { 309 } 310 311 void ItemUpdaterStatic::deleteAll() 312 { 313 // Static layout has only one active and function pnor 314 // There is no implementation for this interface 315 } 316 317 void ItemUpdaterStatic::freeSpace() 318 { 319 // For now assume static layout only has 1 active PNOR, 320 // so erase the active PNOR 321 for (const auto& iter : activations) 322 { 323 if (iter.second.get()->activation() == 324 server::Activation::Activations::Active) 325 { 326 erase(iter.second->versionId); 327 break; 328 } 329 } 330 } 331 332 void ItemUpdaterStatic::updateFunctionalAssociation( 333 const std::string& versionId) 334 { 335 functionalVersionId = versionId; 336 ItemUpdater::updateFunctionalAssociation(versionId); 337 } 338 339 void GardReset::reset() 340 { 341 // Clear gard partition 342 std::vector<uint8_t> mboxdArgs; 343 344 auto dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, 345 MBOXD_INTERFACE, "cmd"); 346 // Suspend mboxd - no args required. 347 dbusCall.append(static_cast<uint8_t>(3), mboxdArgs); 348 349 try 350 { 351 bus.call_noreply(dbusCall); 352 } 353 catch (const SdBusError& e) 354 { 355 log<level::ERR>("Error in mboxd suspend call", 356 entry("ERROR=%s", e.what())); 357 elog<InternalFailure>(); 358 } 359 360 // Clear guard partition 361 utils::pnorClear("GUARD"); 362 363 dbusCall = bus.new_method_call(MBOXD_INTERFACE, MBOXD_PATH, MBOXD_INTERFACE, 364 "cmd"); 365 // Resume mboxd with arg 1, indicating that the flash is modified. 366 mboxdArgs.push_back(1); 367 dbusCall.append(static_cast<uint8_t>(4), mboxdArgs); 368 369 try 370 { 371 bus.call_noreply(dbusCall); 372 } 373 catch (const SdBusError& e) 374 { 375 log<level::ERR>("Error in mboxd resume call", 376 entry("ERROR=%s", e.what())); 377 elog<InternalFailure>(); 378 } 379 } 380 381 } // namespace updater 382 } // namespace software 383 } // namespace openpower 384