blob: 400f03c9f9494e887a43cd0a42dc7d2be43ad698 [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 +080028namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server;
29
Lei YUd0bbfa92019-09-11 16:10:54 +080030using namespace phosphor::logging;
Lei YU12c9f4c2019-09-11 15:08:15 +080031using SoftwareActivation = softwareServer::Activation;
32
Lei YU01539e72019-07-31 10:57:38 +080033auto Activation::activation(Activations value) -> Activations
34{
Lei YU12c9f4c2019-09-11 15:08:15 +080035 if (value == Status::Activating)
36 {
Lei YUff83c2a2019-09-12 13:55:18 +080037 value = startActivation();
Lei YU12c9f4c2019-09-11 15:08:15 +080038 }
39 else
40 {
Lei YU81c67722019-09-11 16:47:29 +080041 activationBlocksTransition.reset();
Lei YU90c8a8b2019-09-11 17:20:03 +080042 activationProgress.reset();
Lei YU12c9f4c2019-09-11 15:08:15 +080043 }
44
45 return SoftwareActivation::activation(value);
Lei YU01539e72019-07-31 10:57:38 +080046}
47
48auto Activation::requestedActivation(RequestedActivations value)
49 -> RequestedActivations
50{
Lei YU12c9f4c2019-09-11 15:08:15 +080051 if ((value == SoftwareActivation::RequestedActivations::Active) &&
52 (SoftwareActivation::requestedActivation() !=
53 SoftwareActivation::RequestedActivations::Active))
54 {
Lei YU63f9e712019-10-12 15:16:55 +080055 // PSU image could be activated even when it's in active,
56 // e.g. in case a PSU is replaced and has a older image, it will be
57 // updated with the running PSU image that is stored in BMC.
58 if ((activation() == Status::Ready) ||
59 (activation() == Status::Failed) || activation() == Status::Active)
Lei YU12c9f4c2019-09-11 15:08:15 +080060 {
61 activation(Status::Activating);
62 }
63 }
64 return SoftwareActivation::requestedActivation(value);
65}
66
Patrick Williams374fae52022-07-22 19:26:55 -050067void Activation::unitStateChange(sdbusplus::message_t& msg)
Lei YU12c9f4c2019-09-11 15:08:15 +080068{
69 uint32_t newStateID{};
70 sdbusplus::message::object_path newStateObjPath;
71 std::string newStateUnit{};
72 std::string newStateResult{};
73
Shawn McCarney487e2e12024-11-25 17:19:46 -060074 try
Lei YU12c9f4c2019-09-11 15:08:15 +080075 {
Shawn McCarney487e2e12024-11-25 17:19:46 -060076 // Read the msg and populate each variable
77 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
78
79 if (newStateUnit == psuUpdateUnit)
Lei YU12c9f4c2019-09-11 15:08:15 +080080 {
Shawn McCarney487e2e12024-11-25 17:19:46 -060081 if (newStateResult == "done")
82 {
83 onUpdateDone();
84 }
85 if (newStateResult == "failed" || newStateResult == "dependency")
86 {
87 onUpdateFailed();
88 }
Lei YU12c9f4c2019-09-11 15:08:15 +080089 }
Shawn McCarney487e2e12024-11-25 17:19:46 -060090 }
91 catch (const std::exception& e)
92 {
93 lg2::error("Unable to handle unit state change event: {ERROR}", "ERROR",
94 e);
Lei YU12c9f4c2019-09-11 15:08:15 +080095 }
96}
97
Lei YUff83c2a2019-09-12 13:55:18 +080098bool Activation::doUpdate(const std::string& psuInventoryPath)
99{
Lei YU7f2a2152019-09-16 16:50:18 +0800100 currentUpdatingPsu = psuInventoryPath;
Lei YUff83c2a2019-09-12 13:55:18 +0800101 try
102 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600103 psuUpdateUnit = getUpdateService(currentUpdatingPsu);
Lei YUff83c2a2019-09-12 13:55:18 +0800104 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
105 SYSTEMD_INTERFACE, "StartUnit");
106 method.append(psuUpdateUnit, "replace");
107 bus.call_noreply(method);
108 return true;
109 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600110 catch (const std::exception& e)
Lei YUff83c2a2019-09-12 13:55:18 +0800111 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600112 lg2::error("Error starting update service for PSU {PSU}: {ERROR}",
113 "PSU", psuInventoryPath, "ERROR", e);
Lei YUff83c2a2019-09-12 13:55:18 +0800114 onUpdateFailed();
115 return false;
116 }
117}
118
119bool Activation::doUpdate()
120{
121 // When the queue is empty, all updates are done
122 if (psuQueue.empty())
123 {
124 finishActivation();
125 return true;
126 }
127
128 // Do the update on a PSU
129 const auto& psu = psuQueue.front();
130 return doUpdate(psu);
131}
132
133void Activation::onUpdateDone()
134{
135 auto progress = activationProgress->progress() + progressStep;
136 activationProgress->progress(progress);
137
Lei YU7f2a2152019-09-16 16:50:18 +0800138 // Update the activation association
139 auto assocs = associations();
140 assocs.emplace_back(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
141 currentUpdatingPsu);
Lei YU7f2a2152019-09-16 16:50:18 +0800142 associations(assocs);
143
Lei YUffb36532019-10-15 13:55:24 +0800144 activationListener->onUpdateDone(versionId, currentUpdatingPsu);
145 currentUpdatingPsu.clear();
146
Lei YUff83c2a2019-09-12 13:55:18 +0800147 psuQueue.pop();
148 doUpdate(); // Update the next psu
149}
150
151void Activation::onUpdateFailed()
152{
153 // TODO: report an event
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600154 lg2::error("Failed to update PSU {PSU}", "PSU", psuQueue.front());
Lei YUff83c2a2019-09-12 13:55:18 +0800155 std::queue<std::string>().swap(psuQueue); // Clear the queue
156 activation(Status::Failed);
157}
158
159Activation::Status Activation::startActivation()
Lei YU12c9f4c2019-09-11 15:08:15 +0800160{
Lei YU63f9e712019-10-12 15:16:55 +0800161 // Check if the activation has file path
162 if (path().empty())
163 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600164 lg2::warning(
165 "No image for the activation, skipped version {VERSION_ID}",
166 "VERSION_ID", versionId);
Lei YU63f9e712019-10-12 15:16:55 +0800167 return activation(); // Return the previous activation status
168 }
Lei YU81c67722019-09-11 16:47:29 +0800169
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -0600170 auto psuPaths = utils::getPSUInventoryPaths(bus);
Lei YU12c9f4c2019-09-11 15:08:15 +0800171 if (psuPaths.empty())
172 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600173 lg2::warning("No PSU inventory found");
Lei YUff83c2a2019-09-12 13:55:18 +0800174 return Status::Failed;
Lei YU12c9f4c2019-09-11 15:08:15 +0800175 }
176
Lei YUff83c2a2019-09-12 13:55:18 +0800177 for (const auto& p : psuPaths)
178 {
Lei YU9edb7332019-09-19 14:46:19 +0800179 if (isCompatible(p))
180 {
Lei YU63f9e712019-10-12 15:16:55 +0800181 if (utils::isAssociated(p, associations()))
182 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600183 lg2::notice("PSU {PSU} is already running the image, skipping",
184 "PSU", p);
Lei YU63f9e712019-10-12 15:16:55 +0800185 continue;
186 }
Lei YU9edb7332019-09-19 14:46:19 +0800187 psuQueue.push(p);
188 }
189 else
190 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600191 lg2::notice("PSU {PSU} is not compatible", "PSU", p);
Lei YU9edb7332019-09-19 14:46:19 +0800192 }
193 }
194
195 if (psuQueue.empty())
196 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600197 lg2::warning("No PSU compatible with the software");
Lei YU63f9e712019-10-12 15:16:55 +0800198 return activation(); // Return the previous activation status
Lei YUff83c2a2019-09-12 13:55:18 +0800199 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800200
Lei YU8afeee52019-10-21 15:25:35 +0800201 if (!activationProgress)
202 {
203 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
204 }
205 if (!activationBlocksTransition)
206 {
207 activationBlocksTransition =
208 std::make_unique<ActivationBlocksTransition>(bus, objPath);
209 }
210
Lei YUff83c2a2019-09-12 13:55:18 +0800211 // The progress to be increased for each successful update of PSU
212 // E.g. in case we have 4 PSUs:
Manojkiran Eda33cf9f02024-06-17 14:40:44 +0530213 // 1. Initial progress is 10
Lei YUff83c2a2019-09-12 13:55:18 +0800214 // 2. Add 20 after each update is done, so we will see progress to be 30,
215 // 50, 70, 90
216 // 3. When all PSUs are updated, it will be 100 and the interface is
217 // removed.
218 progressStep = 80 / psuQueue.size();
219 if (doUpdate())
220 {
221 activationProgress->progress(10);
222 return Status::Activating;
223 }
224 else
225 {
226 return Status::Failed;
227 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800228}
229
230void Activation::finishActivation()
231{
Lei YU2e0e2de2019-09-26 16:42:23 +0800232 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800233 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800234
Lei YUd0bbfa92019-09-11 16:10:54 +0800235 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800236
Lei YU99301372019-09-29 16:27:12 +0800237 associationInterface->createActiveAssociation(objPath);
238 associationInterface->addFunctionalAssociation(objPath);
Lei YUa8b966f2020-03-18 10:32:24 +0800239 associationInterface->addUpdateableAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800240
Lei YU1517f5f2019-10-14 16:44:42 +0800241 // Reset RequestedActivations to none so that it could be activated in
242 // future
243 requestedActivation(SoftwareActivation::RequestedActivations::None);
Lei YU12c9f4c2019-09-11 15:08:15 +0800244 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800245}
246
Lei YUd0bbfa92019-09-11 16:10:54 +0800247void Activation::deleteImageManagerObject()
248{
249 // Get the Delete object for <versionID> inside image_manager
Shawn McCarney487e2e12024-11-25 17:19:46 -0600250 std::vector<std::string> services;
Lei YUd0bbfa92019-09-11 16:10:54 +0800251 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
Shawn McCarney487e2e12024-11-25 17:19:46 -0600252 try
253 {
254 services = utils::getServices(bus, objPath.c_str(), deleteInterface);
255 }
256 catch (const std::exception& e)
257 {
258 lg2::error(
259 "Unable to find services to Delete object path {PATH}: {ERROR}",
260 "PATH", objPath, "ERROR", e);
261 }
Lei YUd0bbfa92019-09-11 16:10:54 +0800262
263 // We need to find the phosphor-version-software-manager's version service
264 // to invoke the delete interface
Shawn McCarney487e2e12024-11-25 17:19:46 -0600265 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
266 std::string versionService;
Lei YUd0bbfa92019-09-11 16:10:54 +0800267 for (const auto& service : services)
268 {
269 if (service.find(versionServiceStr) != std::string::npos)
270 {
271 versionService = service;
272 break;
273 }
274 }
275 if (versionService.empty())
276 {
Lei YUe8945ea2019-09-29 17:25:31 +0800277 // When updating a stored image, there is no version object created by
278 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800279 return;
280 }
281
282 // Call the Delete object for <versionID> inside image_manager
Lei YUd0bbfa92019-09-11 16:10:54 +0800283 try
284 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600285 auto method = bus.new_method_call(
286 versionService.c_str(), objPath.c_str(), deleteInterface, "Delete");
Lei YUd0bbfa92019-09-11 16:10:54 +0800287 bus.call(method);
288 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600289 catch (const std::exception& e)
Lei YUd0bbfa92019-09-11 16:10:54 +0800290 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600291 lg2::error("Unable to Delete object path {PATH}: {ERROR}", "PATH",
292 objPath, "ERROR", e);
Lei YUd0bbfa92019-09-11 16:10:54 +0800293 }
294}
295
Lei YU9edb7332019-09-19 14:46:19 +0800296bool Activation::isCompatible(const std::string& psuInventoryPath)
297{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600298 bool isCompat{false};
299 try
Lei YU9edb7332019-09-19 14:46:19 +0800300 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600301 auto service =
302 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
303 auto psuManufacturer = utils::getProperty<std::string>(
304 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
305 MANUFACTURER);
306 auto psuModel = utils::getModel(psuInventoryPath);
Lei YU9edb7332019-09-19 14:46:19 +0800307 // The model shall match
Shawn McCarney487e2e12024-11-25 17:19:46 -0600308 if (psuModel == model)
309 {
310 // If PSU inventory has manufacturer property, it shall match
311 if (psuManufacturer.empty() || (psuManufacturer == manufacturer))
312 {
313 isCompat = true;
314 }
315 }
Lei YU9edb7332019-09-19 14:46:19 +0800316 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600317 catch (const std::exception& e)
Lei YU9edb7332019-09-19 14:46:19 +0800318 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600319 lg2::error(
320 "Unable to determine if PSU {PSU} is compatible with firmware "
321 "versionId {VERSION_ID}: {ERROR}",
322 "PSU", psuInventoryPath, "VERSION_ID", versionId, "ERROR", e);
Lei YU9edb7332019-09-19 14:46:19 +0800323 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600324 return isCompat;
Lei YU9edb7332019-09-19 14:46:19 +0800325}
326
Lei YU2e0e2de2019-09-26 16:42:23 +0800327void Activation::storeImage()
328{
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500329 // If image is not in IMG_DIR (temporary storage) then exit. We don't want
330 // to copy from IMG_DIR_PERSIST or IMG_DIR_BUILTIN.
Lei YUe8945ea2019-09-29 17:25:31 +0800331 auto src = path();
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500332 if (!src.starts_with(IMG_DIR))
Lei YUe8945ea2019-09-29 17:25:31 +0800333 {
Lei YUe8945ea2019-09-29 17:25:31 +0800334 return;
335 }
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500336
337 // Store image in persistent dir separated by model
338 // and only store the latest one by removing old ones
339 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YU2e0e2de2019-09-26 16:42:23 +0800340 try
341 {
342 fs::remove_all(dst);
343 fs::create_directories(dst);
344 fs::copy(src, dst);
345 path(dst.string()); // Update the FilePath interface
346 }
347 catch (const fs::filesystem_error& e)
348 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600349 lg2::error("Error storing PSU image: src={SRC}, dst={DST}: {ERROR}",
350 "SRC", src, "DST", dst, "ERROR", e);
Lei YU2e0e2de2019-09-26 16:42:23 +0800351 }
352}
353
Lei YUe8945ea2019-09-29 17:25:31 +0800354std::string Activation::getUpdateService(const std::string& psuInventoryPath)
355{
356 fs::path imagePath(path());
357
358 // The systemd unit shall be escaped
359 std::string args = psuInventoryPath;
360 args += "\\x20";
361 args += imagePath;
362 std::replace(args.begin(), args.end(), '/', '-');
363
364 std::string service = PSU_UPDATE_SERVICE;
365 auto p = service.find('@');
Shawn McCarney487e2e12024-11-25 17:19:46 -0600366 if (p == std::string::npos)
367 {
368 throw std::runtime_error{std::format(
369 "Invalid PSU update service name: {}", PSU_UPDATE_SERVICE)};
370 }
Lei YUe8945ea2019-09-29 17:25:31 +0800371 service.insert(p + 1, args);
372 return service;
373}
374
Lei YU8afeee52019-10-21 15:25:35 +0800375void ActivationBlocksTransition::enableRebootGuard()
376{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600377 lg2::info("PSU image activating - BMC reboots are disabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800378
379 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
380 SYSTEMD_INTERFACE, "StartUnit");
381 method.append("reboot-guard-enable.service", "replace");
382 bus.call_noreply_noerror(method);
383}
384
385void ActivationBlocksTransition::disableRebootGuard()
386{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600387 lg2::info("PSU activation has ended - BMC reboots are re-enabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800388
389 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
390 SYSTEMD_INTERFACE, "StartUnit");
391 method.append("reboot-guard-disable.service", "replace");
392 bus.call_noreply_noerror(method);
393}
394
Lei YU01539e72019-07-31 10:57:38 +0800395} // namespace updater
396} // namespace software
397} // namespace phosphor