kcsbridge: Daemon rewrite
This focuses on improving compile time, output size, and correctness
of command handling. Notably, it adds the ability for the host to cancel
outstanding IPMI calls when the host decides they should time out. It
ensures that canceled calls do not end up writing over the response for
an incoming request. Since the host can now cancel calls, we remove
internal DBus timeouts and instead require the host to drive any timeout
logic it desires for communications. This gives the host more control
and better adheres to the wishes of the specification.
Change-Id: I526169253931b9bcc4910afa0b54b304d4a9bef3
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/phosphor-ipmi-kcs@.service.in b/phosphor-ipmi-kcs@.service.in
index 1d41541..3dacda4 100644
--- a/phosphor-ipmi-kcs@.service.in
+++ b/phosphor-ipmi-kcs@.service.in
@@ -4,6 +4,7 @@
After=phosphor-ipmi-host.service
[Service]
+Type=notify
Restart=always
ExecStart=@BIN@ -c "%i"
SyslogIdentifier=kcsbridged-%i
diff --git a/src/args.cpp b/src/args.cpp
new file mode 100644
index 0000000..b81de5a
--- /dev/null
+++ b/src/args.cpp
@@ -0,0 +1,46 @@
+#include "args.hpp"
+
+#include <fmt/format.h>
+#include <getopt.h>
+
+#include <stdexcept>
+
+namespace kcsbridge
+{
+
+Args::Args(int argc, char* argv[])
+{
+ static const char opts[] = ":c:";
+ static const struct option longopts[] = {
+ {"channel", required_argument, nullptr, 'c'},
+ {nullptr, 0, nullptr, 0},
+ };
+ int c;
+ optind = 0;
+ while ((c = getopt_long(argc, argv, opts, longopts, nullptr)) > 0)
+ {
+ switch (c)
+ {
+ case 'c':
+ channel = optarg;
+ break;
+ case ':':
+ throw std::runtime_error(
+ fmt::format("Missing argument for `{}`", argv[optind - 1]));
+ break;
+ default:
+ throw std::runtime_error(fmt::format(
+ "Invalid command line argument `{}`", argv[optind - 1]));
+ }
+ }
+ if (optind != argc)
+ {
+ throw std::invalid_argument("Requires no additional arguments");
+ }
+ if (channel == nullptr)
+ {
+ throw std::invalid_argument("Missing KCS channel");
+ }
+}
+
+} // namespace kcsbridge
diff --git a/src/args.hpp b/src/args.hpp
new file mode 100644
index 0000000..e528162
--- /dev/null
+++ b/src/args.hpp
@@ -0,0 +1,14 @@
+#pragma once
+#include <cstddef>
+
+namespace kcsbridge
+{
+
+struct Args
+{
+ const char* channel = nullptr;
+
+ Args(int argc, char* argv[]);
+};
+
+} // namespace kcsbridge
diff --git a/src/cmd.cpp b/src/cmd.cpp
new file mode 100644
index 0000000..3a97498
--- /dev/null
+++ b/src/cmd.cpp
@@ -0,0 +1,99 @@
+#include "cmd.hpp"
+
+#include <fmt/format.h>
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/types.hpp>
+
+#include <array>
+#include <map>
+#include <stdexcept>
+#include <tuple>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace kcsbridge
+{
+
+using sdbusplus::bus::bus;
+using sdbusplus::message::message;
+using sdbusplus::slot::slot;
+
+void write(stdplus::Fd& kcs, message&& m)
+{
+ std::array<uint8_t, 1024> buffer;
+ stdplus::span<uint8_t> out(buffer.begin(), 3);
+ try
+ {
+ if (m.is_method_error())
+ {
+ // Extra copy to workaround lack of `const sd_bus_error` constructor
+ auto error = *m.get_error();
+ throw sdbusplus::exception::SdBusError(&error, "ipmid response");
+ }
+ std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
+ ret;
+ m.read(ret);
+ const auto& [netfn, lun, cmd, cc, data] = ret;
+ // Based on the IPMI KCS spec Figure 9-2
+ // netfn needs to be changed to odd in KCS responses
+ if (data.size() + 3 > buffer.size())
+ {
+ throw std::runtime_error(fmt::format(
+ "too large {} > {}", data.size() + 3, buffer.size()));
+ }
+ buffer[0] = (netfn | 1) << 2;
+ buffer[0] |= lun;
+ buffer[1] = cmd;
+ buffer[2] = cc;
+ memcpy(&buffer[3], data.data(), data.size());
+ out = stdplus::span<uint8_t>(buffer.begin(), data.size() + 3);
+ }
+ catch (const std::exception& e)
+ {
+ fmt::print(stderr, "IPMI response failure: {}\n", e.what());
+ buffer[0] |= 1 << 2;
+ buffer[2] = 0xff;
+ }
+ stdplus::fd::writeExact(kcs, out);
+}
+
+void read(stdplus::Fd& kcs, bus& bus, slot& outstanding)
+{
+ std::array<uint8_t, 1024> buffer;
+ auto in = stdplus::fd::read(kcs, buffer);
+ if (in.empty())
+ {
+ return;
+ }
+ if (outstanding)
+ {
+ fmt::print(stderr, "Canceling outstanding request\n");
+ outstanding = slot(nullptr);
+ }
+ if (in.size() < 2)
+ {
+ fmt::print(stderr, "Read too small, ignoring\n");
+ return;
+ }
+ auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
+ "/xyz/openbmc_project/Ipmi",
+ "xyz.openbmc_project.Ipmi.Server", "execute");
+ std::map<std::string, std::variant<int>> options;
+ // Based on the IPMI KCS spec Figure 9-1
+ uint8_t netfn = in[0] >> 2, lun = in[0] & 3, cmd = in[1];
+ m.append(netfn, lun, cmd, in.subspan(2), options);
+ outstanding = m.call_async(
+ stdplus::exception::ignore([&outstanding, &kcs](message&& m) {
+ outstanding = slot(nullptr);
+ write(kcs, std::move(m));
+ }));
+}
+
+} // namespace kcsbridge
diff --git a/src/cmd.hpp b/src/cmd.hpp
new file mode 100644
index 0000000..a6a6fa3
--- /dev/null
+++ b/src/cmd.hpp
@@ -0,0 +1,14 @@
+#pragma once
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/slot.hpp>
+#include <stdplus/fd/intf.hpp>
+
+namespace kcsbridge
+{
+
+void write(stdplus::Fd& kcs, sdbusplus::message::message&& m);
+void read(stdplus::Fd& kcs, sdbusplus::bus::bus& bus,
+ sdbusplus::slot::slot& outstanding);
+
+} // namespace kcsbridge
diff --git a/src/kcsbridged.cpp b/src/kcsbridged.cpp
deleted file mode 100644
index 9380367..0000000
--- a/src/kcsbridged.cpp
+++ /dev/null
@@ -1,343 +0,0 @@
-/* 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 <phosphor-logging/log.hpp>
-#include <sdbusplus/asio/connection.hpp>
-#include <sdbusplus/asio/object_server.hpp>
-
-#include <iostream>
-
-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/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..e8863e8
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,85 @@
+#include "args.hpp"
+#include "cmd.hpp"
+#include "server.hpp"
+
+#include <fmt/format.h>
+#include <systemd/sd-daemon.h>
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/slot.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/io.hpp>
+#include <sdeventplus/source/signal.hpp>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/signal.hpp>
+
+#include <algorithm>
+#include <stdexcept>
+#include <string>
+
+namespace kcsbridge
+{
+
+using sdeventplus::source::IO;
+using sdeventplus::source::Signal;
+using stdplus::fd::OpenAccess;
+using stdplus::fd::OpenFlag;
+using stdplus::fd::OpenFlags;
+
+int execute(const char* channel)
+{
+ // Set up our DBus and event loop
+ auto event = sdeventplus::Event::get_default();
+ auto bus = sdbusplus::bus::new_default();
+ bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+ // Configure basic signal handling
+ auto exit_handler = [&event](Signal&, const struct signalfd_siginfo*) {
+ fmt::print(stderr, "Interrupted, Exiting\n");
+ event.exit(0);
+ };
+ stdplus::signal::block(SIGINT);
+ Signal sig_int(event, SIGINT, exit_handler);
+ stdplus::signal::block(SIGTERM);
+ Signal sig_term(event, SIGTERM, exit_handler);
+
+ // Open an FD for the KCS channel
+ stdplus::ManagedFd kcs = stdplus::fd::open(
+ fmt::format("/dev/{}", channel),
+ OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::NonBlock));
+ sdbusplus::slot::slot slot(nullptr);
+
+ // Add a reader to the bus for handling inbound IPMI
+ IO ioSource(
+ event, kcs.get(), EPOLLIN | EPOLLET,
+ stdplus::exception::ignore(
+ [&kcs, &bus, &slot](IO&, int, uint32_t) { read(kcs, bus, slot); }));
+
+ // Allow processes to affect the state machine
+ std::string dbusChannel = channel;
+ std::replace(dbusChannel.begin(), dbusChannel.end(), '-', '_');
+ auto obj = "/xyz/openbmc_project/Ipmi/Channel/" + dbusChannel;
+ auto srv = "xyz.openbmc_project.Ipmi.Channel." + dbusChannel;
+ auto intf = createSMSHandler(bus, obj.c_str(), kcs);
+ bus.request_name(srv.c_str());
+
+ sd_notify(0, "READY=1");
+ return event.loop();
+}
+
+} // namespace kcsbridge
+
+int main(int argc, char* argv[])
+{
+ try
+ {
+ kcsbridge::Args args(argc, argv);
+ return kcsbridge::execute(args.channel);
+ }
+ catch (const std::exception& e)
+ {
+ fmt::print(stderr, "FAILED: {}\n", e.what());
+ return 1;
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index 7d57e5a..68b6f9a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,35 +1,49 @@
-# 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')
+headers = include_directories('.')
+
+fmt_dep = dependency('fmt', required: false)
+if not fmt_dep.found()
+ fmt_opts = import('cmake').subproject_options()
+ fmt_opts.add_cmake_defines({
+ 'CMAKE_POSITION_INDEPENDENT_CODE': 'ON',
+ 'MASTER_PROJECT': 'OFF',
+ })
+ fmt_proj = import('cmake').subproject(
+ 'fmt',
+ options: fmt_opts,
+ required: false)
+ assert(fmt_proj.found(), 'fmtlib is required')
+ fmt_dep = fmt_proj.dependency('fmt')
+endif
+
+
+deps = [
+ fmt_dep,
+ dependency('stdplus', fallback: ['stdplus', 'stdplus_dep']),
+ dependency('sdbusplus', fallback: ['sdbusplus', 'sdbusplus_dep']),
+]
+
+lib = static_library(
+ 'kcsbridged',
+ 'args.cpp',
+ 'cmd.cpp',
+ 'server.cpp',
+ include_directories: headers,
+ implicit_include_directories: false,
+ dependencies: deps)
+
+dep = declare_dependency(
+ dependencies: deps,
+ include_directories: headers,
+ link_with: lib)
kcsbridged = executable(
'kcsbridged',
- 'kcsbridged.cpp',
+ 'main.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',
+ dep,
+ dependency('sdeventplus', fallback: ['sdeventplus', 'sdeventplus_dep']),
+ dependency('libsystemd'),
],
install: true,
install_dir: get_option('libexecdir'))
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644
index 0000000..836ac43
--- /dev/null
+++ b/src/server.cpp
@@ -0,0 +1,72 @@
+#include "server.hpp"
+
+#include <fmt/format.h>
+#include <linux/ipmi_bmc.h>
+
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/server/interface.hpp>
+#include <sdbusplus/vtable.hpp>
+#include <stdplus/fd/ops.hpp>
+
+#include <stdexcept>
+
+namespace kcsbridge
+{
+
+void setAttention(sdbusplus::message::message& m, stdplus::Fd& kcs)
+{
+ stdplus::fd::ioctl(kcs, IPMI_BMC_IOCTL_SET_SMS_ATN, nullptr);
+ m.new_method_return().method_return();
+}
+
+void clearAttention(sdbusplus::message::message& m, stdplus::Fd& kcs)
+{
+ stdplus::fd::ioctl(kcs, IPMI_BMC_IOCTL_CLEAR_SMS_ATN, nullptr);
+ m.new_method_return().method_return();
+}
+
+void forceAbort(sdbusplus::message::message& m, stdplus::Fd& kcs)
+{
+ stdplus::fd::ioctl(kcs, IPMI_BMC_IOCTL_FORCE_ABORT, nullptr);
+ m.new_method_return().method_return();
+}
+
+template <auto func, typename Data>
+int methodRsp(sd_bus_message* mptr, void* dataptr, sd_bus_error* error) noexcept
+{
+ sdbusplus::message::message m(mptr);
+ try
+ {
+ func(m, *reinterpret_cast<Data*>(dataptr));
+ }
+ catch (const std::exception& e)
+ {
+ fmt::print(stderr, "Method response failed: {}\n", e.what());
+ sd_bus_error_set(error,
+ "xyz.openbmc_project.Common.Error.InternalFailure",
+ "The operation failed internally.");
+ }
+ return 1;
+}
+
+template <typename Data>
+constexpr sdbusplus::vtable::vtable_t dbusMethods[] = {
+ sdbusplus::vtable::start(),
+ sdbusplus::vtable::method("setAttention", "", "",
+ methodRsp<setAttention, Data>),
+ sdbusplus::vtable::method("clearAttention", "", "",
+ methodRsp<clearAttention, Data>),
+ sdbusplus::vtable::method("forceAbort", "", "",
+ methodRsp<forceAbort, Data>),
+ sdbusplus::vtable::end(),
+};
+
+sdbusplus::server::interface::interface createSMSHandler(
+ sdbusplus::bus::bus& bus, const char* obj, stdplus::Fd& kcs)
+{
+ return sdbusplus::server::interface::interface(
+ bus, obj, "xyz.openbmc_project.Ipmi.Channel.SMS",
+ dbusMethods<stdplus::Fd>, reinterpret_cast<stdplus::Fd*>(&kcs));
+}
+
+} // namespace kcsbridge
diff --git a/src/server.hpp b/src/server.hpp
new file mode 100644
index 0000000..6a5f1fe
--- /dev/null
+++ b/src/server.hpp
@@ -0,0 +1,12 @@
+#pragma once
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/interface.hpp>
+#include <stdplus/fd/intf.hpp>
+
+namespace kcsbridge
+{
+
+sdbusplus::server::interface::interface createSMSHandler(
+ sdbusplus::bus::bus& bus, const char* obj, stdplus::Fd& kcs);
+
+}
diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap
new file mode 100644
index 0000000..6847ae5
--- /dev/null
+++ b/subprojects/fmt.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/fmtlib/fmt
+revision = HEAD
diff --git a/subprojects/sdbusplus.wrap b/subprojects/sdbusplus.wrap
new file mode 100644
index 0000000..7f736e7
--- /dev/null
+++ b/subprojects/sdbusplus.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/openbmc/sdbusplus
+revision = HEAD
diff --git a/subprojects/sdeventplus.wrap b/subprojects/sdeventplus.wrap
new file mode 100644
index 0000000..7503664
--- /dev/null
+++ b/subprojects/sdeventplus.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/openbmc/sdeventplus
+revision = HEAD
diff --git a/subprojects/stdplus.wrap b/subprojects/stdplus.wrap
new file mode 100644
index 0000000..00dae65
--- /dev/null
+++ b/subprojects/stdplus.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/openbmc/stdplus
+revision = HEAD