blob: 6b170bdeab8671a48854e9de1cc23984377bd25d [file] [log] [blame]
Patrick Williamsed173c32021-10-06 13:09:19 -05001#pragma once
2
3#include "tinyxml2.h"
4
5#include <phosphor-logging/elog-errors.hpp>
6#include <phosphor-logging/log.hpp>
7
8#include <map>
9#include <sstream>
10#include <stack>
11#include <string>
12#include <variant>
13#include <vector>
14
15namespace bios
16{
17/* Can hold one 'option'
18 * For example
19 * <option text="TIS" value="0x0"/>
20 */
21using OptionType = std::tuple<std::string, std::variant<int64_t, std::string>>;
22
23/* Can hold one 'options'
24 * For example
25 * <options>
26 * <option text="TIS" value="0x0"/>
27 * <option text="PTP FIFO" value="0x1"/>
28 * <option text="PTP CRB" value="0x2"/>
29 * </options>
30 */
31using OptionTypeVector = std::vector<OptionType>;
32
33/* Can hold one 'knob'
34 * For example
35 * <knob type="scalar" setupType="oneof" name="TpmDeviceInterfaceAttempt"
36 * varstoreIndex="14" prompt="Attempt PTP TPM Device Interface"
37 * description="Attempt PTP TPM Device Interface: PTP FIFO, PTP CRB" size="1"
38 * offset="0x0005" depex="Sif( _LIST_ TpmDevice _EQU_ 0 1 ) _AND_ Sif(
39 * TpmDeviceInterfacePtpFifoSupported _EQU_ 0 OR
40 * TpmDeviceInterfacePtpCrbSupported _EQU_ 0 )" default="0x00"
41 *CurrentVal="0x00"> <options> <option text="TIS" value="0x0"/> <option
42 *text="PTP FIFO" value="0x1"/> <option text="PTP CRB" value="0x2"/>
43 * </options>
44 * </knob>
45 */
46using BiosBaseTableTypeEntry =
47 std::tuple<std::string, bool, std::string, std::string, std::string,
48 std::variant<int64_t, std::string>,
49 std::variant<int64_t, std::string>, OptionTypeVector>;
50
51/* Can hold one 'biosknobs'
52 * biosknobs has array of 'knob' */
53using BiosBaseTableType = std::map<std::string, BiosBaseTableTypeEntry>;
54
55namespace knob
56{
57/* These are the operators we support in a 'depex' expression
58 * Note: We also support '_LIST_', 'Sif', 'Gif', 'Dif', and 'NOT'. But they are
59 * handeled sepeartely. */
60enum class DepexOperators
61{
62 unknown = 0,
63 OR,
64 AND,
65 GT,
66 GTE,
67 LTE,
68 LT,
69 EQU,
70 NEQ,
71 MODULO
72};
73
74namespace option
75{
76/* Can hold one 'option' */
77struct option
78{
79 option(std::string text, std::string value) :
80 text(std::move(text)), value(std::move(value))
81 {}
82
83 std::string text;
84 std::string value;
85};
86} // namespace option
87
88/* Can hold one 'knob' */
89struct knob
90{
91 knob(std::string nameStr, std::string currentValStr, int currentVal,
92 std::string descriptionStr, std::string defaultStr,
93 std::string promptStr, std::string depexStr,
94 std::string& setupTypeStr) :
95 nameStr(std::move(nameStr)),
96 currentValStr(std::move(currentValStr)), currentVal(currentVal),
97 descriptionStr(std::move(descriptionStr)),
98 defaultStr(std::move(defaultStr)), promptStr(std::move(promptStr)),
99 depexStr(std::move(depexStr)), depex(false),
100 readOnly(("ReadOnly" == setupTypeStr) ? true : false)
101 {}
102
103 bool depex;
104 bool readOnly;
105 int currentVal;
106
107 std::string nameStr;
108 std::string currentValStr;
109 std::string descriptionStr;
110 std::string defaultStr;
111 std::string promptStr;
112 std::string depexStr;
113
114 /* Can hold one 'options' */
115 std::vector<option::option> options;
116};
117} // namespace knob
118
119/* Class capable of computing 'depex' expression. */
120class Depex
121{
122 public:
123 Depex(std::vector<knob::knob>& knobs) : mKnobs(knobs)
124 {}
125
126 /* Compute 'depex' expression of all knobs in 'biosknobs'. */
127 void compute()
128 {
129 mError.clear();
130
131 for (auto& knob : mKnobs)
132 {
133 /* if 'depex' == "TRUE" no need to execute expression. */
134 if ("TRUE" == knob.depexStr)
135 {
136 knob.depex = true;
137 }
138 else if (!knob.readOnly)
139 {
140 int value = 0;
141
142 if (!evaluateExpression(knob.depexStr, value))
143 {
144 mError.emplace_back("bad depex: " + knob.depexStr +
145 " in knob: " + knob.nameStr);
146 }
147 else
148 {
149 if (value)
150 {
151 knob.depex = true;
152 }
153 }
154 }
155 }
156 }
157
158 /* Returns the number of 'knob's which have a bad 'depex' expression. */
159 size_t getErrorCount()
160 {
161 return mError.size();
162 }
163
164 /* Prints all the 'knob's which have a bad 'depex' expression. */
165 void printError()
166 {
167 for (auto& error : mError)
168 {
169 phosphor::logging::log<phosphor::logging::level::ERR>(
170 error.c_str());
171 }
172 }
173
174 private:
175 /* Returns 'true' if the argument string is a number. */
176 bool isNumber(const std::string& s)
177 {
178 return !s.empty() &&
179 std::find_if(s.begin(), s.end(), [](unsigned char c) {
180 return !std::isdigit(c);
181 }) == s.end();
182 }
183
184 /* Returns 'true' if the argument string is hex representation of a number.
185 */
186 bool isHexNotation(std::string const& s)
187 {
188 return s.compare(0, 2, "0x") == 0 && s.size() > 2 &&
189 s.find_first_not_of("0123456789abcdefABCDEF", 2) ==
190 std::string::npos;
191 }
192
193 /* Function to find current value of a 'knob'
194 * search is done using 'knob' attribute 'name' */
195 bool getValue(std::string& variableName, int& value)
196 {
197 for (auto& knob : mKnobs)
198 {
199 if (knob.nameStr == variableName)
200 {
201 value = knob.currentVal;
202 return true;
203 }
204 }
205
206 std::string error =
207 "Unable to find knob: " + variableName + " in knob list\n";
208 phosphor::logging::log<phosphor::logging::level::ERR>(error.c_str());
209
210 return false;
211 }
212
213 /* Get the expression enclosed within brackets, i.e., between '(' and ')' */
214 bool getSubExpression(const std::string& expression,
215 std::string& subExpression, size_t& i)
216 {
217 int level = 1;
218 subExpression.clear();
219
220 for (; i < expression.length(); i++)
221 {
222 if (expression[i] == '(')
223 {
224 ++level;
225 }
226 else if (expression[i] == ')')
227 {
228 --level;
229 if (level == 0)
230 {
231 break;
232 }
233 }
234
235 subExpression.push_back(expression[i]);
236 }
237
238 if (!subExpression.empty())
239 {
240 return true;
241 }
242
243 return false;
244 }
245
246 /* Function to handle operator '_LIST_'
247 * Convert a '_LIST_' expression to a normal expression
248 * Example "_LIST_ VariableA _EQU_ 0 1" is converted to "VariableA _EQU_ 0
249 * OR VariableA _EQU_ 1" */
250 bool getListExpression(const std::string& expression,
251 std::string& subExpression, size_t& i)
252 {
253 subExpression.clear();
254
255 int cnt = 0;
256 std::string variableStr;
257 std::string operatorStr;
258
259 for (; i < expression.length(); i++)
260 {
261 if (expression[i] == '(')
262 {
263 return false;
264 }
265 else if (expression[i] == ')')
266 {
267 break;
268 }
269 else if (expression[i] == ' ')
270 {
271 /* whitespace */
272 continue;
273 }
274 else
275 {
276 std::string word;
277
278 /* Get the next word in expression string */
279 while ((i < expression.length()) && (expression[i] != ' '))
280 {
281 word.push_back(expression[i++]);
282 }
283
284 if (word == "_OR_" || word == "OR" || word == "_AND_" ||
285 word == "AND" || word == "NOT")
286 {
287 i = i - word.length();
288 break;
289 }
290
291 ++cnt;
292
293 if (cnt == 1)
294 {
295 variableStr = word;
296 }
297 else if (cnt == 2)
298 {
299 operatorStr = word;
300 }
301 else
302 {
303 if (cnt > 3)
304 {
305 subExpression += " OR ";
306 }
307
308 subExpression += "( ";
309 subExpression += variableStr;
310 subExpression += " ";
311 subExpression += operatorStr;
312 subExpression += " ";
313 subExpression += word;
314 subExpression += " )";
315 }
316 }
317 }
318
319 if (!subExpression.empty())
320 {
321 return true;
322 }
323
324 return false;
325 }
326
327 /* Function to handle operator 'NOT'
328 * 1) Find the variable
329 * 2) apply NOT on the variable */
330 bool getNotValue(const std::string& expression, size_t& i, int& value)
331 {
332 std::string word;
333
334 for (; i < expression.length(); i++)
335 {
336 if (expression[i] == ' ')
337 {
338 /* whitespace */
339 continue;
340 }
341 else
342 {
343 /* Get the next word in expression string */
344 while ((i < expression.length()) && (expression[i] != ' '))
345 {
346 word.push_back(expression[i++]);
347 }
348
349 break;
350 }
351 }
352
353 if (!word.empty())
354 {
355 if (getValue(word, value))
356 {
357 value = !value;
358 return true;
359 }
360 }
361
362 return false;
363 }
364
365 /* 1) Pop one operator from operator stack, example 'OR'
366 * 2) Pop two variable from variable stack, example VarA and VarB
367 * 3) Push back result of 'VarA OR VarB' to variable stack
368 * 4) Repeat till operator stack is empty
369 *
370 * The last variable in variable stack is the output of the expression. */
371 bool evaluateExprStack(std::stack<int>& values,
372 std::stack<knob::DepexOperators>& operators,
373 int& output)
374 {
375 if (values.size() != (operators.size() + 1))
376 {
377 return false;
378 }
379
380 while (!operators.empty())
381 {
382 int b = values.top();
383 values.pop();
384
385 int a = values.top();
386 values.pop();
387
388 switch (operators.top())
389 {
390 case knob::DepexOperators::OR:
391 values.emplace(a | b);
392 break;
393
394 case knob::DepexOperators::AND:
395 values.emplace(a & b);
396 break;
397
398 case knob::DepexOperators::EQU:
399 if (a == b)
400 {
401 values.emplace(1);
402 break;
403 }
404
405 values.emplace(0);
406 break;
407
408 case knob::DepexOperators::NEQ:
409 if (a != b)
410 {
411 values.emplace(1);
412 break;
413 }
414
415 values.emplace(0);
416 break;
417
418 case knob::DepexOperators::LTE:
419 if (a <= b)
420 {
421 values.emplace(1);
422 break;
423 }
424
425 values.emplace(0);
426 break;
427
428 case knob::DepexOperators::LT:
429 if (a < b)
430 {
431 values.emplace(1);
432 break;
433 }
434
435 values.emplace(0);
436 break;
437
438 case knob::DepexOperators::GTE:
439 if (a >= b)
440 {
441 values.emplace(1);
442 break;
443 }
444
445 values.emplace(0);
446 break;
447
448 case knob::DepexOperators::GT:
449 if (a > b)
450 {
451 values.emplace(1);
452 break;
453 }
454
455 values.emplace(0);
456 break;
457
458 case knob::DepexOperators::MODULO:
459 if (b == 0)
460 {
461 return false;
462 }
463 values.emplace(a % b);
464 break;
465
466 default:
467 return false;
468 }
469
470 operators.pop();
471 }
472
473 if (values.size() == 1)
474 {
475 output = values.top();
476 values.pop();
477
478 return true;
479 }
480
481 return false;
482 }
483
484 /* Evaluvate one 'depex' expression
485 * 1) Find a word in expression string
486 * 2) If word is a variable push to variable stack
487 * 3) If word is a operator push to operator stack
488 *
489 * Execute the stack at end to get the result of expression. */
490 bool evaluateExpression(const std::string& expression, int& output)
491 {
492 if (expression.empty())
493 {
494 return false;
495 }
496
497 size_t i;
498 int value;
499 std::stack<int> values;
500 std::stack<knob::DepexOperators> operators;
501 std::string subExpression;
502
503 for (i = 0; i < expression.length(); i++)
504 {
505 if (expression[i] == ' ')
506 {
507 /* whitespace */
508 continue;
509 }
510 else
511 {
512 std::string word;
513
514 /* Get the next word in expression string */
515 while ((i < expression.length()) && (expression[i] != ' '))
516 {
517 word.push_back(expression[i++]);
518 }
519
520 if (word == "_OR_" || word == "OR")
521 {
522 /* OR and AND has more precedence than other operators
523 * To handle statements like "a != b or c != d"
524 * we need to execute, for above example, both '!=' before
525 * 'or' */
526 if (!operators.empty())
527 {
528 if (!evaluateExprStack(values, operators, value))
529 {
530 return false;
531 }
532
533 values.emplace(value);
534 }
535
536 operators.emplace(knob::DepexOperators::OR);
537 }
538 else if (word == "_AND_" || word == "AND")
539 {
540 /* OR and AND has more precedence than other operators
541 * To handle statements like "a == b and c == d"
542 * we need to execute, for above example, both '==' before
543 * 'and' */
544 if (!operators.empty())
545 {
546 if (!evaluateExprStack(values, operators, value))
547 {
548 return false;
549 }
550
551 values.emplace(value);
552 }
553
554 operators.emplace(knob::DepexOperators::AND);
555 }
556 else if (word == "_LTE_")
557 {
558 operators.emplace(knob::DepexOperators::LTE);
559 }
560 else if (word == "_LT_")
561 {
562 operators.emplace(knob::DepexOperators::LT);
563 }
564 else if (word == "_GTE_")
565 {
566 operators.emplace(knob::DepexOperators::GTE);
567 }
568 else if (word == "_GT_")
569 {
570 operators.emplace(knob::DepexOperators::GT);
571 }
572 else if (word == "_NEQ_")
573 {
574 operators.emplace(knob::DepexOperators::NEQ);
575 }
576 else if (word == "_EQU_")
577 {
578 operators.emplace(knob::DepexOperators::EQU);
579 }
580 else if (word == "%")
581 {
582 operators.emplace(knob::DepexOperators::MODULO);
583 }
584 else
585 {
586 /* Handle 'Sif(', 'Gif(', 'Dif(' and '('
587 * by taking the inner/sub expression and evaluating it */
588 if (word.back() == '(')
589 {
590 if (!getSubExpression(expression, subExpression, i))
591 break;
592
593 if (!evaluateExpression(subExpression, value))
594 break;
595 }
596 else if (word == "_LIST_")
597 {
598 if (!getListExpression(expression, subExpression, i))
599 break;
600
601 --i;
602
603 if (!evaluateExpression(subExpression, value))
604 break;
605 }
606 else if (word == "NOT")
607 {
608 if (!getNotValue(expression, i, value))
609 break;
610 }
611 else if (isNumber(word) || isHexNotation(word))
612 {
613 try
614 {
615 value = std::stoi(word);
616 }
Patrick Williamsbd51e6a2021-10-06 13:09:44 -0500617 catch (const std::exception& ex)
Patrick Williamsed173c32021-10-06 13:09:19 -0500618 {
619 phosphor::logging::log<
620 phosphor::logging::level::ERR>(ex.what());
621 return false;
622 }
623 }
624 else
625 {
626 if (!getValue(word, value))
627 break;
628 }
629
630 values.emplace(value);
631 }
632 }
633 }
634
635 if (i == expression.length())
636 {
637 if (evaluateExprStack(values, operators, output))
638 {
639 return true;
640 }
641 }
642
643 return false;
644 }
645
646 private:
647 /* To store all 'knob's in 'biosknobs' */
648 std::vector<knob::knob>& mKnobs;
649
650 /* To store all bad 'depex' expression */
651 std::vector<std::string> mError;
652};
653
654class Xml
655{
656 public:
657 Xml(const char* filePath) : mDepex(std::make_unique<Depex>(mKnobs))
658 {
659 if (!getKnobs(filePath))
660 {
661 std::string error =
662 "Unable to get knobs in file: " + std::string(filePath);
663 throw std::runtime_error(error);
664 }
665 }
666
667 /* Fill Bios table with all 'knob's which have output of 'depex' expression
668 * as 'true' */
669 bool getBaseTable(bios::BiosBaseTableType& baseTable)
670 {
671 baseTable.clear();
672
673 for (auto& knob : mKnobs)
674 {
675 if (knob.depex)
676 {
677 std::string text =
678 "xyz.openbmc_project.BIOSConfig.Manager.BoundType.OneOf";
679 bios::OptionTypeVector options;
680
681 for (auto& option : knob.options)
682 {
683 options.emplace_back(text, option.value);
684 }
685
686 bios::BiosBaseTableTypeEntry baseTableEntry = std::make_tuple(
687 "xyz.openbmc_project.BIOSConfig.Manager.AttributeType."
Arun Lal K Md2d60ab2021-12-29 19:42:03 +0000688 "Enumeration",
Patrick Williamsed173c32021-10-06 13:09:19 -0500689 false, knob.nameStr, knob.descriptionStr, "./",
690 knob.currentValStr, knob.defaultStr, options);
691
692 baseTable.emplace(knob.nameStr, baseTableEntry);
693 }
694 }
695
696 if (!baseTable.empty())
697 {
698 return true;
699 }
700
701 return false;
702 }
703
704 /* Execute all 'depex' expression */
705 bool doDepexCompute()
706 {
707 mDepex->compute();
708
709 if (mDepex->getErrorCount())
710 {
711 mDepex->printError();
712 return false;
713 }
714
715 return true;
716 }
717
718 private:
719 /* Get 'option' */
720 void getOption(tinyxml2::XMLElement* pOption)
721 {
722 if (pOption)
723 {
724 std::string valueStr;
725 std::string textStr;
726
727 if (pOption->Attribute("text"))
728 valueStr = pOption->Attribute("text");
729
730 if (pOption->Attribute("value"))
731 textStr = pOption->Attribute("value");
732
733 mKnobs.back().options.emplace_back(pOption->Attribute("text"),
734 pOption->Attribute("value"));
735 }
736 }
737
738 /* Get 'options' */
739 void getOptions(tinyxml2::XMLElement* pKnob)
740 {
741 uint16_t reserveCnt = 0;
742
743 /* Get node options inside knob */
744 tinyxml2::XMLElement* pOptions = pKnob->FirstChildElement("options");
745
746 if (pOptions)
747 {
748 for (tinyxml2::XMLElement* pOption =
749 pOptions->FirstChildElement("option");
750 pOption; pOption = pOption->NextSiblingElement("option"))
751 {
752 ++reserveCnt;
753 }
754
755 mKnobs.back().options.reserve(reserveCnt);
756
757 /* Loop through all option inside options */
758 for (tinyxml2::XMLElement* pOption =
759 pOptions->FirstChildElement("option");
760 pOption; pOption = pOption->NextSiblingElement("option"))
761 {
762 getOption(pOption);
763 }
764 }
765 }
766
767 /* Get 'knob' */
768 void getKnob(tinyxml2::XMLElement* pKnob)
769 {
770 if (pKnob)
771 {
772 int currentVal = 0;
773 std::string nameStr;
774 std::string currentValStr;
775 std::string descriptionStr;
776 std::string defaultStr;
777 std::string depexStr;
778 std::string promptStr;
779 std::string setupTypeStr;
780
781 if (!pKnob->Attribute("name") || !pKnob->Attribute("CurrentVal"))
782 {
783 return;
784 }
785
786 nameStr = pKnob->Attribute("name");
787 currentValStr = pKnob->Attribute("CurrentVal");
Snehalatha Venkatesh713928b2021-12-23 09:41:59 +0000788 std::stringstream ss;
789 ss << std::hex << currentValStr;
790 if (ss.good())
Patrick Williamsed173c32021-10-06 13:09:19 -0500791 {
Snehalatha Venkatesh713928b2021-12-23 09:41:59 +0000792 ss >> currentVal;
Patrick Williamsed173c32021-10-06 13:09:19 -0500793 }
Snehalatha Venkatesh713928b2021-12-23 09:41:59 +0000794 else
Patrick Williamsed173c32021-10-06 13:09:19 -0500795 {
Snehalatha Venkatesh713928b2021-12-23 09:41:59 +0000796 std::string error = "Invalid hex value input " + currentValStr +
797 " for " + nameStr + "\n";
Patrick Williamsed173c32021-10-06 13:09:19 -0500798 phosphor::logging::log<phosphor::logging::level::ERR>(
Snehalatha Venkatesh713928b2021-12-23 09:41:59 +0000799 error.c_str());
Patrick Williamsed173c32021-10-06 13:09:19 -0500800 return;
801 }
Patrick Williamsed173c32021-10-06 13:09:19 -0500802 if (pKnob->Attribute("description"))
803 descriptionStr = pKnob->Attribute("description");
804
805 if (pKnob->Attribute("default"))
806 defaultStr = pKnob->Attribute("default");
807
808 if (pKnob->Attribute("depex"))
809 depexStr = pKnob->Attribute("depex");
810
811 if (pKnob->Attribute("prompt"))
812 promptStr = pKnob->Attribute("prompt");
813
814 if (pKnob->Attribute("setupType"))
815 setupTypeStr = pKnob->Attribute("setupType");
816
817 mKnobs.emplace_back(nameStr, currentValStr, currentVal,
818 descriptionStr, defaultStr, promptStr, depexStr,
819 setupTypeStr);
820
821 getOptions(pKnob);
822 }
823 }
824
825 /* Get 'biosknobs' */
826 bool getKnobs(const char* biosXmlFilePath)
827 {
828 uint16_t reserveCnt = 0;
829
830 mKnobs.clear();
831
832 tinyxml2::XMLDocument biosXml;
833
834 /* Load the XML file into the Doc instance */
835 biosXml.LoadFile(biosXmlFilePath);
836
837 /* Get 'SYSTEM' */
838 tinyxml2::XMLElement* pRootElement = biosXml.RootElement();
839 if (pRootElement)
840 {
841 /* Get 'biosknobs' inside 'SYSTEM' */
842 tinyxml2::XMLElement* pBiosknobs =
843 pRootElement->FirstChildElement("biosknobs");
844 if (pBiosknobs)
845 {
846 for (tinyxml2::XMLElement* pKnob =
847 pBiosknobs->FirstChildElement("knob");
848 pKnob; pKnob = pKnob->NextSiblingElement("knob"))
849 {
850 ++reserveCnt;
851 }
852
853 /* reserve before emplace_back will avoids realloc(s) */
854 mKnobs.reserve(reserveCnt);
855
856 for (tinyxml2::XMLElement* pKnob =
857 pBiosknobs->FirstChildElement("knob");
858 pKnob; pKnob = pKnob->NextSiblingElement("knob"))
859 {
860 getKnob(pKnob);
861 }
862 }
863 }
864
865 if (!mKnobs.empty())
866 {
867 return true;
868 }
869
870 return false;
871 }
872
873 private:
874 /* To store all 'knob's in 'biosknobs' */
875 std::vector<knob::knob> mKnobs;
876
877 /* Object of Depex class to compute 'depex' expression */
878 std::unique_ptr<Depex> mDepex;
879};
880} // namespace bios