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 
37 Manager::Manager(sdbusplus::bus::bus& bus) :
38     bus(bus), timer(std::bind(&Manager::hostTimeout, this)),
39     hostTransitionMatch(
40         bus,
41         sdbusRule::propertiesChanged(HOST_STATE_PATH, HOST_STATE_INTERFACE),
42         std::bind(&Manager::clearQueueOnPowerOn, this, std::placeholders::_1))
43 {
44     // Nothing to do here.
45 }
46 
47 // Called as part of READ_MSG_DATA command
48 IpmiCmdData Manager::getNextCommand()
49 {
50     // Stop the timer. Don't have to Err failure doing so.
51     auto r = timer.stop();
52     if (r < 0)
53     {
54         log<level::ERR>("Failure to STOP the timer",
55                         entry("ERROR=%s", strerror(-r)));
56     }
57 
58     if (this->workQueue.empty())
59     {
60         // Just return a heartbeat in this case.  A spurious SMS_ATN was
61         // asserted for the host (probably from a previous boot).
62         log<level::DEBUG>("Control Host work queue is empty!");
63 
64         return std::make_pair(CMD_HEARTBEAT, 0x00);
65     }
66 
67     // Pop the processed entry off the queue
68     auto command = this->workQueue.front();
69     this->workQueue.pop();
70 
71     // IPMI command is the first element in pair
72     auto ipmiCmdData = std::get<0>(command);
73 
74     // Now, call the user registered functions so that
75     // implementation specific CommandComplete signals
76     // can be sent. `true` indicating Success.
77     std::get<CallBack>(command)(ipmiCmdData, true);
78 
79     // Check for another entry in the queue and kick it off
80     this->checkQueueAndAlertHost();
81 
82     // Tuple of command and data
83     return ipmiCmdData;
84 }
85 
86 // Called when initial timer goes off post sending SMS_ATN
87 void Manager::hostTimeout()
88 {
89     log<level::ERR>("Host control timeout hit!");
90 
91     clearQueue();
92 }
93 
94 void Manager::clearQueue()
95 {
96     // Dequeue all entries and send fail signal
97     while (!this->workQueue.empty())
98     {
99         auto command = this->workQueue.front();
100         this->workQueue.pop();
101 
102         // IPMI command is the first element in pair
103         auto ipmiCmdData = std::get<0>(command);
104 
105         // Call the implementation specific Command Failure.
106         // `false` indicating Failure
107         std::get<CallBack>(command)(ipmiCmdData, false);
108     }
109 }
110 
111 // Called for alerting the host
112 void Manager::checkQueueAndAlertHost()
113 {
114     if (this->workQueue.size() >= 1)
115     {
116         log<level::DEBUG>("Asserting SMS Attention");
117 
118         std::string IPMI_PATH("/org/openbmc/HostIpmi/1");
119         std::string IPMI_INTERFACE("org.openbmc.HostIpmi");
120 
121         auto host = ::ipmi::getService(this->bus, IPMI_INTERFACE, IPMI_PATH);
122 
123         // Start the timer for this transaction
124         auto time = std::chrono::duration_cast<std::chrono::microseconds>(
125             std::chrono::seconds(IPMI_SMS_ATN_ACK_TIMEOUT_SECS));
126 
127         auto r = timer.start(time);
128         if (r < 0)
129         {
130             log<level::ERR>("Error starting timer for control host");
131             return;
132         }
133 
134         auto method =
135             this->bus.new_method_call(host.c_str(), IPMI_PATH.c_str(),
136                                       IPMI_INTERFACE.c_str(), "setAttention");
137         auto reply = this->bus.call(method);
138 
139         if (reply.is_method_error())
140         {
141             log<level::ERR>("Error in setting SMS attention");
142             elog<InternalFailure>();
143         }
144         log<level::DEBUG>("SMS Attention asserted");
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::message& msg)
171 {
172     namespace server = sdbusplus::xyz::openbmc_project::State::server;
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