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