blob: 11da384042eb93a798c7cc61289c75eaa3c6226c [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 McCarney7cb5d052025-01-03 18:27:45 -060053 if (value == SoftwareActivation::RequestedActivations::Active)
Lei YU12c9f4c2019-09-11 15:08:15 +080054 {
Shawn McCarney7cb5d052025-01-03 18:27:45 -060055 if (SoftwareActivation::requestedActivation() !=
56 SoftwareActivation::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 {
65 activation(Status::Activating);
66 }
67 }
68 else if (activation() == Status::Activating)
69 {
70 // Activation was requested when one was already in progress.
71 // Activate again once the current activation is done. New PSU
72 // information may have been found on D-Bus, or a new PSU may have
73 // been plugged in.
74 shouldActivateAgain = true;
Lei YU12c9f4c2019-09-11 15:08:15 +080075 }
76 }
77 return SoftwareActivation::requestedActivation(value);
78}
79
Shawn McCarney17c2c942024-12-10 17:32:30 -060080auto Activation::extendedVersion(std::string value) -> std::string
81{
82 auto info = Version::getExtVersionInfo(value);
83 manufacturer = info["manufacturer"];
84 model = info["model"];
85
86 return ExtendedVersion::extendedVersion(value);
87}
88
Patrick Williams374fae52022-07-22 19:26:55 -050089void Activation::unitStateChange(sdbusplus::message_t& msg)
Lei YU12c9f4c2019-09-11 15:08:15 +080090{
91 uint32_t newStateID{};
92 sdbusplus::message::object_path newStateObjPath;
93 std::string newStateUnit{};
94 std::string newStateResult{};
95
Shawn McCarney487e2e12024-11-25 17:19:46 -060096 try
Lei YU12c9f4c2019-09-11 15:08:15 +080097 {
Shawn McCarney487e2e12024-11-25 17:19:46 -060098 // Read the msg and populate each variable
99 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
100
101 if (newStateUnit == psuUpdateUnit)
Lei YU12c9f4c2019-09-11 15:08:15 +0800102 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600103 if (newStateResult == "done")
104 {
105 onUpdateDone();
106 }
107 if (newStateResult == "failed" || newStateResult == "dependency")
108 {
109 onUpdateFailed();
110 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800111 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600112 }
113 catch (const std::exception& e)
114 {
115 lg2::error("Unable to handle unit state change event: {ERROR}", "ERROR",
116 e);
Lei YU12c9f4c2019-09-11 15:08:15 +0800117 }
118}
119
Lei YUff83c2a2019-09-12 13:55:18 +0800120bool Activation::doUpdate(const std::string& psuInventoryPath)
121{
Lei YU7f2a2152019-09-16 16:50:18 +0800122 currentUpdatingPsu = psuInventoryPath;
Lei YUff83c2a2019-09-12 13:55:18 +0800123 try
124 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600125 psuUpdateUnit = getUpdateService(currentUpdatingPsu);
Lei YUff83c2a2019-09-12 13:55:18 +0800126 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
127 SYSTEMD_INTERFACE, "StartUnit");
128 method.append(psuUpdateUnit, "replace");
129 bus.call_noreply(method);
130 return true;
131 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600132 catch (const std::exception& e)
Lei YUff83c2a2019-09-12 13:55:18 +0800133 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600134 lg2::error("Error starting update service for PSU {PSU}: {ERROR}",
135 "PSU", psuInventoryPath, "ERROR", e);
Lei YUff83c2a2019-09-12 13:55:18 +0800136 onUpdateFailed();
137 return false;
138 }
139}
140
141bool Activation::doUpdate()
142{
143 // When the queue is empty, all updates are done
144 if (psuQueue.empty())
145 {
146 finishActivation();
147 return true;
148 }
149
150 // Do the update on a PSU
151 const auto& psu = psuQueue.front();
152 return doUpdate(psu);
153}
154
155void Activation::onUpdateDone()
156{
157 auto progress = activationProgress->progress() + progressStep;
158 activationProgress->progress(progress);
159
Lei YU7f2a2152019-09-16 16:50:18 +0800160 // Update the activation association
161 auto assocs = associations();
162 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
163 currentUpdatingPsu);
Lei YU7f2a2152019-09-16 16:50:18 +0800164 associations(assocs);
165
Lei YUffb36532019-10-15 13:55:24 +0800166 activationListener->onUpdateDone(versionId, currentUpdatingPsu);
167 currentUpdatingPsu.clear();
168
Lei YUff83c2a2019-09-12 13:55:18 +0800169 psuQueue.pop();
170 doUpdate(); // Update the next psu
171}
172
173void Activation::onUpdateFailed()
174{
175 // TODO: report an event
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600176 lg2::error("Failed to update PSU {PSU}", "PSU", psuQueue.front());
Lei YUff83c2a2019-09-12 13:55:18 +0800177 std::queue<std::string>().swap(psuQueue); // Clear the queue
178 activation(Status::Failed);
Shawn McCarney7cb5d052025-01-03 18:27:45 -0600179 shouldActivateAgain = false;
Lei YUff83c2a2019-09-12 13:55:18 +0800180}
181
182Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800183{
Lei YU63f9e712019-10-12 15:16:55 +0800184 // Check if the activation has file path
185 if (path().empty())
186 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600187 lg2::warning(
188 "No image for the activation, skipped version {VERSION_ID}",
189 "VERSION_ID", versionId);
Lei YU63f9e712019-10-12 15:16:55 +0800190 return activation(); // Return the previous activation status
191 }
Lei YU81c67722019-09-11 16:47:29 +0800192
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -0600193 auto psuPaths = utils::getPSUInventoryPaths(bus);
Lei YU12c9f4c2019-09-11 15:08:15 +0800194 if (psuPaths.empty())
195 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600196 lg2::warning("No PSU inventory found");
Lei YUff83c2a2019-09-12 13:55:18 +0800197 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800198 }
199
Lei YUff83c2a2019-09-12 13:55:18 +0800200 for (const auto& p : psuPaths)
201 {
Shawn McCarney46ea3882024-12-10 11:25:38 -0600202 if (!isPresent(p))
203 {
204 continue;
205 }
Lei YU9edb7332019-09-19 14:46:19 +0800206 if (isCompatible(p))
207 {
Lei YU63f9e712019-10-12 15:16:55 +0800208 if (utils::isAssociated(p, associations()))
209 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600210 lg2::notice("PSU {PSU} is already running the image, skipping",
211 "PSU", p);
Lei YU63f9e712019-10-12 15:16:55 +0800212 continue;
213 }
Lei YU9edb7332019-09-19 14:46:19 +0800214 psuQueue.push(p);
215 }
216 else
217 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600218 lg2::notice("PSU {PSU} is not compatible", "PSU", p);
Lei YU9edb7332019-09-19 14:46:19 +0800219 }
220 }
221
222 if (psuQueue.empty())
223 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600224 lg2::warning("No PSU compatible with the software");
Lei YU63f9e712019-10-12 15:16:55 +0800225 return activation(); // Return the previous activation status
Lei YUff83c2a2019-09-12 13:55:18 +0800226 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800227
Lei YU8afeee52019-10-21 15:25:35 +0800228 if (!activationProgress)
229 {
230 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
231 }
232 if (!activationBlocksTransition)
233 {
234 activationBlocksTransition =
235 std::make_unique<ActivationBlocksTransition>(bus, objPath);
236 }
237
Lei YUff83c2a2019-09-12 13:55:18 +0800238 // The progress to be increased for each successful update of PSU
239 // E.g. in case we have 4 PSUs:
Manojkiran Eda33cf9f02024-06-17 14:40:44 +0530240 // 1. Initial progress is 10
Lei YUff83c2a2019-09-12 13:55:18 +0800241 // 2. Add 20 after each update is done, so we will see progress to be 30,
242 // 50, 70, 90
243 // 3. When all PSUs are updated, it will be 100 and the interface is
244 // removed.
245 progressStep = 80 / psuQueue.size();
246 if (doUpdate())
247 {
248 activationProgress->progress(10);
249 return Status::Activating;
250 }
251 else
252 {
253 return Status::Failed;
254 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800255}
256
257void Activation::finishActivation()
258{
Lei YU2e0e2de2019-09-26 16:42:23 +0800259 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800260 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800261
Lei YUd0bbfa92019-09-11 16:10:54 +0800262 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800263
Lei YU99301372019-09-29 16:27:12 +0800264 associationInterface->createActiveAssociation(objPath);
265 associationInterface->addFunctionalAssociation(objPath);
Lei YUa8b966f2020-03-18 10:32:24 +0800266 associationInterface->addUpdateableAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800267
Lei YU1517f5f2019-10-14 16:44:42 +0800268 // Reset RequestedActivations to none so that it could be activated in
269 // future
270 requestedActivation(SoftwareActivation::RequestedActivations::None);
Lei YU12c9f4c2019-09-11 15:08:15 +0800271 activation(Status::Active);
Shawn McCarney7cb5d052025-01-03 18:27:45 -0600272
273 // Automatically restart activation if a request occurred while code update
274 // was already in progress. New PSU information may have been found on
275 // D-Bus, or a new PSU may have been plugged in.
276 if (shouldActivateAgain)
277 {
278 shouldActivateAgain = false;
279 requestedActivation(SoftwareActivation::RequestedActivations::Active);
280 }
Lei YU01539e72019-07-31 10:57:38 +0800281}
282
Lei YUd0bbfa92019-09-11 16:10:54 +0800283void Activation::deleteImageManagerObject()
284{
285 // Get the Delete object for <versionID> inside image_manager
Shawn McCarney487e2e12024-11-25 17:19:46 -0600286 std::vector<std::string> services;
Lei YUd0bbfa92019-09-11 16:10:54 +0800287 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
Shawn McCarney487e2e12024-11-25 17:19:46 -0600288 try
289 {
290 services = utils::getServices(bus, objPath.c_str(), deleteInterface);
291 }
292 catch (const std::exception& e)
293 {
294 lg2::error(
295 "Unable to find services to Delete object path {PATH}: {ERROR}",
296 "PATH", objPath, "ERROR", e);
297 }
Lei YUd0bbfa92019-09-11 16:10:54 +0800298
299 // We need to find the phosphor-version-software-manager's version service
300 // to invoke the delete interface
Shawn McCarney487e2e12024-11-25 17:19:46 -0600301 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
302 std::string versionService;
Lei YUd0bbfa92019-09-11 16:10:54 +0800303 for (const auto& service : services)
304 {
305 if (service.find(versionServiceStr) != std::string::npos)
306 {
307 versionService = service;
308 break;
309 }
310 }
311 if (versionService.empty())
312 {
Lei YUe8945ea2019-09-29 17:25:31 +0800313 // When updating a stored image, there is no version object created by
314 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800315 return;
316 }
317
318 // Call the Delete object for <versionID> inside image_manager
Lei YUd0bbfa92019-09-11 16:10:54 +0800319 try
320 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600321 auto method = bus.new_method_call(
322 versionService.c_str(), objPath.c_str(), deleteInterface, "Delete");
Lei YUd0bbfa92019-09-11 16:10:54 +0800323 bus.call(method);
324 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600325 catch (const std::exception& e)
Lei YUd0bbfa92019-09-11 16:10:54 +0800326 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600327 lg2::error("Unable to Delete object path {PATH}: {ERROR}", "PATH",
328 objPath, "ERROR", e);
Lei YUd0bbfa92019-09-11 16:10:54 +0800329 }
330}
331
Shawn McCarney46ea3882024-12-10 11:25:38 -0600332bool Activation::isPresent(const std::string& psuInventoryPath)
333{
334 bool isPres{false};
335 try
336 {
337 auto service =
338 utils::getService(bus, psuInventoryPath.c_str(), ITEM_IFACE);
339 isPres = utils::getProperty<bool>(bus, service.c_str(),
340 psuInventoryPath.c_str(), ITEM_IFACE,
341 PRESENT);
342 }
343 catch (const std::exception& e)
344 {
345 // Treat as a warning condition and assume the PSU is missing. The
346 // D-Bus information might not be available if the PSU is missing.
347 lg2::warning("Unable to determine if PSU {PSU} is present: {ERROR}",
348 "PSU", psuInventoryPath, "ERROR", e);
349 }
350 return isPres;
351}
352
Lei YU9edb7332019-09-19 14:46:19 +0800353bool Activation::isCompatible(const std::string& psuInventoryPath)
354{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600355 bool isCompat{false};
356 try
Lei YU9edb7332019-09-19 14:46:19 +0800357 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600358 auto service =
359 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
360 auto psuManufacturer = utils::getProperty<std::string>(
361 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
362 MANUFACTURER);
363 auto psuModel = utils::getModel(psuInventoryPath);
Lei YU9edb7332019-09-19 14:46:19 +0800364 // The model shall match
Shawn McCarney487e2e12024-11-25 17:19:46 -0600365 if (psuModel == model)
366 {
367 // If PSU inventory has manufacturer property, it shall match
368 if (psuManufacturer.empty() || (psuManufacturer == manufacturer))
369 {
370 isCompat = true;
371 }
372 }
Lei YU9edb7332019-09-19 14:46:19 +0800373 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600374 catch (const std::exception& e)
Lei YU9edb7332019-09-19 14:46:19 +0800375 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600376 lg2::error(
377 "Unable to determine if PSU {PSU} is compatible with firmware "
378 "versionId {VERSION_ID}: {ERROR}",
379 "PSU", psuInventoryPath, "VERSION_ID", versionId, "ERROR", e);
Lei YU9edb7332019-09-19 14:46:19 +0800380 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600381 return isCompat;
Lei YU9edb7332019-09-19 14:46:19 +0800382}
383
Lei YU2e0e2de2019-09-26 16:42:23 +0800384void Activation::storeImage()
385{
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500386 // If image is not in IMG_DIR (temporary storage) then exit. We don't want
387 // to copy from IMG_DIR_PERSIST or IMG_DIR_BUILTIN.
Lei YUe8945ea2019-09-29 17:25:31 +0800388 auto src = path();
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500389 if (!src.starts_with(IMG_DIR))
Lei YUe8945ea2019-09-29 17:25:31 +0800390 {
Lei YUe8945ea2019-09-29 17:25:31 +0800391 return;
392 }
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500393
394 // Store image in persistent dir separated by model
395 // and only store the latest one by removing old ones
396 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YU2e0e2de2019-09-26 16:42:23 +0800397 try
398 {
399 fs::remove_all(dst);
400 fs::create_directories(dst);
401 fs::copy(src, dst);
402 path(dst.string()); // Update the FilePath interface
403 }
404 catch (const fs::filesystem_error& e)
405 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600406 lg2::error("Error storing PSU image: src={SRC}, dst={DST}: {ERROR}",
407 "SRC", src, "DST", dst, "ERROR", e);
Lei YU2e0e2de2019-09-26 16:42:23 +0800408 }
409}
410
Lei YUe8945ea2019-09-29 17:25:31 +0800411std::string Activation::getUpdateService(const std::string& psuInventoryPath)
412{
413 fs::path imagePath(path());
414
415 // The systemd unit shall be escaped
416 std::string args = psuInventoryPath;
417 args += "\\x20";
418 args += imagePath;
419 std::replace(args.begin(), args.end(), '/', '-');
420
421 std::string service = PSU_UPDATE_SERVICE;
422 auto p = service.find('@');
Shawn McCarney487e2e12024-11-25 17:19:46 -0600423 if (p == std::string::npos)
424 {
425 throw std::runtime_error{std::format(
426 "Invalid PSU update service name: {}", PSU_UPDATE_SERVICE)};
427 }
Lei YUe8945ea2019-09-29 17:25:31 +0800428 service.insert(p + 1, args);
429 return service;
430}
431
Lei YU8afeee52019-10-21 15:25:35 +0800432void ActivationBlocksTransition::enableRebootGuard()
433{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600434 lg2::info("PSU image activating - BMC reboots are disabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800435
436 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
437 SYSTEMD_INTERFACE, "StartUnit");
438 method.append("reboot-guard-enable.service", "replace");
439 bus.call_noreply_noerror(method);
440}
441
442void ActivationBlocksTransition::disableRebootGuard()
443{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600444 lg2::info("PSU activation has ended - BMC reboots are re-enabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800445
446 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
447 SYSTEMD_INTERFACE, "StartUnit");
448 method.append("reboot-guard-disable.service", "replace");
449 bus.call_noreply_noerror(method);
450}
451
Lei YU01539e72019-07-31 10:57:38 +0800452} // namespace updater
453} // namespace software
454} // namespace phosphor