1 /** 2 * Copyright © 2024 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "config_file_parser.hpp" 18 19 #include "config_file_parser_error.hpp" 20 #include "json_parser_utils.hpp" 21 #include "ucd90160_device.hpp" 22 #include "ucd90320_device.hpp" 23 24 #include <cstdint> 25 #include <exception> 26 #include <fstream> 27 #include <optional> 28 #include <stdexcept> 29 30 using namespace phosphor::power::json_parser_utils; 31 using ConfigFileParserError = phosphor::power::util::ConfigFileParserError; 32 namespace fs = std::filesystem; 33 34 namespace phosphor::power::sequencer::config_file_parser 35 { 36 37 const std::filesystem::path standardConfigFileDirectory{ 38 "/usr/share/phosphor-power-sequencer"}; 39 40 std::filesystem::path find( 41 const std::vector<std::string>& compatibleSystemTypes, 42 const std::filesystem::path& configFileDir) 43 { 44 fs::path pathName, possiblePath; 45 std::string fileName; 46 47 for (const std::string& systemType : compatibleSystemTypes) 48 { 49 // Look for file name that is entire system type + ".json" 50 // Example: com.acme.Hardware.Chassis.Model.MegaServer.json 51 fileName = systemType + ".json"; 52 possiblePath = configFileDir / fileName; 53 if (fs::is_regular_file(possiblePath)) 54 { 55 pathName = possiblePath; 56 break; 57 } 58 59 // Look for file name that is last node of system type + ".json" 60 // Example: MegaServer.json 61 std::string::size_type pos = systemType.rfind('.'); 62 if ((pos != std::string::npos) && ((systemType.size() - pos) > 1)) 63 { 64 fileName = systemType.substr(pos + 1) + ".json"; 65 possiblePath = configFileDir / fileName; 66 if (fs::is_regular_file(possiblePath)) 67 { 68 pathName = possiblePath; 69 break; 70 } 71 } 72 } 73 74 return pathName; 75 } 76 77 std::vector<std::unique_ptr<Chassis>> parse( 78 const std::filesystem::path& pathName, Services& services) 79 { 80 try 81 { 82 // Use standard JSON parser to create tree of JSON elements 83 std::ifstream file{pathName}; 84 json rootElement = json::parse(file); 85 86 // Parse tree of JSON elements and return corresponding C++ objects 87 return internal::parseRoot(rootElement, services); 88 } 89 catch (const std::exception& e) 90 { 91 throw ConfigFileParserError{pathName, e.what()}; 92 } 93 } 94 95 namespace internal 96 { 97 98 std::unique_ptr<Chassis> parseChassis( 99 const json& element, 100 const std::map<std::string, JSONRefWrapper>& chassisTemplates, 101 Services& services) 102 { 103 verifyIsObject(element); 104 105 // If chassis object is not using a template, parse properties normally 106 if (!element.contains("template_id")) 107 { 108 bool isChassisTemplate{false}; 109 return parseChassisProperties(element, isChassisTemplate, NO_VARIABLES, 110 services); 111 } 112 113 // Parse chassis object that is using a template 114 unsigned int propertyCount{0}; 115 116 // Optional comments property; value not stored 117 if (element.contains("comments")) 118 { 119 ++propertyCount; 120 } 121 122 // Required template_id property 123 const json& templateIDElement = getRequiredProperty(element, "template_id"); 124 std::string templateID = parseString(templateIDElement); 125 ++propertyCount; 126 127 // Required template_variable_values property 128 const json& variablesElement = 129 getRequiredProperty(element, "template_variable_values"); 130 std::map<std::string, std::string> variables = 131 parseVariables(variablesElement); 132 ++propertyCount; 133 134 // Verify no invalid properties exist 135 verifyPropertyCount(element, propertyCount); 136 137 // Get reference to chassis template JSON 138 auto it = chassisTemplates.find(templateID); 139 if (it == chassisTemplates.end()) 140 { 141 throw std::invalid_argument{ 142 "Invalid chassis template id: " + templateID}; 143 } 144 const json& templateElement = it->second.get(); 145 146 // Parse properties in template using variable values for this chassis 147 bool isChassisTemplate{true}; 148 return parseChassisProperties(templateElement, isChassisTemplate, variables, 149 services); 150 } 151 152 std::vector<std::unique_ptr<Chassis>> parseChassisArray( 153 const json& element, 154 const std::map<std::string, JSONRefWrapper>& chassisTemplates, 155 Services& services) 156 { 157 verifyIsArray(element); 158 std::vector<std::unique_ptr<Chassis>> chassis; 159 for (auto& chassisElement : element) 160 { 161 chassis.emplace_back( 162 parseChassis(chassisElement, chassisTemplates, services)); 163 } 164 return chassis; 165 } 166 167 std::unique_ptr<Chassis> parseChassisProperties( 168 const json& element, bool isChassisTemplate, 169 const std::map<std::string, std::string>& variables, Services& services) 170 171 { 172 verifyIsObject(element); 173 unsigned int propertyCount{0}; 174 175 // Optional comments property; value not stored 176 if (element.contains("comments")) 177 { 178 ++propertyCount; 179 } 180 181 // Required id property if this is a chassis template 182 // Don't parse again; this was already parsed by parseChassisTemplate() 183 if (isChassisTemplate) 184 { 185 getRequiredProperty(element, "id"); 186 ++propertyCount; 187 } 188 189 // Required number property 190 const json& numberElement = getRequiredProperty(element, "number"); 191 unsigned int number = parseUnsignedInteger(numberElement, variables); 192 if (number < 1) 193 { 194 throw std::invalid_argument{"Invalid chassis number: Must be > 0"}; 195 } 196 ++propertyCount; 197 198 // Required inventory_path property 199 const json& inventoryPathElement = 200 getRequiredProperty(element, "inventory_path"); 201 std::string inventoryPath = 202 parseString(inventoryPathElement, false, variables); 203 ++propertyCount; 204 205 // Required power_sequencers property 206 const json& powerSequencersElement = 207 getRequiredProperty(element, "power_sequencers"); 208 std::vector<std::unique_ptr<PowerSequencerDevice>> powerSequencers = 209 parsePowerSequencerArray(powerSequencersElement, variables, services); 210 ++propertyCount; 211 212 // Verify no invalid properties exist 213 verifyPropertyCount(element, propertyCount); 214 215 return std::make_unique<Chassis>(number, inventoryPath, 216 std::move(powerSequencers)); 217 } 218 219 std::tuple<std::string, JSONRefWrapper> parseChassisTemplate( 220 const json& element) 221 { 222 verifyIsObject(element); 223 unsigned int propertyCount{0}; 224 225 // Optional comments property; value not stored 226 if (element.contains("comments")) 227 { 228 ++propertyCount; 229 } 230 231 // Required id property 232 const json& idElement = getRequiredProperty(element, "id"); 233 std::string id = parseString(idElement); 234 ++propertyCount; 235 236 // Required number property 237 // Just verify it exists; cannot be parsed without variable values 238 getRequiredProperty(element, "number"); 239 ++propertyCount; 240 241 // Required inventory_path property 242 // Just verify it exists; cannot be parsed without variable values 243 getRequiredProperty(element, "inventory_path"); 244 ++propertyCount; 245 246 // Required power_sequencers property 247 // Just verify it exists; cannot be parsed without variable values 248 getRequiredProperty(element, "power_sequencers"); 249 ++propertyCount; 250 251 // Verify no invalid properties exist 252 verifyPropertyCount(element, propertyCount); 253 254 return {id, JSONRefWrapper{element}}; 255 } 256 257 std::map<std::string, JSONRefWrapper> parseChassisTemplateArray( 258 const json& element) 259 { 260 verifyIsArray(element); 261 std::map<std::string, JSONRefWrapper> chassisTemplates; 262 for (auto& chassisTemplateElement : element) 263 { 264 chassisTemplates.emplace(parseChassisTemplate(chassisTemplateElement)); 265 } 266 return chassisTemplates; 267 } 268 269 GPIO parseGPIO(const json& element, 270 const std::map<std::string, std::string>& variables) 271 { 272 verifyIsObject(element); 273 unsigned int propertyCount{0}; 274 275 // Required line property 276 const json& lineElement = getRequiredProperty(element, "line"); 277 unsigned int line = parseUnsignedInteger(lineElement, variables); 278 ++propertyCount; 279 280 // Optional active_low property 281 bool activeLow{false}; 282 auto activeLowIt = element.find("active_low"); 283 if (activeLowIt != element.end()) 284 { 285 activeLow = parseBoolean(*activeLowIt, variables); 286 ++propertyCount; 287 } 288 289 // Verify no invalid properties exist 290 verifyPropertyCount(element, propertyCount); 291 292 return GPIO(line, activeLow); 293 } 294 295 std::tuple<uint8_t, uint16_t> parseI2CInterface( 296 const nlohmann::json& element, 297 const std::map<std::string, std::string>& variables) 298 { 299 verifyIsObject(element); 300 unsigned int propertyCount{0}; 301 302 // Required bus property 303 const json& busElement = getRequiredProperty(element, "bus"); 304 uint8_t bus = parseUint8(busElement, variables); 305 ++propertyCount; 306 307 // Required address property 308 const json& addressElement = getRequiredProperty(element, "address"); 309 uint16_t address = parseHexByte(addressElement, variables); 310 ++propertyCount; 311 312 // Verify no invalid properties exist 313 verifyPropertyCount(element, propertyCount); 314 315 return {bus, address}; 316 } 317 318 std::unique_ptr<PowerSequencerDevice> parsePowerSequencer( 319 const nlohmann::json& element, 320 const std::map<std::string, std::string>& variables, Services& services) 321 { 322 verifyIsObject(element); 323 unsigned int propertyCount{0}; 324 325 // Optional comments property; value not stored 326 if (element.contains("comments")) 327 { 328 ++propertyCount; 329 } 330 331 // Required type property 332 const json& typeElement = getRequiredProperty(element, "type"); 333 std::string type = parseString(typeElement, false, variables); 334 ++propertyCount; 335 336 // Required i2c_interface property 337 const json& i2cInterfaceElement = 338 getRequiredProperty(element, "i2c_interface"); 339 auto [bus, address] = parseI2CInterface(i2cInterfaceElement, variables); 340 ++propertyCount; 341 342 // Required power_control_gpio_name property 343 const json& powerControlGPIONameElement = 344 getRequiredProperty(element, "power_control_gpio_name"); 345 std::string powerControlGPIOName = 346 parseString(powerControlGPIONameElement, false, variables); 347 ++propertyCount; 348 349 // Required power_good_gpio_name property 350 const json& powerGoodGPIONameElement = 351 getRequiredProperty(element, "power_good_gpio_name"); 352 std::string powerGoodGPIOName = 353 parseString(powerGoodGPIONameElement, false, variables); 354 ++propertyCount; 355 356 // Required rails property 357 const json& railsElement = getRequiredProperty(element, "rails"); 358 std::vector<std::unique_ptr<Rail>> rails = 359 parseRailArray(railsElement, variables); 360 ++propertyCount; 361 362 // Verify no invalid properties exist 363 verifyPropertyCount(element, propertyCount); 364 365 if (type == UCD90160Device::deviceName) 366 { 367 return std::make_unique<UCD90160Device>( 368 bus, address, powerControlGPIOName, powerGoodGPIOName, 369 std::move(rails), services); 370 } 371 else if (type == UCD90320Device::deviceName) 372 { 373 return std::make_unique<UCD90320Device>( 374 bus, address, powerControlGPIOName, powerGoodGPIOName, 375 std::move(rails), services); 376 } 377 throw std::invalid_argument{"Invalid power sequencer type: " + type}; 378 } 379 380 std::vector<std::unique_ptr<PowerSequencerDevice>> parsePowerSequencerArray( 381 const nlohmann::json& element, 382 const std::map<std::string, std::string>& variables, Services& services) 383 { 384 verifyIsArray(element); 385 std::vector<std::unique_ptr<PowerSequencerDevice>> powerSequencers; 386 for (auto& powerSequencerElement : element) 387 { 388 powerSequencers.emplace_back( 389 parsePowerSequencer(powerSequencerElement, variables, services)); 390 } 391 return powerSequencers; 392 } 393 394 std::unique_ptr<Rail> parseRail( 395 const json& element, const std::map<std::string, std::string>& variables) 396 { 397 verifyIsObject(element); 398 unsigned int propertyCount{0}; 399 400 // Required name property 401 const json& nameElement = getRequiredProperty(element, "name"); 402 std::string name = parseString(nameElement, false, variables); 403 ++propertyCount; 404 405 // Optional presence property 406 std::optional<std::string> presence{}; 407 auto presenceIt = element.find("presence"); 408 if (presenceIt != element.end()) 409 { 410 presence = parseString(*presenceIt, false, variables); 411 ++propertyCount; 412 } 413 414 // Optional page property 415 std::optional<uint8_t> page{}; 416 auto pageIt = element.find("page"); 417 if (pageIt != element.end()) 418 { 419 page = parseUint8(*pageIt, variables); 420 ++propertyCount; 421 } 422 423 // Optional is_power_supply_rail property 424 bool isPowerSupplyRail{false}; 425 auto isPowerSupplyRailIt = element.find("is_power_supply_rail"); 426 if (isPowerSupplyRailIt != element.end()) 427 { 428 isPowerSupplyRail = parseBoolean(*isPowerSupplyRailIt, variables); 429 ++propertyCount; 430 } 431 432 // Optional check_status_vout property 433 bool checkStatusVout{false}; 434 auto checkStatusVoutIt = element.find("check_status_vout"); 435 if (checkStatusVoutIt != element.end()) 436 { 437 checkStatusVout = parseBoolean(*checkStatusVoutIt, variables); 438 ++propertyCount; 439 } 440 441 // Optional compare_voltage_to_limit property 442 bool compareVoltageToLimit{false}; 443 auto compareVoltageToLimitIt = element.find("compare_voltage_to_limit"); 444 if (compareVoltageToLimitIt != element.end()) 445 { 446 compareVoltageToLimit = 447 parseBoolean(*compareVoltageToLimitIt, variables); 448 ++propertyCount; 449 } 450 451 // Optional gpio property 452 std::optional<GPIO> gpio{}; 453 auto gpioIt = element.find("gpio"); 454 if (gpioIt != element.end()) 455 { 456 gpio = parseGPIO(*gpioIt, variables); 457 ++propertyCount; 458 } 459 460 // If check_status_vout or compare_voltage_to_limit property is true, the 461 // page property is required; verify page was specified 462 if ((checkStatusVout || compareVoltageToLimit) && !page.has_value()) 463 { 464 throw std::invalid_argument{"Required property missing: page"}; 465 } 466 467 // Verify no invalid properties exist 468 verifyPropertyCount(element, propertyCount); 469 470 return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail, 471 checkStatusVout, compareVoltageToLimit, gpio); 472 } 473 474 std::vector<std::unique_ptr<Rail>> parseRailArray( 475 const json& element, const std::map<std::string, std::string>& variables) 476 { 477 verifyIsArray(element); 478 std::vector<std::unique_ptr<Rail>> rails; 479 for (auto& railElement : element) 480 { 481 rails.emplace_back(parseRail(railElement, variables)); 482 } 483 return rails; 484 } 485 486 std::vector<std::unique_ptr<Chassis>> parseRoot(const json& element, 487 Services& services) 488 { 489 verifyIsObject(element); 490 unsigned int propertyCount{0}; 491 492 // Optional comments property; value not stored 493 if (element.contains("comments")) 494 { 495 ++propertyCount; 496 } 497 498 // Optional chassis_templates property 499 std::map<std::string, JSONRefWrapper> chassisTemplates{}; 500 auto chassisTemplatesIt = element.find("chassis_templates"); 501 if (chassisTemplatesIt != element.end()) 502 { 503 chassisTemplates = parseChassisTemplateArray(*chassisTemplatesIt); 504 ++propertyCount; 505 } 506 507 // Required chassis property 508 const json& chassisElement = getRequiredProperty(element, "chassis"); 509 std::vector<std::unique_ptr<Chassis>> chassis = 510 parseChassisArray(chassisElement, chassisTemplates, services); 511 ++propertyCount; 512 513 // Verify no invalid properties exist 514 verifyPropertyCount(element, propertyCount); 515 516 return chassis; 517 } 518 519 std::map<std::string, std::string> parseVariables(const json& element) 520 { 521 verifyIsObject(element); 522 523 std::map<std::string, std::string> variables; 524 std::string name, value; 525 for (const auto& [nameElement, valueElement] : element.items()) 526 { 527 name = parseString(nameElement); 528 value = parseString(valueElement); 529 variables.emplace(name, value); 530 } 531 return variables; 532 } 533 534 } // namespace internal 535 536 } // namespace phosphor::power::sequencer::config_file_parser 537