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