1 /**
2 * Copyright 2017 Google Inc.
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 #ifdef ENABLE_IPMI_SNOOP
18 #include "ipmisnoop/ipmisnoop.hpp"
19 #endif
20 #include "lpcsnoop/snoop.hpp"
21
22 #include <endian.h>
23 #include <fcntl.h>
24 #include <getopt.h>
25 #include <sys/epoll.h>
26 #include <systemd/sd-event.h>
27 #include <unistd.h>
28
29 #include <sdeventplus/event.hpp>
30 #include <sdeventplus/source/event.hpp>
31 #include <sdeventplus/source/io.hpp>
32 #include <sdeventplus/source/signal.hpp>
33 #include <sdeventplus/source/time.hpp>
34 #include <sdeventplus/utility/sdbus.hpp>
35 #include <stdplus/signal.hpp>
36
37 #include <chrono>
38 #include <cstdint>
39 #include <exception>
40 #include <functional>
41 #include <iostream>
42 #include <optional>
43 #include <thread>
44
45 static size_t codeSize = 1; /* Size of each POST code in bytes */
46 static bool verbose = false;
47 static std::function<bool(std::vector<uint8_t>&, ssize_t)> procPostCode;
48
usage(const char * name)49 static void usage(const char* name)
50 {
51 fprintf(stderr,
52 "Usage: %s\n"
53 #ifdef ENABLE_IPMI_SNOOP
54 " -h, --host <host instances> Default is '0'\n"
55 #else
56 " -d, --device <DEVICE> use <DEVICE> file.\n"
57 " -r, --rate-limit=<N> Only process N POST codes from the "
58 "device per second.\n"
59 " -b, --bytes <SIZE> set POST code length to <SIZE> bytes. "
60 "Default is 1\n"
61 #endif
62 " -v, --verbose Prints verbose information while running\n\n",
63 name);
64 }
65
66 /**
67 * Call once for each POST code received. If the number of POST codes exceeds
68 * the configured rate limit, this function will disable the snoop device IO
69 * source until the end of the 1 second interval, then re-enable it.
70 *
71 * @return Whether the rate limit is exceeded.
72 */
rateLimit(PostReporter & reporter,sdeventplus::source::IO & ioSource)73 bool rateLimit(PostReporter& reporter, sdeventplus::source::IO& ioSource)
74 {
75 if (reporter.rateLimit == 0)
76 {
77 // Rate limiting is disabled.
78 return false;
79 }
80
81 using Clock = sdeventplus::Clock<sdeventplus::ClockId::Monotonic>;
82
83 static constexpr std::chrono::seconds rateLimitInterval(1);
84 static unsigned int rateLimitCount = 0;
85 static Clock::time_point rateLimitEndTime;
86
87 const sdeventplus::Event& event = ioSource.get_event();
88
89 if (rateLimitCount == 0)
90 {
91 // Initialize the end time when we start a new interval
92 rateLimitEndTime = Clock(event).now() + rateLimitInterval;
93 }
94
95 if (++rateLimitCount < reporter.rateLimit)
96 {
97 return false;
98 }
99
100 rateLimitCount = 0;
101
102 if (rateLimitEndTime < Clock(event).now())
103 {
104 return false;
105 }
106
107 if (verbose)
108 {
109 fprintf(stderr, "Hit POST code rate limit - disabling temporarily\n");
110 }
111
112 ioSource.set_enabled(sdeventplus::source::Enabled::Off);
113 sdeventplus::source::Time<sdeventplus::ClockId::Monotonic>(
114 event, rateLimitEndTime, std::chrono::milliseconds(100),
115 [&ioSource](auto&, auto) {
116 if (verbose)
117 {
118 fprintf(stderr, "Reenabling POST code handler\n");
119 }
120 ioSource.set_enabled(sdeventplus::source::Enabled::On);
121 })
122 .set_floating(true);
123 return true;
124 }
125
126 /*
127 * Split input code into multiple 2 bytes PCC code, If the PCC code prefix
128 * matches the check code, store each PCC code in aspeedPCCBuffer, or clear
129 * aspeedPCCBuffer if the prefix does not match.
130 *
131 * Each PCC code contains one byte of port number (MSB) and another byte of
132 * partial postcode (LSB). To get a complete postcode, the PCC code should
133 * followed the sequence of 0x40AA, 0x41BB, 0x42CC & 0x43DD. When
134 * aspeedPCCBuffer contains enough PCC codes, the postcode will be assigned as
135 * 0xDDCCBBAA.
136 */
aspeedPCC(std::vector<uint8_t> & code,ssize_t readb)137 bool aspeedPCC(std::vector<uint8_t>& code, ssize_t readb)
138 {
139 // Size of data coming from the PCC hardware
140 constexpr size_t pccSize = sizeof(uint16_t);
141 // Required PCC count of a full postcode, if codeSize is 8 bytes, it means
142 // it require 4 PCC codes in correct sequence to get a complete postcode.
143 const size_t fullPostPCCCount = codeSize / pccSize;
144 // A PCC buffer for storing PCC code in sequence.
145 static std::vector<uint16_t> aspeedPCCBuffer;
146 constexpr uint16_t firstPCCPortNumber = 0x4000;
147 constexpr uint16_t pccPortNumberMask = 0xFF00;
148 constexpr uint16_t pccPostCodeMask = 0x00FF;
149 constexpr uint8_t byteShift = 8;
150
151 uint16_t* codePtr = reinterpret_cast<uint16_t*>(code.data());
152
153 for (size_t i = 0; i < (readb / pccSize); i++)
154 {
155 uint16_t checkCode =
156 firstPCCPortNumber +
157 ((aspeedPCCBuffer.size() % fullPostPCCCount) << byteShift);
158
159 if (checkCode == (codePtr[i] & pccPortNumberMask))
160 {
161 aspeedPCCBuffer.emplace_back(codePtr[i]);
162 }
163 else
164 {
165 aspeedPCCBuffer.clear();
166
167 // keep the PCC code if codePtr[i] matches with 0x40XX as first PCC
168 // code in buffer.
169 if ((codePtr[i] & pccPortNumberMask) == firstPCCPortNumber)
170 {
171 aspeedPCCBuffer.emplace_back(codePtr[i]);
172 }
173 }
174 }
175
176 if (aspeedPCCBuffer.size() < fullPostPCCCount)
177 {
178 // not receive full postcode yet.
179 return false;
180 }
181
182 // Remove the prefix bytes and combine the partial postcodes together.
183 code.clear();
184 for (size_t i = fullPostPCCCount; i > 0; --i)
185 {
186 code.push_back(aspeedPCCBuffer[i - 1] & pccPostCodeMask);
187 }
188 aspeedPCCBuffer.erase(aspeedPCCBuffer.begin(),
189 aspeedPCCBuffer.begin() + fullPostPCCCount);
190
191 return true;
192 }
193
194 /*
195 * Callback handling IO event from the POST code fd. i.e. there is new
196 * POST code available to read.
197 */
PostCodeEventHandler(PostReporter * reporter,sdeventplus::source::IO & s,int postFd,uint32_t)198 void PostCodeEventHandler(PostReporter* reporter, sdeventplus::source::IO& s,
199 int postFd, uint32_t)
200 {
201 std::vector<uint8_t> code(codeSize, 0);
202 ssize_t readb;
203
204 while ((readb = read(postFd, code.data(), codeSize)) > 0)
205 {
206 if (procPostCode && procPostCode(code, readb) == false)
207 {
208 return;
209 }
210
211 if (verbose)
212 {
213 fprintf(stderr, "Code: 0x");
214 for (const auto& byte : code)
215 {
216 fprintf(stderr, "%02x", byte);
217 }
218 fprintf(stderr, "\n");
219 }
220 // HACK: Always send property changed signal even for the same code
221 // since we are single threaded, external users will never see the
222 // first value.
223 code[0] = ~code[0];
224 reporter->value(std::make_tuple(code, secondary_post_code_t{}), true);
225 code[0] = ~code[0];
226 reporter->value(std::make_tuple(code, secondary_post_code_t{}));
227
228 // read depends on old data being cleared since it doesn't always read
229 // the full code size
230 code.resize(codeSize);
231 std::fill(code.begin(), code.end(), 0);
232
233 if (rateLimit(*reporter, s))
234 {
235 return;
236 }
237 }
238
239 if (readb < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
240 {
241 return;
242 }
243
244 /* Read failure. */
245 if (readb == 0)
246 {
247 fprintf(stderr, "Unexpected EOF reading postcode\n");
248 }
249 else
250 {
251 fprintf(stderr, "Failed to read postcode: %s\n", strerror(errno));
252 }
253 s.get_event().exit(1);
254 }
255
256 /*
257 * TODO(venture): this only listens one of the possible snoop ports, but
258 * doesn't share the namespace.
259 *
260 * This polls() the lpc snoop character device and it owns the dbus object
261 * whose value is the latest port 80h value.
262 */
main(int argc,char * argv[])263 int main(int argc, char* argv[])
264 {
265 int postFd = -1;
266 unsigned int rateLimit = 0;
267
268 int opt;
269
270 std::vector<std::string> host;
271
272 // clang-format off
273 static const struct option long_options[] = {
274 #ifdef ENABLE_IPMI_SNOOP
275 {"host", optional_argument, NULL, 'h'},
276 #else
277 {"device", optional_argument, NULL, 'd'},
278 {"rate-limit", optional_argument, NULL, 'r'},
279 {"bytes", required_argument, NULL, 'b'},
280 #endif
281 {"verbose", no_argument, NULL, 'v'},
282 {0, 0, 0, 0}
283 };
284 // clang-format on
285
286 constexpr const char* optstring =
287 #ifdef ENABLE_IPMI_SNOOP
288 "h:"
289 #else
290 "d:r:b:"
291 #endif
292 "v";
293
294 while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1)
295 {
296 switch (opt)
297 {
298 case 0:
299 break;
300 case 'h':
301 {
302 std::string_view instances = optarg;
303 size_t pos = 0;
304
305 while ((pos = instances.find(" ")) != std::string::npos)
306 {
307 host.emplace_back(instances.substr(0, pos));
308 instances.remove_prefix(pos + 1);
309 }
310 host.emplace_back(instances);
311 break;
312 }
313 case 'b':
314 {
315 codeSize = atoi(optarg);
316
317 if (codeSize < 1 || codeSize > 8)
318 {
319 fprintf(stderr,
320 "Invalid POST code size '%s'. Must be "
321 "an integer from 1 to 8.\n",
322 optarg);
323 exit(EXIT_FAILURE);
324 }
325 break;
326 }
327 case 'd':
328 if (std::string(optarg).starts_with("/dev/aspeed-lpc-pcc"))
329 {
330 procPostCode = aspeedPCC;
331 }
332
333 postFd = open(optarg, O_NONBLOCK);
334 if (postFd < 0)
335 {
336 fprintf(stderr, "Unable to open: %s\n", optarg);
337 return -1;
338 }
339 break;
340 case 'r':
341 {
342 int argVal = -1;
343 try
344 {
345 argVal = std::stoi(optarg);
346 }
347 catch (...)
348 {}
349
350 if (argVal < 1)
351 {
352 fprintf(stderr, "Invalid rate limit '%s'. Must be >= 1.\n",
353 optarg);
354 return EXIT_FAILURE;
355 }
356
357 rateLimit = static_cast<unsigned int>(argVal);
358 fprintf(stderr, "Rate limiting to %d POST codes per second.\n",
359 argVal);
360 break;
361 }
362 case 'v':
363 verbose = true;
364 break;
365 default:
366 usage(argv[0]);
367 return EXIT_FAILURE;
368 }
369 }
370
371 auto bus = sdbusplus::bus::new_default();
372
373 #ifdef ENABLE_IPMI_SNOOP
374 std::cout << "Verbose = " << verbose << std::endl;
375 int ret = postCodeIpmiHandler(ipmiSnoopObject, snoopDbus, bus, host);
376 if (ret < 0)
377 {
378 fprintf(stderr, "Error in postCodeIpmiHandler\n");
379 return ret;
380 }
381 return 0;
382 #endif
383
384 bool deferSignals = true;
385
386 // Add systemd object manager.
387 sdbusplus::server::manager_t snoopdManager(bus, snoopObject);
388
389 PostReporter reporter(bus, snoopObject, deferSignals);
390 reporter.emit_object_added();
391 bus.request_name(snoopDbus);
392
393 // Create sdevent and add IO source
394 try
395 {
396 sdeventplus::Event event = sdeventplus::Event::get_default();
397 std::optional<sdeventplus::source::IO> reporterSource;
398 if (postFd > 0)
399 {
400 reporter.rateLimit = rateLimit;
401 reporterSource.emplace(
402 event, postFd, EPOLLIN,
403 std::bind_front(PostCodeEventHandler, &reporter));
404 }
405 // Enable bus to handle incoming IO and bus events
406 auto intCb = [](sdeventplus::source::Signal& source,
407 const struct signalfd_siginfo*) {
408 source.get_event().exit(0);
409 };
410 stdplus::signal::block(SIGINT);
411 sdeventplus::source::Signal(event, SIGINT, intCb).set_floating(true);
412 stdplus::signal::block(SIGTERM);
413 sdeventplus::source::Signal(event, SIGTERM, std::move(intCb))
414 .set_floating(true);
415 return sdeventplus::utility::loopWithBus(event, bus);
416 }
417 catch (const std::exception& e)
418 {
419 fprintf(stderr, "%s\n", e.what());
420 }
421
422 if (postFd > -1)
423 {
424 close(postFd);
425 }
426
427 return 0;
428 }
429