blob: dfc8fd7dd207d82ff7ea8374360297a3e4dc53c7 [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{
Shawn McCarney56760ee2025-02-04 14:11:54 -060053 if (value == RequestedActivations::Active)
Lei YU12c9f4c2019-09-11 15:08:15 +080054 {
Shawn McCarney7cb5d052025-01-03 18:27:45 -060055 if (SoftwareActivation::requestedActivation() !=
Shawn McCarney56760ee2025-02-04 14:11:54 -060056 RequestedActivations::Active)
Lei YU12c9f4c2019-09-11 15:08:15 +080057 {
Shawn McCarney7cb5d052025-01-03 18:27:45 -060058 // PSU image could be activated even when it's in active,
59 // e.g. in case a PSU is replaced and has a older image, it will be
60 // updated with the running PSU image that is stored in BMC.
61 if ((activation() == Status::Ready) ||
62 (activation() == Status::Failed) ||
63 (activation() == Status::Active))
64 {
Shawn McCarney56760ee2025-02-04 14:11:54 -060065 if (activation(Status::Activating) != Status::Activating)
66 {
67 // Activation attempt failed
68 value = RequestedActivations::None;
69 }
Shawn McCarney7cb5d052025-01-03 18:27:45 -060070 }
71 }
72 else if (activation() == Status::Activating)
73 {
74 // Activation was requested when one was already in progress.
75 // Activate again once the current activation is done. New PSU
76 // information may have been found on D-Bus, or a new PSU may have
77 // been plugged in.
78 shouldActivateAgain = true;
Lei YU12c9f4c2019-09-11 15:08:15 +080079 }
80 }
81 return SoftwareActivation::requestedActivation(value);
82}
83
Shawn McCarney17c2c942024-12-10 17:32:30 -060084auto Activation::extendedVersion(std::string value) -> std::string
85{
86 auto info = Version::getExtVersionInfo(value);
87 manufacturer = info["manufacturer"];
88 model = info["model"];
89
90 return ExtendedVersion::extendedVersion(value);
91}
92
Patrick Williams374fae52022-07-22 19:26:55 -050093void Activation::unitStateChange(sdbusplus::message_t& msg)
Lei YU12c9f4c2019-09-11 15:08:15 +080094{
95 uint32_t newStateID{};
96 sdbusplus::message::object_path newStateObjPath;
97 std::string newStateUnit{};
98 std::string newStateResult{};
99
Shawn McCarney487e2e12024-11-25 17:19:46 -0600100 try
Lei YU12c9f4c2019-09-11 15:08:15 +0800101 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600102 // Read the msg and populate each variable
103 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
104
105 if (newStateUnit == psuUpdateUnit)
Lei YU12c9f4c2019-09-11 15:08:15 +0800106 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600107 if (newStateResult == "done")
108 {
109 onUpdateDone();
110 }
111 if (newStateResult == "failed" || newStateResult == "dependency")
112 {
113 onUpdateFailed();
114 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800115 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600116 }
117 catch (const std::exception& e)
118 {
119 lg2::error("Unable to handle unit state change event: {ERROR}", "ERROR",
120 e);
Lei YU12c9f4c2019-09-11 15:08:15 +0800121 }
122}
123
Lei YUff83c2a2019-09-12 13:55:18 +0800124bool Activation::doUpdate(const std::string& psuInventoryPath)
125{
Lei YU7f2a2152019-09-16 16:50:18 +0800126 currentUpdatingPsu = psuInventoryPath;
Lei YUff83c2a2019-09-12 13:55:18 +0800127 try
128 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600129 psuUpdateUnit = getUpdateService(currentUpdatingPsu);
Lei YUff83c2a2019-09-12 13:55:18 +0800130 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
131 SYSTEMD_INTERFACE, "StartUnit");
132 method.append(psuUpdateUnit, "replace");
133 bus.call_noreply(method);
134 return true;
135 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600136 catch (const std::exception& e)
Lei YUff83c2a2019-09-12 13:55:18 +0800137 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600138 lg2::error("Error starting update service for PSU {PSU}: {ERROR}",
139 "PSU", psuInventoryPath, "ERROR", e);
Lei YUff83c2a2019-09-12 13:55:18 +0800140 onUpdateFailed();
141 return false;
142 }
143}
144
145bool Activation::doUpdate()
146{
147 // When the queue is empty, all updates are done
148 if (psuQueue.empty())
149 {
150 finishActivation();
151 return true;
152 }
153
154 // Do the update on a PSU
155 const auto& psu = psuQueue.front();
156 return doUpdate(psu);
157}
158
159void Activation::onUpdateDone()
160{
161 auto progress = activationProgress->progress() + progressStep;
162 activationProgress->progress(progress);
163
Lei YU7f2a2152019-09-16 16:50:18 +0800164 // Update the activation association
165 auto assocs = associations();
166 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
167 currentUpdatingPsu);
Lei YU7f2a2152019-09-16 16:50:18 +0800168 associations(assocs);
169
Lei YUffb36532019-10-15 13:55:24 +0800170 activationListener->onUpdateDone(versionId, currentUpdatingPsu);
171 currentUpdatingPsu.clear();
172
Lei YUff83c2a2019-09-12 13:55:18 +0800173 psuQueue.pop();
174 doUpdate(); // Update the next psu
175}
176
177void Activation::onUpdateFailed()
178{
179 // TODO: report an event
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600180 lg2::error("Failed to update PSU {PSU}", "PSU", psuQueue.front());
Lei YUff83c2a2019-09-12 13:55:18 +0800181 std::queue<std::string>().swap(psuQueue); // Clear the queue
182 activation(Status::Failed);
Shawn McCarney56760ee2025-02-04 14:11:54 -0600183 requestedActivation(RequestedActivations::None);
Shawn McCarney7cb5d052025-01-03 18:27:45 -0600184 shouldActivateAgain = false;
Lei YUff83c2a2019-09-12 13:55:18 +0800185}
186
187Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800188{
Lei YU63f9e712019-10-12 15:16:55 +0800189 // Check if the activation has file path
190 if (path().empty())
191 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600192 lg2::warning(
193 "No image for the activation, skipped version {VERSION_ID}",
194 "VERSION_ID", versionId);
Lei YU63f9e712019-10-12 15:16:55 +0800195 return activation(); // Return the previous activation status
196 }
Lei YU81c67722019-09-11 16:47:29 +0800197
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -0600198 auto psuPaths = utils::getPSUInventoryPaths(bus);
Lei YU12c9f4c2019-09-11 15:08:15 +0800199 if (psuPaths.empty())
200 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600201 lg2::warning("No PSU inventory found");
Lei YUff83c2a2019-09-12 13:55:18 +0800202 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800203 }
204
Lei YUff83c2a2019-09-12 13:55:18 +0800205 for (const auto& p : psuPaths)
206 {
Shawn McCarney46ea3882024-12-10 11:25:38 -0600207 if (!isPresent(p))
208 {
209 continue;
210 }
Lei YU9edb7332019-09-19 14:46:19 +0800211 if (isCompatible(p))
212 {
Lei YU63f9e712019-10-12 15:16:55 +0800213 if (utils::isAssociated(p, associations()))
214 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600215 lg2::notice("PSU {PSU} is already running the image, skipping",
216 "PSU", p);
Lei YU63f9e712019-10-12 15:16:55 +0800217 continue;
218 }
Lei YU9edb7332019-09-19 14:46:19 +0800219 psuQueue.push(p);
220 }
221 else
222 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600223 lg2::notice("PSU {PSU} is not compatible", "PSU", p);
Lei YU9edb7332019-09-19 14:46:19 +0800224 }
225 }
226
227 if (psuQueue.empty())
228 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600229 lg2::warning("No PSU compatible with the software");
Lei YU63f9e712019-10-12 15:16:55 +0800230 return activation(); // Return the previous activation status
Lei YUff83c2a2019-09-12 13:55:18 +0800231 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800232
Lei YU8afeee52019-10-21 15:25:35 +0800233 if (!activationProgress)
234 {
235 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
236 }
237 if (!activationBlocksTransition)
238 {
239 activationBlocksTransition =
240 std::make_unique<ActivationBlocksTransition>(bus, objPath);
241 }
242
Lei YUff83c2a2019-09-12 13:55:18 +0800243 // The progress to be increased for each successful update of PSU
244 // E.g. in case we have 4 PSUs:
Manojkiran Eda33cf9f02024-06-17 14:40:44 +0530245 // 1. Initial progress is 10
Lei YUff83c2a2019-09-12 13:55:18 +0800246 // 2. Add 20 after each update is done, so we will see progress to be 30,
247 // 50, 70, 90
248 // 3. When all PSUs are updated, it will be 100 and the interface is
249 // removed.
250 progressStep = 80 / psuQueue.size();
251 if (doUpdate())
252 {
253 activationProgress->progress(10);
254 return Status::Activating;
255 }
256 else
257 {
258 return Status::Failed;
259 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800260}
261
262void Activation::finishActivation()
263{
Lei YU2e0e2de2019-09-26 16:42:23 +0800264 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800265 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800266
Lei YUd0bbfa92019-09-11 16:10:54 +0800267 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800268
Lei YU99301372019-09-29 16:27:12 +0800269 associationInterface->createActiveAssociation(objPath);
270 associationInterface->addFunctionalAssociation(objPath);
Lei YUa8b966f2020-03-18 10:32:24 +0800271 associationInterface->addUpdateableAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800272
Lei YU1517f5f2019-10-14 16:44:42 +0800273 // Reset RequestedActivations to none so that it could be activated in
274 // future
Shawn McCarney56760ee2025-02-04 14:11:54 -0600275 requestedActivation(RequestedActivations::None);
Lei YU12c9f4c2019-09-11 15:08:15 +0800276 activation(Status::Active);
Shawn McCarney7cb5d052025-01-03 18:27:45 -0600277
278 // Automatically restart activation if a request occurred while code update
279 // was already in progress. New PSU information may have been found on
280 // D-Bus, or a new PSU may have been plugged in.
281 if (shouldActivateAgain)
282 {
283 shouldActivateAgain = false;
Shawn McCarney56760ee2025-02-04 14:11:54 -0600284 requestedActivation(RequestedActivations::Active);
Shawn McCarney7cb5d052025-01-03 18:27:45 -0600285 }
Lei YU01539e72019-07-31 10:57:38 +0800286}
287
Lei YUd0bbfa92019-09-11 16:10:54 +0800288void Activation::deleteImageManagerObject()
289{
290 // Get the Delete object for <versionID> inside image_manager
Shawn McCarney487e2e12024-11-25 17:19:46 -0600291 std::vector<std::string> services;
Lei YUd0bbfa92019-09-11 16:10:54 +0800292 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
Shawn McCarney487e2e12024-11-25 17:19:46 -0600293 try
294 {
295 services = utils::getServices(bus, objPath.c_str(), deleteInterface);
296 }
297 catch (const std::exception& e)
298 {
299 lg2::error(
300 "Unable to find services to Delete object path {PATH}: {ERROR}",
301 "PATH", objPath, "ERROR", e);
302 }
Lei YUd0bbfa92019-09-11 16:10:54 +0800303
304 // We need to find the phosphor-version-software-manager's version service
305 // to invoke the delete interface
Shawn McCarney487e2e12024-11-25 17:19:46 -0600306 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
307 std::string versionService;
Lei YUd0bbfa92019-09-11 16:10:54 +0800308 for (const auto& service : services)
309 {
310 if (service.find(versionServiceStr) != std::string::npos)
311 {
312 versionService = service;
313 break;
314 }
315 }
316 if (versionService.empty())
317 {
Lei YUe8945ea2019-09-29 17:25:31 +0800318 // When updating a stored image, there is no version object created by
319 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800320 return;
321 }
322
323 // Call the Delete object for <versionID> inside image_manager
Lei YUd0bbfa92019-09-11 16:10:54 +0800324 try
325 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600326 auto method = bus.new_method_call(
327 versionService.c_str(), objPath.c_str(), deleteInterface, "Delete");
Lei YUd0bbfa92019-09-11 16:10:54 +0800328 bus.call(method);
329 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600330 catch (const std::exception& e)
Lei YUd0bbfa92019-09-11 16:10:54 +0800331 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600332 lg2::error("Unable to Delete object path {PATH}: {ERROR}", "PATH",
333 objPath, "ERROR", e);
Lei YUd0bbfa92019-09-11 16:10:54 +0800334 }
335}
336
Shawn McCarney46ea3882024-12-10 11:25:38 -0600337bool Activation::isPresent(const std::string& psuInventoryPath)
338{
339 bool isPres{false};
340 try
341 {
342 auto service =
343 utils::getService(bus, psuInventoryPath.c_str(), ITEM_IFACE);
344 isPres = utils::getProperty<bool>(bus, service.c_str(),
345 psuInventoryPath.c_str(), ITEM_IFACE,
346 PRESENT);
347 }
348 catch (const std::exception& e)
349 {
350 // Treat as a warning condition and assume the PSU is missing. The
351 // D-Bus information might not be available if the PSU is missing.
352 lg2::warning("Unable to determine if PSU {PSU} is present: {ERROR}",
353 "PSU", psuInventoryPath, "ERROR", e);
354 }
355 return isPres;
356}
357
Lei YU9edb7332019-09-19 14:46:19 +0800358bool Activation::isCompatible(const std::string& psuInventoryPath)
359{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600360 bool isCompat{false};
361 try
Lei YU9edb7332019-09-19 14:46:19 +0800362 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600363 auto service =
364 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
365 auto psuManufacturer = utils::getProperty<std::string>(
366 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
367 MANUFACTURER);
368 auto psuModel = utils::getModel(psuInventoryPath);
Lei YU9edb7332019-09-19 14:46:19 +0800369 // The model shall match
Shawn McCarney487e2e12024-11-25 17:19:46 -0600370 if (psuModel == model)
371 {
372 // If PSU inventory has manufacturer property, it shall match
373 if (psuManufacturer.empty() || (psuManufacturer == manufacturer))
374 {
375 isCompat = true;
376 }
377 }
Lei YU9edb7332019-09-19 14:46:19 +0800378 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600379 catch (const std::exception& e)
Lei YU9edb7332019-09-19 14:46:19 +0800380 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600381 lg2::error(
382 "Unable to determine if PSU {PSU} is compatible with firmware "
383 "versionId {VERSION_ID}: {ERROR}",
384 "PSU", psuInventoryPath, "VERSION_ID", versionId, "ERROR", e);
Lei YU9edb7332019-09-19 14:46:19 +0800385 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600386 return isCompat;
Lei YU9edb7332019-09-19 14:46:19 +0800387}
388
Lei YU2e0e2de2019-09-26 16:42:23 +0800389void Activation::storeImage()
390{
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500391 // If image is not in IMG_DIR (temporary storage) then exit. We don't want
392 // to copy from IMG_DIR_PERSIST or IMG_DIR_BUILTIN.
Lei YUe8945ea2019-09-29 17:25:31 +0800393 auto src = path();
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500394 if (!src.starts_with(IMG_DIR))
Lei YUe8945ea2019-09-29 17:25:31 +0800395 {
Lei YUe8945ea2019-09-29 17:25:31 +0800396 return;
397 }
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500398
399 // Store image in persistent dir separated by model
400 // and only store the latest one by removing old ones
401 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YU2e0e2de2019-09-26 16:42:23 +0800402 try
403 {
404 fs::remove_all(dst);
405 fs::create_directories(dst);
406 fs::copy(src, dst);
407 path(dst.string()); // Update the FilePath interface
408 }
409 catch (const fs::filesystem_error& e)
410 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600411 lg2::error("Error storing PSU image: src={SRC}, dst={DST}: {ERROR}",
412 "SRC", src, "DST", dst, "ERROR", e);
Lei YU2e0e2de2019-09-26 16:42:23 +0800413 }
414}
415
Lei YUe8945ea2019-09-29 17:25:31 +0800416std::string Activation::getUpdateService(const std::string& psuInventoryPath)
417{
418 fs::path imagePath(path());
419
420 // The systemd unit shall be escaped
421 std::string args = psuInventoryPath;
422 args += "\\x20";
423 args += imagePath;
424 std::replace(args.begin(), args.end(), '/', '-');
425
426 std::string service = PSU_UPDATE_SERVICE;
427 auto p = service.find('@');
Shawn McCarney487e2e12024-11-25 17:19:46 -0600428 if (p == std::string::npos)
429 {
430 throw std::runtime_error{std::format(
431 "Invalid PSU update service name: {}", PSU_UPDATE_SERVICE)};
432 }
Lei YUe8945ea2019-09-29 17:25:31 +0800433 service.insert(p + 1, args);
434 return service;
435}
436
Lei YU8afeee52019-10-21 15:25:35 +0800437void ActivationBlocksTransition::enableRebootGuard()
438{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600439 lg2::info("PSU image activating - BMC reboots are disabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800440
441 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
442 SYSTEMD_INTERFACE, "StartUnit");
443 method.append("reboot-guard-enable.service", "replace");
444 bus.call_noreply_noerror(method);
445}
446
447void ActivationBlocksTransition::disableRebootGuard()
448{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600449 lg2::info("PSU activation has ended - BMC reboots are re-enabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800450
451 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
452 SYSTEMD_INTERFACE, "StartUnit");
453 method.append("reboot-guard-disable.service", "replace");
454 bus.call_noreply_noerror(method);
455}
456
Lei YU01539e72019-07-31 10:57:38 +0800457} // namespace updater
458} // namespace software
459} // namespace phosphor