1 #pragma once 2 3 #include "tinyxml2.h" 4 5 #include <phosphor-logging/elog-errors.hpp> 6 #include <phosphor-logging/log.hpp> 7 #include <types.hpp> 8 9 #include <map> 10 #include <sstream> 11 #include <stack> 12 #include <string> 13 #include <variant> 14 #include <vector> 15 16 namespace bios 17 { 18 /* Can hold one 'option' 19 * For example 20 * <option text="TIS" value="0x0"/> 21 */ 22 using OptionType = std::tuple<std::string, ipmi::DbusVariant, std::string>; 23 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 */ 32 using 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 */ 47 using BiosBaseTableTypeEntry = 48 std::tuple<std::string, bool, std::string, std::string, std::string, 49 ipmi::DbusVariant, ipmi::DbusVariant, 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 GT, 66 GTE, 67 LTE, 68 LT, 69 EQU, 70 NEQ, 71 MODULO 72 }; 73 74 namespace option 75 { 76 /* Can hold one 'option' */ 77 struct option 78 { optionbios::knob::option::option79 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' */ 89 struct knob 90 { knobbios::knob::knob91 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 depex(false), readOnly(("ReadOnly" == setupTypeStr) ? true : false), 96 currentVal(currentVal), nameStr(std::move(nameStr)), 97 currentValStr(std::move(currentValStr)), 98 descriptionStr(std::move(descriptionStr)), 99 defaultStr(std::move(defaultStr)), promptStr(std::move(promptStr)), 100 depexStr(std::move(depexStr)) 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. */ 120 class Depex 121 { 122 public: Depex(std::vector<knob::knob> & knobs)123 Depex(std::vector<knob::knob>& knobs) : mKnobs(knobs) {} 124 125 /* Compute 'depex' expression of all knobs in 'biosknobs'. */ compute()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. */ getErrorCount()158 size_t getErrorCount() 159 { 160 return mError.size(); 161 } 162 163 /* Prints all the 'knob's which have a bad 'depex' expression. */ printError()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. */ isNumber(const std::string & s)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 */ isHexNotation(const std::string & s)185 bool isHexNotation(const std::string& s) 186 { 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' */ getValue(std::string & variableName,int & value)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 205 std::string error = 206 "Unable to find knob: " + variableName + " in knob list\n"; 207 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 ')' */ getSubExpression(const std::string & expression,std::string & subExpression,size_t & i)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" */ getListExpression(const std::string & expression,std::string & subExpression,size_t & i)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 if (operatorStr == "_EQU_" || operatorStr == "EQU") 305 { 306 subExpression += " OR "; 307 } 308 if (operatorStr == "_NEQ_" || operatorStr == "NEQ") 309 { 310 subExpression += " AND "; 311 } 312 } 313 314 subExpression += "( "; 315 subExpression += variableStr; 316 subExpression += " "; 317 subExpression += operatorStr; 318 subExpression += " "; 319 subExpression += word; 320 subExpression += " )"; 321 } 322 } 323 } 324 325 if (!subExpression.empty()) 326 { 327 return true; 328 } 329 330 return false; 331 } 332 333 /* Function to handle operator 'NOT' 334 * 1) Find the variable 335 * 2) apply NOT on the variable */ getNotValue(const std::string & expression,size_t & i,int & value)336 bool getNotValue(const std::string& expression, size_t& i, int& value) 337 { 338 std::string word; 339 340 for (; i < expression.length(); i++) 341 { 342 if (expression[i] == ' ') 343 { 344 /* whitespace */ 345 continue; 346 } 347 else 348 { 349 /* Get the next word in expression string */ 350 while ((i < expression.length()) && (expression[i] != ' ')) 351 { 352 word.push_back(expression[i++]); 353 } 354 355 break; 356 } 357 } 358 359 if (!word.empty()) 360 { 361 if (getValue(word, value)) 362 { 363 value = !value; 364 return true; 365 } 366 } 367 368 return false; 369 } 370 371 /* 1) Pop one operator from operator stack, example 'OR' 372 * 2) Pop two variable from variable stack, example VarA and VarB 373 * 3) Push back result of 'VarA OR VarB' to variable stack 374 * 4) Repeat till operator stack is empty 375 * 376 * The last variable in variable stack is the output of the expression. */ evaluateExprStack(std::stack<int> & values,std::stack<knob::DepexOperators> & operators,int & output)377 bool evaluateExprStack(std::stack<int>& values, 378 std::stack<knob::DepexOperators>& operators, 379 int& output) 380 { 381 if (values.size() != (operators.size() + 1)) 382 { 383 return false; 384 } 385 386 while (!operators.empty()) 387 { 388 int b = values.top(); 389 values.pop(); 390 391 int a = values.top(); 392 values.pop(); 393 394 switch (operators.top()) 395 { 396 case knob::DepexOperators::OR: 397 values.emplace(a | b); 398 break; 399 400 case knob::DepexOperators::AND: 401 values.emplace(a & b); 402 break; 403 404 case knob::DepexOperators::EQU: 405 if (a == b) 406 { 407 values.emplace(1); 408 break; 409 } 410 411 values.emplace(0); 412 break; 413 414 case knob::DepexOperators::NEQ: 415 if (a != b) 416 { 417 values.emplace(1); 418 break; 419 } 420 421 values.emplace(0); 422 break; 423 424 case knob::DepexOperators::LTE: 425 if (a <= b) 426 { 427 values.emplace(1); 428 break; 429 } 430 431 values.emplace(0); 432 break; 433 434 case knob::DepexOperators::LT: 435 if (a < b) 436 { 437 values.emplace(1); 438 break; 439 } 440 441 values.emplace(0); 442 break; 443 444 case knob::DepexOperators::GTE: 445 if (a >= b) 446 { 447 values.emplace(1); 448 break; 449 } 450 451 values.emplace(0); 452 break; 453 454 case knob::DepexOperators::GT: 455 if (a > b) 456 { 457 values.emplace(1); 458 break; 459 } 460 461 values.emplace(0); 462 break; 463 464 case knob::DepexOperators::MODULO: 465 if (b == 0) 466 { 467 return false; 468 } 469 values.emplace(a % b); 470 break; 471 472 default: 473 return false; 474 } 475 476 operators.pop(); 477 } 478 479 if (values.size() == 1) 480 { 481 output = values.top(); 482 values.pop(); 483 484 return true; 485 } 486 487 return false; 488 } 489 490 /* Evaluvate one 'depex' expression 491 * 1) Find a word in expression string 492 * 2) If word is a variable push to variable stack 493 * 3) If word is a operator push to operator stack 494 * 495 * Execute the stack at end to get the result of expression. */ evaluateExpression(const std::string & expression,int & output)496 bool evaluateExpression(const std::string& expression, int& output) 497 { 498 if (expression.empty()) 499 { 500 return false; 501 } 502 503 size_t i; 504 int value; 505 bool ifFormSetOperator = false; 506 std::stack<int> values; 507 std::stack<knob::DepexOperators> operators; 508 std::string subExpression; 509 510 for (i = 0; i < expression.length(); i++) 511 { 512 if (expression[i] == ' ') 513 { 514 /* whitespace */ 515 continue; 516 } 517 else 518 { 519 std::string word; 520 521 /* Get the next word in expression string */ 522 while ((i < expression.length()) && (expression[i] != ' ')) 523 { 524 word.push_back(expression[i++]); 525 } 526 527 if (word == "_OR_" || word == "OR") 528 { 529 /* OR and AND has more precedence than other operators 530 * To handle statements like "a != b or c != d" 531 * we need to execute, for above example, both '!=' before 532 * 'or' */ 533 if (!operators.empty()) 534 { 535 if (!evaluateExprStack(values, operators, value)) 536 { 537 return false; 538 } 539 540 values.emplace(value); 541 } 542 543 operators.emplace(knob::DepexOperators::OR); 544 } 545 else if (word == "_AND_" || word == "AND") 546 { 547 /* OR and AND has more precedence than other operators 548 * To handle statements like "a == b and c == d" 549 * we need to execute, for above example, both '==' before 550 * 'and' */ 551 if (!operators.empty()) 552 { 553 if (!evaluateExprStack(values, operators, value)) 554 { 555 return false; 556 } 557 558 values.emplace(value); 559 } 560 561 operators.emplace(knob::DepexOperators::AND); 562 } 563 else if (word == "_LTE_") 564 { 565 operators.emplace(knob::DepexOperators::LTE); 566 } 567 else if (word == "_LT_") 568 { 569 operators.emplace(knob::DepexOperators::LT); 570 } 571 else if (word == "_GTE_") 572 { 573 operators.emplace(knob::DepexOperators::GTE); 574 } 575 else if (word == "_GT_") 576 { 577 operators.emplace(knob::DepexOperators::GT); 578 } 579 else if (word == "_NEQ_") 580 { 581 operators.emplace(knob::DepexOperators::NEQ); 582 } 583 else if (word == "_EQU_") 584 { 585 operators.emplace(knob::DepexOperators::EQU); 586 } 587 else if (word == "%") 588 { 589 operators.emplace(knob::DepexOperators::MODULO); 590 } 591 else 592 { 593 /* Handle 'Sif(', 'Gif(', 'Dif(' and '(' 594 * by taking the inner/sub expression and evaluating it */ 595 if (word.back() == '(') 596 { 597 if (word == "Sif(" || word == "Gif(" || word == "Dif(") 598 { 599 ifFormSetOperator = true; 600 } 601 if (!getSubExpression(expression, subExpression, i)) 602 break; 603 604 if (!evaluateExpression(subExpression, value)) 605 break; 606 } 607 else if (word == "_LIST_") 608 { 609 if (!getListExpression(expression, subExpression, i)) 610 break; 611 612 --i; 613 614 if (!evaluateExpression(subExpression, value)) 615 break; 616 } 617 else if (word == "NOT") 618 { 619 if (!getNotValue(expression, i, value)) 620 break; 621 } 622 else if (isNumber(word) || isHexNotation(word)) 623 { 624 try 625 { 626 value = std::stoi(word, nullptr, 0); 627 } 628 catch (const std::exception& ex) 629 { 630 phosphor::logging::log< 631 phosphor::logging::level::ERR>(ex.what()); 632 return false; 633 } 634 } 635 else 636 { 637 if (!getValue(word, value)) 638 break; 639 } 640 641 /* 'Sif(', 'Gif(', 'Dif( == IF NOT, 642 we have to negate the vaule */ 643 if (ifFormSetOperator == true) 644 { 645 value = !value; 646 } 647 values.emplace(value); 648 } 649 } 650 } 651 652 if (i == expression.length()) 653 { 654 if (evaluateExprStack(values, operators, output)) 655 { 656 return true; 657 } 658 } 659 660 return false; 661 } 662 663 private: 664 /* To store all 'knob's in 'biosknobs' */ 665 std::vector<knob::knob>& mKnobs; 666 667 /* To store all bad 'depex' expression */ 668 std::vector<std::string> mError; 669 }; 670 671 class Xml 672 { 673 public: Xml(const char * filePath)674 Xml(const char* filePath) : mDepex(std::make_unique<Depex>(mKnobs)) 675 { 676 if (!getKnobs(filePath)) 677 { 678 std::string error = 679 "Unable to get knobs in file: " + std::string(filePath); 680 throw std::runtime_error(error); 681 } 682 } 683 684 /* Fill Bios table with all 'knob's which have output of 'depex' expression 685 * as 'true' */ getBaseTable(bios::BiosBaseTableType & baseTable)686 bool getBaseTable(bios::BiosBaseTableType& baseTable) 687 { 688 baseTable.clear(); 689 690 for (auto& knob : mKnobs) 691 { 692 if (knob.depex) 693 { 694 std::string text = 695 "xyz.openbmc_project.BIOSConfig.Manager.BoundType.OneOf"; 696 bios::OptionTypeVector options; 697 698 for (auto& option : knob.options) 699 { 700 options.emplace_back(text, option.value, option.text); 701 } 702 703 bios::BiosBaseTableTypeEntry baseTableEntry = std::make_tuple( 704 "xyz.openbmc_project.BIOSConfig.Manager.AttributeType." 705 "Enumeration", 706 false, knob.promptStr, knob.descriptionStr, "./", 707 knob.currentValStr, knob.defaultStr, options); 708 709 baseTable.emplace(knob.nameStr, baseTableEntry); 710 } 711 } 712 713 if (!baseTable.empty()) 714 { 715 return true; 716 } 717 718 return false; 719 } 720 721 /* Execute all 'depex' expression */ doDepexCompute()722 bool doDepexCompute() 723 { 724 mDepex->compute(); 725 726 if (mDepex->getErrorCount()) 727 { 728 mDepex->printError(); 729 return false; 730 } 731 732 return true; 733 } 734 735 private: 736 /* Get 'option' */ getOption(tinyxml2::XMLElement * pOption)737 void getOption(tinyxml2::XMLElement* pOption) 738 { 739 if (pOption) 740 { 741 std::string valueStr; 742 std::string textStr; 743 744 if (pOption->Attribute("text")) 745 valueStr = pOption->Attribute("text"); 746 747 if (pOption->Attribute("value")) 748 textStr = pOption->Attribute("value"); 749 750 mKnobs.back().options.emplace_back(pOption->Attribute("text"), 751 pOption->Attribute("value")); 752 } 753 } 754 755 /* Get 'options' */ getOptions(tinyxml2::XMLElement * pKnob)756 void getOptions(tinyxml2::XMLElement* pKnob) 757 { 758 uint16_t reserveCnt = 0; 759 760 /* Get node options inside knob */ 761 tinyxml2::XMLElement* pOptions = pKnob->FirstChildElement("options"); 762 763 if (pOptions) 764 { 765 for (tinyxml2::XMLElement* pOption = 766 pOptions->FirstChildElement("option"); 767 pOption; pOption = pOption->NextSiblingElement("option")) 768 { 769 ++reserveCnt; 770 } 771 772 mKnobs.back().options.reserve(reserveCnt); 773 774 /* Loop through all option inside options */ 775 for (tinyxml2::XMLElement* pOption = 776 pOptions->FirstChildElement("option"); 777 pOption; pOption = pOption->NextSiblingElement("option")) 778 { 779 getOption(pOption); 780 } 781 } 782 } 783 784 /* Get 'knob' */ getKnob(tinyxml2::XMLElement * pKnob)785 void getKnob(tinyxml2::XMLElement* pKnob) 786 { 787 if (pKnob) 788 { 789 int currentVal = 0; 790 std::string nameStr; 791 std::string currentValStr; 792 std::string descriptionStr; 793 std::string defaultStr; 794 std::string depexStr; 795 std::string promptStr; 796 std::string setupTypeStr; 797 798 if (!pKnob->Attribute("name") || !pKnob->Attribute("CurrentVal")) 799 { 800 return; 801 } 802 803 nameStr = pKnob->Attribute("name"); 804 currentValStr = pKnob->Attribute("CurrentVal"); 805 std::stringstream ss; 806 ss << std::hex << currentValStr; 807 if (ss.good()) 808 { 809 ss >> currentVal; 810 } 811 else 812 { 813 std::string error = "Invalid hex value input " + currentValStr + 814 " for " + nameStr + "\n"; 815 phosphor::logging::log<phosphor::logging::level::ERR>( 816 error.c_str()); 817 return; 818 } 819 if (pKnob->Attribute("description")) 820 descriptionStr = pKnob->Attribute("description"); 821 822 if (pKnob->Attribute("default")) 823 defaultStr = pKnob->Attribute("default"); 824 825 if (pKnob->Attribute("depex")) 826 depexStr = pKnob->Attribute("depex"); 827 828 if (pKnob->Attribute("prompt")) 829 promptStr = pKnob->Attribute("prompt"); 830 831 if (pKnob->Attribute("setupType")) 832 setupTypeStr = pKnob->Attribute("setupType"); 833 834 mKnobs.emplace_back(nameStr, currentValStr, currentVal, 835 descriptionStr, defaultStr, promptStr, depexStr, 836 setupTypeStr); 837 838 getOptions(pKnob); 839 } 840 } 841 842 /* Get 'biosknobs' */ getKnobs(const char * biosXmlFilePath)843 bool getKnobs(const char* biosXmlFilePath) 844 { 845 uint16_t reserveCnt = 0; 846 847 mKnobs.clear(); 848 849 tinyxml2::XMLDocument biosXml; 850 851 /* Load the XML file into the Doc instance */ 852 biosXml.LoadFile(biosXmlFilePath); 853 854 /* Get 'SYSTEM' */ 855 tinyxml2::XMLElement* pRootElement = biosXml.RootElement(); 856 if (pRootElement) 857 { 858 /* Get 'biosknobs' inside 'SYSTEM' */ 859 tinyxml2::XMLElement* pBiosknobs = 860 pRootElement->FirstChildElement("biosknobs"); 861 if (pBiosknobs) 862 { 863 for (tinyxml2::XMLElement* pKnob = 864 pBiosknobs->FirstChildElement("knob"); 865 pKnob; pKnob = pKnob->NextSiblingElement("knob")) 866 { 867 ++reserveCnt; 868 } 869 870 /* reserve before emplace_back will avoids realloc(s) */ 871 mKnobs.reserve(reserveCnt); 872 873 for (tinyxml2::XMLElement* pKnob = 874 pBiosknobs->FirstChildElement("knob"); 875 pKnob; pKnob = pKnob->NextSiblingElement("knob")) 876 { 877 getKnob(pKnob); 878 } 879 } 880 } 881 882 if (!mKnobs.empty()) 883 { 884 return true; 885 } 886 887 return false; 888 } 889 890 private: 891 /* To store all 'knob's in 'biosknobs' */ 892 std::vector<knob::knob> mKnobs; 893 894 /* Object of Depex class to compute 'depex' expression */ 895 std::unique_ptr<Depex> mDepex; 896 }; 897 } // namespace bios 898