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