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