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( 174 method, 175 std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count()); 176 reply.read(paths); 177 178 runningVersion = std::get<std::vector<std::string>>(paths)[0]; 179 180 auto method1 = bus.new_method_call(mapperService, activeObjPath, 181 propIntf, "Get"); 182 method1.append("xyz.openbmc_project.Association", "endpoints"); 183 184 auto reply1 = bus.call( 185 method1, 186 std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count()); 187 reply1.read(paths); 188 for (const auto& path : std::get<std::vector<std::string>>(paths)) 189 { 190 if (path != runningVersion) 191 { 192 nonRunningVersion = path; 193 break; 194 } 195 } 196 } 197 catch (const std::exception& e) 198 { 199 error( 200 "failed to make a d-bus call to Object Mapper Association, ERROR={ERR_EXCEP}", 201 "ERR_EXCEP", e.what()); 202 return; 203 } 204 205 using namespace sdbusplus::bus::match::rules; 206 captureNextBootSideChange.push_back( 207 std::make_unique<sdbusplus::bus::match_t>( 208 pldm::utils::DBusHandler::getBus(), 209 propertiesChanged(runningVersion, redundancyIntf), 210 [this](sdbusplus::message_t& msg) { 211 DbusChangedProps props; 212 std::string iface; 213 msg.read(iface, props); 214 processPriorityChangeNotification(props); 215 })); 216 fwUpdateMatcher.push_back(std::make_unique<sdbusplus::bus::match_t>( 217 pldm::utils::DBusHandler::getBus(), 218 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 219 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 220 [this](sdbusplus::message_t& msg) { 221 DBusInterfaceAdded interfaces; 222 sdbusplus::message::object_path path; 223 msg.read(path, interfaces); 224 225 for (auto& interface : interfaces) 226 { 227 if (interface.first == "xyz.openbmc_project.Software.Activation") 228 { 229 auto imageInterface = "xyz.openbmc_project.Software.Activation"; 230 auto imageObjPath = path.str.c_str(); 231 232 try 233 { 234 auto propVal = dBusIntf->getDbusPropertyVariant( 235 imageObjPath, "Activation", imageInterface); 236 const auto& imageProp = std::get<std::string>(propVal); 237 if (imageProp == "xyz.openbmc_project.Software." 238 "Activation.Activations.Ready" && 239 isCodeUpdateInProgress()) 240 { 241 newImageId = path.str; 242 if (!imageActivationMatch) 243 { 244 imageActivationMatch = 245 std::make_unique<sdbusplus::bus::match_t>( 246 pldm::utils::DBusHandler::getBus(), 247 propertiesChanged(newImageId, 248 "xyz.openbmc_project." 249 "Software.Activation"), 250 [this](sdbusplus::message_t& msg) { 251 DbusChangedProps props; 252 std::string iface; 253 msg.read(iface, props); 254 const auto itr = props.find("Activation"); 255 if (itr != props.end()) 256 { 257 PropertyValue value = itr->second; 258 auto propVal = std::get<std::string>(value); 259 if (propVal == 260 "xyz.openbmc_project.Software." 261 "Activation.Activations.Active") 262 { 263 CodeUpdateState state = 264 CodeUpdateState::END; 265 setCodeUpdateProgress(false); 266 auto sensorId = 267 getFirmwareUpdateSensor(); 268 sendStateSensorEvent( 269 sensorId, PLDM_STATE_SENSOR_STATE, 270 0, uint8_t(state), 271 uint8_t(CodeUpdateState::START)); 272 newImageId.clear(); 273 } 274 else if (propVal == "xyz.openbmc_project." 275 "Software.Activation." 276 "Activations.Failed" || 277 propVal == "xyz.openbmc_" 278 "project.Software." 279 "Activation." 280 "Activations." 281 "Invalid") 282 { 283 CodeUpdateState state = 284 CodeUpdateState::FAIL; 285 setCodeUpdateProgress(false); 286 auto sensorId = 287 getFirmwareUpdateSensor(); 288 sendStateSensorEvent( 289 sensorId, PLDM_STATE_SENSOR_STATE, 290 0, uint8_t(state), 291 uint8_t(CodeUpdateState::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", imageInterface, 317 "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( 387 "Failed to delete image, OBJ_PATH={OBJ_PATH}, INTERFACE={INTERFACE}, ERROR={ERR_EXCEP}", 388 "OBJ_PATH", SW_OBJ_PATH, "INTERFACE", DELETE_INTF, "ERR_EXCEP", 389 e.what()); 390 return; 391 } 392 } 393 394 uint8_t fetchBootSide(uint16_t entityInstance, CodeUpdate* codeUpdate) 395 { 396 uint8_t sensorOpState = tSideNum; 397 if (entityInstance == 0) 398 { 399 auto currSide = codeUpdate->fetchCurrentBootSide(); 400 if (currSide == Pside) 401 { 402 sensorOpState = pSideNum; 403 } 404 } 405 else if (entityInstance == 1) 406 { 407 auto nextSide = codeUpdate->fetchNextBootSide(); 408 if (nextSide == Pside) 409 { 410 sensorOpState = pSideNum; 411 } 412 } 413 else 414 { 415 sensorOpState = PLDM_SENSOR_UNKNOWN; 416 } 417 418 return sensorOpState; 419 } 420 421 int setBootSide(uint16_t entityInstance, uint8_t currState, 422 const std::vector<set_effecter_state_field>& stateField, 423 CodeUpdate* codeUpdate) 424 { 425 int rc = PLDM_SUCCESS; 426 auto side = (stateField[currState].effecter_state == pSideNum) ? "P" : "T"; 427 428 if (entityInstance == 0) 429 { 430 rc = codeUpdate->setCurrentBootSide(side); 431 } 432 else if (entityInstance == 1) 433 { 434 rc = codeUpdate->setNextBootSide(side); 435 } 436 else 437 { 438 rc = PLDM_PLATFORM_INVALID_STATE_VALUE; 439 } 440 return rc; 441 } 442 443 template <typename... T> 444 int executeCmd(const T&... t) 445 { 446 std::stringstream cmd; 447 ((cmd << t << " "), ...) << std::endl; 448 FILE* pipe = popen(cmd.str().c_str(), "r"); 449 if (!pipe) 450 { 451 throw std::runtime_error("popen() failed!"); 452 } 453 int rc = pclose(pipe); 454 if (WEXITSTATUS(rc)) 455 { 456 std::cerr << "Error executing: "; 457 ((std::cerr << " " << t), ...); 458 std::cerr << "\n"; 459 return -1; 460 } 461 462 return 0; 463 } 464 465 int processCodeUpdateLid(const std::string& filePath) 466 { 467 struct LidHeader 468 { 469 uint16_t magicNumber; 470 uint16_t headerVersion; 471 uint32_t lidNumber; 472 uint32_t lidDate; 473 uint16_t lidTime; 474 uint16_t lidClass; 475 uint32_t lidCrc; 476 uint32_t lidSize; 477 uint32_t headerSize; 478 }; 479 LidHeader header; 480 481 std::ifstream ifs(filePath, std::ios::in | std::ios::binary); 482 if (!ifs) 483 { 484 error("ifstream open error: {DIR_PATH}", "DIR_PATH", filePath.c_str()); 485 return PLDM_ERROR; 486 } 487 ifs.seekg(0); 488 ifs.read(reinterpret_cast<char*>(&header), sizeof(header)); 489 490 // File size should be the value of lid size minus the header size 491 auto fileSize = fs::file_size(filePath); 492 fileSize -= htonl(header.headerSize); 493 if (fileSize < htonl(header.lidSize)) 494 { 495 // File is not completely written yet 496 ifs.close(); 497 return PLDM_SUCCESS; 498 } 499 500 constexpr auto magicNumber = 0x0222; 501 if (htons(header.magicNumber) != magicNumber) 502 { 503 error("Invalid magic number: {DIR_PATH}", "DIR_PATH", filePath.c_str()); 504 ifs.close(); 505 return PLDM_ERROR; 506 } 507 508 fs::create_directories(imageDirPath); 509 fs::create_directories(lidDirPath); 510 511 constexpr auto bmcClass = 0x2000; 512 if (htons(header.lidClass) == bmcClass) 513 { 514 // Skip the header and concatenate the BMC LIDs into a tar file 515 std::ofstream ofs(tarImagePath, 516 std::ios::out | std::ios::binary | std::ios::app); 517 ifs.seekg(htonl(header.headerSize)); 518 ofs << ifs.rdbuf(); 519 ofs.flush(); 520 ofs.close(); 521 } 522 else 523 { 524 std::stringstream lidFileName; 525 lidFileName << std::hex << htonl(header.lidNumber) << ".lid"; 526 auto lidNoHeaderPath = fs::path(lidDirPath) / lidFileName.str(); 527 std::ofstream ofs(lidNoHeaderPath, 528 std::ios::out | std::ios::binary | std::ios::trunc); 529 ifs.seekg(htonl(header.headerSize)); 530 ofs << ifs.rdbuf(); 531 ofs.flush(); 532 ofs.close(); 533 } 534 535 ifs.close(); 536 fs::remove(filePath); 537 return PLDM_SUCCESS; 538 } 539 540 int CodeUpdate::assembleCodeUpdateImage() 541 { 542 pid_t pid = fork(); 543 544 if (pid == 0) 545 { 546 pid_t nextPid = fork(); 547 if (nextPid == 0) 548 { 549 // Create the hostfw squashfs image from the LID files without 550 // header 551 auto rc = executeCmd("/usr/sbin/mksquashfs", lidDirPath.c_str(), 552 hostfwImagePath.c_str(), "-all-root", 553 "-no-recovery"); 554 if (rc < 0) 555 { 556 error("Error occurred during the mksqusquashfs call"); 557 setCodeUpdateProgress(false); 558 auto sensorId = getFirmwareUpdateSensor(); 559 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0, 560 uint8_t(CodeUpdateState::FAIL), 561 uint8_t(CodeUpdateState::START)); 562 exit(EXIT_FAILURE); 563 } 564 565 fs::create_directories(updateDirPath); 566 567 // Extract the BMC tarball content 568 rc = executeCmd("/bin/tar", "-xf", tarImagePath.c_str(), "-C", 569 updateDirPath); 570 if (rc < 0) 571 { 572 setCodeUpdateProgress(false); 573 auto sensorId = getFirmwareUpdateSensor(); 574 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0, 575 uint8_t(CodeUpdateState::FAIL), 576 uint8_t(CodeUpdateState::START)); 577 exit(EXIT_FAILURE); 578 } 579 580 // Add the hostfw image to the directory where the contents were 581 // extracted 582 fs::copy_file(hostfwImagePath, 583 fs::path(updateDirPath) / hostfwImageName, 584 fs::copy_options::overwrite_existing); 585 586 // Remove the tarball file, then re-generate it with so that the 587 // hostfw image becomes part of the tarball 588 fs::remove(tarImagePath); 589 rc = executeCmd("/bin/tar", "-cf", tarImagePath, ".", "-C", 590 updateDirPath); 591 if (rc < 0) 592 { 593 error("Error occurred during the generation of the tarball"); 594 setCodeUpdateProgress(false); 595 auto sensorId = getFirmwareUpdateSensor(); 596 sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0, 597 uint8_t(CodeUpdateState::FAIL), 598 uint8_t(CodeUpdateState::START)); 599 exit(EXIT_FAILURE); 600 } 601 602 // Copy the tarball to the update directory to trigger the phosphor 603 // software manager to create a version interface 604 fs::copy_file(tarImagePath, updateImagePath, 605 fs::copy_options::overwrite_existing); 606 607 // Cleanup 608 fs::remove_all(updateDirPath); 609 fs::remove_all(lidDirPath); 610 fs::remove_all(imageDirPath); 611 612 exit(EXIT_SUCCESS); 613 } 614 else if (nextPid < 0) 615 { 616 error("Error occurred during fork. ERROR={ERR}", "ERR", errno); 617 exit(EXIT_FAILURE); 618 } 619 620 // Do nothing as parent. When parent exits, child will be reparented 621 // under init and be reaped properly. 622 exit(0); 623 } 624 else if (pid > 0) 625 { 626 int status; 627 if (waitpid(pid, &status, 0) < 0) 628 { 629 error("Error occurred during waitpid. ERROR={ERR}", "ERR", errno); 630 631 return PLDM_ERROR; 632 } 633 else if (WEXITSTATUS(status) != 0) 634 { 635 error( 636 "Failed to execute the assembling of the image. STATUS={IMG_STATUS}", 637 "IMG_STATUS", status); 638 return PLDM_ERROR; 639 } 640 } 641 else 642 { 643 error("Error occurred during fork. ERROR={ERR}", "ERR", errno); 644 return PLDM_ERROR; 645 } 646 647 return PLDM_SUCCESS; 648 } 649 650 } // namespace responder 651 } // namespace pldm 652