/** * Copyright © 2024 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 "config_file_parser.hpp" #include "config_file_parser_error.hpp" #include "gpios_only_device.hpp" #include "json_parser_utils.hpp" #include "ucd90160_device.hpp" #include "ucd90320_device.hpp" #include #include #include #include #include using namespace phosphor::power::json_parser_utils; using ConfigFileParserError = phosphor::power::util::ConfigFileParserError; namespace fs = std::filesystem; namespace phosphor::power::sequencer::config_file_parser { const std::filesystem::path standardConfigFileDirectory{ "/usr/share/phosphor-power-sequencer"}; std::filesystem::path find( const std::vector& compatibleSystemTypes, const std::filesystem::path& configFileDir) { fs::path pathName, possiblePath; std::string fileName; for (const std::string& systemType : compatibleSystemTypes) { // Look for file name that is entire system type + ".json" // Example: com.acme.Hardware.Chassis.Model.MegaServer.json fileName = systemType + ".json"; possiblePath = configFileDir / fileName; if (fs::is_regular_file(possiblePath)) { pathName = possiblePath; break; } // Look for file name that is last node of system type + ".json" // Example: MegaServer.json std::string::size_type pos = systemType.rfind('.'); if ((pos != std::string::npos) && ((systemType.size() - pos) > 1)) { fileName = systemType.substr(pos + 1) + ".json"; possiblePath = configFileDir / fileName; if (fs::is_regular_file(possiblePath)) { pathName = possiblePath; break; } } } return pathName; } std::vector> parse( const std::filesystem::path& pathName, Services& services) { try { // Use standard JSON parser to create tree of JSON elements std::ifstream file{pathName}; json rootElement = json::parse(file); // Parse tree of JSON elements and return corresponding C++ objects return internal::parseRoot(rootElement, services); } catch (const std::exception& e) { throw ConfigFileParserError{pathName, e.what()}; } } namespace internal { std::unique_ptr parseChassis( const json& element, const std::map& chassisTemplates, Services& services) { verifyIsObject(element); // If chassis object is not using a template, parse properties normally if (!element.contains("template_id")) { bool isChassisTemplate{false}; return parseChassisProperties(element, isChassisTemplate, NO_VARIABLES, services); } // Parse chassis object that is using a template unsigned int propertyCount{0}; // Optional comments property; value not stored if (element.contains("comments")) { ++propertyCount; } // Required template_id property const json& templateIDElement = getRequiredProperty(element, "template_id"); std::string templateID = parseString(templateIDElement); ++propertyCount; // Required template_variable_values property const json& variablesElement = getRequiredProperty(element, "template_variable_values"); std::map variables = parseVariables(variablesElement); ++propertyCount; // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); // Get reference to chassis template JSON auto it = chassisTemplates.find(templateID); if (it == chassisTemplates.end()) { throw std::invalid_argument{ "Invalid chassis template id: " + templateID}; } const json& templateElement = it->second.get(); // Parse properties in template using variable values for this chassis bool isChassisTemplate{true}; return parseChassisProperties(templateElement, isChassisTemplate, variables, services); } std::vector> parseChassisArray( const json& element, const std::map& chassisTemplates, Services& services) { verifyIsArray(element); std::vector> chassis; for (auto& chassisElement : element) { chassis.emplace_back( parseChassis(chassisElement, chassisTemplates, services)); } return chassis; } std::unique_ptr parseChassisProperties( const json& element, bool isChassisTemplate, const std::map& variables, Services& services) { verifyIsObject(element); unsigned int propertyCount{0}; // Optional comments property; value not stored if (element.contains("comments")) { ++propertyCount; } // Required id property if this is a chassis template // Don't parse again; this was already parsed by parseChassisTemplate() if (isChassisTemplate) { getRequiredProperty(element, "id"); ++propertyCount; } // Required number property const json& numberElement = getRequiredProperty(element, "number"); unsigned int number = parseUnsignedInteger(numberElement, variables); if (number < 1) { throw std::invalid_argument{"Invalid chassis number: Must be > 0"}; } ++propertyCount; // Required inventory_path property const json& inventoryPathElement = getRequiredProperty(element, "inventory_path"); std::string inventoryPath = parseString(inventoryPathElement, false, variables); ++propertyCount; // Required power_sequencers property const json& powerSequencersElement = getRequiredProperty(element, "power_sequencers"); std::vector> powerSequencers = parsePowerSequencerArray(powerSequencersElement, variables, services); ++propertyCount; // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); return std::make_unique(number, inventoryPath, std::move(powerSequencers)); } std::tuple parseChassisTemplate( const json& element) { verifyIsObject(element); unsigned int propertyCount{0}; // Optional comments property; value not stored if (element.contains("comments")) { ++propertyCount; } // Required id property const json& idElement = getRequiredProperty(element, "id"); std::string id = parseString(idElement); ++propertyCount; // Required number property // Just verify it exists; cannot be parsed without variable values getRequiredProperty(element, "number"); ++propertyCount; // Required inventory_path property // Just verify it exists; cannot be parsed without variable values getRequiredProperty(element, "inventory_path"); ++propertyCount; // Required power_sequencers property // Just verify it exists; cannot be parsed without variable values getRequiredProperty(element, "power_sequencers"); ++propertyCount; // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); return {id, JSONRefWrapper{element}}; } std::map parseChassisTemplateArray( const json& element) { verifyIsArray(element); std::map chassisTemplates; for (auto& chassisTemplateElement : element) { chassisTemplates.emplace(parseChassisTemplate(chassisTemplateElement)); } return chassisTemplates; } PgoodGPIO parseGPIO(const json& element, const std::map& variables) { verifyIsObject(element); unsigned int propertyCount{0}; // Required line property const json& lineElement = getRequiredProperty(element, "line"); unsigned int line = parseUnsignedInteger(lineElement, variables); ++propertyCount; // Optional active_low property bool activeLow{false}; auto activeLowIt = element.find("active_low"); if (activeLowIt != element.end()) { activeLow = parseBoolean(*activeLowIt, variables); ++propertyCount; } // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); return PgoodGPIO(line, activeLow); } std::tuple parseI2CInterface( const nlohmann::json& element, const std::map& variables) { verifyIsObject(element); unsigned int propertyCount{0}; // Required bus property const json& busElement = getRequiredProperty(element, "bus"); uint8_t bus = parseUint8(busElement, variables); ++propertyCount; // Required address property const json& addressElement = getRequiredProperty(element, "address"); uint16_t address = parseHexByte(addressElement, variables); ++propertyCount; // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); return {bus, address}; } std::unique_ptr parsePowerSequencer( const nlohmann::json& element, const std::map& variables, Services& services) { verifyIsObject(element); unsigned int propertyCount{0}; // Optional comments property; value not stored if (element.contains("comments")) { ++propertyCount; } // Required type property const json& typeElement = getRequiredProperty(element, "type"); std::string type = parseString(typeElement, false, variables); ++propertyCount; // i2c_interface property is required for some device types uint8_t bus{0}; uint16_t address{0}; auto i2cInterfaceIt = element.find("i2c_interface"); if (i2cInterfaceIt != element.end()) { std::tie(bus, address) = parseI2CInterface(*i2cInterfaceIt, variables); ++propertyCount; } else if (type != GPIOsOnlyDevice::deviceName) { throw std::invalid_argument{"Required property missing: i2c_interface"}; } // Required power_control_gpio_name property const json& powerControlGPIONameElement = getRequiredProperty(element, "power_control_gpio_name"); std::string powerControlGPIOName = parseString(powerControlGPIONameElement, false, variables); ++propertyCount; // Required power_good_gpio_name property const json& powerGoodGPIONameElement = getRequiredProperty(element, "power_good_gpio_name"); std::string powerGoodGPIOName = parseString(powerGoodGPIONameElement, false, variables); ++propertyCount; // rails property is required for some device types std::vector> rails{}; auto railsIt = element.find("rails"); if (railsIt != element.end()) { rails = parseRailArray(*railsIt, variables); ++propertyCount; } else if (type != GPIOsOnlyDevice::deviceName) { throw std::invalid_argument{"Required property missing: rails"}; } // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); if (type == UCD90160Device::deviceName) { return std::make_unique( bus, address, powerControlGPIOName, powerGoodGPIOName, std::move(rails), services); } else if (type == UCD90320Device::deviceName) { return std::make_unique( bus, address, powerControlGPIOName, powerGoodGPIOName, std::move(rails), services); } else if (type == GPIOsOnlyDevice::deviceName) { return std::make_unique(powerControlGPIOName, powerGoodGPIOName, services); } throw std::invalid_argument{"Invalid power sequencer type: " + type}; } std::vector> parsePowerSequencerArray( const nlohmann::json& element, const std::map& variables, Services& services) { verifyIsArray(element); std::vector> powerSequencers; for (auto& powerSequencerElement : element) { powerSequencers.emplace_back( parsePowerSequencer(powerSequencerElement, variables, services)); } return powerSequencers; } std::unique_ptr parseRail( const json& element, const std::map& variables) { verifyIsObject(element); unsigned int propertyCount{0}; // Required name property const json& nameElement = getRequiredProperty(element, "name"); std::string name = parseString(nameElement, false, variables); ++propertyCount; // Optional presence property std::optional presence{}; auto presenceIt = element.find("presence"); if (presenceIt != element.end()) { presence = parseString(*presenceIt, false, variables); ++propertyCount; } // Optional page property std::optional page{}; auto pageIt = element.find("page"); if (pageIt != element.end()) { page = parseUint8(*pageIt, variables); ++propertyCount; } // Optional is_power_supply_rail property bool isPowerSupplyRail{false}; auto isPowerSupplyRailIt = element.find("is_power_supply_rail"); if (isPowerSupplyRailIt != element.end()) { isPowerSupplyRail = parseBoolean(*isPowerSupplyRailIt, variables); ++propertyCount; } // Optional check_status_vout property bool checkStatusVout{false}; auto checkStatusVoutIt = element.find("check_status_vout"); if (checkStatusVoutIt != element.end()) { checkStatusVout = parseBoolean(*checkStatusVoutIt, variables); ++propertyCount; } // Optional compare_voltage_to_limit property bool compareVoltageToLimit{false}; auto compareVoltageToLimitIt = element.find("compare_voltage_to_limit"); if (compareVoltageToLimitIt != element.end()) { compareVoltageToLimit = parseBoolean(*compareVoltageToLimitIt, variables); ++propertyCount; } // Optional gpio property std::optional gpio{}; auto gpioIt = element.find("gpio"); if (gpioIt != element.end()) { gpio = parseGPIO(*gpioIt, variables); ++propertyCount; } // If check_status_vout or compare_voltage_to_limit property is true, the // page property is required; verify page was specified if ((checkStatusVout || compareVoltageToLimit) && !page.has_value()) { throw std::invalid_argument{"Required property missing: page"}; } // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); return std::make_unique(name, presence, page, isPowerSupplyRail, checkStatusVout, compareVoltageToLimit, gpio); } std::vector> parseRailArray( const json& element, const std::map& variables) { verifyIsArray(element); std::vector> rails; for (auto& railElement : element) { rails.emplace_back(parseRail(railElement, variables)); } return rails; } std::vector> parseRoot(const json& element, Services& services) { verifyIsObject(element); unsigned int propertyCount{0}; // Optional comments property; value not stored if (element.contains("comments")) { ++propertyCount; } // Optional chassis_templates property std::map chassisTemplates{}; auto chassisTemplatesIt = element.find("chassis_templates"); if (chassisTemplatesIt != element.end()) { chassisTemplates = parseChassisTemplateArray(*chassisTemplatesIt); ++propertyCount; } // Required chassis property const json& chassisElement = getRequiredProperty(element, "chassis"); std::vector> chassis = parseChassisArray(chassisElement, chassisTemplates, services); ++propertyCount; // Verify no invalid properties exist verifyPropertyCount(element, propertyCount); return chassis; } std::map parseVariables(const json& element) { verifyIsObject(element); std::map variables; std::string name, value; for (const auto& [nameElement, valueElement] : element.items()) { name = parseString(nameElement); value = parseString(valueElement); variables.emplace(name, value); } return variables; } } // namespace internal } // namespace phosphor::power::sequencer::config_file_parser