xref: /openbmc/phosphor-power/phosphor-power-sequencer/src/config_file_parser.cpp (revision d62367d6b4018179fb53c596860e511979447d2e)
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