| #pragma once |
| |
| #include "tinyxml2.h" |
| |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/log.hpp> |
| |
| #include <map> |
| #include <sstream> |
| #include <stack> |
| #include <string> |
| #include <variant> |
| #include <vector> |
| |
| namespace bios |
| { |
| /* Can hold one 'option' |
| * For example |
| * <option text="TIS" value="0x0"/> |
| */ |
| using OptionType = std::tuple<std::string, std::variant<int64_t, std::string>>; |
| |
| /* Can hold one 'options' |
| * For example |
| * <options> |
| * <option text="TIS" value="0x0"/> |
| * <option text="PTP FIFO" value="0x1"/> |
| * <option text="PTP CRB" value="0x2"/> |
| * </options> |
| */ |
| using OptionTypeVector = std::vector<OptionType>; |
| |
| /* Can hold one 'knob' |
| * For example |
| * <knob type="scalar" setupType="oneof" name="TpmDeviceInterfaceAttempt" |
| * varstoreIndex="14" prompt="Attempt PTP TPM Device Interface" |
| * description="Attempt PTP TPM Device Interface: PTP FIFO, PTP CRB" size="1" |
| * offset="0x0005" depex="Sif( _LIST_ TpmDevice _EQU_ 0 1 ) _AND_ Sif( |
| * TpmDeviceInterfacePtpFifoSupported _EQU_ 0 OR |
| * TpmDeviceInterfacePtpCrbSupported _EQU_ 0 )" default="0x00" |
| *CurrentVal="0x00"> <options> <option text="TIS" value="0x0"/> <option |
| *text="PTP FIFO" value="0x1"/> <option text="PTP CRB" value="0x2"/> |
| * </options> |
| * </knob> |
| */ |
| using BiosBaseTableTypeEntry = |
| std::tuple<std::string, bool, std::string, std::string, std::string, |
| std::variant<int64_t, std::string>, |
| std::variant<int64_t, std::string>, OptionTypeVector>; |
| |
| /* Can hold one 'biosknobs' |
| * biosknobs has array of 'knob' */ |
| using BiosBaseTableType = std::map<std::string, BiosBaseTableTypeEntry>; |
| |
| namespace knob |
| { |
| /* These are the operators we support in a 'depex' expression |
| * Note: We also support '_LIST_', 'Sif', 'Gif', 'Dif', and 'NOT'. But they are |
| * handeled sepeartely. */ |
| enum class DepexOperators |
| { |
| unknown = 0, |
| OR, |
| AND, |
| GT, |
| GTE, |
| LTE, |
| LT, |
| EQU, |
| NEQ, |
| MODULO |
| }; |
| |
| namespace option |
| { |
| /* Can hold one 'option' */ |
| struct option |
| { |
| option(std::string text, std::string value) : |
| text(std::move(text)), value(std::move(value)) |
| {} |
| |
| std::string text; |
| std::string value; |
| }; |
| } // namespace option |
| |
| /* Can hold one 'knob' */ |
| struct knob |
| { |
| knob(std::string nameStr, std::string currentValStr, int currentVal, |
| std::string descriptionStr, std::string defaultStr, |
| std::string promptStr, std::string depexStr, |
| std::string& setupTypeStr) : |
| nameStr(std::move(nameStr)), |
| currentValStr(std::move(currentValStr)), currentVal(currentVal), |
| descriptionStr(std::move(descriptionStr)), |
| defaultStr(std::move(defaultStr)), promptStr(std::move(promptStr)), |
| depexStr(std::move(depexStr)), depex(false), |
| readOnly(("ReadOnly" == setupTypeStr) ? true : false) |
| {} |
| |
| bool depex; |
| bool readOnly; |
| int currentVal; |
| |
| std::string nameStr; |
| std::string currentValStr; |
| std::string descriptionStr; |
| std::string defaultStr; |
| std::string promptStr; |
| std::string depexStr; |
| |
| /* Can hold one 'options' */ |
| std::vector<option::option> options; |
| }; |
| } // namespace knob |
| |
| /* Class capable of computing 'depex' expression. */ |
| class Depex |
| { |
| public: |
| Depex(std::vector<knob::knob>& knobs) : mKnobs(knobs) |
| {} |
| |
| /* Compute 'depex' expression of all knobs in 'biosknobs'. */ |
| void compute() |
| { |
| mError.clear(); |
| |
| for (auto& knob : mKnobs) |
| { |
| /* if 'depex' == "TRUE" no need to execute expression. */ |
| if ("TRUE" == knob.depexStr) |
| { |
| knob.depex = true; |
| } |
| else if (!knob.readOnly) |
| { |
| int value = 0; |
| |
| if (!evaluateExpression(knob.depexStr, value)) |
| { |
| mError.emplace_back("bad depex: " + knob.depexStr + |
| " in knob: " + knob.nameStr); |
| } |
| else |
| { |
| if (value) |
| { |
| knob.depex = true; |
| } |
| } |
| } |
| } |
| } |
| |
| /* Returns the number of 'knob's which have a bad 'depex' expression. */ |
| size_t getErrorCount() |
| { |
| return mError.size(); |
| } |
| |
| /* Prints all the 'knob's which have a bad 'depex' expression. */ |
| void printError() |
| { |
| for (auto& error : mError) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| error.c_str()); |
| } |
| } |
| |
| private: |
| /* Returns 'true' if the argument string is a number. */ |
| bool isNumber(const std::string& s) |
| { |
| return !s.empty() && |
| std::find_if(s.begin(), s.end(), [](unsigned char c) { |
| return !std::isdigit(c); |
| }) == s.end(); |
| } |
| |
| /* Returns 'true' if the argument string is hex representation of a number. |
| */ |
| bool isHexNotation(std::string const& s) |
| { |
| return s.compare(0, 2, "0x") == 0 && s.size() > 2 && |
| s.find_first_not_of("0123456789abcdefABCDEF", 2) == |
| std::string::npos; |
| } |
| |
| /* Function to find current value of a 'knob' |
| * search is done using 'knob' attribute 'name' */ |
| bool getValue(std::string& variableName, int& value) |
| { |
| for (auto& knob : mKnobs) |
| { |
| if (knob.nameStr == variableName) |
| { |
| value = knob.currentVal; |
| return true; |
| } |
| } |
| |
| std::string error = |
| "Unable to find knob: " + variableName + " in knob list\n"; |
| phosphor::logging::log<phosphor::logging::level::ERR>(error.c_str()); |
| |
| return false; |
| } |
| |
| /* Get the expression enclosed within brackets, i.e., between '(' and ')' */ |
| bool getSubExpression(const std::string& expression, |
| std::string& subExpression, size_t& i) |
| { |
| int level = 1; |
| subExpression.clear(); |
| |
| for (; i < expression.length(); i++) |
| { |
| if (expression[i] == '(') |
| { |
| ++level; |
| } |
| else if (expression[i] == ')') |
| { |
| --level; |
| if (level == 0) |
| { |
| break; |
| } |
| } |
| |
| subExpression.push_back(expression[i]); |
| } |
| |
| if (!subExpression.empty()) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Function to handle operator '_LIST_' |
| * Convert a '_LIST_' expression to a normal expression |
| * Example "_LIST_ VariableA _EQU_ 0 1" is converted to "VariableA _EQU_ 0 |
| * OR VariableA _EQU_ 1" */ |
| bool getListExpression(const std::string& expression, |
| std::string& subExpression, size_t& i) |
| { |
| subExpression.clear(); |
| |
| int cnt = 0; |
| std::string variableStr; |
| std::string operatorStr; |
| |
| for (; i < expression.length(); i++) |
| { |
| if (expression[i] == '(') |
| { |
| return false; |
| } |
| else if (expression[i] == ')') |
| { |
| break; |
| } |
| else if (expression[i] == ' ') |
| { |
| /* whitespace */ |
| continue; |
| } |
| else |
| { |
| std::string word; |
| |
| /* Get the next word in expression string */ |
| while ((i < expression.length()) && (expression[i] != ' ')) |
| { |
| word.push_back(expression[i++]); |
| } |
| |
| if (word == "_OR_" || word == "OR" || word == "_AND_" || |
| word == "AND" || word == "NOT") |
| { |
| i = i - word.length(); |
| break; |
| } |
| |
| ++cnt; |
| |
| if (cnt == 1) |
| { |
| variableStr = word; |
| } |
| else if (cnt == 2) |
| { |
| operatorStr = word; |
| } |
| else |
| { |
| if (cnt > 3) |
| { |
| subExpression += " OR "; |
| } |
| |
| subExpression += "( "; |
| subExpression += variableStr; |
| subExpression += " "; |
| subExpression += operatorStr; |
| subExpression += " "; |
| subExpression += word; |
| subExpression += " )"; |
| } |
| } |
| } |
| |
| if (!subExpression.empty()) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Function to handle operator 'NOT' |
| * 1) Find the variable |
| * 2) apply NOT on the variable */ |
| bool getNotValue(const std::string& expression, size_t& i, int& value) |
| { |
| std::string word; |
| |
| for (; i < expression.length(); i++) |
| { |
| if (expression[i] == ' ') |
| { |
| /* whitespace */ |
| continue; |
| } |
| else |
| { |
| /* Get the next word in expression string */ |
| while ((i < expression.length()) && (expression[i] != ' ')) |
| { |
| word.push_back(expression[i++]); |
| } |
| |
| break; |
| } |
| } |
| |
| if (!word.empty()) |
| { |
| if (getValue(word, value)) |
| { |
| value = !value; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* 1) Pop one operator from operator stack, example 'OR' |
| * 2) Pop two variable from variable stack, example VarA and VarB |
| * 3) Push back result of 'VarA OR VarB' to variable stack |
| * 4) Repeat till operator stack is empty |
| * |
| * The last variable in variable stack is the output of the expression. */ |
| bool evaluateExprStack(std::stack<int>& values, |
| std::stack<knob::DepexOperators>& operators, |
| int& output) |
| { |
| if (values.size() != (operators.size() + 1)) |
| { |
| return false; |
| } |
| |
| while (!operators.empty()) |
| { |
| int b = values.top(); |
| values.pop(); |
| |
| int a = values.top(); |
| values.pop(); |
| |
| switch (operators.top()) |
| { |
| case knob::DepexOperators::OR: |
| values.emplace(a | b); |
| break; |
| |
| case knob::DepexOperators::AND: |
| values.emplace(a & b); |
| break; |
| |
| case knob::DepexOperators::EQU: |
| if (a == b) |
| { |
| values.emplace(1); |
| break; |
| } |
| |
| values.emplace(0); |
| break; |
| |
| case knob::DepexOperators::NEQ: |
| if (a != b) |
| { |
| values.emplace(1); |
| break; |
| } |
| |
| values.emplace(0); |
| break; |
| |
| case knob::DepexOperators::LTE: |
| if (a <= b) |
| { |
| values.emplace(1); |
| break; |
| } |
| |
| values.emplace(0); |
| break; |
| |
| case knob::DepexOperators::LT: |
| if (a < b) |
| { |
| values.emplace(1); |
| break; |
| } |
| |
| values.emplace(0); |
| break; |
| |
| case knob::DepexOperators::GTE: |
| if (a >= b) |
| { |
| values.emplace(1); |
| break; |
| } |
| |
| values.emplace(0); |
| break; |
| |
| case knob::DepexOperators::GT: |
| if (a > b) |
| { |
| values.emplace(1); |
| break; |
| } |
| |
| values.emplace(0); |
| break; |
| |
| case knob::DepexOperators::MODULO: |
| if (b == 0) |
| { |
| return false; |
| } |
| values.emplace(a % b); |
| break; |
| |
| default: |
| return false; |
| } |
| |
| operators.pop(); |
| } |
| |
| if (values.size() == 1) |
| { |
| output = values.top(); |
| values.pop(); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Evaluvate one 'depex' expression |
| * 1) Find a word in expression string |
| * 2) If word is a variable push to variable stack |
| * 3) If word is a operator push to operator stack |
| * |
| * Execute the stack at end to get the result of expression. */ |
| bool evaluateExpression(const std::string& expression, int& output) |
| { |
| if (expression.empty()) |
| { |
| return false; |
| } |
| |
| size_t i; |
| int value; |
| std::stack<int> values; |
| std::stack<knob::DepexOperators> operators; |
| std::string subExpression; |
| |
| for (i = 0; i < expression.length(); i++) |
| { |
| if (expression[i] == ' ') |
| { |
| /* whitespace */ |
| continue; |
| } |
| else |
| { |
| std::string word; |
| |
| /* Get the next word in expression string */ |
| while ((i < expression.length()) && (expression[i] != ' ')) |
| { |
| word.push_back(expression[i++]); |
| } |
| |
| if (word == "_OR_" || word == "OR") |
| { |
| /* OR and AND has more precedence than other operators |
| * To handle statements like "a != b or c != d" |
| * we need to execute, for above example, both '!=' before |
| * 'or' */ |
| if (!operators.empty()) |
| { |
| if (!evaluateExprStack(values, operators, value)) |
| { |
| return false; |
| } |
| |
| values.emplace(value); |
| } |
| |
| operators.emplace(knob::DepexOperators::OR); |
| } |
| else if (word == "_AND_" || word == "AND") |
| { |
| /* OR and AND has more precedence than other operators |
| * To handle statements like "a == b and c == d" |
| * we need to execute, for above example, both '==' before |
| * 'and' */ |
| if (!operators.empty()) |
| { |
| if (!evaluateExprStack(values, operators, value)) |
| { |
| return false; |
| } |
| |
| values.emplace(value); |
| } |
| |
| operators.emplace(knob::DepexOperators::AND); |
| } |
| else if (word == "_LTE_") |
| { |
| operators.emplace(knob::DepexOperators::LTE); |
| } |
| else if (word == "_LT_") |
| { |
| operators.emplace(knob::DepexOperators::LT); |
| } |
| else if (word == "_GTE_") |
| { |
| operators.emplace(knob::DepexOperators::GTE); |
| } |
| else if (word == "_GT_") |
| { |
| operators.emplace(knob::DepexOperators::GT); |
| } |
| else if (word == "_NEQ_") |
| { |
| operators.emplace(knob::DepexOperators::NEQ); |
| } |
| else if (word == "_EQU_") |
| { |
| operators.emplace(knob::DepexOperators::EQU); |
| } |
| else if (word == "%") |
| { |
| operators.emplace(knob::DepexOperators::MODULO); |
| } |
| else |
| { |
| /* Handle 'Sif(', 'Gif(', 'Dif(' and '(' |
| * by taking the inner/sub expression and evaluating it */ |
| if (word.back() == '(') |
| { |
| if (!getSubExpression(expression, subExpression, i)) |
| break; |
| |
| if (!evaluateExpression(subExpression, value)) |
| break; |
| } |
| else if (word == "_LIST_") |
| { |
| if (!getListExpression(expression, subExpression, i)) |
| break; |
| |
| --i; |
| |
| if (!evaluateExpression(subExpression, value)) |
| break; |
| } |
| else if (word == "NOT") |
| { |
| if (!getNotValue(expression, i, value)) |
| break; |
| } |
| else if (isNumber(word) || isHexNotation(word)) |
| { |
| try |
| { |
| value = std::stoi(word); |
| } |
| catch (const std::exception& ex) |
| { |
| phosphor::logging::log< |
| phosphor::logging::level::ERR>(ex.what()); |
| return false; |
| } |
| } |
| else |
| { |
| if (!getValue(word, value)) |
| break; |
| } |
| |
| values.emplace(value); |
| } |
| } |
| } |
| |
| if (i == expression.length()) |
| { |
| if (evaluateExprStack(values, operators, output)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private: |
| /* To store all 'knob's in 'biosknobs' */ |
| std::vector<knob::knob>& mKnobs; |
| |
| /* To store all bad 'depex' expression */ |
| std::vector<std::string> mError; |
| }; |
| |
| class Xml |
| { |
| public: |
| Xml(const char* filePath) : mDepex(std::make_unique<Depex>(mKnobs)) |
| { |
| if (!getKnobs(filePath)) |
| { |
| std::string error = |
| "Unable to get knobs in file: " + std::string(filePath); |
| throw std::runtime_error(error); |
| } |
| } |
| |
| /* Fill Bios table with all 'knob's which have output of 'depex' expression |
| * as 'true' */ |
| bool getBaseTable(bios::BiosBaseTableType& baseTable) |
| { |
| baseTable.clear(); |
| |
| for (auto& knob : mKnobs) |
| { |
| if (knob.depex) |
| { |
| std::string text = |
| "xyz.openbmc_project.BIOSConfig.Manager.BoundType.OneOf"; |
| bios::OptionTypeVector options; |
| |
| for (auto& option : knob.options) |
| { |
| options.emplace_back(text, option.value); |
| } |
| |
| bios::BiosBaseTableTypeEntry baseTableEntry = std::make_tuple( |
| "xyz.openbmc_project.BIOSConfig.Manager.AttributeType." |
| "Enumeration", |
| false, knob.nameStr, knob.descriptionStr, "./", |
| knob.currentValStr, knob.defaultStr, options); |
| |
| baseTable.emplace(knob.nameStr, baseTableEntry); |
| } |
| } |
| |
| if (!baseTable.empty()) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Execute all 'depex' expression */ |
| bool doDepexCompute() |
| { |
| mDepex->compute(); |
| |
| if (mDepex->getErrorCount()) |
| { |
| mDepex->printError(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private: |
| /* Get 'option' */ |
| void getOption(tinyxml2::XMLElement* pOption) |
| { |
| if (pOption) |
| { |
| std::string valueStr; |
| std::string textStr; |
| |
| if (pOption->Attribute("text")) |
| valueStr = pOption->Attribute("text"); |
| |
| if (pOption->Attribute("value")) |
| textStr = pOption->Attribute("value"); |
| |
| mKnobs.back().options.emplace_back(pOption->Attribute("text"), |
| pOption->Attribute("value")); |
| } |
| } |
| |
| /* Get 'options' */ |
| void getOptions(tinyxml2::XMLElement* pKnob) |
| { |
| uint16_t reserveCnt = 0; |
| |
| /* Get node options inside knob */ |
| tinyxml2::XMLElement* pOptions = pKnob->FirstChildElement("options"); |
| |
| if (pOptions) |
| { |
| for (tinyxml2::XMLElement* pOption = |
| pOptions->FirstChildElement("option"); |
| pOption; pOption = pOption->NextSiblingElement("option")) |
| { |
| ++reserveCnt; |
| } |
| |
| mKnobs.back().options.reserve(reserveCnt); |
| |
| /* Loop through all option inside options */ |
| for (tinyxml2::XMLElement* pOption = |
| pOptions->FirstChildElement("option"); |
| pOption; pOption = pOption->NextSiblingElement("option")) |
| { |
| getOption(pOption); |
| } |
| } |
| } |
| |
| /* Get 'knob' */ |
| void getKnob(tinyxml2::XMLElement* pKnob) |
| { |
| if (pKnob) |
| { |
| int currentVal = 0; |
| std::string nameStr; |
| std::string currentValStr; |
| std::string descriptionStr; |
| std::string defaultStr; |
| std::string depexStr; |
| std::string promptStr; |
| std::string setupTypeStr; |
| |
| if (!pKnob->Attribute("name") || !pKnob->Attribute("CurrentVal")) |
| { |
| return; |
| } |
| |
| nameStr = pKnob->Attribute("name"); |
| currentValStr = pKnob->Attribute("CurrentVal"); |
| std::stringstream ss; |
| ss << std::hex << currentValStr; |
| if (ss.good()) |
| { |
| ss >> currentVal; |
| } |
| else |
| { |
| std::string error = "Invalid hex value input " + currentValStr + |
| " for " + nameStr + "\n"; |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| error.c_str()); |
| return; |
| } |
| if (pKnob->Attribute("description")) |
| descriptionStr = pKnob->Attribute("description"); |
| |
| if (pKnob->Attribute("default")) |
| defaultStr = pKnob->Attribute("default"); |
| |
| if (pKnob->Attribute("depex")) |
| depexStr = pKnob->Attribute("depex"); |
| |
| if (pKnob->Attribute("prompt")) |
| promptStr = pKnob->Attribute("prompt"); |
| |
| if (pKnob->Attribute("setupType")) |
| setupTypeStr = pKnob->Attribute("setupType"); |
| |
| mKnobs.emplace_back(nameStr, currentValStr, currentVal, |
| descriptionStr, defaultStr, promptStr, depexStr, |
| setupTypeStr); |
| |
| getOptions(pKnob); |
| } |
| } |
| |
| /* Get 'biosknobs' */ |
| bool getKnobs(const char* biosXmlFilePath) |
| { |
| uint16_t reserveCnt = 0; |
| |
| mKnobs.clear(); |
| |
| tinyxml2::XMLDocument biosXml; |
| |
| /* Load the XML file into the Doc instance */ |
| biosXml.LoadFile(biosXmlFilePath); |
| |
| /* Get 'SYSTEM' */ |
| tinyxml2::XMLElement* pRootElement = biosXml.RootElement(); |
| if (pRootElement) |
| { |
| /* Get 'biosknobs' inside 'SYSTEM' */ |
| tinyxml2::XMLElement* pBiosknobs = |
| pRootElement->FirstChildElement("biosknobs"); |
| if (pBiosknobs) |
| { |
| for (tinyxml2::XMLElement* pKnob = |
| pBiosknobs->FirstChildElement("knob"); |
| pKnob; pKnob = pKnob->NextSiblingElement("knob")) |
| { |
| ++reserveCnt; |
| } |
| |
| /* reserve before emplace_back will avoids realloc(s) */ |
| mKnobs.reserve(reserveCnt); |
| |
| for (tinyxml2::XMLElement* pKnob = |
| pBiosknobs->FirstChildElement("knob"); |
| pKnob; pKnob = pKnob->NextSiblingElement("knob")) |
| { |
| getKnob(pKnob); |
| } |
| } |
| } |
| |
| if (!mKnobs.empty()) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private: |
| /* To store all 'knob's in 'biosknobs' */ |
| std::vector<knob::knob> mKnobs; |
| |
| /* Object of Depex class to compute 'depex' expression */ |
| std::unique_ptr<Depex> mDepex; |
| }; |
| } // namespace bios |