blob: f49d7c5fb13a7094c07206f0884f0981a3649705 [file] [log] [blame]
Dung Caofaf6a6a2020-12-28 04:44:45 +00001/*
2 * Copyright (c) 2021 Ampere Computing LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * This is a daemon that forwards requests and receive responses from SSIF over
17 * the D-Bus IPMI Interface.
18 */
19
20#include <getopt.h>
21#include <linux/ipmi_bmc.h>
22
23#include <CLI/CLI.hpp>
24#include <boost/algorithm/string/replace.hpp>
25#include <boost/asio.hpp>
26#include <phosphor-logging/log.hpp>
27#include <sdbusplus/asio/connection.hpp>
28#include <sdbusplus/asio/object_server.hpp>
29
30#include <iostream>
31
32using namespace phosphor::logging;
33
34static constexpr const char devBase[] = "/dev/ipmi-ssif-host";
35/* SSIF use IPMI SSIF channel */
36static constexpr const char* ssifBus =
37 "xyz.openbmc_project.Ipmi.Channel.ipmi_ssif";
38static constexpr const char* ssifObj =
39 "/xyz/openbmc_project/Ipmi/Channel/ipmi_ssif";
40
41class SsifChannel
42{
43 public:
44 static constexpr size_t ssifMessageSize = 4096;
45 static constexpr uint8_t netFnShift = 2;
46 static constexpr uint8_t lunMask = (1 << netFnShift) - 1;
47
48 SsifChannel(std::shared_ptr<boost::asio::io_context>& io,
49 std::shared_ptr<sdbusplus::asio::connection>& bus,
50 const std::string& channel, bool verbose);
51 bool initOK() const
52 {
53 return !!dev;
54 }
55 void channelAbort(const char* msg, const boost::system::error_code& ec);
56 void async_read();
57 void processMessage(const boost::system::error_code& ecRd, size_t rlen);
58
59 protected:
60 std::array<uint8_t, ssifMessageSize> xferBuffer;
61 std::shared_ptr<boost::asio::io_context> io;
62 std::shared_ptr<sdbusplus::asio::connection> bus;
63 std::shared_ptr<sdbusplus::asio::object_server> server;
64 std::unique_ptr<boost::asio::posix::stream_descriptor> dev = nullptr;
65 bool verbose;
66};
67
68SsifChannel::SsifChannel(std::shared_ptr<boost::asio::io_context>& io,
69 std::shared_ptr<sdbusplus::asio::connection>& bus,
70 const std::string& device, bool verbose) :
71 io(io),
72 bus(bus), verbose(verbose)
73{
74 std::string devName = devBase;
75 if (!device.empty())
76 {
77 devName = device;
78 }
79
80 // open device
81 int fd = open(devName.c_str(), O_RDWR | O_NONBLOCK);
82 if (fd < 0)
83 {
84 log<level::ERR>("Couldn't open SSIF driver with flags O_RDWR",
85 entry("FILENAME=%s", devName.c_str()),
86 entry("ERROR=%s", strerror(errno)));
87 return;
88 }
89 else
90 {
91 dev = std::make_unique<boost::asio::posix::stream_descriptor>(*io,
92 fd);
93 }
94
95 async_read();
96 // register interfaces...
97 server = std::make_shared<sdbusplus::asio::object_server>(bus);
98 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
99 server->add_interface(ssifObj, ssifBus);
100 iface->initialize();
101}
102
103void SsifChannel::channelAbort(const char* msg,
104 const boost::system::error_code& ec)
105{
106 log<level::ERR>(msg, entry("ERROR=%s", ec.message().c_str()));
107 // bail; maybe a restart from systemd can clear the error
108 io->stop();
109}
110
111void SsifChannel::async_read()
112{
113 boost::asio::async_read(*dev,
114 boost::asio::buffer(xferBuffer, xferBuffer.size()),
115 boost::asio::transfer_at_least(2),
116 [this](const boost::system::error_code& ec,
117 size_t rlen) {
118 processMessage(ec, rlen);
119 });
120}
121
122void SsifChannel::processMessage(const boost::system::error_code& ecRd,
123 size_t rlen)
124{
125 if (ecRd || rlen < 2)
126 {
127 channelAbort("Failed to read req msg", ecRd);
128 return;
129 }
130 async_read();
131
132 auto rawIter = xferBuffer.cbegin();
133 auto rawEnd = rawIter + rlen;
134 uint8_t len = rawIter[0] + 1;
135 uint8_t netfn = rawIter[1] >> netFnShift;
136 uint8_t lun = rawIter[1] & lunMask;
137 uint8_t cmd = rawIter[2];
138 if (verbose)
139 {
140 log<level::INFO>("Read req msg", entry("NETFN=0x%02x", netfn),
141 entry("LUN=0x%02x", lun),
142 entry("CMD=0x%02x", cmd));
143 }
144 // copy out payload
145 std::vector<uint8_t> data(&rawIter[3], rawEnd);
146 // non-session bridges still need to pass an empty options map
147 std::map<std::string, std::variant<int>> options;
148 // the response is a tuple because dbus can only return a single value
149 using IpmiDbusRspType = std::tuple<uint8_t, uint8_t, uint8_t, uint8_t,
150 std::vector<uint8_t>>;
151 static constexpr const char ipmiQueueService[] =
152 "xyz.openbmc_project.Ipmi.Host";
153 static constexpr const char ipmiQueuePath[] =
154 "/xyz/openbmc_project/Ipmi";
155 static constexpr const char ipmiQueueIntf[] =
156 "xyz.openbmc_project.Ipmi.Server";
157 static constexpr const char ipmiQueueMethod[] = "execute";
158 bus->async_method_call(
159 [this, netfnCap{netfn}, lunCap{lun},
160 cmdCap{cmd}](const boost::system::error_code& ec,
161 const IpmiDbusRspType& response) {
162 std::vector<uint8_t> rsp;
163 const auto& [netfn, lun, cmd, cc, payload] = response;
164 if (ec)
165 {
166 log<level::ERR>(
167 "ssif<->ipmid bus error:", entry("NETFN=0x%02x", netfn),
168 entry("LUN=0x%02x", lun), entry("CMD=0x%02x", cmd),
169 entry("ERROR=%s", ec.message().c_str()));
170 // send unspecified error for a D-Bus error
171 constexpr uint8_t ccResponseNotAvailable = 0xce;
172 rsp.resize(sizeof(uint8_t) + sizeof(netfn) + sizeof(cmd) +
173 sizeof(cc));
174 rsp[0] = 3;
175 rsp[1] =
176 ((netfnCap + 1) << netFnShift) | (lunCap & lunMask);
177 rsp[2] = cmdCap;
178 rsp[3] = ccResponseNotAvailable;
179 }
180 else
181 {
182 rsp.resize(sizeof(uint8_t) + sizeof(netfn) + sizeof(cmd) +
183 sizeof(cc) + payload.size());
184
185 // write the response
186 auto rspIter = rsp.begin();
187 rspIter[0] = payload.size() + 3;
188 rspIter[1] = (netfn << netFnShift) | (lun & lunMask);
189 rspIter[2] = cmd;
190 rspIter[3] = cc;
191 if (payload.size())
192 {
193 std::copy(payload.cbegin(), payload.cend(),
194 &rspIter[4]);
195 }
196 }
197 if (verbose)
198 {
199 log<level::INFO>(
200 "Send rsp msg", entry("NETFN=0x%02x", netfn),
201 entry("LUN=0x%02x", lun), entry("CMD=0x%02x", cmd),
202 entry("CC=0x%02x", cc));
203 }
204 boost::system::error_code ecWr;
205 size_t wlen =
206 boost::asio::write(*dev, boost::asio::buffer(rsp), ecWr);
207 if (ecWr || wlen != rsp.size())
208 {
209 log<level::ERR>(
210 "Failed to send rsp msg", entry("SIZE=%d", wlen),
211 entry("EXPECT=%d", rsp.size()),
212 entry("ERROR=%s", ecWr.message().c_str()),
213 entry("NETFN=0x%02x", netfn), entry("LUN=0x%02x", lun),
214 entry("CMD=0x%02x", cmd), entry("CC=0x%02x", cc));
215 }
216 },
217 ipmiQueueService, ipmiQueuePath, ipmiQueueIntf, ipmiQueueMethod,
218 netfn, lun, cmd, data, options);
219}
220
221
222int main(int argc, char* argv[])
223{
224 CLI::App app("SSIF IPMI bridge");
225 std::string device;
226 app.add_option("-d,--device", device,
227 "use <DEVICE> file. Default is /dev/ipmi-ssif-host");
228 bool verbose = false;
229 app.add_option("-v,--verbose", verbose, "print more verbose output");
230 CLI11_PARSE(app, argc, argv);
231
232 // Connect to system bus
233 auto io = std::make_shared<boost::asio::io_context>();
234 sd_bus* dbus;
235 sd_bus_default_system(&dbus);
236 auto bus = std::make_shared<sdbusplus::asio::connection>(*io, dbus);
237 bus->request_name(ssifBus);
238 // Create the SSIF channel, listening on D-Bus and on the SSIF device
239 SsifChannel ssifchannel(io, bus, device, verbose);
240 if (!ssifchannel.initOK())
241 {
242 return EXIT_FAILURE;
243 }
244 io->run();
245
246 return 0;
247}