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, Services& services) 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, services); 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 Services& services) 103 { 104 verifyIsObject(element); 105 106 // If chassis object is not using a template, parse properties normally 107 if (!element.contains("template_id")) 108 { 109 bool isChassisTemplate{false}; 110 return parseChassisProperties(element, isChassisTemplate, NO_VARIABLES, 111 services); 112 } 113 114 // Parse chassis object that is using a template 115 unsigned int propertyCount{0}; 116 117 // Optional comments property; value not stored 118 if (element.contains("comments")) 119 { 120 ++propertyCount; 121 } 122 123 // Required template_id property 124 const json& templateIDElement = getRequiredProperty(element, "template_id"); 125 std::string templateID = parseString(templateIDElement); 126 ++propertyCount; 127 128 // Required template_variable_values property 129 const json& variablesElement = 130 getRequiredProperty(element, "template_variable_values"); 131 std::map<std::string, std::string> variables = 132 parseVariables(variablesElement); 133 ++propertyCount; 134 135 // Verify no invalid properties exist 136 verifyPropertyCount(element, propertyCount); 137 138 // Get reference to chassis template JSON 139 auto it = chassisTemplates.find(templateID); 140 if (it == chassisTemplates.end()) 141 { 142 throw std::invalid_argument{ 143 "Invalid chassis template id: " + templateID}; 144 } 145 const json& templateElement = it->second.get(); 146 147 // Parse properties in template using variable values for this chassis 148 bool isChassisTemplate{true}; 149 return parseChassisProperties(templateElement, isChassisTemplate, variables, 150 services); 151 } 152 153 std::vector<std::unique_ptr<Chassis>> parseChassisArray( 154 const json& element, 155 const std::map<std::string, JSONRefWrapper>& chassisTemplates, 156 Services& services) 157 { 158 verifyIsArray(element); 159 std::vector<std::unique_ptr<Chassis>> chassis; 160 for (auto& chassisElement : element) 161 { 162 chassis.emplace_back( 163 parseChassis(chassisElement, chassisTemplates, services)); 164 } 165 return chassis; 166 } 167 168 std::unique_ptr<Chassis> parseChassisProperties( 169 const json& element, bool isChassisTemplate, 170 const std::map<std::string, std::string>& variables, Services& services) 171 172 { 173 verifyIsObject(element); 174 unsigned int propertyCount{0}; 175 176 // Optional comments property; value not stored 177 if (element.contains("comments")) 178 { 179 ++propertyCount; 180 } 181 182 // Required id property if this is a chassis template 183 // Don't parse again; this was already parsed by parseChassisTemplate() 184 if (isChassisTemplate) 185 { 186 getRequiredProperty(element, "id"); 187 ++propertyCount; 188 } 189 190 // Required number property 191 const json& numberElement = getRequiredProperty(element, "number"); 192 unsigned int number = parseUnsignedInteger(numberElement, variables); 193 if (number < 1) 194 { 195 throw std::invalid_argument{"Invalid chassis number: Must be > 0"}; 196 } 197 ++propertyCount; 198 199 // Required inventory_path property 200 const json& inventoryPathElement = 201 getRequiredProperty(element, "inventory_path"); 202 std::string inventoryPath = 203 parseString(inventoryPathElement, false, variables); 204 ++propertyCount; 205 206 // Required power_sequencers property 207 const json& powerSequencersElement = 208 getRequiredProperty(element, "power_sequencers"); 209 std::vector<std::unique_ptr<PowerSequencerDevice>> powerSequencers = 210 parsePowerSequencerArray(powerSequencersElement, variables, services); 211 ++propertyCount; 212 213 // Verify no invalid properties exist 214 verifyPropertyCount(element, propertyCount); 215 216 return std::make_unique<Chassis>(number, inventoryPath, 217 std::move(powerSequencers)); 218 } 219 220 std::tuple<std::string, JSONRefWrapper> parseChassisTemplate( 221 const json& element) 222 { 223 verifyIsObject(element); 224 unsigned int propertyCount{0}; 225 226 // Optional comments property; value not stored 227 if (element.contains("comments")) 228 { 229 ++propertyCount; 230 } 231 232 // Required id property 233 const json& idElement = getRequiredProperty(element, "id"); 234 std::string id = parseString(idElement); 235 ++propertyCount; 236 237 // Required number property 238 // Just verify it exists; cannot be parsed without variable values 239 getRequiredProperty(element, "number"); 240 ++propertyCount; 241 242 // Required inventory_path property 243 // Just verify it exists; cannot be parsed without variable values 244 getRequiredProperty(element, "inventory_path"); 245 ++propertyCount; 246 247 // Required power_sequencers property 248 // Just verify it exists; cannot be parsed without variable values 249 getRequiredProperty(element, "power_sequencers"); 250 ++propertyCount; 251 252 // Verify no invalid properties exist 253 verifyPropertyCount(element, propertyCount); 254 255 return {id, JSONRefWrapper{element}}; 256 } 257 258 std::map<std::string, JSONRefWrapper> parseChassisTemplateArray( 259 const json& element) 260 { 261 verifyIsArray(element); 262 std::map<std::string, JSONRefWrapper> chassisTemplates; 263 for (auto& chassisTemplateElement : element) 264 { 265 chassisTemplates.emplace(parseChassisTemplate(chassisTemplateElement)); 266 } 267 return chassisTemplates; 268 } 269 270 PgoodGPIO parseGPIO(const json& element, 271 const std::map<std::string, std::string>& variables) 272 { 273 verifyIsObject(element); 274 unsigned int propertyCount{0}; 275 276 // Required line property 277 const json& lineElement = getRequiredProperty(element, "line"); 278 unsigned int line = parseUnsignedInteger(lineElement, variables); 279 ++propertyCount; 280 281 // Optional active_low property 282 bool activeLow{false}; 283 auto activeLowIt = element.find("active_low"); 284 if (activeLowIt != element.end()) 285 { 286 activeLow = parseBoolean(*activeLowIt, variables); 287 ++propertyCount; 288 } 289 290 // Verify no invalid properties exist 291 verifyPropertyCount(element, propertyCount); 292 293 return PgoodGPIO(line, activeLow); 294 } 295 296 std::tuple<uint8_t, uint16_t> parseI2CInterface( 297 const nlohmann::json& element, 298 const std::map<std::string, std::string>& variables) 299 { 300 verifyIsObject(element); 301 unsigned int propertyCount{0}; 302 303 // Required bus property 304 const json& busElement = getRequiredProperty(element, "bus"); 305 uint8_t bus = parseUint8(busElement, variables); 306 ++propertyCount; 307 308 // Required address property 309 const json& addressElement = getRequiredProperty(element, "address"); 310 uint16_t address = parseHexByte(addressElement, variables); 311 ++propertyCount; 312 313 // Verify no invalid properties exist 314 verifyPropertyCount(element, propertyCount); 315 316 return {bus, address}; 317 } 318 319 std::unique_ptr<PowerSequencerDevice> parsePowerSequencer( 320 const nlohmann::json& element, 321 const std::map<std::string, std::string>& variables, Services& services) 322 { 323 verifyIsObject(element); 324 unsigned int propertyCount{0}; 325 326 // Optional comments property; value not stored 327 if (element.contains("comments")) 328 { 329 ++propertyCount; 330 } 331 332 // Required type property 333 const json& typeElement = getRequiredProperty(element, "type"); 334 std::string type = parseString(typeElement, false, variables); 335 ++propertyCount; 336 337 // i2c_interface property is required for some device types 338 uint8_t bus{0}; 339 uint16_t address{0}; 340 auto i2cInterfaceIt = element.find("i2c_interface"); 341 if (i2cInterfaceIt != element.end()) 342 { 343 std::tie(bus, address) = parseI2CInterface(*i2cInterfaceIt, variables); 344 ++propertyCount; 345 } 346 else if (type != GPIOsOnlyDevice::deviceName) 347 { 348 throw std::invalid_argument{"Required property missing: i2c_interface"}; 349 } 350 351 // Required power_control_gpio_name property 352 const json& powerControlGPIONameElement = 353 getRequiredProperty(element, "power_control_gpio_name"); 354 std::string powerControlGPIOName = 355 parseString(powerControlGPIONameElement, false, variables); 356 ++propertyCount; 357 358 // Required power_good_gpio_name property 359 const json& powerGoodGPIONameElement = 360 getRequiredProperty(element, "power_good_gpio_name"); 361 std::string powerGoodGPIOName = 362 parseString(powerGoodGPIONameElement, false, variables); 363 ++propertyCount; 364 365 // rails property is required for some device types 366 std::vector<std::unique_ptr<Rail>> rails{}; 367 auto railsIt = element.find("rails"); 368 if (railsIt != element.end()) 369 { 370 rails = parseRailArray(*railsIt, variables); 371 ++propertyCount; 372 } 373 else if (type != GPIOsOnlyDevice::deviceName) 374 { 375 throw std::invalid_argument{"Required property missing: rails"}; 376 } 377 378 // Verify no invalid properties exist 379 verifyPropertyCount(element, propertyCount); 380 381 if (type == UCD90160Device::deviceName) 382 { 383 return std::make_unique<UCD90160Device>( 384 bus, address, powerControlGPIOName, powerGoodGPIOName, 385 std::move(rails), services); 386 } 387 else if (type == UCD90320Device::deviceName) 388 { 389 return std::make_unique<UCD90320Device>( 390 bus, address, powerControlGPIOName, powerGoodGPIOName, 391 std::move(rails), services); 392 } 393 else if (type == GPIOsOnlyDevice::deviceName) 394 { 395 return std::make_unique<GPIOsOnlyDevice>(powerControlGPIOName, 396 powerGoodGPIOName); 397 } 398 throw std::invalid_argument{"Invalid power sequencer type: " + type}; 399 } 400 401 std::vector<std::unique_ptr<PowerSequencerDevice>> parsePowerSequencerArray( 402 const nlohmann::json& element, 403 const std::map<std::string, std::string>& variables, Services& services) 404 { 405 verifyIsArray(element); 406 std::vector<std::unique_ptr<PowerSequencerDevice>> powerSequencers; 407 for (auto& powerSequencerElement : element) 408 { 409 powerSequencers.emplace_back( 410 parsePowerSequencer(powerSequencerElement, variables, services)); 411 } 412 return powerSequencers; 413 } 414 415 std::unique_ptr<Rail> parseRail( 416 const json& element, const std::map<std::string, std::string>& variables) 417 { 418 verifyIsObject(element); 419 unsigned int propertyCount{0}; 420 421 // Required name property 422 const json& nameElement = getRequiredProperty(element, "name"); 423 std::string name = parseString(nameElement, false, variables); 424 ++propertyCount; 425 426 // Optional presence property 427 std::optional<std::string> presence{}; 428 auto presenceIt = element.find("presence"); 429 if (presenceIt != element.end()) 430 { 431 presence = parseString(*presenceIt, false, variables); 432 ++propertyCount; 433 } 434 435 // Optional page property 436 std::optional<uint8_t> page{}; 437 auto pageIt = element.find("page"); 438 if (pageIt != element.end()) 439 { 440 page = parseUint8(*pageIt, variables); 441 ++propertyCount; 442 } 443 444 // Optional is_power_supply_rail property 445 bool isPowerSupplyRail{false}; 446 auto isPowerSupplyRailIt = element.find("is_power_supply_rail"); 447 if (isPowerSupplyRailIt != element.end()) 448 { 449 isPowerSupplyRail = parseBoolean(*isPowerSupplyRailIt, variables); 450 ++propertyCount; 451 } 452 453 // Optional check_status_vout property 454 bool checkStatusVout{false}; 455 auto checkStatusVoutIt = element.find("check_status_vout"); 456 if (checkStatusVoutIt != element.end()) 457 { 458 checkStatusVout = parseBoolean(*checkStatusVoutIt, variables); 459 ++propertyCount; 460 } 461 462 // Optional compare_voltage_to_limit property 463 bool compareVoltageToLimit{false}; 464 auto compareVoltageToLimitIt = element.find("compare_voltage_to_limit"); 465 if (compareVoltageToLimitIt != element.end()) 466 { 467 compareVoltageToLimit = 468 parseBoolean(*compareVoltageToLimitIt, variables); 469 ++propertyCount; 470 } 471 472 // Optional gpio property 473 std::optional<PgoodGPIO> gpio{}; 474 auto gpioIt = element.find("gpio"); 475 if (gpioIt != element.end()) 476 { 477 gpio = parseGPIO(*gpioIt, variables); 478 ++propertyCount; 479 } 480 481 // If check_status_vout or compare_voltage_to_limit property is true, the 482 // page property is required; verify page was specified 483 if ((checkStatusVout || compareVoltageToLimit) && !page.has_value()) 484 { 485 throw std::invalid_argument{"Required property missing: page"}; 486 } 487 488 // Verify no invalid properties exist 489 verifyPropertyCount(element, propertyCount); 490 491 return std::make_unique<Rail>(name, presence, page, isPowerSupplyRail, 492 checkStatusVout, compareVoltageToLimit, gpio); 493 } 494 495 std::vector<std::unique_ptr<Rail>> parseRailArray( 496 const json& element, const std::map<std::string, std::string>& variables) 497 { 498 verifyIsArray(element); 499 std::vector<std::unique_ptr<Rail>> rails; 500 for (auto& railElement : element) 501 { 502 rails.emplace_back(parseRail(railElement, variables)); 503 } 504 return rails; 505 } 506 507 std::vector<std::unique_ptr<Chassis>> parseRoot(const json& element, 508 Services& services) 509 { 510 verifyIsObject(element); 511 unsigned int propertyCount{0}; 512 513 // Optional comments property; value not stored 514 if (element.contains("comments")) 515 { 516 ++propertyCount; 517 } 518 519 // Optional chassis_templates property 520 std::map<std::string, JSONRefWrapper> chassisTemplates{}; 521 auto chassisTemplatesIt = element.find("chassis_templates"); 522 if (chassisTemplatesIt != element.end()) 523 { 524 chassisTemplates = parseChassisTemplateArray(*chassisTemplatesIt); 525 ++propertyCount; 526 } 527 528 // Required chassis property 529 const json& chassisElement = getRequiredProperty(element, "chassis"); 530 std::vector<std::unique_ptr<Chassis>> chassis = 531 parseChassisArray(chassisElement, chassisTemplates, services); 532 ++propertyCount; 533 534 // Verify no invalid properties exist 535 verifyPropertyCount(element, propertyCount); 536 537 return chassis; 538 } 539 540 std::map<std::string, std::string> parseVariables(const json& element) 541 { 542 verifyIsObject(element); 543 544 std::map<std::string, std::string> variables; 545 std::string name, value; 546 for (const auto& [nameElement, valueElement] : element.items()) 547 { 548 name = parseString(nameElement); 549 value = parseString(valueElement); 550 variables.emplace(name, value); 551 } 552 return variables; 553 } 554 555 } // namespace internal 556 557 } // namespace phosphor::power::sequencer::config_file_parser 558