xref: /openbmc/phosphor-power/phosphor-power-sequencer/src/config_file_parser.cpp (revision fdaa950b078e162b99dd1358f66b942a26b3bd19)
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 
find(const std::vector<std::string> & compatibleSystemTypes,const std::filesystem::path & configFileDir)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 
parse(const std::filesystem::path & pathName,Services & services)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 
parseChassis(const json & element,const std::map<std::string,JSONRefWrapper> & chassisTemplates,Services & services)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 
parseChassisArray(const json & element,const std::map<std::string,JSONRefWrapper> & chassisTemplates,Services & services)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 
parseChassisProperties(const json & element,bool isChassisTemplate,const std::map<std::string,std::string> & variables,Services & services)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 
parseChassisTemplate(const json & element)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 
parseChassisTemplateArray(const json & element)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 
parseGPIO(const json & element,const std::map<std::string,std::string> & variables)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 
parseI2CInterface(const nlohmann::json & element,const std::map<std::string,std::string> & variables)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 
parsePowerSequencer(const nlohmann::json & element,const std::map<std::string,std::string> & variables,Services & services)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, services);
397     }
398     throw std::invalid_argument{"Invalid power sequencer type: " + type};
399 }
400 
parsePowerSequencerArray(const nlohmann::json & element,const std::map<std::string,std::string> & variables,Services & services)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 
parseRail(const json & element,const std::map<std::string,std::string> & variables)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 
parseRailArray(const json & element,const std::map<std::string,std::string> & variables)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 
parseRoot(const json & element,Services & services)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 
parseVariables(const json & element)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