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 52 std::string CodeUpdate::fetchCurrentBootSide() 53 { 54 return currBootSide; 55 } 56 57 std::string CodeUpdate::fetchNextBootSide() 58 { 59 return nextBootSide; 60 } 61 62 int CodeUpdate::setCurrentBootSide(const std::string& currSide) 63 { 64 currBootSide = currSide; 65 return PLDM_SUCCESS; 66 } 67 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 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 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 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 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 341 void CodeUpdate::setOemPlatformHandler( 342 pldm::responder::oem_platform::Handler* handler) 343 { 344 oemPlatformHandler = handler; 345 } 346 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 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 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 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 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> 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 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 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