control: PCIe card metadata wrapper class
This class introduces a PCIeCardMetadata class to manage JSON files
containing PCIe card floor indexes. These can then be used by actions
that want to set the fan floors based on which PCIe cards are present.
It provides a lookup() method that takes four properties from the
PCIeDevice D-Bus interface to uniquely identify a card, and then returns
the floor index for the card if it doesn't have a temperature sensor on
it, or the 'has temp sensor' value which will be true.
The code first loads /etc/phosphor-fan-presence/control/pcie_cards.json
if it exists. If that isn't present, it then tries
/usr/share/phosphor-fan-presence/control/pcie_cards.json. After
that, it tries
/usr/share/phosphor-fan-presence/control/<system-name>/pcie_cards.json
where <system-name> comes from the list of system names passed into
the constructor.
It will overwrite any file entries that match as it goes on, so if a
system has to override just a few of the floor indexes from a more
generic file it can be done without having to duplicate entries for
cards that are the same.
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I81f2476dd58d1529ee6484243e7d8f1e49027cf9
diff --git a/control/Makefile.am b/control/Makefile.am
index 71e3282..3cd2c0a 100644
--- a/control/Makefile.am
+++ b/control/Makefile.am
@@ -89,7 +89,8 @@
json/actions/count_state_floor.cpp \
json/actions/get_managed_objects.cpp \
json/utils/flight_recorder.cpp \
- json/utils/modifier.cpp
+ json/utils/modifier.cpp \
+ json/utils/pcie_card_metadata.cpp
else
phosphor_fan_control_SOURCES += \
argument.cpp \
diff --git a/control/json/utils/pcie_card_metadata.cpp b/control/json/utils/pcie_card_metadata.cpp
new file mode 100644
index 0000000..e33f747
--- /dev/null
+++ b/control/json/utils/pcie_card_metadata.cpp
@@ -0,0 +1,183 @@
+/**
+ * Copyright © 2022 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pcie_card_metadata.hpp"
+
+#include "json_config.hpp"
+
+#include <iostream>
+
+static constexpr auto cardFileName = "pcie_cards.json";
+
+namespace phosphor::fan::control::json
+{
+
+namespace fs = std::filesystem;
+using namespace phosphor::fan;
+
+PCIeCardMetadata::PCIeCardMetadata(const std::vector<std::string>& systemNames)
+{
+ loadCards(systemNames);
+}
+
+void PCIeCardMetadata::loadCards(const std::vector<std::string>& systemNames)
+{
+ const auto defaultPath = fs::path{"control"} / cardFileName;
+
+ // Look in the override location first
+ auto confFile = fs::path{confOverridePath} / defaultPath;
+
+ if (!fs::exists(confFile))
+ {
+ confFile = fs::path{confBasePath} / defaultPath;
+ }
+
+ if (fs::exists(confFile))
+ {
+ log<level::DEBUG>(
+ fmt::format("Loading PCIe card file {}", confFile.native())
+ .c_str());
+ auto json = JsonConfig::load(confFile);
+ load(json);
+ }
+
+ // Go from least specific to most specific in the system names so files in
+ // the latter category can override ones in the former.
+ for (auto nameIt = systemNames.rbegin(); nameIt != systemNames.rend();
+ ++nameIt)
+ {
+ const auto basePath = fs::path{"control"} / *nameIt / cardFileName;
+
+ // Look in the override location first
+ auto confFile = fs::path{confOverridePath} / basePath;
+
+ if (!fs::exists(confFile))
+ {
+ confFile = fs::path{confBasePath} / basePath;
+ }
+
+ if (fs::exists(confFile))
+ {
+ log<level::DEBUG>(
+ fmt::format("Loading PCIe card file {}", confFile.native())
+ .c_str());
+
+ auto json = JsonConfig::load(confFile);
+ load(json);
+ }
+ }
+
+ if (_cards.empty())
+ {
+ throw std::runtime_error{
+ "No valid PCIe card entries found in any JSON"};
+ }
+}
+
+void PCIeCardMetadata::load(const nlohmann::json& json)
+{
+ if (!json.contains("cards") || !json.at("cards").is_array())
+ {
+ throw std::runtime_error{
+ fmt::format("Missing 'cards' array in PCIe card JSON")};
+ }
+
+ for (const auto& card : json.at("cards"))
+ {
+ if (!card.contains("vendor_id") || !card.contains("device_id") ||
+ !card.contains("subsystem_vendor_id") ||
+ !card.contains("subsystem_id") ||
+ !(card.contains("has_temp_sensor") || card.contains("floor_index")))
+ {
+ throw std::runtime_error{"Invalid PCIe card json"};
+ }
+
+ Metadata data;
+ data.vendorID =
+ std::stoul(card.at("vendor_id").get<std::string>(), nullptr, 16);
+ data.deviceID =
+ std::stoul(card.at("device_id").get<std::string>(), nullptr, 16);
+ data.subsystemVendorID = std::stoul(
+ card.at("subsystem_vendor_id").get<std::string>(), nullptr, 16);
+ data.subsystemID =
+ std::stoul(card.at("subsystem_id").get<std::string>(), nullptr, 16);
+
+ data.hasTempSensor = card.value("has_temp_sensor", false);
+ data.floorIndex = card.value("floor_index", -1);
+
+ auto iter = std::find(_cards.begin(), _cards.end(), data);
+ if (iter != _cards.end())
+ {
+ iter->vendorID = data.vendorID;
+ iter->deviceID = data.deviceID;
+ iter->subsystemVendorID = data.subsystemVendorID;
+ iter->subsystemID = data.subsystemID;
+ iter->floorIndex = data.floorIndex;
+ iter->hasTempSensor = data.hasTempSensor;
+ }
+ else
+ {
+ _cards.push_back(std::move(data));
+ }
+ }
+}
+
+void PCIeCardMetadata::dump() const
+{
+ for (const auto& entry : _cards)
+ {
+ std::cerr << "--------------------------------------------------"
+ << "\n";
+ std::cerr << "vendorID: " << std::hex << entry.vendorID << "\n";
+ std::cerr << "deviceID: " << entry.deviceID << "\n";
+ std::cerr << "subsysVendorID: " << entry.subsystemVendorID << "\n";
+ std::cerr << "subsystemID: " << entry.subsystemID << "\n";
+ std::cerr << "hasTempSensor: " << std::dec << entry.hasTempSensor
+ << "\n";
+ std::cerr << "floorIndex: " << entry.floorIndex << "\n";
+ }
+}
+
+std::optional<std::variant<int32_t, bool>>
+ PCIeCardMetadata::lookup(uint16_t deviceID, uint16_t vendorID,
+ uint16_t subsystemID,
+ uint16_t subsystemVendorID) const
+{
+ log<level::DEBUG>(fmt::format("Lookup {:#x} ${:#x} {:#x} {:#x}", deviceID,
+ vendorID, subsystemID, subsystemVendorID)
+ .c_str());
+ auto card =
+ std::find_if(_cards.begin(), _cards.end(),
+ [&deviceID, &vendorID, &subsystemID,
+ &subsystemVendorID](const auto& card) {
+ return (deviceID == card.deviceID) &&
+ (vendorID == card.vendorID) &&
+ (subsystemID == card.subsystemID) &&
+ (subsystemVendorID == card.subsystemVendorID);
+ });
+
+ if (card != _cards.end())
+ {
+ if (card->hasTempSensor)
+ {
+ return true;
+ }
+ return card->floorIndex;
+ }
+ return std::nullopt;
+}
+
+} // namespace phosphor::fan::control::json
diff --git a/control/json/utils/pcie_card_metadata.hpp b/control/json/utils/pcie_card_metadata.hpp
new file mode 100644
index 0000000..a879a51
--- /dev/null
+++ b/control/json/utils/pcie_card_metadata.hpp
@@ -0,0 +1,150 @@
+/**
+ * Copyright © 2022 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <nlohmann/json.hpp>
+
+#include <filesystem>
+#include <optional>
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace phosphor::fan::control::json
+{
+
+/**
+ * @class PCIeCardMetadata
+ *
+ * This class provides the ability for an action to look up a PCIe card's
+ * fan floor index or temp sensor name based on its metadata, which
+ * consists of 4 properties from the PCIeDevice D-Bus interface.
+ *
+ * The metadata is stored in one or more (see loadCards) JSON files, which
+ * look like:
+ * {
+ * "cards": [
+ * {
+ * "name": "TestCard",
+ * "device_id": "0x1",
+ * "vendor_id": "0x2",
+ * "subsystem_id": "0x3",
+ * "subsystem_vendor_id": "0x4",
+ * "floor_index": 3
+ * },
+ * ...
+ * ]
+ * }
+ *
+ * If the card has a temperature sensor on it, then it doesn't
+ * need a floor index and instead will have:
+ * "has_temp_sensor": true
+ */
+class PCIeCardMetadata
+{
+ public:
+ PCIeCardMetadata() = delete;
+ ~PCIeCardMetadata() = default;
+ PCIeCardMetadata(const PCIeCardMetadata&) = delete;
+ PCIeCardMetadata& operator=(const PCIeCardMetadata&) = delete;
+ PCIeCardMetadata(PCIeCardMetadata&&) = delete;
+ PCIeCardMetadata& operator=(PCIeCardMetadata&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] systemNames - The system names values
+ */
+ PCIeCardMetadata(const std::vector<std::string>& systemNames);
+
+ /**
+ * @brief Look up a floor index based on a card's metadata
+ *
+ * @param[in] deviceID - Function0DeviceId value
+ * @param[in] vendorID - Function0VendorId value
+ * @param[in] subsystemID - Function0SubsystemId value
+ * @param[in] subsystemVendorID - Function0SubsystemVendorId value
+ *
+ * @return optional<variant<int32, bool>> -
+ * Either the floor index for that entry, or true saying
+ * it has a temp sensor.
+ *
+ * If no entry is found, it will return std::nullopt.
+ */
+ std::optional<std::variant<int32_t, bool>>
+ lookup(uint16_t deviceID, uint16_t vendorID, uint16_t subsystemID,
+ uint16_t subsystemVendorID) const;
+
+ private:
+ /**
+ * Structure to hold card metadata.
+ */
+ struct Metadata
+ {
+ uint16_t vendorID;
+ uint16_t deviceID;
+ uint16_t subsystemVendorID;
+ uint16_t subsystemID;
+ int32_t floorIndex;
+ bool hasTempSensor;
+
+ bool operator==(const Metadata& other)
+ {
+ return (vendorID == other.vendorID) &&
+ (deviceID == other.deviceID) &&
+ (subsystemVendorID == other.subsystemVendorID) &&
+ (subsystemID == other.subsystemID);
+ }
+ };
+
+ /**
+ * @brief Loads the metadata from JSON files
+ *
+ * It will load a pcie_cards.json file in the default location if it
+ * is present.
+ *
+ * If systemNames isn't empty, it will load in any 'pcie_cards.json'
+ * files it finds in directories based on those names, overwriting any
+ * entries that were in any previous files. This allows different
+ * floor indexes for some cards for the different systems in a flash
+ * image without needing to specify every possible card in every
+ * system specific JSON file.
+ *
+ * If no valid config files are found it will throw an exception.
+ *
+ * @param[in] systemNames - The system names values
+ */
+ void loadCards(const std::vector<std::string>& systemNames);
+
+ /**
+ * @brief Loads in the card info from the JSON
+ *
+ * @param[in] json - The JSON containing a cards array
+ */
+ void load(const nlohmann::json& json);
+
+ /**
+ * @brief Dumps the cards vector for debug
+ */
+ void dump() const;
+
+ /**
+ * @brief The card metadata
+ */
+ std::vector<Metadata> _cards;
+};
+
+} // namespace phosphor::fan::control::json