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