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