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