blob: a37cf5fb2855e0a8eb5c204edb7a2e38436f0e53 [file] [log] [blame]
Vernon Mauery9ce5a9a2019-03-12 13:59:25 -07001/* Copyright 2017 - 2019 Intel
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <getopt.h>
17#include <linux/ipmi_bmc.h>
18
19#include <CLI/CLI.hpp>
20#include <boost/asio.hpp>
21#include <iostream>
22#include <phosphor-logging/log.hpp>
23#include <sdbusplus/asio/connection.hpp>
24#include <sdbusplus/asio/object_server.hpp>
25
26using namespace phosphor::logging;
27
28namespace
29{
30namespace io_control
31{
32struct ClearSmsAttention
33{
34 // Get the name of the IO control command.
35 int name() const
36 {
37 return static_cast<int>(IPMI_BMC_IOCTL_CLEAR_SMS_ATN);
38 }
39
40 // Get the address of the command data.
41 boost::asio::detail::ioctl_arg_type* data()
42 {
43 return nullptr;
44 }
45};
46
47struct SetSmsAttention
48{
49 // Get the name of the IO control command.
50 int name() const
51 {
52 return static_cast<int>(IPMI_BMC_IOCTL_SET_SMS_ATN);
53 }
54
55 // Get the address of the command data.
56 boost::asio::detail::ioctl_arg_type* data()
57 {
58 return nullptr;
59 }
60};
61
62struct ForceAbort
63{
64 // Get the name of the IO control command.
65 int name() const
66 {
67 return static_cast<int>(IPMI_BMC_IOCTL_FORCE_ABORT);
68 }
69
70 // Get the address of the command data.
71 boost::asio::detail::ioctl_arg_type* data()
72 {
73 return nullptr;
74 }
75};
76} // namespace io_control
77
78class SmsChannel
79{
80 public:
81 static constexpr size_t kcsMessageSize = 256;
82 static constexpr uint8_t netFnShift = 2;
83 static constexpr uint8_t lunMask = (1 << netFnShift) - 1;
84
85 SmsChannel(std::shared_ptr<boost::asio::io_context>& io,
86 std::shared_ptr<sdbusplus::asio::connection>& bus,
87 const std::string& channel, bool verbose) :
88 io(io),
89 bus(bus), verbose(verbose)
90 {
91 static constexpr const char devBase[] = "/dev/";
92 std::string devName = devBase + channel;
93 // open device
94 int fd = open(devName.c_str(), O_RDWR | O_NONBLOCK);
95 if (fd < 0)
96 {
97 log<level::ERR>("Couldn't open SMS channel O_RDWR",
98 entry("FILENAME=%s", devName.c_str()),
99 entry("ERROR=%s", strerror(errno)));
100 return;
101 }
102 else
103 {
104 dev = std::make_unique<boost::asio::posix::stream_descriptor>(*io,
105 fd);
106 }
107
108 async_read();
109
110 // register interfaces...
111 server = std::make_shared<sdbusplus::asio::object_server>(bus);
112
113 static constexpr const char pathBase[] =
114 "/xyz/openbmc_project/Ipmi/Channel/";
115 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
116 server->add_interface(pathBase + channel,
117 "xyz.openbmc_project.Ipmi.Channel.SMS");
118 iface->register_method("setAttention",
119 [this]() { return setAttention(); });
120 iface->register_method("clearAttention",
121 [this]() { return clearAttention(); });
122 iface->register_method("forceAbort", [this]() { return forceAbort(); });
123 iface->initialize();
124 }
125
126 bool initOK() const
127 {
128 return !!dev;
129 }
130
131 void channelAbort(const char* msg, const boost::system::error_code& ec)
132 {
133 log<level::ERR>(msg, entry("ERROR=%s", ec.message().c_str()));
134 // bail; maybe a restart from systemd can clear the error
135 io->stop();
136 }
137
138 void async_read()
139 {
140 boost::asio::async_read(
141 *dev, boost::asio::buffer(xferBuffer, xferBuffer.size()),
142 boost::asio::transfer_at_least(2),
143 [this](const boost::system::error_code& ec, size_t rlen) {
144 processMessage(ec, rlen);
145 });
146 }
147
148 void processMessage(const boost::system::error_code& ecRd, size_t rlen)
149 {
150 if (ecRd || rlen < 2)
151 {
152 channelAbort("Failed to read req msg", ecRd);
153 return;
154 }
155
156 async_read();
157
158 // trim raw to be only bytes returned from read
159 // separate netfn/lun/cmd from payload
160 auto rawIter = xferBuffer.cbegin();
161 auto rawEnd = rawIter + rlen;
162 uint8_t netfn = *rawIter >> netFnShift;
163 uint8_t lun = *rawIter++ & lunMask;
164 uint8_t cmd = *rawIter++;
165 if (verbose)
166 {
167 log<level::INFO>("Read req msg", entry("NETFN=0x%02x", netfn),
168 entry("LUN=0x%02x", lun),
169 entry("CMD=0x%02x", cmd));
170 }
171 // copy out payload
172 std::vector<uint8_t> data(rawIter, rawEnd);
173 // non-session bridges still need to pass an empty options map
174 std::map<std::string, sdbusplus::message::variant<int>> options;
175 // the response is a tuple because dbus can only return a single value
176 using IpmiDbusRspType = std::tuple<uint8_t, uint8_t, uint8_t, uint8_t,
177 std::vector<uint8_t>>;
178 static constexpr const char ipmiQueueService[] =
179 "xyz.openbmc_project.Ipmi.Host";
180 static constexpr const char ipmiQueuePath[] =
181 "/xyz/openbmc_project/Ipmi";
182 static constexpr const char ipmiQueueIntf[] =
183 "xyz.openbmc_project.Ipmi.Server";
184 static constexpr const char ipmiQueueMethod[] = "execute";
185 bus->async_method_call(
186 [this, netfnCap{netfn}, lunCap{lun},
187 cmdCap{cmd}](const boost::system::error_code& ec,
188 const IpmiDbusRspType& response) {
189 std::vector<uint8_t> rsp;
190 const auto& [netfn, lun, cmd, cc, payload] = response;
191 if (ec)
192 {
193 log<level::ERR>(
194 "kcs<->ipmid bus error:", entry("NETFN=0x%02x", netfn),
195 entry("LUN=0x%02x", lun), entry("CMD=0x%02x", cmd),
196 entry("ERROR=%s", ec.message().c_str()));
197 // send unspecified error for a D-Bus error
198 constexpr uint8_t ccResponseNotAvailable = 0xce;
199 rsp.resize(sizeof(netfn) + sizeof(cmd) + sizeof(cc));
200 rsp[0] =
201 ((netfnCap + 1) << netFnShift) | (lunCap & lunMask);
202 rsp[1] = cmdCap;
203 rsp[2] = ccResponseNotAvailable;
204 }
205 else
206 {
207 rsp.resize(sizeof(netfn) + sizeof(cmd) + sizeof(cc) +
208 payload.size());
209
210 // write the response
211 auto rspIter = rsp.begin();
212 *rspIter++ = (netfn << netFnShift) | (lun & lunMask);
213 *rspIter++ = cmd;
214 *rspIter++ = cc;
215 if (payload.size())
216 {
217 std::copy(payload.cbegin(), payload.cend(), rspIter);
218 }
219 }
220 if (verbose)
221 {
222 log<level::INFO>(
223 "Send rsp msg", entry("NETFN=0x%02x", netfn),
224 entry("LUN=0x%02x", lun), entry("CMD=0x%02x", cmd),
225 entry("CC=0x%02x", cc));
226 }
227 boost::system::error_code ecWr;
228 size_t wlen =
229 boost::asio::write(*dev, boost::asio::buffer(rsp), ecWr);
230 if (ecWr || wlen != rsp.size())
231 {
232 log<level::ERR>(
233 "Failed to send rsp msg", entry("SIZE=%d", wlen),
234 entry("EXPECT=%d", rsp.size()),
235 entry("ERROR=%s", ecWr.message().c_str()),
236 entry("NETFN=0x%02x", netfn), entry("LUN=0x%02x", lun),
237 entry("CMD=0x%02x", cmd), entry("CC=0x%02x", cc));
238 }
239 },
240 ipmiQueueService, ipmiQueuePath, ipmiQueueIntf, ipmiQueueMethod,
241 netfn, lun, cmd, data, options);
242 }
243
244 int64_t setAttention()
245 {
246 if (verbose)
247 {
248 log<level::INFO>("Sending SET_SMS_ATTENTION");
249 }
250 io_control::SetSmsAttention command;
251 boost::system::error_code ec;
252 dev->io_control(command, ec);
253 if (ec)
254 {
255 log<level::ERR>("Couldn't SET_SMS_ATTENTION",
256 entry("ERROR=%s", ec.message().c_str()));
257 return ec.value();
258 }
259 return 0;
260 }
261
262 int64_t clearAttention()
263 {
264 if (verbose)
265 {
266 log<level::INFO>("Sending CLEAR_SMS_ATTENTION");
267 }
268 io_control::ClearSmsAttention command;
269 boost::system::error_code ec;
270 dev->io_control(command, ec);
271 if (ec)
272 {
273 log<level::ERR>("Couldn't CLEAR_SMS_ATTENTION",
274 entry("ERROR=%s", ec.message().c_str()));
275 return ec.value();
276 }
277 return 0;
278 }
279
280 int64_t forceAbort()
281 {
282 if (verbose)
283 {
284 log<level::INFO>("Sending FORCE_ABORT");
285 }
286 io_control::ForceAbort command;
287 boost::system::error_code ec;
288 dev->io_control(command, ec);
289 if (ec)
290 {
291 log<level::ERR>("Couldn't FORCE_ABORT",
292 entry("ERROR=%s", ec.message().c_str()));
293 return ec.value();
294 }
295 return 0;
296 }
297
298 protected:
299 std::array<uint8_t, kcsMessageSize> xferBuffer;
300 std::shared_ptr<boost::asio::io_context> io;
301 std::shared_ptr<sdbusplus::asio::connection> bus;
302 std::shared_ptr<sdbusplus::asio::object_server> server;
303 std::unique_ptr<boost::asio::posix::stream_descriptor> dev = nullptr;
304 bool verbose;
305};
306
307} // namespace
308
Vernon Mauery9ce5a9a2019-03-12 13:59:25 -0700309int main(int argc, char* argv[])
310{
311 CLI::App app("KCS IPMI bridge");
312 std::string channel;
Vernon Mauery2cdc4952019-04-12 11:10:18 -0700313 app.add_option("-c,--channel", channel, "channel name. e.g., ipmi-kcs3")
314 ->required();
Vernon Mauery9ce5a9a2019-03-12 13:59:25 -0700315 bool verbose = false;
316 app.add_option("-v,--verbose", verbose, "print more verbose output");
Vernon Mauery9ce5a9a2019-03-12 13:59:25 -0700317 CLI11_PARSE(app, argc, argv);
318
Vernon Mauery9ce5a9a2019-03-12 13:59:25 -0700319 // Connect to system bus
320 auto io = std::make_shared<boost::asio::io_context>();
321 sd_bus* dbus;
322 sd_bus_default_system(&dbus);
323 auto bus = std::make_shared<sdbusplus::asio::connection>(*io, dbus);
324
325 // Create the channel, listening on D-Bus and on the SMS device
326 SmsChannel smsChannel(io, bus, channel, verbose);
327
328 if (!smsChannel.initOK())
329 {
330 return EXIT_FAILURE;
331 }
332
333 static constexpr const char busBase[] = "xyz.openbmc_project.Ipmi.Channel.";
334 std::string busName(busBase + channel);
335 bus->request_name(busName.c_str());
336
337 io->run();
338
339 return 0;
340}