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