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