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