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
Manager(sdbusplus::bus_t & bus)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
getNextCommand()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
hostTimeout()85 void Manager::hostTimeout()
86 {
87 log<level::ERR>("Host control timeout hit!");
88
89 clearQueue();
90 }
91
clearQueue()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
checkQueueAndAlertHost()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
execute(CommandHandler command)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
clearQueueOnPowerOn(sdbusplus::message_t & msg)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