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