1 #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 
15 namespace bios
16 {
17 /* Can hold one 'option'
18  * For example
19  *  <option text="TIS" value="0x0"/>
20  */
21 using 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  */
31 using 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  */
46 using 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' */
53 using BiosBaseTableType = std::map<std::string, BiosBaseTableTypeEntry>;
54 
55 namespace 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. */
60 enum class DepexOperators
61 {
62     unknown = 0,
63     OR,
64     AND,
65     LTE,
66     LT,
67     EQU,
68     NEQ,
69     MODULO
70 };
71 
72 namespace option
73 {
74 /* Can hold one 'option' */
75 struct 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' */
87 struct 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. */
118 class 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 
624 class 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
849