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 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 lg2::error("Failure to STOP the timer: {ERROR}", "ERROR", strerror(-r));
53 }
54
55 if (this->workQueue.empty())
56 {
57 // Just return a heartbeat in this case. A spurious SMS_ATN was
58 // asserted for the host (probably from a previous boot).
59 lg2::debug("Control Host work queue is empty!");
60
61 return std::make_pair(CMD_HEARTBEAT, 0x00);
62 }
63
64 // Pop the processed entry off the queue
65 auto command = this->workQueue.front();
66 this->workQueue.pop();
67
68 // IPMI command is the first element in pair
69 auto ipmiCmdData = std::get<0>(command);
70
71 // Now, call the user registered functions so that
72 // implementation specific CommandComplete signals
73 // can be sent. `true` indicating Success.
74 std::get<CallBack>(command)(ipmiCmdData, true);
75
76 // Check for another entry in the queue and kick it off
77 this->checkQueueAndAlertHost();
78
79 // Tuple of command and data
80 return ipmiCmdData;
81 }
82
83 // Called when initial timer goes off post sending SMS_ATN
hostTimeout()84 void Manager::hostTimeout()
85 {
86 lg2::error("Host control timeout hit!");
87
88 clearQueue();
89 }
90
clearQueue()91 void Manager::clearQueue()
92 {
93 // Dequeue all entries and send fail signal
94 while (!this->workQueue.empty())
95 {
96 auto command = this->workQueue.front();
97 this->workQueue.pop();
98
99 // IPMI command is the first element in pair
100 auto ipmiCmdData = std::get<0>(command);
101
102 // Call the implementation specific Command Failure.
103 // `false` indicating Failure
104 std::get<CallBack>(command)(ipmiCmdData, false);
105 }
106 }
107
108 // Called for alerting the host
checkQueueAndAlertHost()109 void Manager::checkQueueAndAlertHost()
110 {
111 if (this->workQueue.size() >= 1)
112 {
113 lg2::debug("Asserting SMS Attention");
114
115 std::string HOST_IPMI_SVC("org.openbmc.HostIpmi");
116 std::string IPMI_PATH("/org/openbmc/HostIpmi/1");
117 std::string IPMI_INTERFACE("org.openbmc.HostIpmi");
118
119 // Start the timer for this transaction
120 auto time = std::chrono::duration_cast<std::chrono::microseconds>(
121 std::chrono::seconds(IPMI_SMS_ATN_ACK_TIMEOUT_SECS));
122
123 auto r = timer.start(time);
124 if (r < 0)
125 {
126 lg2::error("Error starting timer for control host");
127 return;
128 }
129
130 auto method =
131 this->bus.new_method_call(HOST_IPMI_SVC.c_str(), IPMI_PATH.c_str(),
132 IPMI_INTERFACE.c_str(), "setAttention");
133
134 try
135 {
136 auto reply = this->bus.call(method);
137
138 lg2::debug("SMS Attention asserted");
139 }
140 catch (sdbusplus::exception_t& e)
141 {
142 lg2::error("Error when call setAttention method");
143 }
144 }
145 }
146
147 // Called by specific implementations that provide commands
execute(CommandHandler command)148 void Manager::execute(CommandHandler command)
149 {
150 lg2::debug("Pushing cmd on to queue, command: {COMMAND}", "COMMAND",
151 std::get<0>(command).first);
152
153 this->workQueue.emplace(command);
154
155 // Alert host if this is only command in queue otherwise host will
156 // be notified of next message after processing the current one
157 if (this->workQueue.size() == 1)
158 {
159 this->checkQueueAndAlertHost();
160 }
161 else
162 {
163 lg2::info("Command in process, no attention");
164 }
165
166 return;
167 }
168
clearQueueOnPowerOn(sdbusplus::message_t & msg)169 void Manager::clearQueueOnPowerOn(sdbusplus::message_t& msg)
170 {
171 namespace server = sdbusplus::server::xyz::openbmc_project::state;
172
173 ::ipmi::DbusInterface interface;
174 ::ipmi::PropertyMap properties;
175
176 msg.read(interface, properties);
177
178 if (properties.find(HOST_TRANS_PROP) == properties.end())
179 {
180 return;
181 }
182
183 auto& requestedState =
184 std::get<std::string>(properties.at(HOST_TRANS_PROP));
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