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