ssifbridge: Replace ssifbridge with C++ version

Rewrite the C-style ssifbridge in modern C++.

Test:
On linux host, run 'ipmitool mc info' or 'ipmitool fru print'

Change-Id: I5970853abbf4b2cfe893c76e819a22047baaa4a4
Signed-off-by: Dung Cao <dung@os.amperecomputing.com>
diff --git a/ssifbridged.cpp b/ssifbridged.cpp
new file mode 100644
index 0000000..f49d7c5
--- /dev/null
+++ b/ssifbridged.cpp
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2021 Ampere Computing LLC
+ *
+ * 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.
+ *
+ * This is a daemon that forwards requests and receive responses from SSIF over
+ * the D-Bus IPMI Interface.
+ */
+
+#include <getopt.h>
+#include <linux/ipmi_bmc.h>
+
+#include <CLI/CLI.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/asio.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <iostream>
+
+using namespace phosphor::logging;
+
+static constexpr const char devBase[] = "/dev/ipmi-ssif-host";
+/* SSIF use IPMI SSIF channel */
+static constexpr const char* ssifBus =
+    "xyz.openbmc_project.Ipmi.Channel.ipmi_ssif";
+static constexpr const char* ssifObj =
+    "/xyz/openbmc_project/Ipmi/Channel/ipmi_ssif";
+
+class SsifChannel
+{
+  public:
+    static constexpr size_t ssifMessageSize = 4096;
+    static constexpr uint8_t netFnShift = 2;
+    static constexpr uint8_t lunMask = (1 << netFnShift) - 1;
+
+    SsifChannel(std::shared_ptr<boost::asio::io_context>& io,
+                   std::shared_ptr<sdbusplus::asio::connection>& bus,
+                   const std::string& channel, bool verbose);
+    bool initOK() const
+    {
+        return !!dev;
+    }
+    void channelAbort(const char* msg, const boost::system::error_code& ec);
+    void async_read();
+    void processMessage(const boost::system::error_code& ecRd, size_t rlen);
+
+  protected:
+    std::array<uint8_t, ssifMessageSize> 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;
+};
+
+SsifChannel::SsifChannel(std::shared_ptr<boost::asio::io_context>& io,
+                         std::shared_ptr<sdbusplus::asio::connection>& bus,
+                         const std::string& device, bool verbose) :
+    io(io),
+    bus(bus), verbose(verbose)
+{
+    std::string devName = devBase;
+    if (!device.empty())
+    {
+        devName = device;
+    }
+
+    // open device
+    int fd = open(devName.c_str(), O_RDWR | O_NONBLOCK);
+    if (fd < 0)
+    {
+        log<level::ERR>("Couldn't open SSIF driver with flags 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);
+    std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+        server->add_interface(ssifObj, ssifBus);
+    iface->initialize();
+}
+
+void SsifChannel::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 SsifChannel::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 SsifChannel::processMessage(const boost::system::error_code& ecRd,
+                                 size_t rlen)
+{
+    if (ecRd || rlen < 2)
+    {
+        channelAbort("Failed to read req msg", ecRd);
+        return;
+    }
+    async_read();
+
+    auto rawIter = xferBuffer.cbegin();
+    auto rawEnd = rawIter + rlen;
+    uint8_t len = rawIter[0] + 1;
+    uint8_t netfn = rawIter[1] >> netFnShift;
+    uint8_t lun = rawIter[1] & lunMask;
+    uint8_t cmd = rawIter[2];
+    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[3], 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>(
+                    "ssif<->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(uint8_t) + sizeof(netfn) + sizeof(cmd) +
+                           sizeof(cc));
+                rsp[0] = 3;
+                rsp[1] =
+                    ((netfnCap + 1) << netFnShift) | (lunCap & lunMask);
+                rsp[2] = cmdCap;
+                rsp[3] = ccResponseNotAvailable;
+            }
+            else
+            {
+                rsp.resize(sizeof(uint8_t) + sizeof(netfn) + sizeof(cmd) +
+                           sizeof(cc) + payload.size());
+
+                // write the response
+                auto rspIter = rsp.begin();
+                rspIter[0] = payload.size() + 3;
+                rspIter[1] = (netfn << netFnShift) | (lun & lunMask);
+                rspIter[2] = cmd;
+                rspIter[3] = cc;
+                if (payload.size())
+                {
+                    std::copy(payload.cbegin(), payload.cend(),
+                              &rspIter[4]);
+                }
+            }
+            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);
+}
+
+
+int main(int argc, char* argv[])
+{
+    CLI::App app("SSIF IPMI bridge");
+    std::string device;
+    app.add_option("-d,--device", device,
+                   "use <DEVICE> file. Default is /dev/ipmi-ssif-host");
+    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);
+    bus->request_name(ssifBus);
+    // Create the SSIF channel, listening on D-Bus and on the SSIF device
+    SsifChannel ssifchannel(io, bus, device, verbose);
+    if (!ssifchannel.initOK())
+    {
+        return EXIT_FAILURE;
+    }
+    io->run();
+
+    return 0;
+}