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