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'))