blob: 8a7114bd0dd8930439f16b7203b89b46c81d0f96 [file] [log] [blame]
William A. Kennington III4c68ffb2021-02-16 15:53:43 -08001/*
2 * Copyright 2021 Google 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
17#include "args.hpp"
18#include "dbus.hpp"
19
20#include <fmt/format.h>
21#include <linux/ipmi_bmc.h>
22#include <systemd/sd-daemon.h>
23
24#include <sdbusplus/bus.hpp>
25#include <sdbusplus/exception.hpp>
26#include <sdbusplus/server/interface.hpp>
27#include <sdbusplus/vtable.hpp>
28#include <sdeventplus/event.hpp>
29#include <sdeventplus/source/io.hpp>
30#include <sdeventplus/source/signal.hpp>
31#include <stdplus/fd/create.hpp>
32#include <stdplus/fd/ops.hpp>
33#include <stdplus/signal.hpp>
34
35#include <array>
36#include <map>
37#include <stdexcept>
38#include <tuple>
39#include <utility>
40#include <variant>
41
42namespace kcsbridge
43{
44
45using sdbusplus::bus::bus;
46using sdbusplus::message::message;
47using sdeventplus::source::IO;
48using sdeventplus::source::Signal;
49using stdplus::fd::OpenAccess;
50using stdplus::fd::OpenFlag;
51using stdplus::fd::OpenFlags;
52
53void setAttention(message& m, stdplus::Fd& kcs)
54{
55 stdplus::fd::ioctl(kcs, IPMI_BMC_IOCTL_SET_SMS_ATN, nullptr);
56 m.new_method_return().method_return();
57}
58
59void clearAttention(message& m, stdplus::Fd& kcs)
60{
61 stdplus::fd::ioctl(kcs, IPMI_BMC_IOCTL_CLEAR_SMS_ATN, nullptr);
62 m.new_method_return().method_return();
63}
64
65void forceAbort(message& m, stdplus::Fd& kcs)
66{
67 stdplus::fd::ioctl(kcs, IPMI_BMC_IOCTL_FORCE_ABORT, nullptr);
68 m.new_method_return().method_return();
69}
70
71template <typename Data>
72constexpr sdbusplus::vtable::vtable_t dbusMethods[] = {
73 sdbusplus::vtable::start(),
74 sdbusplus::vtable::method("setAttention", "", "",
75 methodRsp<setAttention, Data>),
76 sdbusplus::vtable::method("clearAttention", "", "",
77 methodRsp<clearAttention, Data>),
78 sdbusplus::vtable::method("forceAbort", "", "",
79 methodRsp<forceAbort, Data>),
80 sdbusplus::vtable::end(),
81};
82
83void write(stdplus::Fd& kcs, message&& m)
84{
85 std::array<uint8_t, 1024> buffer;
86 stdplus::span<uint8_t> out(buffer.begin(), 3);
87 try
88 {
89 if (m.is_method_error())
90 {
91 // Extra copy to workaround lack of `const sd_bus_error` constructor
92 auto error = *m.get_error();
93 throw sdbusplus::exception::SdBusError(&error, "ipmid response");
94 }
95 std::tuple<uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
96 ret;
97 m.read(ret);
98 const auto& [netfn, lun, cmd, cc, data] = ret;
99 // Based on the IPMI KCS spec Figure 9-2
100 // netfn needs to be changed to odd in KCS responses
101 buffer[0] = (netfn | 1) << 2;
102 buffer[0] |= lun;
103 buffer[1] = cmd;
104 buffer[2] = cc;
105 memcpy(&buffer[3], data.data(), data.size());
106 out = stdplus::span<uint8_t>(buffer.begin(), data.size() + 3);
107 }
108 catch (const std::exception& e)
109 {
110 fmt::print(stderr, "IPMI response failure: {}\n", e.what());
111 buffer[0] |= 1 << 2;
112 buffer[2] = 0xff;
113 }
114 stdplus::fd::writeExact(kcs, out);
115}
116
117void read(stdplus::Fd& kcs, bus& bus, ManagedSdBusSlot& slot)
118{
119 std::array<uint8_t, 1024> buffer;
120 auto in = stdplus::fd::read(kcs, buffer);
121 if (in.empty())
122 {
123 return;
124 }
125 if (slot)
126 {
127 fmt::print(stderr, "Canceling outstanding request\n");
128 slot.reset();
129 }
130 if (in.size() < 2)
131 {
132 fmt::print(stderr, "Read too small, ignoring\n");
133 return;
134 }
135 auto m = bus.new_method_call("xyz.openbmc_project.Ipmi.Host",
136 "/xyz/openbmc_project/Ipmi",
137 "xyz.openbmc_project.Ipmi.Server", "execute");
138 std::map<std::string, std::variant<int>> options;
139 // Based on the IPMI KCS spec Figure 9-1
140 uint8_t netfn = in[0] >> 2, lun = in[0] & 3, cmd = in[1];
141 m.append(netfn, lun, cmd, in.subspan(2), options);
142 slot = busCallAsync(std::move(m), [&](message&& m) {
143 slot.reset();
144 write(kcs, std::move(m));
145 });
146}
147
148int execute(const char* channel)
149{
150 // Set up our DBus and event loop
151 auto event = sdeventplus::Event::get_default();
152 auto bus = sdbusplus::bus::new_default();
153 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
154
155 // Configure basic signal handling
156 auto exit_handler = [&](Signal&, const struct signalfd_siginfo*) {
157 fmt::print(stderr, "Interrupted, Exiting\n");
158 event.exit(0);
159 };
160 stdplus::signal::block(SIGINT);
161 Signal sig_int(event, SIGINT, exit_handler);
162 stdplus::signal::block(SIGTERM);
163 Signal sig_term(event, SIGTERM, exit_handler);
164
165 // Open an FD for the KCS channel
166 stdplus::ManagedFd kcs = stdplus::fd::open(
167 fmt::format("/dev/{}", channel),
168 OpenFlags(OpenAccess::ReadWrite).set(OpenFlag::NonBlock));
169 ManagedSdBusSlot slot(std::nullopt);
170
171 // Add a reader to the bus for handling inbound IPMI
172 IO ioSource(event, kcs.get(), EPOLLIN | EPOLLET,
173 [&](IO&, int, uint32_t) { read(kcs, bus, slot); });
174
175 // Allow processes to affect the state machine
176 std::optional<sdbusplus::server::interface::interface> intf;
177 {
178 std::string dbusChannel = channel;
179 std::replace(dbusChannel.begin(), dbusChannel.end(), '-', '_');
180 auto obj = "/xyz/openbmc_project/Ipmi/Channel/" + dbusChannel;
181 auto srv = "com.google.gbmc." + dbusChannel;
182 intf.emplace(bus, obj.c_str(), "xyz.openbmc_project.Ipmi.Channel.SMS",
183 dbusMethods<stdplus::Fd>,
184 reinterpret_cast<stdplus::Fd*>(&kcs));
185 bus.request_name(srv.c_str());
186 }
187
188 sd_notify(0, "READY=1");
189 return event.loop();
190}
191
192} // namespace kcsbridge
193
194int main(int argc, char* argv[])
195{
196 try
197 {
198 kcsbridge::Args args(argc, argv);
199 return kcsbridge::execute(args.channel);
200 }
201 catch (const std::exception& e)
202 {
203 fmt::print(stderr, "FAILED: {}\n", e.what());
204 return 1;
205 }
206}