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