blob: 65db23b2f95dff539091706a3b57ca0bc4b49c42 [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 {
Shawn McCarney46ea3882024-12-10 11:25:38 -0600179 if (!isPresent(p))
180 {
181 continue;
182 }
Lei YU9edb7332019-09-19 14:46:19 +0800183 if (isCompatible(p))
184 {
Lei YU63f9e712019-10-12 15:16:55 +0800185 if (utils::isAssociated(p, associations()))
186 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600187 lg2::notice("PSU {PSU} is already running the image, skipping",
188 "PSU", p);
Lei YU63f9e712019-10-12 15:16:55 +0800189 continue;
190 }
Lei YU9edb7332019-09-19 14:46:19 +0800191 psuQueue.push(p);
192 }
193 else
194 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600195 lg2::notice("PSU {PSU} is not compatible", "PSU", p);
Lei YU9edb7332019-09-19 14:46:19 +0800196 }
197 }
198
199 if (psuQueue.empty())
200 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600201 lg2::warning("No PSU compatible with the software");
Lei YU63f9e712019-10-12 15:16:55 +0800202 return activation(); // Return the previous activation status
Lei YUff83c2a2019-09-12 13:55:18 +0800203 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800204
Lei YU8afeee52019-10-21 15:25:35 +0800205 if (!activationProgress)
206 {
207 activationProgress = std::make_unique<ActivationProgress>(bus, objPath);
208 }
209 if (!activationBlocksTransition)
210 {
211 activationBlocksTransition =
212 std::make_unique<ActivationBlocksTransition>(bus, objPath);
213 }
214
Lei YUff83c2a2019-09-12 13:55:18 +0800215 // The progress to be increased for each successful update of PSU
216 // E.g. in case we have 4 PSUs:
Manojkiran Eda33cf9f02024-06-17 14:40:44 +0530217 // 1. Initial progress is 10
Lei YUff83c2a2019-09-12 13:55:18 +0800218 // 2. Add 20 after each update is done, so we will see progress to be 30,
219 // 50, 70, 90
220 // 3. When all PSUs are updated, it will be 100 and the interface is
221 // removed.
222 progressStep = 80 / psuQueue.size();
223 if (doUpdate())
224 {
225 activationProgress->progress(10);
226 return Status::Activating;
227 }
228 else
229 {
230 return Status::Failed;
231 }
Lei YU12c9f4c2019-09-11 15:08:15 +0800232}
233
234void Activation::finishActivation()
235{
Lei YU2e0e2de2019-09-26 16:42:23 +0800236 storeImage();
Lei YU90c8a8b2019-09-11 17:20:03 +0800237 activationProgress->progress(100);
Lei YU81c67722019-09-11 16:47:29 +0800238
Lei YUd0bbfa92019-09-11 16:10:54 +0800239 deleteImageManagerObject();
Lei YU7f2a2152019-09-16 16:50:18 +0800240
Lei YU99301372019-09-29 16:27:12 +0800241 associationInterface->createActiveAssociation(objPath);
242 associationInterface->addFunctionalAssociation(objPath);
Lei YUa8b966f2020-03-18 10:32:24 +0800243 associationInterface->addUpdateableAssociation(objPath);
Lei YU7f2a2152019-09-16 16:50:18 +0800244
Lei YU1517f5f2019-10-14 16:44:42 +0800245 // Reset RequestedActivations to none so that it could be activated in
246 // future
247 requestedActivation(SoftwareActivation::RequestedActivations::None);
Lei YU12c9f4c2019-09-11 15:08:15 +0800248 activation(Status::Active);
Lei YU01539e72019-07-31 10:57:38 +0800249}
250
Lei YUd0bbfa92019-09-11 16:10:54 +0800251void Activation::deleteImageManagerObject()
252{
253 // Get the Delete object for <versionID> inside image_manager
Shawn McCarney487e2e12024-11-25 17:19:46 -0600254 std::vector<std::string> services;
Lei YUd0bbfa92019-09-11 16:10:54 +0800255 constexpr auto deleteInterface = "xyz.openbmc_project.Object.Delete";
Shawn McCarney487e2e12024-11-25 17:19:46 -0600256 try
257 {
258 services = utils::getServices(bus, objPath.c_str(), deleteInterface);
259 }
260 catch (const std::exception& e)
261 {
262 lg2::error(
263 "Unable to find services to Delete object path {PATH}: {ERROR}",
264 "PATH", objPath, "ERROR", e);
265 }
Lei YUd0bbfa92019-09-11 16:10:54 +0800266
267 // We need to find the phosphor-version-software-manager's version service
268 // to invoke the delete interface
Shawn McCarney487e2e12024-11-25 17:19:46 -0600269 constexpr auto versionServiceStr = "xyz.openbmc_project.Software.Version";
270 std::string versionService;
Lei YUd0bbfa92019-09-11 16:10:54 +0800271 for (const auto& service : services)
272 {
273 if (service.find(versionServiceStr) != std::string::npos)
274 {
275 versionService = service;
276 break;
277 }
278 }
279 if (versionService.empty())
280 {
Lei YUe8945ea2019-09-29 17:25:31 +0800281 // When updating a stored image, there is no version object created by
282 // "xyz.openbmc_project.Software.Version" service, so skip it.
Lei YUd0bbfa92019-09-11 16:10:54 +0800283 return;
284 }
285
286 // Call the Delete object for <versionID> inside image_manager
Lei YUd0bbfa92019-09-11 16:10:54 +0800287 try
288 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600289 auto method = bus.new_method_call(
290 versionService.c_str(), objPath.c_str(), deleteInterface, "Delete");
Lei YUd0bbfa92019-09-11 16:10:54 +0800291 bus.call(method);
292 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600293 catch (const std::exception& e)
Lei YUd0bbfa92019-09-11 16:10:54 +0800294 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600295 lg2::error("Unable to Delete object path {PATH}: {ERROR}", "PATH",
296 objPath, "ERROR", e);
Lei YUd0bbfa92019-09-11 16:10:54 +0800297 }
298}
299
Shawn McCarney46ea3882024-12-10 11:25:38 -0600300bool Activation::isPresent(const std::string& psuInventoryPath)
301{
302 bool isPres{false};
303 try
304 {
305 auto service =
306 utils::getService(bus, psuInventoryPath.c_str(), ITEM_IFACE);
307 isPres = utils::getProperty<bool>(bus, service.c_str(),
308 psuInventoryPath.c_str(), ITEM_IFACE,
309 PRESENT);
310 }
311 catch (const std::exception& e)
312 {
313 // Treat as a warning condition and assume the PSU is missing. The
314 // D-Bus information might not be available if the PSU is missing.
315 lg2::warning("Unable to determine if PSU {PSU} is present: {ERROR}",
316 "PSU", psuInventoryPath, "ERROR", e);
317 }
318 return isPres;
319}
320
Lei YU9edb7332019-09-19 14:46:19 +0800321bool Activation::isCompatible(const std::string& psuInventoryPath)
322{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600323 bool isCompat{false};
324 try
Lei YU9edb7332019-09-19 14:46:19 +0800325 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600326 auto service =
327 utils::getService(bus, psuInventoryPath.c_str(), ASSET_IFACE);
328 auto psuManufacturer = utils::getProperty<std::string>(
329 bus, service.c_str(), psuInventoryPath.c_str(), ASSET_IFACE,
330 MANUFACTURER);
331 auto psuModel = utils::getModel(psuInventoryPath);
Lei YU9edb7332019-09-19 14:46:19 +0800332 // The model shall match
Shawn McCarney487e2e12024-11-25 17:19:46 -0600333 if (psuModel == model)
334 {
335 // If PSU inventory has manufacturer property, it shall match
336 if (psuManufacturer.empty() || (psuManufacturer == manufacturer))
337 {
338 isCompat = true;
339 }
340 }
Lei YU9edb7332019-09-19 14:46:19 +0800341 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600342 catch (const std::exception& e)
Lei YU9edb7332019-09-19 14:46:19 +0800343 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600344 lg2::error(
345 "Unable to determine if PSU {PSU} is compatible with firmware "
346 "versionId {VERSION_ID}: {ERROR}",
347 "PSU", psuInventoryPath, "VERSION_ID", versionId, "ERROR", e);
Lei YU9edb7332019-09-19 14:46:19 +0800348 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600349 return isCompat;
Lei YU9edb7332019-09-19 14:46:19 +0800350}
351
Lei YU2e0e2de2019-09-26 16:42:23 +0800352void Activation::storeImage()
353{
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500354 // If image is not in IMG_DIR (temporary storage) then exit. We don't want
355 // to copy from IMG_DIR_PERSIST or IMG_DIR_BUILTIN.
Lei YUe8945ea2019-09-29 17:25:31 +0800356 auto src = path();
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500357 if (!src.starts_with(IMG_DIR))
Lei YUe8945ea2019-09-29 17:25:31 +0800358 {
Lei YUe8945ea2019-09-29 17:25:31 +0800359 return;
360 }
Shawn McCarneyddf525f2024-09-27 09:27:06 -0500361
362 // Store image in persistent dir separated by model
363 // and only store the latest one by removing old ones
364 auto dst = fs::path(IMG_DIR_PERSIST) / model;
Lei YU2e0e2de2019-09-26 16:42:23 +0800365 try
366 {
367 fs::remove_all(dst);
368 fs::create_directories(dst);
369 fs::copy(src, dst);
370 path(dst.string()); // Update the FilePath interface
371 }
372 catch (const fs::filesystem_error& e)
373 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600374 lg2::error("Error storing PSU image: src={SRC}, dst={DST}: {ERROR}",
375 "SRC", src, "DST", dst, "ERROR", e);
Lei YU2e0e2de2019-09-26 16:42:23 +0800376 }
377}
378
Lei YUe8945ea2019-09-29 17:25:31 +0800379std::string Activation::getUpdateService(const std::string& psuInventoryPath)
380{
381 fs::path imagePath(path());
382
383 // The systemd unit shall be escaped
384 std::string args = psuInventoryPath;
385 args += "\\x20";
386 args += imagePath;
387 std::replace(args.begin(), args.end(), '/', '-');
388
389 std::string service = PSU_UPDATE_SERVICE;
390 auto p = service.find('@');
Shawn McCarney487e2e12024-11-25 17:19:46 -0600391 if (p == std::string::npos)
392 {
393 throw std::runtime_error{std::format(
394 "Invalid PSU update service name: {}", PSU_UPDATE_SERVICE)};
395 }
Lei YUe8945ea2019-09-29 17:25:31 +0800396 service.insert(p + 1, args);
397 return service;
398}
399
Lei YU8afeee52019-10-21 15:25:35 +0800400void ActivationBlocksTransition::enableRebootGuard()
401{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600402 lg2::info("PSU image activating - BMC reboots are disabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800403
404 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
405 SYSTEMD_INTERFACE, "StartUnit");
406 method.append("reboot-guard-enable.service", "replace");
407 bus.call_noreply_noerror(method);
408}
409
410void ActivationBlocksTransition::disableRebootGuard()
411{
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600412 lg2::info("PSU activation has ended - BMC reboots are re-enabled.");
Lei YU8afeee52019-10-21 15:25:35 +0800413
414 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
415 SYSTEMD_INTERFACE, "StartUnit");
416 method.append("reboot-guard-disable.service", "replace");
417 bus.call_noreply_noerror(method);
418}
419
Lei YU01539e72019-07-31 10:57:38 +0800420} // namespace updater
421} // namespace software
422} // namespace phosphor