1 #include "inband_code_update.hpp"
2
3 #include "libpldmresponder/pdr.hpp"
4 #include "oem_ibm_handler.hpp"
5 #include "xyz/openbmc_project/Common/error.hpp"
6
7 #include <arpa/inet.h>
8 #include <libpldm/entity.h>
9
10 #include <phosphor-logging/lg2.hpp>
11 #include <sdbusplus/server.hpp>
12 #include <xyz/openbmc_project/Dump/NewDump/server.hpp>
13
14 #include <exception>
15 #include <fstream>
16
17 PHOSPHOR_LOG2_USING;
18
19 namespace pldm
20 {
21 using namespace utils;
22
23 namespace responder
24 {
25 using namespace oem_ibm_platform;
26
27 /** @brief Directory where the lid files without a header are stored */
28 auto lidDirPath = fs::path(LID_STAGING_DIR) / "lid";
29
30 /** @brief Directory where the image files are stored as they are built */
31 auto imageDirPath = fs::path(LID_STAGING_DIR) / "image";
32
33 /** @brief Directory where the code update tarball files are stored */
34 auto updateDirPath = fs::path(LID_STAGING_DIR) / "update";
35
36 /** @brief The file name of the code update tarball */
37 constexpr auto tarImageName = "image.tar";
38
39 /** @brief The file name of the hostfw image */
40 constexpr auto hostfwImageName = "image-hostfw";
41
42 /** @brief The path to the code update tarball file */
43 auto tarImagePath = fs::path(imageDirPath) / tarImageName;
44
45 /** @brief The path to the hostfw image */
46 auto hostfwImagePath = fs::path(imageDirPath) / hostfwImageName;
47
48 /** @brief The path to the tarball file expected by the phosphor software
49 * manager */
50 auto updateImagePath = fs::path("/tmp/images") / tarImageName;
51
fetchCurrentBootSide()52 std::string CodeUpdate::fetchCurrentBootSide()
53 {
54 return currBootSide;
55 }
56
fetchNextBootSide()57 std::string CodeUpdate::fetchNextBootSide()
58 {
59 return nextBootSide;
60 }
61
setCurrentBootSide(const std::string & currSide)62 int CodeUpdate::setCurrentBootSide(const std::string& currSide)
63 {
64 currBootSide = currSide;
65 return PLDM_SUCCESS;
66 }
67
setNextBootSide(const std::string & nextSide)68 int CodeUpdate::setNextBootSide(const std::string& nextSide)
69 {
70 nextBootSide = nextSide;
71 std::string objPath{};
72 if (nextBootSide == currBootSide)
73 {
74 objPath = runningVersion;
75 }
76 else
77 {
78 objPath = nonRunningVersion;
79 }
80 if (objPath.empty())
81 {
82 error("no nonRunningVersion present");
83 return PLDM_PLATFORM_INVALID_STATE_VALUE;
84 }
85
86 pldm::utils::DBusMapping dbusMapping{objPath, redundancyIntf, "Priority",
87 "uint8_t"};
88 uint8_t val = 0;
89 pldm::utils::PropertyValue value = static_cast<uint8_t>(val);
90 try
91 {
92 dBusIntf->setDbusProperty(dbusMapping, value);
93 }
94 catch (const std::exception& e)
95 {
96 error("Failed to set the next boot side to {PATH} , error - {ERROR}",
97 "PATH", objPath, "ERROR", e);
98 return PLDM_ERROR;
99 }
100 return PLDM_SUCCESS;
101 }
102
setRequestedApplyTime()103 int CodeUpdate::setRequestedApplyTime()
104 {
105 int rc = PLDM_SUCCESS;
106 pldm::utils::PropertyValue value =
107 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
108 DBusMapping dbusMapping;
109 dbusMapping.objectPath = "/xyz/openbmc_project/software/apply_time";
110 dbusMapping.interface = "xyz.openbmc_project.Software.ApplyTime";
111 dbusMapping.propertyName = "RequestedApplyTime";
112 dbusMapping.propertyType = "string";
113 try
114 {
115 pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
116 }
117 catch (const std::exception& e)
118 {
119 error(
120 "Failed to set property '{PROPERTY}' at path '{PATH}' and interface '{INTERFACE}', error - {ERROR}",
121 "PATH", dbusMapping.objectPath, "INTERFACE", dbusMapping.interface,
122 "PROPERTY", dbusMapping.propertyName, "ERROR", e);
123 rc = PLDM_ERROR;
124 }
125 return rc;
126 }
127
setRequestedActivation()128 int CodeUpdate::setRequestedActivation()
129 {
130 int rc = PLDM_SUCCESS;
131 pldm::utils::PropertyValue value =
132 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active";
133 DBusMapping dbusMapping;
134 dbusMapping.objectPath = newImageId;
135 dbusMapping.interface = "xyz.openbmc_project.Software.Activation";
136 dbusMapping.propertyName = "RequestedActivation";
137 dbusMapping.propertyType = "string";
138 try
139 {
140 pldm::utils::DBusHandler().setDbusProperty(dbusMapping, value);
141 }
142 catch (const std::exception& e)
143 {
144 error(
145 "Failed to set property {PROPERTY} at path '{PATH}' and interface '{INTERFACE}', error - {ERROR}",
146 "PATH", dbusMapping.objectPath, "INTERFACE", dbusMapping.interface,
147 "PROPERTY", dbusMapping.propertyName, "ERROR", e);
148 rc = PLDM_ERROR;
149 }
150 return rc;
151 }
152
setVersions()153 void CodeUpdate::setVersions()
154 {
155 static constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
156 static constexpr auto functionalObjPath =
157 "/xyz/openbmc_project/software/functional";
158 static constexpr auto activeObjPath =
159 "/xyz/openbmc_project/software/active";
160 static constexpr auto propIntf = "org.freedesktop.DBus.Properties";
161
162 auto& bus = dBusIntf->getBus();
163 try
164 {
165 auto method = bus.new_method_call(mapperService, functionalObjPath,
166 propIntf, "Get");
167 method.append("xyz.openbmc_project.Association", "endpoints");
168 std::variant<std::vector<std::string>> paths;
169
170 auto reply = bus.call(method, dbusTimeout);
171 reply.read(paths);
172
173 runningVersion = std::get<std::vector<std::string>>(paths)[0];
174
175 auto method1 =
176 bus.new_method_call(mapperService, activeObjPath, propIntf, "Get");
177 method1.append("xyz.openbmc_project.Association", "endpoints");
178
179 auto reply1 = bus.call(method1, dbusTimeout);
180 reply1.read(paths);
181 for (const auto& path : std::get<std::vector<std::string>>(paths))
182 {
183 if (path != runningVersion)
184 {
185 nonRunningVersion = path;
186 break;
187 }
188 }
189 }
190 catch (const std::exception& e)
191 {
192 error(
193 "Failed to make a d-bus call to Object Mapper Association, error - {ERROR}",
194 "ERROR", e);
195 return;
196 }
197
198 using namespace sdbusplus::bus::match::rules;
199 captureNextBootSideChange.push_back(
200 std::make_unique<sdbusplus::bus::match_t>(
201 pldm::utils::DBusHandler::getBus(),
202 propertiesChanged(runningVersion, redundancyIntf),
203 [this](sdbusplus::message_t& msg) {
204 DbusChangedProps props;
205 std::string iface;
206 msg.read(iface, props);
207 processPriorityChangeNotification(props);
208 }));
209 fwUpdateMatcher.push_back(std::make_unique<sdbusplus::bus::match_t>(
210 pldm::utils::DBusHandler::getBus(),
211 "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
212 "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
213 [this](sdbusplus::message_t& msg) {
214 DBusInterfaceAdded interfaces;
215 sdbusplus::message::object_path path;
216 msg.read(path, interfaces);
217
218 for (auto& interface : interfaces)
219 {
220 if (interface.first ==
221 "xyz.openbmc_project.Software.Activation")
222 {
223 auto imageInterface =
224 "xyz.openbmc_project.Software.Activation";
225 auto imageObjPath = path.str.c_str();
226
227 try
228 {
229 auto propVal = dBusIntf->getDbusPropertyVariant(
230 imageObjPath, "Activation", imageInterface);
231 const auto& imageProp = std::get<std::string>(propVal);
232 if (imageProp == "xyz.openbmc_project.Software."
233 "Activation.Activations.Ready" &&
234 isCodeUpdateInProgress())
235 {
236 newImageId = path.str;
237 if (!imageActivationMatch)
238 {
239 imageActivationMatch = std::make_unique<
240 sdbusplus::bus::match_t>(
241 pldm::utils::DBusHandler::getBus(),
242 propertiesChanged(newImageId,
243 "xyz.openbmc_project."
244 "Software.Activation"),
245 [this](sdbusplus::message_t& msg) {
246 DbusChangedProps props;
247 std::string iface;
248 msg.read(iface, props);
249 const auto itr =
250 props.find("Activation");
251 if (itr != props.end())
252 {
253 PropertyValue value = itr->second;
254 auto propVal =
255 std::get<std::string>(value);
256 if (propVal ==
257 "xyz.openbmc_project.Software."
258 "Activation.Activations.Active")
259 {
260 CodeUpdateState state =
261 CodeUpdateState::END;
262 setCodeUpdateProgress(false);
263 auto sensorId =
264 getFirmwareUpdateSensor();
265 sendStateSensorEvent(
266 sensorId,
267 PLDM_STATE_SENSOR_STATE, 0,
268 uint8_t(state),
269 uint8_t(CodeUpdateState::
270 START));
271 newImageId.clear();
272 }
273 else if (propVal ==
274 "xyz.openbmc_project."
275 "Software.Activation."
276 "Activations.Failed" ||
277 propVal ==
278 "xyz.openbmc_"
279 "project.Software."
280 "Activation."
281 "Activations."
282 "Invalid")
283 {
284 CodeUpdateState state =
285 CodeUpdateState::FAIL;
286 setCodeUpdateProgress(false);
287 auto sensorId =
288 getFirmwareUpdateSensor();
289 sendStateSensorEvent(
290 sensorId,
291 PLDM_STATE_SENSOR_STATE, 0,
292 uint8_t(state),
293 uint8_t(CodeUpdateState::
294 START));
295 newImageId.clear();
296 }
297 }
298 });
299 }
300 auto rc = setRequestedActivation();
301 if (rc != PLDM_SUCCESS)
302 {
303 error("Could not set Requested Activation");
304 CodeUpdateState state = CodeUpdateState::FAIL;
305 setCodeUpdateProgress(false);
306 auto sensorId = getFirmwareUpdateSensor();
307 sendStateSensorEvent(
308 sensorId, PLDM_STATE_SENSOR_STATE, 0,
309 uint8_t(state),
310 uint8_t(CodeUpdateState::START));
311 }
312 break;
313 }
314 }
315 catch (const sdbusplus::exception_t& e)
316 {
317 error(
318 "Failed to get activation status for interface '{INTERFACE}' and object path '{PATH}', error - {ERROR}",
319 "ERROR", e, "INTERFACE", imageInterface, "PATH",
320 imageObjPath);
321 }
322 }
323 }
324 }));
325 }
326
processPriorityChangeNotification(const DbusChangedProps & chProperties)327 void CodeUpdate::processPriorityChangeNotification(
328 const DbusChangedProps& chProperties)
329 {
330 static constexpr auto propName = "Priority";
331 const auto it = chProperties.find(propName);
332 if (it == chProperties.end())
333 {
334 return;
335 }
336 uint8_t newVal = std::get<uint8_t>(it->second);
337 nextBootSide = (newVal == 0) ? currBootSide
338 : ((currBootSide == Tside) ? Pside : Tside);
339 }
340
setOemPlatformHandler(pldm::responder::oem_platform::Handler * handler)341 void CodeUpdate::setOemPlatformHandler(
342 pldm::responder::oem_platform::Handler* handler)
343 {
344 oemPlatformHandler = handler;
345 }
346
clearDirPath(const std::string & dirPath)347 void CodeUpdate::clearDirPath(const std::string& dirPath)
348 {
349 if (!fs::is_directory(dirPath))
350 {
351 error("The directory '{PATH}' does not exist", "PATH", dirPath);
352 return;
353 }
354 for (const auto& iter : fs::directory_iterator(dirPath))
355 {
356 fs::remove_all(iter);
357 }
358 }
359
sendStateSensorEvent(uint16_t sensorId,enum sensor_event_class_states sensorEventClass,uint8_t sensorOffset,uint8_t eventState,uint8_t prevEventState)360 void CodeUpdate::sendStateSensorEvent(
361 uint16_t sensorId, enum sensor_event_class_states sensorEventClass,
362 uint8_t sensorOffset, uint8_t eventState, uint8_t prevEventState)
363 {
364 pldm::responder::oem_ibm_platform::Handler* oemIbmPlatformHandler =
365 dynamic_cast<pldm::responder::oem_ibm_platform::Handler*>(
366 oemPlatformHandler);
367 oemIbmPlatformHandler->sendStateSensorEvent(
368 sensorId, sensorEventClass, sensorOffset, eventState, prevEventState);
369 }
370
deleteImage()371 void CodeUpdate::deleteImage()
372 {
373 static constexpr auto UPDATER_SERVICE =
374 "xyz.openbmc_project.Software.BMC.Updater";
375 static constexpr auto SW_OBJ_PATH = "/xyz/openbmc_project/software";
376 static constexpr auto DELETE_INTF =
377 "xyz.openbmc_project.Collection.DeleteAll";
378
379 auto& bus = dBusIntf->getBus();
380 try
381 {
382 auto method = bus.new_method_call(UPDATER_SERVICE, SW_OBJ_PATH,
383 DELETE_INTF, "DeleteAll");
384 bus.call_noreply(method, dbusTimeout);
385 }
386 catch (const std::exception& e)
387 {
388 error(
389 "Failed to delete image at path '{PATH}' and interface '{INTERFACE}', error - {ERROR}",
390 "PATH", SW_OBJ_PATH, "INTERFACE", DELETE_INTF, "ERROR", e);
391 return;
392 }
393 }
394
fetchBootSide(uint16_t entityInstance,CodeUpdate * codeUpdate)395 uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate)
396 {
397 uint8_t sensorOpState = tSideNum;
398 if (entityInstance == 0)
399 {
400 auto currSide = codeUpdate->fetchCurrentBootSide();
401 if (currSide == Pside)
402 {
403 sensorOpState = pSideNum;
404 }
405 }
406 else if (entityInstance == 1)
407 {
408 auto nextSide = codeUpdate->fetchNextBootSide();
409 if (nextSide == Pside)
410 {
411 sensorOpState = pSideNum;
412 }
413 }
414 else
415 {
416 sensorOpState = PLDM_SENSOR_UNKNOWN;
417 }
418
419 return sensorOpState;
420 }
421
setBootSide(uint16_t entityInstance,uint8_t currState,const std::vector<set_effecter_state_field> & stateField,CodeUpdate * codeUpdate)422 int setBootSide(uint16_t entityInstance, uint8_t currState,
423 const std::vector<set_effecter_state_field>& stateField,
424 CodeUpdate* codeUpdate)
425 {
426 int rc = PLDM_SUCCESS;
427 auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T";
428
429 if (entityInstance == 0)
430 {
431 rc = codeUpdate->setCurrentBootSide(side);
432 }
433 else if (entityInstance == 1)
434 {
435 rc = codeUpdate->setNextBootSide(side);
436 }
437 else
438 {
439 rc = PLDM_PLATFORM_INVALID_STATE_VALUE;
440 }
441 return rc;
442 }
443
444 template <typename... T>
executeCmd(const T &...t)445 int executeCmd(const T&... t)
446 {
447 std::stringstream cmd;
448 ((cmd << t << " "), ...) << std::endl;
449 FILE* pipe = popen(cmd.str().c_str(), "r");
450 if (!pipe)
451 {
452 throw std::runtime_error("popen() failed!");
453 }
454 int rc = pclose(pipe);
455 if (WEXITSTATUS(rc))
456 {
457 std::cerr << "Error executing: ";
458 ((std::cerr << " " << t), ...);
459 std::cerr << "\n";
460 return -1;
461 }
462
463 return 0;
464 }
465
processCodeUpdateLid(const std::string & filePath)466 int processCodeUpdateLid(const std::string& filePath)
467 {
468 struct LidHeader
469 {
470 uint16_t magicNumber;
471 uint16_t headerVersion;
472 uint32_t lidNumber;
473 uint32_t lidDate;
474 uint16_t lidTime;
475 uint16_t lidClass;
476 uint32_t lidCrc;
477 uint32_t lidSize;
478 uint32_t headerSize;
479 };
480 LidHeader header;
481
482 std::ifstream ifs(filePath, std::ios::in | std::ios::binary);
483 if (!ifs)
484 {
485 error("Failed to opening file '{FILE}' ifstream", "PATH", filePath);
486 return PLDM_ERROR;
487 }
488 ifs.seekg(0);
489 ifs.read(reinterpret_cast<char*>(&header), sizeof(header));
490
491 // File size should be the value of lid size minus the header size
492 auto fileSize = fs::file_size(filePath);
493 fileSize -= htonl(header.headerSize);
494 if (fileSize < htonl(header.lidSize))
495 {
496 // File is not completely written yet
497 ifs.close();
498 return PLDM_SUCCESS;
499 }
500
501 constexpr auto magicNumber = 0x0222;
502 if (htons(header.magicNumber) != magicNumber)
503 {
504 error("Invalid magic number for file '{PATH}'", "PATH", filePath);
505 ifs.close();
506 return PLDM_ERROR;
507 }
508
509 fs::create_directories(imageDirPath);
510 fs::create_directories(lidDirPath);
511
512 constexpr auto bmcClass = 0x2000;
513 if (htons(header.lidClass) == bmcClass)
514 {
515 // Skip the header and concatenate the BMC LIDs into a tar file
516 std::ofstream ofs(tarImagePath,
517 std::ios::out | std::ios::binary | std::ios::app);
518 ifs.seekg(htonl(header.headerSize));
519 ofs << ifs.rdbuf();
520 ofs.flush();
521 ofs.close();
522 }
523 else
524 {
525 std::stringstream lidFileName;
526 lidFileName << std::hex << htonl(header.lidNumber) << ".lid";
527 auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str();
528 std::ofstream ofs(lidNoHeaderPath,
529 std::ios::out | std::ios::binary | std::ios::trunc);
530 ifs.seekg(htonl(header.headerSize));
531 ofs << ifs.rdbuf();
532 ofs.flush();
533 ofs.close();
534 }
535
536 ifs.close();
537 fs::remove(filePath);
538 return PLDM_SUCCESS;
539 }
540
assembleCodeUpdateImage()541 int CodeUpdate::assembleCodeUpdateImage()
542 {
543 pid_t pid = fork();
544
545 if (pid == 0)
546 {
547 pid_t nextPid = fork();
548 if (nextPid == 0)
549 {
550 // Create the hostfw squashfs image from the LID files without
551 // header
552 auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(),
553 hostfwImagePath.c_str(), "-all-root",
554 "-no-recovery");
555 if (rc < 0)
556 {
557 error("Error occurred during the mksqusquashfs call");
558 setCodeUpdateProgress(false);
559 auto sensorId = getFirmwareUpdateSensor();
560 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
561 uint8_t(CodeUpdateState::FAIL),
562 uint8_t(CodeUpdateState::START));
563 exit(EXIT_FAILURE);
564 }
565
566 fs::create_directories(updateDirPath);
567
568 // Extract the BMC tarball content
569 rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C",
570 updateDirPath);
571 if (rc < 0)
572 {
573 setCodeUpdateProgress(false);
574 auto sensorId = getFirmwareUpdateSensor();
575 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
576 uint8_t(CodeUpdateState::FAIL),
577 uint8_t(CodeUpdateState::START));
578 exit(EXIT_FAILURE);
579 }
580
581 // Add the hostfw image to the directory where the contents were
582 // extracted
583 fs::copy_file(hostfwImagePath,
584 fs::path(updateDirPath) / hostfwImageName,
585 fs::copy_options::overwrite_existing);
586
587 // Remove the tarball file, then re-generate it with so that the
588 // hostfw image becomes part of the tarball
589 fs::remove(tarImagePath);
590 rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C",
591 updateDirPath);
592 if (rc < 0)
593 {
594 error("Error occurred during the generation of the tarball");
595 setCodeUpdateProgress(false);
596 auto sensorId = getFirmwareUpdateSensor();
597 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
598 uint8_t(CodeUpdateState::FAIL),
599 uint8_t(CodeUpdateState::START));
600 exit(EXIT_FAILURE);
601 }
602
603 // Copy the tarball to the update directory to trigger the phosphor
604 // software manager to create a version interface
605 fs::copy_file(tarImagePath, updateImagePath,
606 fs::copy_options::overwrite_existing);
607
608 // Cleanup
609 fs::remove_all(updateDirPath);
610 fs::remove_all(lidDirPath);
611 fs::remove_all(imageDirPath);
612
613 exit(EXIT_SUCCESS);
614 }
615 else if (nextPid < 0)
616 {
617 error("Failure occurred during fork, error number - {ERROR_NUM}",
618 "ERROR_NUM", errno);
619 exit(EXIT_FAILURE);
620 }
621
622 // Do nothing as parent. When parent exits, child will be reparented
623 // under init and be reaped properly.
624 exit(0);
625 }
626 else if (pid > 0)
627 {
628 int status;
629 if (waitpid(pid, &status, 0) < 0)
630 {
631 error("Error occurred during waitpid, error number - {ERROR_NUM}",
632 "ERROR_NUM", errno);
633
634 return PLDM_ERROR;
635 }
636 else if (WEXITSTATUS(status) != 0)
637 {
638 error(
639 "Failed to execute the assembling of the image, status is {IMG_STATUS}",
640 "IMG_STATUS", status);
641 return PLDM_ERROR;
642 }
643 }
644 else
645 {
646 error("Error occurred during fork, error number - {ERROR_NUM}}",
647 "ERROR_NUM", errno);
648 return PLDM_ERROR;
649 }
650
651 return PLDM_SUCCESS;
652 }
653
654 } // namespace responder
655 } // namespace pldm
656