build: Make a src subdirectory
This will allow us to organize our c++ source separately from our
top-level metadata.
Change-Id: I3141fa63bf9be2ae1b1efee7a6f95ba53a569f5d
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/kcsbridged.cpp b/src/kcsbridged.cpp
new file mode 100644
index 0000000..00914b5
--- /dev/null
+++ b/src/kcsbridged.cpp
@@ -0,0 +1,342 @@
+/* Copyright 2017 - 2019 Intel
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <linux/ipmi_bmc.h>
+
+#include <CLI/CLI.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/asio.hpp>
+#include <iostream>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+using namespace phosphor::logging;
+
+namespace
+{
+namespace io_control
+{
+struct ClearSmsAttention
+{
+ // Get the name of the IO control command.
+ int name() const
+ {
+ return static_cast<int>(IPMI_BMC_IOCTL_CLEAR_SMS_ATN);
+ }
+
+ // Get the address of the command data.
+ boost::asio::detail::ioctl_arg_type* data()
+ {
+ return nullptr;
+ }
+};
+
+struct SetSmsAttention
+{
+ // Get the name of the IO control command.
+ int name() const
+ {
+ return static_cast<int>(IPMI_BMC_IOCTL_SET_SMS_ATN);
+ }
+
+ // Get the address of the command data.
+ boost::asio::detail::ioctl_arg_type* data()
+ {
+ return nullptr;
+ }
+};
+
+struct ForceAbort
+{
+ // Get the name of the IO control command.
+ int name() const
+ {
+ return static_cast<int>(IPMI_BMC_IOCTL_FORCE_ABORT);
+ }
+
+ // Get the address of the command data.
+ boost::asio::detail::ioctl_arg_type* data()
+ {
+ return nullptr;
+ }
+};
+} // namespace io_control
+
+class SmsChannel
+{
+ public:
+ static constexpr size_t kcsMessageSize = 4096;
+ static constexpr uint8_t netFnShift = 2;
+ static constexpr uint8_t lunMask = (1 << netFnShift) - 1;
+
+ SmsChannel(std::shared_ptr<boost::asio::io_context>& io,
+ std::shared_ptr<sdbusplus::asio::connection>& bus,
+ const std::string& channel, bool verbose) :
+ io(io),
+ bus(bus), verbose(verbose)
+ {
+ static constexpr const char devBase[] = "/dev/";
+ std::string devName = devBase + channel;
+ // open device
+ int fd = open(devName.c_str(), O_RDWR | O_NONBLOCK);
+ if (fd < 0)
+ {
+ log<level::ERR>("Couldn't open SMS channel O_RDWR",
+ entry("FILENAME=%s", devName.c_str()),
+ entry("ERROR=%s", strerror(errno)));
+ return;
+ }
+ else
+ {
+ dev = std::make_unique<boost::asio::posix::stream_descriptor>(*io,
+ fd);
+ }
+
+ async_read();
+
+ // register interfaces...
+ server = std::make_shared<sdbusplus::asio::object_server>(bus);
+
+ static constexpr const char pathBase[] =
+ "/xyz/openbmc_project/Ipmi/Channel/";
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+ server->add_interface(
+ pathBase + boost::replace_all_copy(channel, "-", "_"),
+ "xyz.openbmc_project.Ipmi.Channel.SMS");
+ iface->register_method("setAttention",
+ [this]() { return setAttention(); });
+ iface->register_method("clearAttention",
+ [this]() { return clearAttention(); });
+ iface->register_method("forceAbort", [this]() { return forceAbort(); });
+ iface->initialize();
+ }
+
+ bool initOK() const
+ {
+ return !!dev;
+ }
+
+ void channelAbort(const char* msg, const boost::system::error_code& ec)
+ {
+ log<level::ERR>(msg, entry("ERROR=%s", ec.message().c_str()));
+ // bail; maybe a restart from systemd can clear the error
+ io->stop();
+ }
+
+ void async_read()
+ {
+ boost::asio::async_read(
+ *dev, boost::asio::buffer(xferBuffer, xferBuffer.size()),
+ boost::asio::transfer_at_least(2),
+ [this](const boost::system::error_code& ec, size_t rlen) {
+ processMessage(ec, rlen);
+ });
+ }
+
+ void processMessage(const boost::system::error_code& ecRd, size_t rlen)
+ {
+ if (ecRd || rlen < 2)
+ {
+ channelAbort("Failed to read req msg", ecRd);
+ return;
+ }
+
+ async_read();
+
+ // trim raw to be only bytes returned from read
+ // separate netfn/lun/cmd from payload
+ auto rawIter = xferBuffer.cbegin();
+ auto rawEnd = rawIter + rlen;
+ uint8_t netfn = *rawIter >> netFnShift;
+ uint8_t lun = *rawIter++ & lunMask;
+ uint8_t cmd = *rawIter++;
+ if (verbose)
+ {
+ log<level::INFO>("Read req msg", entry("NETFN=0x%02x", netfn),
+ entry("LUN=0x%02x", lun),
+ entry("CMD=0x%02x", cmd));
+ }
+ // copy out payload
+ std::vector<uint8_t> data(rawIter, rawEnd);
+ // non-session bridges still need to pass an empty options map
+ std::map<std::string, std::variant<int>> options;
+ // the response is a tuple because dbus can only return a single value
+ using IpmiDbusRspType = std::tuple<uint8_t, uint8_t, uint8_t, uint8_t,
+ std::vector<uint8_t>>;
+ static constexpr const char ipmiQueueService[] =
+ "xyz.openbmc_project.Ipmi.Host";
+ static constexpr const char ipmiQueuePath[] =
+ "/xyz/openbmc_project/Ipmi";
+ static constexpr const char ipmiQueueIntf[] =
+ "xyz.openbmc_project.Ipmi.Server";
+ static constexpr const char ipmiQueueMethod[] = "execute";
+ bus->async_method_call(
+ [this, netfnCap{netfn}, lunCap{lun},
+ cmdCap{cmd}](const boost::system::error_code& ec,
+ const IpmiDbusRspType& response) {
+ std::vector<uint8_t> rsp;
+ const auto& [netfn, lun, cmd, cc, payload] = response;
+ if (ec)
+ {
+ log<level::ERR>(
+ "kcs<->ipmid bus error:", entry("NETFN=0x%02x", netfn),
+ entry("LUN=0x%02x", lun), entry("CMD=0x%02x", cmd),
+ entry("ERROR=%s", ec.message().c_str()));
+ // send unspecified error for a D-Bus error
+ constexpr uint8_t ccResponseNotAvailable = 0xce;
+ rsp.resize(sizeof(netfn) + sizeof(cmd) + sizeof(cc));
+ rsp[0] =
+ ((netfnCap + 1) << netFnShift) | (lunCap & lunMask);
+ rsp[1] = cmdCap;
+ rsp[2] = ccResponseNotAvailable;
+ }
+ else
+ {
+ rsp.resize(sizeof(netfn) + sizeof(cmd) + sizeof(cc) +
+ payload.size());
+
+ // write the response
+ auto rspIter = rsp.begin();
+ *rspIter++ = (netfn << netFnShift) | (lun & lunMask);
+ *rspIter++ = cmd;
+ *rspIter++ = cc;
+ if (payload.size())
+ {
+ std::copy(payload.cbegin(), payload.cend(), rspIter);
+ }
+ }
+ if (verbose)
+ {
+ log<level::INFO>(
+ "Send rsp msg", entry("NETFN=0x%02x", netfn),
+ entry("LUN=0x%02x", lun), entry("CMD=0x%02x", cmd),
+ entry("CC=0x%02x", cc));
+ }
+ boost::system::error_code ecWr;
+ size_t wlen =
+ boost::asio::write(*dev, boost::asio::buffer(rsp), ecWr);
+ if (ecWr || wlen != rsp.size())
+ {
+ log<level::ERR>(
+ "Failed to send rsp msg", entry("SIZE=%d", wlen),
+ entry("EXPECT=%d", rsp.size()),
+ entry("ERROR=%s", ecWr.message().c_str()),
+ entry("NETFN=0x%02x", netfn), entry("LUN=0x%02x", lun),
+ entry("CMD=0x%02x", cmd), entry("CC=0x%02x", cc));
+ }
+ },
+ ipmiQueueService, ipmiQueuePath, ipmiQueueIntf, ipmiQueueMethod,
+ netfn, lun, cmd, data, options);
+ }
+
+ int64_t setAttention()
+ {
+ if (verbose)
+ {
+ log<level::INFO>("Sending SET_SMS_ATTENTION");
+ }
+ io_control::SetSmsAttention command;
+ boost::system::error_code ec;
+ dev->io_control(command, ec);
+ if (ec)
+ {
+ log<level::ERR>("Couldn't SET_SMS_ATTENTION",
+ entry("ERROR=%s", ec.message().c_str()));
+ return ec.value();
+ }
+ return 0;
+ }
+
+ int64_t clearAttention()
+ {
+ if (verbose)
+ {
+ log<level::INFO>("Sending CLEAR_SMS_ATTENTION");
+ }
+ io_control::ClearSmsAttention command;
+ boost::system::error_code ec;
+ dev->io_control(command, ec);
+ if (ec)
+ {
+ log<level::ERR>("Couldn't CLEAR_SMS_ATTENTION",
+ entry("ERROR=%s", ec.message().c_str()));
+ return ec.value();
+ }
+ return 0;
+ }
+
+ int64_t forceAbort()
+ {
+ if (verbose)
+ {
+ log<level::INFO>("Sending FORCE_ABORT");
+ }
+ io_control::ForceAbort command;
+ boost::system::error_code ec;
+ dev->io_control(command, ec);
+ if (ec)
+ {
+ log<level::ERR>("Couldn't FORCE_ABORT",
+ entry("ERROR=%s", ec.message().c_str()));
+ return ec.value();
+ }
+ return 0;
+ }
+
+ protected:
+ std::array<uint8_t, kcsMessageSize> xferBuffer;
+ std::shared_ptr<boost::asio::io_context> io;
+ std::shared_ptr<sdbusplus::asio::connection> bus;
+ std::shared_ptr<sdbusplus::asio::object_server> server;
+ std::unique_ptr<boost::asio::posix::stream_descriptor> dev = nullptr;
+ bool verbose;
+};
+
+} // namespace
+
+int main(int argc, char* argv[])
+{
+ CLI::App app("KCS IPMI bridge");
+ std::string channel;
+ app.add_option("-c,--channel", channel, "channel name. e.g., ipmi-kcs3")
+ ->required();
+ bool verbose = false;
+ app.add_option("-v,--verbose", verbose, "print more verbose output");
+ CLI11_PARSE(app, argc, argv);
+
+ // Connect to system bus
+ auto io = std::make_shared<boost::asio::io_context>();
+ sd_bus* dbus;
+ sd_bus_default_system(&dbus);
+ auto bus = std::make_shared<sdbusplus::asio::connection>(*io, dbus);
+
+ // Create the channel, listening on D-Bus and on the SMS device
+ SmsChannel smsChannel(io, bus, channel, verbose);
+
+ if (!smsChannel.initOK())
+ {
+ return EXIT_FAILURE;
+ }
+
+ static constexpr const char busBase[] = "xyz.openbmc_project.Ipmi.Channel.";
+ std::string busName(busBase + boost::replace_all_copy(channel, "-", "_"));
+ bus->request_name(busName.c_str());
+
+ io->run();
+
+ return 0;
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..00394f0
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,35 @@
+# CLI11 might not have a pkg-config. It is header only so just make
+# sure we can access the needed symbols from the header.
+cli11_dep = dependency('cli11', required: false)
+has_cli11 = meson.get_compiler('cpp').has_header_symbol(
+ 'CLI/CLI.hpp',
+ 'CLI::App',
+ dependencies: cli11_dep,
+ required: false)
+assert(has_cli11, 'CLI11 is required')
+
+executable(
+ 'kcsbridged',
+ 'kcsbridged.cpp',
+ implicit_include_directories: false,
+ dependencies: [
+ dependency(
+ 'boost',
+ modules: [
+ 'coroutine',
+ 'context',
+ ],
+ ),
+ cli11_dep,
+ dependency('phosphor-logging'),
+ dependency('sdbusplus'),
+ ],
+ cpp_args: [
+ '-DBOOST_ASIO_DISABLE_THREADS',
+ '-DBOOST_ALL_NO_LIB',
+ '-DBOOST_SYSTEM_NO_DEPRECATED',
+ '-DBOOST_ERROR_CODE_HEADER_ONLY',
+ '-DBOOST_COROUTINES_NO_DEPRECATION_WARNING',
+ ],
+ install: true,
+ install_dir: get_option('bindir'))