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