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 "gpios_only_device.hpp" 21 #include "json_parser_utils.hpp" 22 #include "ucd90160_device.hpp" 23 #include "ucd90320_device.hpp" 24 25 #include <cstdint> 26 #include <exception> 27 #include <fstream> 28 #include <optional> 29 #include <stdexcept> 30 31 using namespace phosphor::power::json_parser_utils; 32 using ConfigFileParserError = phosphor::power::util::ConfigFileParserError; 33 namespace fs = std::filesystem; 34 35 namespace phosphor::power::sequencer::config_file_parser 36 { 37 38 const std::filesystem::path standardConfigFileDirectory{ 39 "/usr/share/phosphor-power-sequencer"}; 40 41 std::filesystem::path find( 42 const std::vector<std::string>& compatibleSystemTypes, 43 const std::filesystem::path& configFileDir) 44 { 45 fs::path pathName, possiblePath; 46 std::string fileName; 47 48 for (const std::string& systemType : compatibleSystemTypes) 49 { 50 // Look for file name that is entire system type + ".json" 51 // Example: com.acme.Hardware.Chassis.Model.MegaServer.json 52 fileName = systemType + ".json"; 53 possiblePath = configFileDir / fileName; 54 if (fs::is_regular_file(possiblePath)) 55 { 56 pathName = possiblePath; 57 break; 58 } 59 60 // Look for file name that is last node of system type + ".json" 61 // Example: MegaServer.json 62 std::string::size_type pos = systemType.rfind('.'); 63 if ((pos != std::string::npos) && ((systemType.size() - pos) > 1)) 64 { 65 fileName = systemType.substr(pos + 1) + ".json"; 66 possiblePath = configFileDir / fileName; 67 if (fs::is_regular_file(possiblePath)) 68 { 69 pathName = possiblePath; 70 break; 71 } 72 } 73 } 74 75 return pathName; 76 } 77 78 std::vector<std::unique_ptr<Chassis>> parse( 79 const std::filesystem::path& pathName) 80 { 81 try 82 { 83 // Use standard JSON parser to create tree of JSON elements 84 std::ifstream file{pathName}; 85 json rootElement = json::parse(file); 86 87 // Parse tree of JSON elements and return corresponding C++ objects 88 return internal::parseRoot(rootElement); 89 } 90 catch (const std::exception& e) 91 { 92 throw ConfigFileParserError{pathName, e.what()}; 93 } 94 } 95 96 namespace internal 97 { 98 99 std::unique_ptr<Chassis> parseChassis( 100 const json& element, 101 const std::map<std::string, JSONRefWrapper>& chassisTemplates) 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 } 111 112 // Parse chassis object that is using a template 113 unsigned int propertyCount{0}; 114 115 // Optional comments property; value not stored 116 if (element.contains("comments")) 117 { 118 ++propertyCount; 119 } 120 121 // Required template_id property 122 const json& templateIDElement = getRequiredProperty(element, "template_id"); 123 std::string templateID = parseString(templateIDElement); 124 ++propertyCount; 125 126 // Required template_variable_values property 127 const json& variablesElement = 128 getRequiredProperty(element, "template_variable_values"); 129 std::map<std::string, std::string> variables = 130 parseVariables(variablesElement); 131 ++propertyCount; 132 133 // Verify no invalid properties exist 134 verifyPropertyCount(element, propertyCount); 135 136 // Get reference to chassis template JSON 137 auto it = chassisTemplates.find(templateID); 138 if (it == chassisTemplates.end()) 139 { 140 throw std::invalid_argument{ 141 "Invalid chassis template id: " + templateID}; 142 } 143 const json& templateElement = it->second.get(); 144 145 // Parse properties in template using variable values for this chassis 146 bool isChassisTemplate{true}; 147 return parseChassisProperties(templateElement, isChassisTemplate, 148 variables); 149 } 150 151 std::vector<std::unique_ptr<Chassis>> parseChassisArray( 152 const json& element, 153 const std::map<std::string, JSONRefWrapper>& chassisTemplates) 154 { 155 verifyIsArray(element); 156 std::vector<std::unique_ptr<Chassis>> chassis; 157 for (auto& chassisElement : element) 158 { 159 chassis.emplace_back(parseChassis(chassisElement, chassisTemplates)); 160 } 161 return chassis; 162 } 163 164 std::unique_ptr<Chassis> parseChassisProperties( 165 const json& element, bool isChassisTemplate, 166 const std::map<std::string, std::string>& variables) 167 168 { 169 verifyIsObject(element); 170 unsigned int propertyCount{0}; 171 172 // Optional comments property; value not stored 173 if (element.contains("comments")) 174 { 175 ++propertyCount; 176 } 177 178 // Required id property if this is a chassis template 179 // Don't parse again; this was already parsed by parseChassisTemplate() 180 if (isChassisTemplate) 181 { 182 getRequiredProperty(element, "id"); 183 ++propertyCount; 184 } 185 186 // Required number property 187 const json& numberElement = getRequiredProperty(element, "number"); 188 unsigned int number = parseUnsignedInteger(numberElement, variables); 189 if (number < 1) 190 { 191 throw std::invalid_argument{"Invalid chassis number: Must be > 0"}; 192 } 193 ++propertyCount; 194 195 // Required inventory_path property 196 const json& inventoryPathElement = 197 getRequiredProperty(element, "inventory_path"); 198 std::string inventoryPath = 199 parseString(inventoryPathElement, false, variables); 200 ++propertyCount; 201 202 // Required power_sequencers property 203 const json& powerSequencersElement = 204 getRequiredProperty(element, "power_sequencers"); 205 std::vector<std::unique_ptr<PowerSequencerDevice>> powerSequencers = 206 parsePowerSequencerArray(powerSequencersElement, variables); 207 ++propertyCount; 208 209 // Optional status_monitoring property 210 ChassisStatusMonitorOptions monitorOptions{}; 211 auto statusMonitoringIt = element.find("status_monitoring"); 212 if (statusMonitoringIt != element.end()) 213 { 214 monitorOptions = parseStatusMonitoring(*statusMonitoringIt, variables); 215 ++propertyCount; 216 } 217 218 // Verify no invalid properties exist 219 verifyPropertyCount(element, propertyCount); 220 221 return std::make_unique<Chassis>( 222 number, inventoryPath, std::move(powerSequencers), monitorOptions); 223 } 224 225 std::tuple<std::string, JSONRefWrapper> parseChassisTemplate( 226 const json& element) 227 { 228 verifyIsObject(element); 229 unsigned int propertyCount{0}; 230 231 // Optional comments property; value not stored 232 if (element.contains("comments")) 233 { 234 ++propertyCount; 235 } 236 237 // Required id property 238 const json& idElement = getRequiredProperty(element, "id"); 239 std::string id = parseString(idElement); 240 ++propertyCount; 241 242 // Required number property 243 // Just verify it exists; cannot be parsed without variable values 244 getRequiredProperty(element, "number"); 245 ++propertyCount; 246 247 // Required inventory_path property 248 // Just verify it exists; cannot be parsed without variable values 249 getRequiredProperty(element, "inventory_path"); 250 ++propertyCount; 251 252 // Required power_sequencers property 253 // Just verify it exists; cannot be parsed without variable values 254 getRequiredProperty(element, "power_sequencers"); 255 ++propertyCount; 256 257 // Optional status_monitoring property 258 // Cannot be parsed without variable values 259 if (element.contains("status_monitoring")) 260 { 261 ++propertyCount; 262 } 263 264 // Verify no invalid properties exist 265 verifyPropertyCount(element, propertyCount); 266 267 return {id, JSONRefWrapper{element}}; 268 } 269 270 std::map<std::string, JSONRefWrapper> parseChassisTemplateArray( 271 const json& element) 272 { 273 verifyIsArray(element); 274 std::map<std::string, JSONRefWrapper> chassisTemplates; 275 for (auto& chassisTemplateElement : element) 276 { 277 chassisTemplates.emplace(parseChassisTemplate(chassisTemplateElement)); 278 } 279 return chassisTemplates; 280 } 281 282 ChassisStatusMonitorOptions parseStatusMonitoring( 283 const json& element, const std::map<std::string, std::string>& variables) 284 { 285 verifyIsObject(element); 286 unsigned int propertyCount{0}; 287 288 // Note: all ChassisStatusMonitorOptions fields default to false 289 ChassisStatusMonitorOptions options; 290 291 // Optional is_present_monitored property 292 auto propIt = element.find("is_present_monitored"); 293 if (propIt != element.end()) 294 { 295 options.isPresentMonitored = parseBoolean(*propIt, variables); 296 ++propertyCount; 297 } 298 299 // Optional is_available_monitored property 300 propIt = element.find("is_available_monitored"); 301 if (propIt != element.end()) 302 { 303 options.isAvailableMonitored = parseBoolean(*propIt, variables); 304 ++propertyCount; 305 } 306 307 // Optional is_enabled_monitored property 308 propIt = element.find("is_enabled_monitored"); 309 if (propIt != element.end()) 310 { 311 options.isEnabledMonitored = parseBoolean(*propIt, variables); 312 ++propertyCount; 313 } 314 315 // Optional is_input_power_status_monitored property 316 propIt = element.find("is_input_power_status_monitored"); 317 if (propIt != element.end()) 318 { 319 options.isInputPowerStatusMonitored = parseBoolean(*propIt, variables); 320 ++propertyCount; 321 } 322 323 // Optional is_power_supplies_status_monitored property 324 propIt = element.find("is_power_supplies_status_monitored"); 325 if (propIt != element.end()) 326 { 327 options.isPowerSuppliesStatusMonitored = 328 parseBoolean(*propIt, variables); 329 ++propertyCount; 330 } 331 332 // Verify no invalid properties exist 333 verifyPropertyCount(element, propertyCount); 334 335 return options; 336 } 337 338 PgoodGPIO parseGPIO(const json& element, 339 const std::map<std::string, std::string>& variables) 340 { 341 verifyIsObject(element); 342 unsigned int propertyCount{0}; 343 344 // Required line property 345 const json& lineElement = getRequiredProperty(element, "line"); 346 unsigned int line = parseUnsignedInteger(lineElement, variables); 347 ++propertyCount; 348 349 // Optional active_low property 350 bool activeLow{false}; 351 auto activeLowIt = element.find("active_low"); 352 if (activeLowIt != element.end()) 353 { 354 activeLow = parseBoolean(*activeLowIt, variables); 355 ++propertyCount; 356 } 357 358 // Verify no invalid properties exist 359 verifyPropertyCount(element, propertyCount); 360 361 return PgoodGPIO(line, activeLow); 362 } 363 364 std::tuple<uint8_t, uint16_t> parseI2CInterface( 365 const nlohmann::json& element, 366 const std::map<std::string, std::string>& variables) 367 { 368 verifyIsObject(element); 369 unsigned int propertyCount{0}; 370 371 // Required bus property 372 const json& busElement = getRequiredProperty(element, "bus"); 373 uint8_t bus = parseUint8(busElement, variables); 374 ++propertyCount; 375 376 // Required address property 377 const json& addressElement = getRequiredProperty(element, "address"); 378 uint16_t address = parseHexByte(addressElement, variables); 379 ++propertyCount; 380 381 // Verify no invalid properties exist 382 verifyPropertyCount(element, propertyCount); 383 384 return {bus, address}; 385 } 386 387 std::unique_ptr<PowerSequencerDevice> parsePowerSequencer( 388 const nlohmann::json& element, 389 const std::map<std::string, std::string>& variables) 390 { 391 verifyIsObject(element); 392 unsigned int propertyCount{0}; 393 394 // Optional comments property; value not stored 395 if (element.contains("comments")) 396 { 397 ++propertyCount; 398 } 399 400 // Required type property 401 const json& typeElement = getRequiredProperty(element, "type"); 402 std::string type = parseString(typeElement, false, variables); 403 ++propertyCount; 404 405 // i2c_interface property is required for some device types 406 uint8_t bus{0}; 407 uint16_t address{0}; 408 auto i2cInterfaceIt = element.find("i2c_interface"); 409 if (i2cInterfaceIt != element.end()) 410 { 411 std::tie(bus, address) = parseI2CInterface(*i2cInterfaceIt, variables); 412 ++propertyCount; 413 } 414 else if (type != GPIOsOnlyDevice::deviceName) 415 { 416 throw std::invalid_argument{"Required property missing: i2c_interface"}; 417 } 418 419 // Required power_control_gpio_name property 420 const json& powerControlGPIONameElement = 421 getRequiredProperty(element, "power_control_gpio_name"); 422 std::string powerControlGPIOName = 423 parseString(powerControlGPIONameElement, false, variables); 424 ++propertyCount; 425 426 // Required power_good_gpio_name property 427 const json& powerGoodGPIONameElement = 428 getRequiredProperty(element, "power_good_gpio_name"); 429 std::string powerGoodGPIOName = 430 parseString(powerGoodGPIONameElement, false, variables); 431 ++propertyCount; 432 433 // rails property is required for some device types 434 std::vector<std::unique_ptr<Rail>> rails{}; 435 auto railsIt = element.find("rails"); 436 if (railsIt != element.end()) 437 { 438 rails = parseRailArray(*railsIt, variables); 439 ++propertyCount; 440 } 441 else if (type != GPIOsOnlyDevice::deviceName) 442 { 443 throw std::invalid_argument{"Required property missing: rails"}; 444 } 445 446 // Verify no invalid properties exist 447 verifyPropertyCount(element, propertyCount); 448 449 if (type == UCD90160Device::deviceName) 450 { 451 return std::make_unique<UCD90160Device>( 452 bus, address, powerControlGPIOName, powerGoodGPIOName, 453 std::move(rails)); 454 } 455 else if (type == UCD90320Device::deviceName) 456 { 457 return std::make_unique<UCD90320Device>( 458 bus, address, powerControlGPIOName, powerGoodGPIOName, 459 std::move(rails)); 460 } 461 else if (type == GPIOsOnlyDevice::deviceName) 462 { 463 return std::make_unique<GPIOsOnlyDevice>(powerControlGPIOName, 464 powerGoodGPIOName); 465 } 466 throw std::invalid_argument{"Invalid power sequencer type: " + type}; 467 } 468 469 std::vector<std::unique_ptr<PowerSequencerDevice>> parsePowerSequencerArray( 470 const nlohmann::json& element, 471 const std::map<std::string, std::string>& variables) 472 { 473 verifyIsArray(element); 474 std::vector<std::unique_ptr<PowerSequencerDevice>> powerSequencers; 475 for (auto& powerSequencerElement : element) 476 { 477 powerSequencers.emplace_back( 478 parsePowerSequencer(powerSequencerElement, variables)); 479 } 480 return powerSequencers; 481 } 482 483 std::unique_ptr<Rail> parseRail( 484 const json& element, const std::map<std::string, std::string>& variables) 485 { 486 verifyIsObject(element); 487 unsigned int propertyCount{0}; 488 489 // Required name property 490 const json& nameElement = getRequiredProperty(element, "name"); 491 std::string name = parseString(nameElement, false, variables); 492 ++propertyCount; 493 494 // Optional presence property 495 std::optional<std::string> presence{}; 496 auto presenceIt = element.find("presence"); 497 if (presenceIt != element.end()) 498 { 499 presence = parseString(*presenceIt, false, variables); 500 ++propertyCount; 501 } 502 503 // Optional page property 504 std::optional<uint8_t> page{}; 505 auto pageIt = element.find("page"); 506 if (pageIt != element.end()) 507 { 508 page = parseUint8(*pageIt, variables); 509 ++propertyCount; 510 } 511 512 // Optional is_power_supply_rail property 513 bool isPowerSupplyRail{false}; 514 auto isPowerSupplyRailIt = element.find("is_power_supply_rail"); 515 if (isPowerSupplyRailIt != element.end()) 516 { 517 isPowerSupplyRail = parseBoolean(*isPowerSupplyRailIt, variables); 518 ++propertyCount; 519 } 520 521 // Optional check_status_vout property 522 bool checkStatusVout{false}; 523 auto checkStatusVoutIt = element.find("check_status_vout"); 524 if (checkStatusVoutIt != element.end()) 525 { 526 checkStatusVout = parseBoolean(*checkStatusVoutIt, variables); 527 ++propertyCount; 528 } 529 530 // Optional compare_voltage_to_limit property 531 bool compareVoltageToLimit{false}; 532 auto compareVoltageToLimitIt = element.find("compare_voltage_to_limit"); 533 if (compareVoltageToLimitIt != element.end()) 534 { 535 compareVoltageToLimit = 536 parseBoolean(*compareVoltageToLimitIt, variables); 537 ++propertyCount; 538 } 539 540 // Optional gpio property 541 std::optional<PgoodGPIO> gpio{}; 542 auto gpioIt = element.find("gpio"); 543 if (gpioIt != element.end()) 544 { 545 gpio = parseGPIO(*gpioIt, variables); 546 ++propertyCount; 547 } 548 549 // If check_status_vout or compare_voltage_to_limit property is true, the 550 // page property is required; verify page was specified 551 if ((checkStatusVout || compareVoltageToLimit) && !page.has_value()) 552 { 553 throw std::invalid_argument{"Required property missing: page"}; 554 } 555 556 // Verify no invalid properties exist 557 verifyPropertyCount(element, propertyCount); 558 559 return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail, 560 checkStatusVout, compareVoltageToLimit, gpio); 561 } 562 563 std::vector<std::unique_ptr<Rail>> parseRailArray( 564 const json& element, const std::map<std::string, std::string>& variables) 565 { 566 verifyIsArray(element); 567 std::vector<std::unique_ptr<Rail>> rails; 568 for (auto& railElement : element) 569 { 570 rails.emplace_back(parseRail(railElement, variables)); 571 } 572 return rails; 573 } 574 575 std::vector<std::unique_ptr<Chassis>> parseRoot(const json& element) 576 { 577 verifyIsObject(element); 578 unsigned int propertyCount{0}; 579 580 // Optional comments property; value not stored 581 if (element.contains("comments")) 582 { 583 ++propertyCount; 584 } 585 586 // Optional chassis_templates property 587 std::map<std::string, JSONRefWrapper> chassisTemplates{}; 588 auto chassisTemplatesIt = element.find("chassis_templates"); 589 if (chassisTemplatesIt != element.end()) 590 { 591 chassisTemplates = parseChassisTemplateArray(*chassisTemplatesIt); 592 ++propertyCount; 593 } 594 595 // Required chassis property 596 const json& chassisElement = getRequiredProperty(element, "chassis"); 597 std::vector<std::unique_ptr<Chassis>> chassis = 598 parseChassisArray(chassisElement, chassisTemplates); 599 ++propertyCount; 600 601 // Verify no invalid properties exist 602 verifyPropertyCount(element, propertyCount); 603 604 return chassis; 605 } 606 607 std::map<std::string, std::string> parseVariables(const json& element) 608 { 609 verifyIsObject(element); 610 611 std::map<std::string, std::string> variables; 612 std::string name, value; 613 for (const auto& [nameElement, valueElement] : element.items()) 614 { 615 name = parseString(nameElement); 616 value = parseString(valueElement); 617 variables.emplace(name, value); 618 } 619 return variables; 620 } 621 622 } // namespace internal 623 624 } // namespace phosphor::power::sequencer::config_file_parser 625