blob: 8eea808bfdb7da0bdfc29ad2110ab65213853ab5 [file] [log] [blame]
Lei YU322f3f42019-02-21 16:10:41 +08001#include "config.h"
2
3#include "item_updater_static.hpp"
4
Lei YUb53425d2019-02-22 11:38:40 +08005#include "activation_static.hpp"
Lei YUe4994462019-03-14 14:41:53 +08006#include "utils.hpp"
Lei YUdec8cf92019-02-21 17:47:05 +08007#include "version.hpp"
8
Brad Bishop8facccf2020-11-04 09:44:58 -05009#include <phosphor-logging/elog-errors.hpp>
10#include <phosphor-logging/log.hpp>
11#include <xyz/openbmc_project/Common/error.hpp>
12
Lei YUa7b4ade2019-02-25 17:49:29 +080013#include <array>
Lei YUdec8cf92019-02-21 17:47:05 +080014#include <cstring>
15#include <filesystem>
16#include <fstream>
Lei YUa2e67162019-02-22 17:35:24 +080017#include <sstream>
Lei YUdec8cf92019-02-21 17:47:05 +080018#include <string>
Lei YU5efca582019-02-25 17:05:29 +080019#include <tuple>
Lei YUdec8cf92019-02-21 17:47:05 +080020
21using namespace sdbusplus::xyz::openbmc_project::Common::Error;
22using namespace phosphor::logging;
23
24// When you see server:: you know we're referencing our base class
25namespace server = sdbusplus::xyz::openbmc_project::Software::server;
26namespace fs = std::filesystem;
27
28namespace utils
29{
30
31template <typename... Ts>
32std::string concat_string(Ts const&... ts)
33{
34 std::stringstream s;
35 ((s << ts << " "), ...) << std::endl;
36 return s.str();
37}
38
39// Helper function to run pflash command
Lei YU5efca582019-02-25 17:05:29 +080040// Returns return code and the stdout
Lei YUdec8cf92019-02-21 17:47:05 +080041template <typename... Ts>
Lei YU5efca582019-02-25 17:05:29 +080042std::pair<int, std::string> pflash(Ts const&... ts)
Lei YUdec8cf92019-02-21 17:47:05 +080043{
44 std::array<char, 512> buffer;
45 std::string cmd = concat_string("pflash", ts...);
46 std::stringstream result;
Lei YU5efca582019-02-25 17:05:29 +080047 int rc;
48 FILE* pipe = popen(cmd.c_str(), "r");
Lei YUdec8cf92019-02-21 17:47:05 +080049 if (!pipe)
50 {
51 throw std::runtime_error("popen() failed!");
52 }
Lei YU5efca582019-02-25 17:05:29 +080053 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
Lei YUdec8cf92019-02-21 17:47:05 +080054 {
55 result << buffer.data();
56 }
Lei YU5efca582019-02-25 17:05:29 +080057 rc = pclose(pipe);
58 return {rc, result.str()};
Lei YUdec8cf92019-02-21 17:47:05 +080059}
60
61std::string getPNORVersion()
62{
63 // A signed version partition will have an extra 4K header starting with
64 // the magic number 17082011 in big endian:
65 // https://github.com/open-power/skiboot/blob/master/libstb/container.h#L47
66
67 constexpr uint8_t MAGIC[] = {0x17, 0x08, 0x20, 0x11};
68 constexpr auto MAGIC_SIZE = sizeof(MAGIC);
69 static_assert(MAGIC_SIZE == 4);
70
71 auto tmp = fs::temp_directory_path();
72 std::string tmpDir(tmp / "versionXXXXXX");
73 if (!mkdtemp(tmpDir.data()))
74 {
75 log<level::ERR>("Failed to create temp dir");
76 return {};
77 }
78
79 fs::path versionFile = tmpDir;
80 versionFile /= "version";
81
Lei YU5efca582019-02-25 17:05:29 +080082 auto [rc, r] =
83 pflash("-P VERSION -r", versionFile.string(), "2>&1 > /dev/null");
84 if (rc != 0)
85 {
86 log<level::ERR>("Failed to read VERSION", entry("RETURNCODE=%d", rc));
87 return {};
88 }
89
Lei YUdec8cf92019-02-21 17:47:05 +080090 std::ifstream f(versionFile.c_str(), std::ios::in | std::ios::binary);
91 uint8_t magic[MAGIC_SIZE];
92 std::string version;
93
94 f.read(reinterpret_cast<char*>(magic), MAGIC_SIZE);
95 f.seekg(0, std::ios::beg);
96 if (std::memcmp(magic, MAGIC, MAGIC_SIZE) == 0)
97 {
98 // Skip the first 4K header
99 f.ignore(4096);
100 }
101
102 getline(f, version, '\0');
103 f.close();
104
105 // Clear the temp dir
106 std::error_code ec;
107 fs::remove_all(tmpDir, ec);
108 if (ec)
109 {
110 log<level::ERR>("Failed to remove temp dir",
111 entry("DIR=%s", tmpDir.c_str()),
112 entry("ERR=%s", ec.message().c_str()));
113 }
114
115 return version;
116}
117
Lei YU6cecc9b2019-02-26 17:27:23 +0800118void pnorClear(const std::string& part, bool shouldEcc = true)
Lei YUa7b4ade2019-02-25 17:49:29 +0800119{
120 int rc;
121 std::tie(rc, std::ignore) =
Lei YU6cecc9b2019-02-26 17:27:23 +0800122 utils::pflash("-P", part, shouldEcc ? "-c" : "-e", "-f >/dev/null");
Lei YUa7b4ade2019-02-25 17:49:29 +0800123 if (rc != 0)
124 {
125 log<level::ERR>("Failed to clear partition",
126 entry("PART=%s", part.c_str()),
127 entry("RETURNCODE=%d", rc));
128 }
129 else
130 {
131 log<level::INFO>("Clear partition successfully",
132 entry("PART=%s", part.c_str()));
133 }
134}
135
Lei YU6cecc9b2019-02-26 17:27:23 +0800136// The pair contains the partition name and if it should use ECC clear
137using PartClear = std::pair<std::string, bool>;
138
139std::vector<PartClear> getPartsToClear(const std::string& info)
140{
141 std::vector<PartClear> ret;
142 std::istringstream iss(info);
143 std::string line;
144
145 while (std::getline(iss, line))
146 {
147 // Each line looks like
148 // ID=06 MVPD 0x0012d000..0x001bd000 (actual=0x00090000) [E--P--F-C-]
149 // Flag 'F' means REPROVISION
Alexander Filippovfa9a6be2019-12-18 12:06:47 +0300150 // Flag 'E' means ECC required
Lei YU07830762019-03-01 17:17:28 +0800151 auto pos = line.find('[');
152 if (pos == std::string::npos)
153 {
154 continue;
155 }
156 auto flags = line.substr(pos);
Lei YU6cecc9b2019-02-26 17:27:23 +0800157 if (flags.find('F') != std::string::npos)
158 {
159 // This is a partition to be cleared
Lei YU07830762019-03-01 17:17:28 +0800160 pos = line.find_first_of(' '); // After "ID=xx"
161 if (pos == std::string::npos)
162 {
163 continue;
164 }
165 line = line.substr(pos); // Skiping "ID=xx"
166
167 pos = line.find_first_not_of(' '); // After spaces
168 if (pos == std::string::npos)
169 {
170 continue;
171 }
172 line = line.substr(pos); // Skipping spaces
173
174 pos = line.find_first_of(' '); // The end of part name
175 if (pos == std::string::npos)
176 {
177 continue;
178 }
179 line = line.substr(0, pos); // The part name
180
Alexander Filippovfa9a6be2019-12-18 12:06:47 +0300181 bool ecc = flags.find('E') != std::string::npos;
Lei YU6cecc9b2019-02-26 17:27:23 +0800182 ret.emplace_back(line, ecc);
183 }
184 }
185 return ret;
186}
187
188// Get partitions that should be cleared
189std::vector<PartClear> getPartsToClear()
190{
191 const auto& [rc, pflashInfo] = pflash("-i | grep ^ID | grep 'F'");
192 return getPartsToClear(pflashInfo);
193}
194
Lei YUdec8cf92019-02-21 17:47:05 +0800195} // namespace utils
196
Lei YU322f3f42019-02-21 16:10:41 +0800197namespace openpower
198{
199namespace software
200{
201namespace updater
202{
Lei YU5efca582019-02-25 17:05:29 +0800203
Lei YU322f3f42019-02-21 16:10:41 +0800204std::unique_ptr<Activation> ItemUpdaterStatic::createActivationObject(
205 const std::string& path, const std::string& versionId,
206 const std::string& extVersion,
207 sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations
208 activationStatus,
209 AssociationList& assocs)
210{
Lei YUb53425d2019-02-22 11:38:40 +0800211 return std::make_unique<ActivationStatic>(
212 bus, path, *this, versionId, extVersion, activationStatus, assocs);
Lei YU322f3f42019-02-21 16:10:41 +0800213}
214
215std::unique_ptr<Version> ItemUpdaterStatic::createVersionObject(
216 const std::string& objPath, const std::string& versionId,
217 const std::string& versionString,
218 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
219 versionPurpose,
220 const std::string& filePath)
221{
Lei YUb53425d2019-02-22 11:38:40 +0800222 auto version = std::make_unique<Version>(
223 bus, objPath, *this, versionId, versionString, versionPurpose, filePath,
224 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1));
225 version->deleteObject = std::make_unique<Delete>(bus, objPath, *version);
226 return version;
Lei YU322f3f42019-02-21 16:10:41 +0800227}
228
Brad Bishopc8f22502020-11-06 14:42:09 -0500229bool ItemUpdaterStatic::validateImage(const std::string&)
Lei YU322f3f42019-02-21 16:10:41 +0800230{
Lei YUb53425d2019-02-22 11:38:40 +0800231 // There is no need to validate static layout pnor
Lei YU322f3f42019-02-21 16:10:41 +0800232 return true;
233}
234
235void ItemUpdaterStatic::processPNORImage()
236{
Lei YUdec8cf92019-02-21 17:47:05 +0800237 auto fullVersion = utils::getPNORVersion();
238
239 const auto& [version, extendedVersion] = Version::getVersions(fullVersion);
240 auto id = Version::getId(version);
241
Lei YU91add6d2019-03-01 14:23:40 +0800242 if (id.empty())
243 {
244 // Possibly a corrupted PNOR
245 return;
246 }
247
Lei YUdec8cf92019-02-21 17:47:05 +0800248 auto activationState = server::Activation::Activations::Active;
249 if (version.empty())
250 {
251 log<level::ERR>("Failed to read version",
252 entry("VERSION=%s", fullVersion.c_str()));
253 activationState = server::Activation::Activations::Invalid;
254 }
255
256 if (extendedVersion.empty())
257 {
258 log<level::ERR>("Failed to read extendedVersion",
259 entry("VERSION=%s", fullVersion.c_str()));
260 activationState = server::Activation::Activations::Invalid;
261 }
262
263 auto purpose = server::Version::VersionPurpose::Host;
264 auto path = fs::path(SOFTWARE_OBJPATH) / id;
265 AssociationList associations = {};
266
267 if (activationState == server::Activation::Activations::Active)
268 {
269 // Create an association to the host inventory item
270 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
271 ACTIVATION_REV_ASSOCIATION,
272 HOST_INVENTORY_PATH));
273
274 // Create an active association since this image is active
275 createActiveAssociation(path);
276 }
277
Adriana Kobylak3c810372020-07-15 16:47:03 -0500278 // All updateable firmware components must expose the updateable
279 // association.
280 createUpdateableAssociation(path);
281
Lei YUb53425d2019-02-22 11:38:40 +0800282 // Create Activation instance for this version.
283 activations.insert(std::make_pair(
284 id, std::make_unique<ActivationStatic>(bus, path, *this, id,
285 extendedVersion, activationState,
286 associations)));
287
288 // If Active, create RedundancyPriority instance for this version.
289 if (activationState == server::Activation::Activations::Active)
290 {
291 // For now only one PNOR is supported with static layout
292 activations.find(id)->second->redundancyPriority =
293 std::make_unique<RedundancyPriority>(
294 bus, path, *(activations.find(id)->second), 0);
295 }
296
Lei YUdec8cf92019-02-21 17:47:05 +0800297 // Create Version instance for this version.
298 auto versionPtr = std::make_unique<Version>(
299 bus, path, *this, id, version, purpose, "",
300 std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1));
301 versionPtr->deleteObject = std::make_unique<Delete>(bus, path, *versionPtr);
302 versions.insert(std::make_pair(id, std::move(versionPtr)));
303
304 if (!id.empty())
305 {
306 updateFunctionalAssociation(id);
307 }
Lei YU322f3f42019-02-21 16:10:41 +0800308}
309
310void ItemUpdaterStatic::reset()
311{
Lei YU6cecc9b2019-02-26 17:27:23 +0800312 auto partitions = utils::getPartsToClear();
Lei YUa7b4ade2019-02-25 17:49:29 +0800313
Lei YUe4994462019-03-14 14:41:53 +0800314 utils::hiomapdSuspend(bus);
Lei YUa7b4ade2019-02-25 17:49:29 +0800315
Lei YUa7b4ade2019-02-25 17:49:29 +0800316 for (auto p : partitions)
317 {
318 utils::pnorClear(p.first, p.second);
319 }
320
Lei YUe4994462019-03-14 14:41:53 +0800321 utils::hiomapdResume(bus);
Lei YU322f3f42019-02-21 16:10:41 +0800322}
323
324bool ItemUpdaterStatic::isVersionFunctional(const std::string& versionId)
325{
Lei YUa2e67162019-02-22 17:35:24 +0800326 return versionId == functionalVersionId;
Lei YU322f3f42019-02-21 16:10:41 +0800327}
328
Brad Bishopc8f22502020-11-06 14:42:09 -0500329void ItemUpdaterStatic::freePriority(uint8_t, const std::string&)
Brad Bishop8facccf2020-11-04 09:44:58 -0500330{}
Lei YU322f3f42019-02-21 16:10:41 +0800331
332void ItemUpdaterStatic::deleteAll()
333{
Lei YUa2e67162019-02-22 17:35:24 +0800334 // Static layout has only one active and function pnor
335 // There is no implementation for this interface
Lei YU322f3f42019-02-21 16:10:41 +0800336}
337
Lei YU6da3dae2019-02-28 14:26:37 +0800338bool ItemUpdaterStatic::freeSpace()
Lei YU322f3f42019-02-21 16:10:41 +0800339{
Lei YUa2e67162019-02-22 17:35:24 +0800340 // For now assume static layout only has 1 active PNOR,
341 // so erase the active PNOR
342 for (const auto& iter : activations)
343 {
344 if (iter.second.get()->activation() ==
345 server::Activation::Activations::Active)
346 {
Lei YU6da3dae2019-02-28 14:26:37 +0800347 return erase(iter.second->versionId);
Lei YUa2e67162019-02-22 17:35:24 +0800348 }
349 }
Lei YU91add6d2019-03-01 14:23:40 +0800350 // No active PNOR means PNOR is empty or corrupted
351 return true;
Lei YUa2e67162019-02-22 17:35:24 +0800352}
353
354void ItemUpdaterStatic::updateFunctionalAssociation(
355 const std::string& versionId)
356{
357 functionalVersionId = versionId;
358 ItemUpdater::updateFunctionalAssociation(versionId);
Lei YU322f3f42019-02-21 16:10:41 +0800359}
360
Lei YU716de5b2019-03-01 16:03:53 +0800361void GardResetStatic::reset()
Lei YU322f3f42019-02-21 16:10:41 +0800362{
Lei YU5efca582019-02-25 17:05:29 +0800363 // Clear gard partition
Lei YUe4994462019-03-14 14:41:53 +0800364 utils::hiomapdSuspend(bus);
Lei YU5efca582019-02-25 17:05:29 +0800365
Lei YUa7b4ade2019-02-25 17:49:29 +0800366 utils::pnorClear("GUARD");
Lei YU5efca582019-02-25 17:05:29 +0800367
Lei YUe4994462019-03-14 14:41:53 +0800368 utils::hiomapdResume(bus);
Lei YU322f3f42019-02-21 16:10:41 +0800369}
370
371} // namespace updater
372} // namespace software
373} // namespace openpower