blob: a7ffd6cd1f9e03d88e2ee0589c21f7fcf017c6d7 [file] [log] [blame]
Lei YU12c9f4c2019-09-11 15:08:15 +08001#include "config.h"
2
Lei YU01539e72019-07-31 10:57:38 +08003#include "activation.hpp"
4
Lei YU12c9f4c2019-09-11 15:08:15 +08005#include "utils.hpp"
6
Lei YUd0bbfa92019-09-11 16:10:54 +08007#include <phosphor-logging/elog-errors.hpp>
Shawn McCarneycdf86de2024-11-26 10:02:14 -06008#include <phosphor-logging/lg2.hpp>
Lei YU12c9f4c2019-09-11 15:08:15 +08009
Shawn McCarney487e2e12024-11-25 17:19:46 -060010#include <exception>
Patrick Williams5670b182023-05-10 07:50:50 -050011#include <filesystem>
Shawn McCarney487e2e12024-11-25 17:19:46 -060012#include <format>
13#include <stdexcept>
14#include <vector>
Patrick Williams5670b182023-05-10 07:50:50 -050015
Lei YU01539e72019-07-31 10:57:38 +080016namespace phosphor
17{
18namespace software
19{
20namespace updater
21{
22
Lei YU12c9f4c2019-09-11 15:08:15 +080023constexpr auto SYSTEMD_BUSNAME = "org.freedesktop.systemd1";
24constexpr auto SYSTEMD_PATH = "/org/freedesktop/systemd1";
25constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
26
27namespace fs = std::filesystem;
Lei YU01539e72019-07-31 10:57:38 +080028
Lei YUd0bbfa92019-09-11 16:10:54 +080029using namespace phosphor::logging;
Shawn McCarney17c2c942024-12-10 17:32:30 -060030using SoftwareActivation =
31 sdbusplus::server::xyz::openbmc_project::software::Activation;
32using ExtendedVersion =
33 sdbusplus::server::xyz::openbmc_project::software::ExtendedVersion;
Lei YU12c9f4c2019-09-11 15:08:15 +080034
Lei YU01539e72019-07-31 10:57:38 +080035auto Activation::activation(Activations value) -> Activations
36{
Lei YU12c9f4c2019-09-11 15:08:15 +080037 if (value == Status::Activating)
38 {
Lei YUff83c2a2019-09-12 13:55:18 +080039 value = startActivation();
Lei YU12c9f4c2019-09-11 15:08:15 +080040 }
41 else
42 {
Lei YU81c67722019-09-11 16:47:29 +080043 activationBlocksTransition.reset();
Lei YU90c8a8b2019-09-11 17:20:03 +080044 activationProgress.reset();
Lei YU12c9f4c2019-09-11 15:08:15 +080045 }
46
47 return SoftwareActivation::activation(value);
Lei YU01539e72019-07-31 10:57:38 +080048}
49
50auto Activation::requestedActivation(RequestedActivations value)
51 -> RequestedActivations
52{
Lei YU12c9f4c2019-09-11 15:08:15 +080053 if ((value == SoftwareActivation::RequestedActivations::Active) &&
54 (SoftwareActivation::requestedActivation() !=
55 SoftwareActivation::RequestedActivations::Active))
56 {
Lei YU63f9e712019-10-12 15:16:55 +080057 // PSU image could be activated even when it's in active,
58 // e.g. in case a PSU is replaced and has a older image, it will be
59 // updated with the running PSU image that is stored in BMC.
60 if ((activation() == Status::Ready) ||
61 (activation() == Status::Failed) || activation() == Status::Active)
Lei YU12c9f4c2019-09-11 15:08:15 +080062 {
63 activation(Status::Activating);
64 }
65 }
66 return SoftwareActivation::requestedActivation(value);
67}
68
Shawn McCarney17c2c942024-12-10 17:32:30 -060069auto Activation::extendedVersion(std::string value) -> std::string
70{
71 auto info = Version::getExtVersionInfo(value);
72 manufacturer = info["manufacturer"];
73 model = info["model"];
74
75 return ExtendedVersion::extendedVersion(value);
76}
77
Patrick Williams374fae52022-07-22 19:26:55 -050078void Activation::unitStateChange(sdbusplus::message_t& msg)
Lei YU12c9f4c2019-09-11 15:08:15 +080079{
80 uint32_t newStateID{};
81 sdbusplus::message::object_path newStateObjPath;
82 std::string newStateUnit{};
83 std::string newStateResult{};
84
Shawn McCarney487e2e12024-11-25 17:19:46 -060085 try
Lei YU12c9f4c2019-09-11 15:08:15 +080086 {
Shawn McCarney487e2e12024-11-25 17:19:46 -060087 // Read the msg and populate each variable
88 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
89
90 if (newStateUnit == psuUpdateUnit)
Lei YU12c9f4c2019-09-11 15:08:15 +080091 {
Shawn McCarney487e2e12024-11-25 17:19:46 -060092 if (newStateResult == "done")
93 {
94 onUpdateDone();
95 }
96 if (newStateResult == "failed" || newStateResult == "dependency")
97 {
98 onUpdateFailed();
99 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800100 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600101 }
102 catch (const std::exception& e)
103 {
104 lg2::error("Unable to handle unit state change event: {ERROR}", "ERROR",
105 e);
Lei YU12c9f4c2019-09-11 15:08:15 +0800106 }
107}
108
Lei YUff83c2a2019-09-12 13:55:18 +0800109bool Activation::doUpdate(const std::string& psuInventoryPath)
110{
Lei YU7f2a2152019-09-16 16:50:18 +0800111 currentUpdatingPsu = psuInventoryPath;
Lei YUff83c2a2019-09-12 13:55:18 +0800112 try
113 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600114 psuUpdateUnit = getUpdateService(currentUpdatingPsu);
Lei YUff83c2a2019-09-12 13:55:18 +0800115 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
116 SYSTEMD_INTERFACE, "StartUnit");
117 method.append(psuUpdateUnit, "replace");
118 bus.call_noreply(method);
119 return true;
120 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600121 catch (const std::exception& e)
Lei YUff83c2a2019-09-12 13:55:18 +0800122 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600123 lg2::error("Error starting update service for PSU {PSU}: {ERROR}",
124 "PSU", psuInventoryPath, "ERROR", e);
Lei YUff83c2a2019-09-12 13:55:18 +0800125 onUpdateFailed();
126 return false;
127 }
128}
129
130bool Activation::doUpdate()
131{
132 // When the queue is empty, all updates are done
133 if (psuQueue.empty())
134 {
135 finishActivation();
136 return true;
137 }
138
139 // Do the update on a PSU
140 const auto& psu = psuQueue.front();
141 return doUpdate(psu);
142}
143
144void Activation::onUpdateDone()
145{
146 auto progress = activationProgress->progress() + progressStep;
147 activationProgress->progress(progress);
148
Lei YU7f2a2152019-09-16 16:50:18 +0800149 // Update the activation association
150 auto assocs = associations();
151 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
152 currentUpdatingPsu);
Lei YU7f2a2152019-09-16 16:50:18 +0800153 associations(assocs);
154
Lei YUffb36532019-10-15 13:55:24 +0800155 activationListener->onUpdateDone(versionId, currentUpdatingPsu);
156 currentUpdatingPsu.clear();
157
Lei YUff83c2a2019-09-12 13:55:18 +0800158 psuQueue.pop();
159 doUpdate(); // Update the next psu
160}
161
162void Activation::onUpdateFailed()
163{
164 // TODO: report an event
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600165 lg2::error("Failed to update PSU {PSU}", "PSU", psuQueue.front());
Lei YUff83c2a2019-09-12 13:55:18 +0800166 std::queue<std::string>().swap(psuQueue); // Clear the queue
167 activation(Status::Failed);
168}
169
170Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800171{
Lei YU63f9e712019-10-12 15:16:55 +0800172 // Check if the activation has file path
173 if (path().empty())
174 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600175 lg2::warning(
176 "No image for the activation, skipped version {VERSION_ID}",
177 "VERSION_ID", versionId);
Lei YU63f9e712019-10-12 15:16:55 +0800178 return activation(); // Return the previous activation status
179 }
Lei YU81c67722019-09-11 16:47:29 +0800180
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -0600181 auto psuPaths = utils::getPSUInventoryPaths(bus);
Lei YU12c9f4c2019-09-11 15:08:15 +0800182 if (psuPaths.empty())
183 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600184 lg2::warning("No PSU inventory found");
Lei YUff83c2a2019-09-12 13:55:18 +0800185 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800186 }
187
Lei YUff83c2a2019-09-12 13:55:18 +0800188 for (const auto& p : psuPaths)
189 {
Shawn McCarney46ea3882024-12-10 11:25:38 -0600190 if (!isPresent(p))
191 {
192 continue;
193 }
Lei YU9edb7332019-09-19 14:46:19 +0800194 if (isCompatible(p))
195 {
Lei YU63f9e712019-10-12 15:16:55 +0800196 if (utils::isAssociated(p, associations()))
197 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600198 lg2::notice("PSU {PSU} is already running the image, skipping",
199 "PSU", p);
Lei YU63f9e712019-10-12 15:16:55 +0800200 continue;
201 }
Lei YU9edb7332019-09-19 14:46:19 +0800202 psuQueue.push(p);
203 }
204 else
205 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600206 lg2::notice("PSU {PSU} is not compatible", "PSU", p);
Lei YU9edb7332019-09-19 14:46:19 +0800207 }
208 }
209
210 if (psuQueue.empty())
211 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600212 lg2::warning("No PSU compatible with the software");
Lei YU63f9e712019-10-12 15:16:55 +0800213 return activation(); // Return the previous activation status
Lei YUff83c2a2019-09-12 13:55:18 +0800214 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800215
Lei YU8afeee52019-10-21 15:25:35 +0800216 if (!activationProgress)
217 {
218 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
219 }
220 if (!activationBlocksTransition)
221 {
222 activationBlocksTransition =
223 std::make_unique<ActivationBlocksTransition>(bus, objPath);
224 }
225
Lei YUff83c2a2019-09-12 13:55:18 +0800226 // The progress to be increased for each successful update of PSU
227 // E.g. in case we have 4 PSUs:
Manojkiran Eda33cf9f02024-06-17 14:40:44 +0530228 // 1. Initial progress is 10
Lei YUff83c2a2019-09-12 13:55:18 +0800229 // 2. Add 20 after each update is done, so we will see progress to be 30,
230 // 50, 70, 90
231 // 3. When all PSUs are updated, it will be 100 and the interface is
232 // removed.
233 progressStep = 80 / psuQueue.size();
234 if (doUpdate())
235 {
236 activationProgress->progress(10);
237 return Status::Activating;
238 }
239 else
240 {
241 return Status::Failed;
242 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800243}
244
245void Activation::finishActivation()
246{
Lei YU2e0e2de2019-09-26 16:42:23 +0800247 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800248 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800249
Lei YUd0bbfa92019-09-11 16:10:54 +0800250 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800251
Lei YU99301372019-09-29 16:27:12 +0800252 associationInterface->createActiveAssociation(objPath);
253 associationInterface->addFunctionalAssociation(objPath);
Lei YUa8b966f2020-03-18 10:32:24 +0800254 associationInterface->addUpdateableAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800255
Lei YU1517f5f2019-10-14 16:44:42 +0800256 // Reset RequestedActivations to none so that it could be activated in
257 // future
258 requestedActivation(SoftwareActivation::RequestedActivations::None);
Lei YU12c9f4c2019-09-11 15:08:15 +0800259 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800260}
261
Lei YUd0bbfa92019-09-11 16:10:54 +0800262void Activation::deleteImageManagerObject()
263{
264 // Get the Delete object for <versionID> inside image_manager
Shawn McCarney487e2e12024-11-25 17:19:46 -0600265 std::vector<std::string> services;
Lei YUd0bbfa92019-09-11 16:10:54 +0800266 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
Shawn McCarney487e2e12024-11-25 17:19:46 -0600267 try
268 {
269 services = utils::getServices(bus, objPath.c_str(), deleteInterface);
270 }
271 catch (const std::exception& e)
272 {
273 lg2::error(
274 "Unable to find services to Delete object path {PATH}: {ERROR}",
275 "PATH", objPath, "ERROR", e);
276 }
Lei YUd0bbfa92019-09-11 16:10:54 +0800277
278 // We need to find the phosphor-version-software-manager's version service
279 // to invoke the delete interface
Shawn McCarney487e2e12024-11-25 17:19:46 -0600280 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
281 std::string versionService;
Lei YUd0bbfa92019-09-11 16:10:54 +0800282 for (const auto& service : services)
283 {
284 if (service.find(versionServiceStr) != std::string::npos)
285 {
286 versionService = service;
287 break;
288 }
289 }
290 if (versionService.empty())
291 {
Lei YUe8945ea2019-09-29 17:25:31 +0800292 // When updating a stored image, there is no version object created by
293 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800294 return;
295 }
296
297 // Call the Delete object for <versionID> inside image_manager
Lei YUd0bbfa92019-09-11 16:10:54 +0800298 try
299 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600300 auto method = bus.new_method_call(
301 versionService.c_str(), objPath.c_str(), deleteInterface, "Delete");
Lei YUd0bbfa92019-09-11 16:10:54 +0800302 bus.call(method);
303 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600304 catch (const std::exception& e)
Lei YUd0bbfa92019-09-11 16:10:54 +0800305 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600306 lg2::error("Unable to Delete object path {PATH}: {ERROR}", "PATH",
307 objPath, "ERROR", e);
Lei YUd0bbfa92019-09-11 16:10:54 +0800308 }
309}
310
Shawn McCarney46ea3882024-12-10 11:25:38 -0600311bool Activation::isPresent(const std::string& psuInventoryPath)
312{
313 bool isPres{false};
314 try
315 {
316 auto service =
317 utils::getService(bus, psuInventoryPath.c_str(), ITEM_IFACE);
318 isPres = utils::getProperty<bool>(bus, service.c_str(),
319 psuInventoryPath.c_str(), ITEM_IFACE,
320 PRESENT);
321 }
322 catch (const std::exception& e)
323 {
324 // Treat as a warning condition and assume the PSU is missing. The
325 // D-Bus information might not be available if the PSU is missing.
326 lg2::warning("Unable to determine if PSU {PSU} is present: {ERROR}",
327 "PSU", psuInventoryPath, "ERROR", e);
328 }
329 return isPres;
330}
331
Lei YU9edb7332019-09-19 14:46:19 +0800332bool Activation::isCompatible(const std::string& psuInventoryPath)
333{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600334 bool isCompat{false};
335 try
Lei YU9edb7332019-09-19 14:46:19 +0800336 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600337 auto service =
338 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
339 auto psuManufacturer = utils::getProperty<std::string>(
340 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
341 MANUFACTURER);
342 auto psuModel = utils::getModel(psuInventoryPath);
Lei YU9edb7332019-09-19 14:46:19 +0800343 // The model shall match
Shawn McCarney487e2e12024-11-25 17:19:46 -0600344 if (psuModel == model)
345 {
346 // If PSU inventory has manufacturer property, it shall match
347 if (psuManufacturer.empty() || (psuManufacturer == manufacturer))
348 {
349 isCompat = true;
350 }
351 }
Lei YU9edb7332019-09-19 14:46:19 +0800352 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600353 catch (const std::exception& e)
Lei YU9edb7332019-09-19 14:46:19 +0800354 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600355 lg2::error(
356 "Unable to determine if PSU {PSU} is compatible with firmware "
357 "versionId {VERSION_ID}: {ERROR}",
358 "PSU", psuInventoryPath, "VERSION_ID", versionId, "ERROR", e);
Lei YU9edb7332019-09-19 14:46:19 +0800359 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600360 return isCompat;
Lei YU9edb7332019-09-19 14:46:19 +0800361}
362
Lei YU2e0e2de2019-09-26 16:42:23 +0800363void Activation::storeImage()
364{
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500365 // If image is not in IMG_DIR (temporary storage) then exit. We don't want
366 // to copy from IMG_DIR_PERSIST or IMG_DIR_BUILTIN.
Lei YUe8945ea2019-09-29 17:25:31 +0800367 auto src = path();
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500368 if (!src.starts_with(IMG_DIR))
Lei YUe8945ea2019-09-29 17:25:31 +0800369 {
Lei YUe8945ea2019-09-29 17:25:31 +0800370 return;
371 }
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500372
373 // Store image in persistent dir separated by model
374 // and only store the latest one by removing old ones
375 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YU2e0e2de2019-09-26 16:42:23 +0800376 try
377 {
378 fs::remove_all(dst);
379 fs::create_directories(dst);
380 fs::copy(src, dst);
381 path(dst.string()); // Update the FilePath interface
382 }
383 catch (const fs::filesystem_error& e)
384 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600385 lg2::error("Error storing PSU image: src={SRC}, dst={DST}: {ERROR}",
386 "SRC", src, "DST", dst, "ERROR", e);
Lei YU2e0e2de2019-09-26 16:42:23 +0800387 }
388}
389
Lei YUe8945ea2019-09-29 17:25:31 +0800390std::string Activation::getUpdateService(const std::string& psuInventoryPath)
391{
392 fs::path imagePath(path());
393
394 // The systemd unit shall be escaped
395 std::string args = psuInventoryPath;
396 args += "\\x20";
397 args += imagePath;
398 std::replace(args.begin(), args.end(), '/', '-');
399
400 std::string service = PSU_UPDATE_SERVICE;
401 auto p = service.find('@');
Shawn McCarney487e2e12024-11-25 17:19:46 -0600402 if (p == std::string::npos)
403 {
404 throw std::runtime_error{std::format(
405 "Invalid PSU update service name: {}", PSU_UPDATE_SERVICE)};
406 }
Lei YUe8945ea2019-09-29 17:25:31 +0800407 service.insert(p + 1, args);
408 return service;
409}
410
Lei YU8afeee52019-10-21 15:25:35 +0800411void ActivationBlocksTransition::enableRebootGuard()
412{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600413 lg2::info("PSU image activating - BMC reboots are disabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800414
415 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
416 SYSTEMD_INTERFACE, "StartUnit");
417 method.append("reboot-guard-enable.service", "replace");
418 bus.call_noreply_noerror(method);
419}
420
421void ActivationBlocksTransition::disableRebootGuard()
422{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600423 lg2::info("PSU activation has ended - BMC reboots are re-enabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800424
425 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
426 SYSTEMD_INTERFACE, "StartUnit");
427 method.append("reboot-guard-disable.service", "replace");
428 bus.call_noreply_noerror(method);
429}
430
Lei YU01539e72019-07-31 10:57:38 +0800431} // namespace updater
432} // namespace software
433} // namespace phosphor