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