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