#include "terminus.hpp" #include "libpldm/platform.h" #include "dbus_impl_fru.hpp" #include "terminus_manager.hpp" #include <common/utils.hpp> #include <ranges> namespace pldm { namespace platform_mc { Terminus::Terminus(pldm_tid_t tid, uint64_t supportedTypes) : initialized(false), maxBufferSize(PLDM_PLATFORM_EVENT_MSG_MAX_BUFFER_SIZE), synchronyConfigurationSupported(0), pollEvent(false), tid(tid), supportedTypes(supportedTypes) {} bool Terminus::doesSupportType(uint8_t type) { return supportedTypes.test(type); } bool Terminus::doesSupportCommand(uint8_t type, uint8_t command) { if (!doesSupportType(type)) { return false; } try { const size_t idx = type * (PLDM_MAX_CMDS_PER_TYPE / 8) + (command / 8); if (idx >= supportedCmds.size()) { return false; } if (supportedCmds[idx] & (1 << (command % 8))) { lg2::info( "PLDM type {TYPE} command {CMD} is supported by terminus {TID}", "TYPE", type, "CMD", command, "TID", getTid()); return true; } } catch (const std::exception& e) { return false; } return false; } std::optional<std::string_view> Terminus::findTerminusName() { auto it = std::find_if( entityAuxiliaryNamesTbl.begin(), entityAuxiliaryNamesTbl.end(), [](const std::shared_ptr<EntityAuxiliaryNames>& entityAuxiliaryNames) { const auto& [key, entityNames] = *entityAuxiliaryNames; /** * There is only one Overall system container entity in one * terminus. The entity auxiliary name PDR of that terminus with the * that type of containerID will include terminus name. */ return ( entityAuxiliaryNames && key.containerId == PLDM_PLATFORM_ENTITY_SYSTEM_CONTAINER_ID && entityNames.size()); }); if (it != entityAuxiliaryNamesTbl.end()) { const auto& [key, entityNames] = **it; if (!entityNames.size()) { return std::nullopt; } return entityNames[0].second; } return std::nullopt; } bool Terminus::createInventoryPath(std::string tName) { if (tName.empty()) { return false; } /* inventory object is created */ if (inventoryItemBoardInft) { return false; } inventoryPath = "/xyz/openbmc_project/inventory/system/board/" + tName; try { inventoryItemBoardInft = std::make_unique<pldm::dbus_api::PldmEntityReq>( utils::DBusHandler::getBus(), inventoryPath.c_str()); return true; } catch (const sdbusplus::exception_t& e) { lg2::error( "Failed to create Inventory Board interface for device {PATH}", "PATH", inventoryPath); } return false; } void Terminus::parseTerminusPDRs() { std::vector<std::shared_ptr<pldm_numeric_sensor_value_pdr>> numericSensorPdrs{}; std::vector<std::shared_ptr<pldm_compact_numeric_sensor_pdr>> compactNumericSensorPdrs{}; for (auto& pdr : pdrs) { auto pdrHdr = reinterpret_cast<pldm_pdr_hdr*>(pdr.data()); switch (pdrHdr->type) { case PLDM_SENSOR_AUXILIARY_NAMES_PDR: { auto sensorAuxNames = parseSensorAuxiliaryNamesPDR(pdr); if (!sensorAuxNames) { lg2::error( "Failed to parse PDR with type {TYPE} handle {HANDLE}", "TYPE", pdrHdr->type, "HANDLE", static_cast<uint32_t>(pdrHdr->record_handle)); continue; } sensorAuxiliaryNamesTbl.emplace_back(std::move(sensorAuxNames)); break; } case PLDM_NUMERIC_SENSOR_PDR: { auto parsedPdr = parseNumericSensorPDR(pdr); if (!parsedPdr) { lg2::error( "Failed to parse PDR with type {TYPE} handle {HANDLE}", "TYPE", pdrHdr->type, "HANDLE", static_cast<uint32_t>(pdrHdr->record_handle)); continue; } numericSensorPdrs.emplace_back(std::move(parsedPdr)); break; } case PLDM_COMPACT_NUMERIC_SENSOR_PDR: { auto parsedPdr = parseCompactNumericSensorPDR(pdr); if (!parsedPdr) { lg2::error( "Failed to parse PDR with type {TYPE} handle {HANDLE}", "TYPE", pdrHdr->type, "HANDLE", static_cast<uint32_t>(pdrHdr->record_handle)); continue; } auto sensorAuxNames = parseCompactNumericSensorNames(pdr); if (!sensorAuxNames) { lg2::error( "Failed to parse sensor name PDR with type {TYPE} handle {HANDLE}", "TYPE", pdrHdr->type, "HANDLE", static_cast<uint32_t>(pdrHdr->record_handle)); continue; } compactNumericSensorPdrs.emplace_back(std::move(parsedPdr)); sensorAuxiliaryNamesTbl.emplace_back(std::move(sensorAuxNames)); break; } case PLDM_ENTITY_AUXILIARY_NAMES_PDR: { auto entityNames = parseEntityAuxiliaryNamesPDR(pdr); if (!entityNames) { lg2::error( "Failed to parse sensor name PDR with type {TYPE} handle {HANDLE}", "TYPE", pdrHdr->type, "HANDLE", static_cast<uint32_t>(pdrHdr->record_handle)); continue; } entityAuxiliaryNamesTbl.emplace_back(std::move(entityNames)); break; } default: { lg2::error("Unsupported PDR with type {TYPE} handle {HANDLE}", "TYPE", pdrHdr->type, "HANDLE", static_cast<uint32_t>(pdrHdr->record_handle)); break; } } } auto tName = findTerminusName(); if (tName && !tName.value().empty()) { lg2::info("Terminus {TID} has Auxiliary Name {NAME}.", "TID", tid, "NAME", tName.value()); terminusName = static_cast<std::string>(tName.value()); } if (terminusName.empty() && (numericSensorPdrs.size() || compactNumericSensorPdrs.size())) { lg2::error( "Terminus ID {TID}: DOES NOT have name. Skip Adding sensors.", "TID", tid); return; } if (createInventoryPath(terminusName)) { lg2::error("Terminus ID {TID}: Created Inventory path {PATH}.", "TID", tid, "PATH", inventoryPath); } for (auto pdr : numericSensorPdrs) { addNumericSensor(pdr); } for (auto pdr : compactNumericSensorPdrs) { addCompactNumericSensor(pdr); } } std::shared_ptr<SensorAuxiliaryNames> Terminus::getSensorAuxiliaryNames(SensorId id) { auto it = std::find_if( sensorAuxiliaryNamesTbl.begin(), sensorAuxiliaryNamesTbl.end(), [id]( const std::shared_ptr<SensorAuxiliaryNames>& sensorAuxiliaryNames) { const auto& [sensorId, sensorCnt, sensorNames] = *sensorAuxiliaryNames; return sensorId == id; }); if (it != sensorAuxiliaryNamesTbl.end()) { return *it; } return nullptr; }; std::shared_ptr<SensorAuxiliaryNames> Terminus::parseSensorAuxiliaryNamesPDR(const std::vector<uint8_t>& pdrData) { constexpr uint8_t nullTerminator = 0; auto pdr = reinterpret_cast<const struct pldm_sensor_auxiliary_names_pdr*>( pdrData.data()); const uint8_t* ptr = pdr->names; std::vector<AuxiliaryNames> sensorAuxNames{}; char16_t alignedBuffer[PLDM_STR_UTF_16_MAX_LEN]; for ([[maybe_unused]] const auto& sensor : std::views::iota(0, static_cast<int>(pdr->sensor_count))) { const uint8_t nameStringCount = static_cast<uint8_t>(*ptr); ptr += sizeof(uint8_t); AuxiliaryNames nameStrings{}; for ([[maybe_unused]] const auto& count : std::views::iota(0, static_cast<int>(nameStringCount))) { std::string_view nameLanguageTag( reinterpret_cast<const char*>(ptr)); ptr += nameLanguageTag.size() + sizeof(nullTerminator); int u16NameStringLen = 0; for (int i = 0; ptr[i] != 0 || ptr[i + 1] != 0; i += 2) { u16NameStringLen++; } /* include terminator */ u16NameStringLen++; std::fill(std::begin(alignedBuffer), std::end(alignedBuffer), 0); if (u16NameStringLen > PLDM_STR_UTF_16_MAX_LEN) { lg2::error("Sensor name to long."); return nullptr; } memcpy(alignedBuffer, ptr, u16NameStringLen * sizeof(uint16_t)); std::u16string u16NameString(alignedBuffer, u16NameStringLen); ptr += u16NameString.size() * sizeof(uint16_t); std::transform(u16NameString.cbegin(), u16NameString.cend(), u16NameString.begin(), [](uint16_t utf16) { return be16toh(utf16); }); std::string nameString = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{} .to_bytes(u16NameString); nameStrings.emplace_back(std::make_pair( nameLanguageTag, pldm::utils::trimNameForDbus(nameString))); } sensorAuxNames.emplace_back(std::move(nameStrings)); } return std::make_shared<SensorAuxiliaryNames>( pdr->sensor_id, pdr->sensor_count, std::move(sensorAuxNames)); } std::shared_ptr<EntityAuxiliaryNames> Terminus::parseEntityAuxiliaryNamesPDR(const std::vector<uint8_t>& pdrData) { auto names_offset = sizeof(struct pldm_pdr_hdr) + PLDM_PDR_ENTITY_AUXILIARY_NAME_PDR_MIN_LENGTH; auto names_size = pdrData.size() - names_offset; size_t decodedPdrSize = sizeof(struct pldm_entity_auxiliary_names_pdr) + names_size; auto vPdr = std::vector<char>(decodedPdrSize); auto decodedPdr = reinterpret_cast<struct pldm_entity_auxiliary_names_pdr*>(vPdr.data()); auto rc = decode_entity_auxiliary_names_pdr(pdrData.data(), pdrData.size(), decodedPdr, decodedPdrSize); if (rc) { lg2::error( "Failed to decode Entity Auxiliary Name PDR data, error {RC}.", "RC", rc); return nullptr; } auto vNames = std::vector<pldm_entity_auxiliary_name>(decodedPdr->name_string_count); decodedPdr->names = vNames.data(); rc = decode_pldm_entity_auxiliary_names_pdr_index(decodedPdr); if (rc) { lg2::error("Failed to decode Entity Auxiliary Name, error {RC}.", "RC", rc); return nullptr; } AuxiliaryNames nameStrings{}; for (const auto& count : std::views::iota(0, static_cast<int>(decodedPdr->name_string_count))) { std::string_view nameLanguageTag = static_cast<std::string_view>(decodedPdr->names[count].tag); const size_t u16NameStringLen = std::char_traits<char16_t>::length(decodedPdr->names[count].name); std::u16string u16NameString(decodedPdr->names[count].name, u16NameStringLen); std::transform(u16NameString.cbegin(), u16NameString.cend(), u16NameString.begin(), [](uint16_t utf16) { return be16toh(utf16); }); std::string nameString = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{} .to_bytes(u16NameString); nameStrings.emplace_back(std::make_pair( nameLanguageTag, pldm::utils::trimNameForDbus(nameString))); } EntityKey key{decodedPdr->container.entity_type, decodedPdr->container.entity_instance_num, decodedPdr->container.entity_container_id}; return std::make_shared<EntityAuxiliaryNames>(key, nameStrings); } std::shared_ptr<pldm_numeric_sensor_value_pdr> Terminus::parseNumericSensorPDR(const std::vector<uint8_t>& pdr) { const uint8_t* ptr = pdr.data(); auto parsedPdr = std::make_shared<pldm_numeric_sensor_value_pdr>(); auto rc = decode_numeric_sensor_pdr_data(ptr, pdr.size(), parsedPdr.get()); if (rc) { return nullptr; } return parsedPdr; } void Terminus::addNumericSensor( const std::shared_ptr<pldm_numeric_sensor_value_pdr> pdr) { uint16_t sensorId = pdr->sensor_id; if (terminusName.empty()) { lg2::error( "Terminus ID {TID}: DOES NOT have name. Skip Adding sensors.", "TID", tid); return; } std::string sensorName = terminusName + "_" + "Sensor_" + std::to_string(pdr->sensor_id); if (pdr->sensor_auxiliary_names_pdr) { auto sensorAuxiliaryNames = getSensorAuxiliaryNames(sensorId); if (sensorAuxiliaryNames) { const auto& [sensorId, sensorCnt, sensorNames] = *sensorAuxiliaryNames; if (sensorCnt == 1) { for (const auto& [languageTag, name] : sensorNames[0]) { if (languageTag == "en" && !name.empty()) { sensorName = terminusName + "_" + name; } } } } } try { auto sensor = std::make_shared<NumericSensor>( tid, true, pdr, sensorName, inventoryPath); lg2::info("Created NumericSensor {NAME}", "NAME", sensorName); numericSensors.emplace_back(sensor); } catch (const sdbusplus::exception_t& e) { lg2::error( "Failed to create NumericSensor. error - {ERROR} sensorname - {NAME}", "ERROR", e, "NAME", sensorName); } } std::shared_ptr<SensorAuxiliaryNames> Terminus::parseCompactNumericSensorNames(const std::vector<uint8_t>& sPdr) { std::vector<std::vector<std::pair<NameLanguageTag, SensorName>>> sensorAuxNames{}; AuxiliaryNames nameStrings{}; auto pdr = reinterpret_cast<const pldm_compact_numeric_sensor_pdr*>(sPdr.data()); if (sPdr.size() < (sizeof(pldm_compact_numeric_sensor_pdr) - sizeof(uint8_t))) { return nullptr; } if (!pdr->sensor_name_length || (sPdr.size() < (sizeof(pldm_compact_numeric_sensor_pdr) - sizeof(uint8_t) + pdr->sensor_name_length))) { return nullptr; } std::string nameString(reinterpret_cast<const char*>(pdr->sensor_name), pdr->sensor_name_length); nameStrings.emplace_back( std::make_pair("en", pldm::utils::trimNameForDbus(nameString))); sensorAuxNames.emplace_back(std::move(nameStrings)); return std::make_shared<SensorAuxiliaryNames>(pdr->sensor_id, 1, std::move(sensorAuxNames)); } std::shared_ptr<pldm_compact_numeric_sensor_pdr> Terminus::parseCompactNumericSensorPDR(const std::vector<uint8_t>& sPdr) { auto pdr = reinterpret_cast<const pldm_compact_numeric_sensor_pdr*>(sPdr.data()); if (sPdr.size() < sizeof(pldm_compact_numeric_sensor_pdr)) { // Handle error: input data too small to contain valid pdr return nullptr; } auto parsedPdr = std::make_shared<pldm_compact_numeric_sensor_pdr>(); parsedPdr->hdr = pdr->hdr; parsedPdr->terminus_handle = pdr->terminus_handle; parsedPdr->sensor_id = pdr->sensor_id; parsedPdr->entity_type = pdr->entity_type; parsedPdr->entity_instance = pdr->entity_instance; parsedPdr->container_id = pdr->container_id; parsedPdr->sensor_name_length = pdr->sensor_name_length; parsedPdr->base_unit = pdr->base_unit; parsedPdr->unit_modifier = pdr->unit_modifier; parsedPdr->occurrence_rate = pdr->occurrence_rate; parsedPdr->range_field_support = pdr->range_field_support; parsedPdr->warning_high = pdr->warning_high; parsedPdr->warning_low = pdr->warning_low; parsedPdr->critical_high = pdr->critical_high; parsedPdr->critical_low = pdr->critical_low; parsedPdr->fatal_high = pdr->fatal_high; parsedPdr->fatal_low = pdr->fatal_low; return parsedPdr; } void Terminus::addCompactNumericSensor( const std::shared_ptr<pldm_compact_numeric_sensor_pdr> pdr) { uint16_t sensorId = pdr->sensor_id; if (terminusName.empty()) { lg2::error( "Terminus ID {TID}: DOES NOT have name. Skip Adding sensors.", "TID", tid); return; } std::string sensorName = terminusName + "_" + "Sensor_" + std::to_string(pdr->sensor_id); auto sensorAuxiliaryNames = getSensorAuxiliaryNames(sensorId); if (sensorAuxiliaryNames) { const auto& [sensorId, sensorCnt, sensorNames] = *sensorAuxiliaryNames; if (sensorCnt == 1) { for (const auto& [languageTag, name] : sensorNames[0]) { if (languageTag == "en" && !name.empty()) { sensorName = terminusName + "_" + name; } } } } try { auto sensor = std::make_shared<NumericSensor>( tid, true, pdr, sensorName, inventoryPath); lg2::info("Created Compact NumericSensor {NAME}", "NAME", sensorName); numericSensors.emplace_back(sensor); } catch (const sdbusplus::exception_t& e) { lg2::error( "Failed to create Compact NumericSensor. error - {ERROR} sensorname - {NAME}", "ERROR", e, "NAME", sensorName); } } std::shared_ptr<NumericSensor> Terminus::getSensorObject(SensorId id) { if (terminusName.empty()) { lg2::error( "Terminus ID {TID}: DOES NOT have terminus name. No numeric sensor object.", "TID", tid); return nullptr; } if (!numericSensors.size()) { lg2::error("Terminus ID {TID} name {NAME}: DOES NOT have sensor.", "TID", tid, "NAME", terminusName); return nullptr; } for (auto& sensor : numericSensors) { if (!sensor) { continue; } if (sensor->sensorId == id) { return sensor; } } return nullptr; } /** @brief Check if a pointer is go through end of table * @param[in] table - pointer to FRU record table * @param[in] p - pointer to each record of FRU record table * @param[in] tableSize - FRU table size */ static bool isTableEnd(const uint8_t* table, const uint8_t* p, const size_t tableSize) { auto offset = p - table; return (tableSize - offset) < sizeof(struct pldm_fru_record_data_format); } void Terminus::updateInventoryWithFru(const uint8_t* fruData, const size_t fruLen) { auto tmp = getTerminusName(); if (!tmp || tmp.value().empty()) { lg2::error( "Terminus ID {TID}: Failed to update Inventory with Fru Data - error : Terminus name is empty.", "TID", tid); return; } if (createInventoryPath(static_cast<std::string>(tmp.value()))) { lg2::info("Terminus ID {TID}: Created Inventory path.", "TID", tid); } auto ptr = fruData; while (!isTableEnd(fruData, ptr, fruLen)) { auto record = reinterpret_cast<const pldm_fru_record_data_format*>(ptr); ptr += sizeof(pldm_fru_record_data_format) - sizeof(pldm_fru_record_tlv); if (!record->num_fru_fields) { lg2::error( "Invalid number of fields {NUM} of Record ID Type {TYPE} of terminus {TID}", "NUM", record->num_fru_fields, "TYPE", record->record_type, "TID", tid); return; } if (record->record_type != PLDM_FRU_RECORD_TYPE_GENERAL) { lg2::error( "Does not support Fru Record ID Type {TYPE} of terminus {TID}", "TYPE", record->record_type, "TID", tid); for ([[maybe_unused]] const auto& idx : std::views::iota(0, static_cast<int>(record->num_fru_fields))) { auto tlv = reinterpret_cast<const pldm_fru_record_tlv*>(ptr); ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length; } continue; } /* FRU General record type */ for ([[maybe_unused]] const auto& idx : std::views::iota(0, static_cast<int>(record->num_fru_fields))) { auto tlv = reinterpret_cast<const pldm_fru_record_tlv*>(ptr); std::string fruField{}; if (tlv->type != PLDM_FRU_FIELD_TYPE_IANA) { auto strOptional = pldm::utils::fruFieldValuestring(tlv->value, tlv->length); if (!strOptional) { ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length; continue; } fruField = strOptional.value(); if (fruField.empty()) { ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length; continue; } } switch (tlv->type) { case PLDM_FRU_FIELD_TYPE_MODEL: inventoryItemBoardInft->model(fruField); break; case PLDM_FRU_FIELD_TYPE_PN: inventoryItemBoardInft->partNumber(fruField); break; case PLDM_FRU_FIELD_TYPE_SN: inventoryItemBoardInft->serialNumber(fruField); break; case PLDM_FRU_FIELD_TYPE_MANUFAC: inventoryItemBoardInft->manufacturer(fruField); break; case PLDM_FRU_FIELD_TYPE_NAME: inventoryItemBoardInft->names({fruField}); break; case PLDM_FRU_FIELD_TYPE_VERSION: inventoryItemBoardInft->version(fruField); break; case PLDM_FRU_FIELD_TYPE_ASSET_TAG: inventoryItemBoardInft->assetTag(fruField); break; case PLDM_FRU_FIELD_TYPE_VENDOR: case PLDM_FRU_FIELD_TYPE_CHASSIS: case PLDM_FRU_FIELD_TYPE_SKU: case PLDM_FRU_FIELD_TYPE_DESC: case PLDM_FRU_FIELD_TYPE_EC_LVL: case PLDM_FRU_FIELD_TYPE_OTHER: break; case PLDM_FRU_FIELD_TYPE_IANA: auto iana = pldm::utils::fruFieldParserU32(tlv->value, tlv->length); if (!iana) { ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length; continue; } break; } ptr += sizeof(pldm_fru_record_tlv) - 1 + tlv->length; } } } } // namespace platform_mc } // namespace pldm