1 #include "config.h" 2 3 #include "host-cmd-manager.hpp" 4 5 #include "systemintfcmds.hpp" 6 7 #include <chrono> 8 #include <ipmid/utils.hpp> 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/log.hpp> 11 #include <sdbusplus/message/types.hpp> 12 #include <sdbusplus/timer.hpp> 13 #include <xyz/openbmc_project/Common/error.hpp> 14 #include <xyz/openbmc_project/State/Host/server.hpp> 15 16 namespace phosphor 17 { 18 namespace host 19 { 20 namespace command 21 { 22 23 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper"; 24 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper"; 25 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper"; 26 constexpr auto HOST_STATE_PATH = "/xyz/openbmc_project/state/host0"; 27 constexpr auto HOST_STATE_INTERFACE = "xyz.openbmc_project.State.Host"; 28 constexpr auto HOST_TRANS_PROP = "RequestedHostTransition"; 29 30 // For throwing exceptions 31 using namespace phosphor::logging; 32 using InternalFailure = 33 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 34 35 namespace sdbusRule = sdbusplus::bus::match::rules; 36 namespace variant_ns = sdbusplus::message::variant_ns; 37 38 Manager::Manager(sdbusplus::bus::bus& bus) : 39 bus(bus), timer(std::bind(&Manager::hostTimeout, this)), 40 hostTransitionMatch( 41 bus, 42 sdbusRule::propertiesChanged(HOST_STATE_PATH, HOST_STATE_INTERFACE), 43 std::bind(&Manager::clearQueueOnPowerOn, this, std::placeholders::_1)) 44 { 45 // Nothing to do here. 46 } 47 48 // Called as part of READ_MSG_DATA command 49 IpmiCmdData Manager::getNextCommand() 50 { 51 // Stop the timer. Don't have to Err failure doing so. 52 auto r = timer.stop(); 53 if (r < 0) 54 { 55 log<level::ERR>("Failure to STOP the timer", 56 entry("ERROR=%s", strerror(-r))); 57 } 58 59 if (this->workQueue.empty()) 60 { 61 // Just return a heartbeat in this case. A spurious SMS_ATN was 62 // asserted for the host (probably from a previous boot). 63 log<level::DEBUG>("Control Host work queue is empty!"); 64 65 return std::make_pair(CMD_HEARTBEAT, 0x00); 66 } 67 68 // Pop the processed entry off the queue 69 auto command = this->workQueue.front(); 70 this->workQueue.pop(); 71 72 // IPMI command is the first element in pair 73 auto ipmiCmdData = std::get<0>(command); 74 75 // Now, call the user registered functions so that 76 // implementation specific CommandComplete signals 77 // can be sent. `true` indicating Success. 78 std::get<CallBack>(command)(ipmiCmdData, true); 79 80 // Check for another entry in the queue and kick it off 81 this->checkQueueAndAlertHost(); 82 83 // Tuple of command and data 84 return ipmiCmdData; 85 } 86 87 // Called when initial timer goes off post sending SMS_ATN 88 void Manager::hostTimeout() 89 { 90 log<level::ERR>("Host control timeout hit!"); 91 92 clearQueue(); 93 } 94 95 void Manager::clearQueue() 96 { 97 // Dequeue all entries and send fail signal 98 while (!this->workQueue.empty()) 99 { 100 auto command = this->workQueue.front(); 101 this->workQueue.pop(); 102 103 // IPMI command is the first element in pair 104 auto ipmiCmdData = std::get<0>(command); 105 106 // Call the implementation specific Command Failure. 107 // `false` indicating Failure 108 std::get<CallBack>(command)(ipmiCmdData, false); 109 } 110 } 111 112 // Called for alerting the host 113 void Manager::checkQueueAndAlertHost() 114 { 115 if (this->workQueue.size() >= 1) 116 { 117 log<level::DEBUG>("Asserting SMS Attention"); 118 119 std::string IPMI_PATH("/org/openbmc/HostIpmi/1"); 120 std::string IPMI_INTERFACE("org.openbmc.HostIpmi"); 121 122 auto host = ::ipmi::getService(this->bus, IPMI_INTERFACE, IPMI_PATH); 123 124 // Start the timer for this transaction 125 auto time = std::chrono::duration_cast<std::chrono::microseconds>( 126 std::chrono::seconds(IPMI_SMS_ATN_ACK_TIMEOUT_SECS)); 127 128 auto r = timer.start(time); 129 if (r < 0) 130 { 131 log<level::ERR>("Error starting timer for control host"); 132 return; 133 } 134 135 auto method = 136 this->bus.new_method_call(host.c_str(), IPMI_PATH.c_str(), 137 IPMI_INTERFACE.c_str(), "setAttention"); 138 auto reply = this->bus.call(method); 139 140 if (reply.is_method_error()) 141 { 142 log<level::ERR>("Error in setting SMS attention"); 143 elog<InternalFailure>(); 144 } 145 log<level::DEBUG>("SMS Attention asserted"); 146 } 147 } 148 149 // Called by specific implementations that provide commands 150 void Manager::execute(CommandHandler command) 151 { 152 log<level::DEBUG>("Pushing cmd on to queue", 153 entry("COMMAND=%d", std::get<0>(command).first)); 154 155 this->workQueue.emplace(command); 156 157 // Alert host if this is only command in queue otherwise host will 158 // be notified of next message after processing the current one 159 if (this->workQueue.size() == 1) 160 { 161 this->checkQueueAndAlertHost(); 162 } 163 else 164 { 165 log<level::INFO>("Command in process, no attention"); 166 } 167 168 return; 169 } 170 171 void Manager::clearQueueOnPowerOn(sdbusplus::message::message& msg) 172 { 173 namespace server = sdbusplus::xyz::openbmc_project::State::server; 174 175 ::ipmi::DbusInterface interface; 176 ::ipmi::PropertyMap properties; 177 178 msg.read(interface, properties); 179 180 if (properties.find(HOST_TRANS_PROP) == properties.end()) 181 { 182 return; 183 } 184 185 auto& requestedState = 186 variant_ns::get<std::string>(properties.at(HOST_TRANS_PROP)); 187 188 if (server::Host::convertTransitionFromString(requestedState) == 189 server::Host::Transition::On) 190 { 191 clearQueue(); 192 } 193 } 194 195 } // namespace command 196 } // namespace host 197 } // namespace phosphor 198