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