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