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