/** * Copyright 2017 Google Inc. * * 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. */ #ifdef ENABLE_IPMI_SNOOP #include "ipmisnoop/ipmisnoop.hpp" #endif #include "lpcsnoop/snoop.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static size_t codeSize = 1; /* Size of each POST code in bytes */ static bool verbose = false; static std::function&, ssize_t)> procPostCode; static void usage(const char* name) { fprintf(stderr, "Usage: %s\n" #ifdef ENABLE_IPMI_SNOOP " -h, --host Default is '0'\n" #else " -d, --device use file.\n" " -r, --rate-limit= Only process N POST codes from the " "device per second.\n" " -b, --bytes set POST code length to bytes. " "Default is 1\n" #endif " -v, --verbose Prints verbose information while running\n\n", name); } /** * Call once for each POST code received. If the number of POST codes exceeds * the configured rate limit, this function will disable the snoop device IO * source until the end of the 1 second interval, then re-enable it. * * @return Whether the rate limit is exceeded. */ bool rateLimit(PostReporter& reporter, sdeventplus::source::IO& ioSource) { if (reporter.rateLimit == 0) { // Rate limiting is disabled. return false; } using Clock = sdeventplus::Clock; static constexpr std::chrono::seconds rateLimitInterval(1); static unsigned int rateLimitCount = 0; static Clock::time_point rateLimitEndTime; const sdeventplus::Event& event = ioSource.get_event(); if (rateLimitCount == 0) { // Initialize the end time when we start a new interval rateLimitEndTime = Clock(event).now() + rateLimitInterval; } if (++rateLimitCount < reporter.rateLimit) { return false; } rateLimitCount = 0; if (rateLimitEndTime < Clock(event).now()) { return false; } if (verbose) { fprintf(stderr, "Hit POST code rate limit - disabling temporarily\n"); } ioSource.set_enabled(sdeventplus::source::Enabled::Off); sdeventplus::source::Time( event, rateLimitEndTime, std::chrono::milliseconds(100), [&ioSource](auto&, auto) { if (verbose) { fprintf(stderr, "Reenabling POST code handler\n"); } ioSource.set_enabled(sdeventplus::source::Enabled::On); }) .set_floating(true); return true; } /* * Split input code into multiple 2 bytes PCC code, If the PCC code prefix * matches the check code, store each PCC code in aspeedPCCBuffer, or clear * aspeedPCCBuffer if the prefix does not match. * * Each PCC code contains one byte of port number (MSB) and another byte of * partial postcode (LSB). To get a complete postcode, the PCC code should * followed the sequence of 0x40AA, 0x41BB, 0x42CC & 0x43DD. When * aspeedPCCBuffer contains enough PCC codes, the postcode will be assigned as * 0xDDCCBBAA. */ bool aspeedPCC(std::vector& code, ssize_t readb) { // Size of data coming from the PCC hardware constexpr size_t pccSize = sizeof(uint16_t); // Required PCC count of a full postcode, if codeSize is 8 bytes, it means // it require 4 PCC codes in correct sequence to get a complete postcode. const size_t fullPostPCCCount = codeSize / pccSize; // A PCC buffer for storing PCC code in sequence. static std::vector aspeedPCCBuffer; constexpr uint16_t firstPCCPortNumber = 0x4000; constexpr uint16_t pccPortNumberMask = 0xFF00; constexpr uint16_t pccPostCodeMask = 0x00FF; constexpr uint8_t byteShift = 8; uint16_t* codePtr = reinterpret_cast(code.data()); for (size_t i = 0; i < (readb / pccSize); i++) { uint16_t checkCode = firstPCCPortNumber + ((aspeedPCCBuffer.size() % fullPostPCCCount) << byteShift); if (checkCode == (codePtr[i] & pccPortNumberMask)) { aspeedPCCBuffer.emplace_back(codePtr[i]); } else { aspeedPCCBuffer.clear(); // keep the PCC code if codePtr[i] matches with 0x40XX as first PCC // code in buffer. if ((codePtr[i] & pccPortNumberMask) == firstPCCPortNumber) { aspeedPCCBuffer.emplace_back(codePtr[i]); } } } if (aspeedPCCBuffer.size() < fullPostPCCCount) { // not receive full postcode yet. return false; } // Remove the prefix bytes and combine the partial postcodes together. code.clear(); for (size_t i = fullPostPCCCount; i > 0; --i) { code.push_back(aspeedPCCBuffer[i - 1] & pccPostCodeMask); } aspeedPCCBuffer.erase(aspeedPCCBuffer.begin(), aspeedPCCBuffer.begin() + fullPostPCCCount); return true; } /* * Callback handling IO event from the POST code fd. i.e. there is new * POST code available to read. */ void PostCodeEventHandler(PostReporter* reporter, sdeventplus::source::IO& s, int postFd, uint32_t) { std::vector code(codeSize, 0); ssize_t readb; while ((readb = read(postFd, code.data(), codeSize)) > 0) { if (procPostCode && procPostCode(code, readb) == false) { return; } if (verbose) { fprintf(stderr, "Code: 0x"); for (const auto& byte : code) { fprintf(stderr, "%02x", byte); } fprintf(stderr, "\n"); } // HACK: Always send property changed signal even for the same code // since we are single threaded, external users will never see the // first value. code[0] = ~code[0]; reporter->value(std::make_tuple(code, secondary_post_code_t{}), true); code[0] = ~code[0]; reporter->value(std::make_tuple(code, secondary_post_code_t{})); // read depends on old data being cleared since it doesn't always read // the full code size code.resize(codeSize); std::fill(code.begin(), code.end(), 0); if (rateLimit(*reporter, s)) { return; } } if (readb < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { return; } /* Read failure. */ if (readb == 0) { fprintf(stderr, "Unexpected EOF reading postcode\n"); } else { fprintf(stderr, "Failed to read postcode: %s\n", strerror(errno)); } s.get_event().exit(1); } /* * TODO(venture): this only listens one of the possible snoop ports, but * doesn't share the namespace. * * This polls() the lpc snoop character device and it owns the dbus object * whose value is the latest port 80h value. */ int main(int argc, char* argv[]) { int postFd = -1; unsigned int rateLimit = 0; int opt; std::vector host; // clang-format off static const struct option long_options[] = { #ifdef ENABLE_IPMI_SNOOP {"host", optional_argument, nullptr, 'h'}, #else {"device", optional_argument, nullptr, 'd'}, {"rate-limit", optional_argument, nullptr, 'r'}, {"bytes", required_argument, nullptr, 'b'}, #endif {"verbose", no_argument, nullptr, 'v'}, {nullptr, 0, nullptr, 0} }; // clang-format on constexpr const char* optstring = #ifdef ENABLE_IPMI_SNOOP "h:" #else "d:r:b:" #endif "v"; while ((opt = getopt_long(argc, argv, optstring, long_options, nullptr)) != -1) { switch (opt) { case 0: break; case 'h': { std::string_view instances = optarg; size_t pos = 0; while ((pos = instances.find(" ")) != std::string::npos) { host.emplace_back(instances.substr(0, pos)); instances.remove_prefix(pos + 1); } host.emplace_back(instances); break; } case 'b': { codeSize = atoi(optarg); if (codeSize < 1 || codeSize > 8) { fprintf(stderr, "Invalid POST code size '%s'. Must be " "an integer from 1 to 8.\n", optarg); exit(EXIT_FAILURE); } break; } case 'd': if (std::string(optarg).starts_with("/dev/aspeed-lpc-pcc")) { procPostCode = aspeedPCC; } postFd = open(optarg, O_NONBLOCK); if (postFd < 0) { fprintf(stderr, "Unable to open: %s\n", optarg); return -1; } break; case 'r': { int argVal = -1; try { argVal = std::stoi(optarg); } catch (...) {} if (argVal < 1) { fprintf(stderr, "Invalid rate limit '%s'. Must be >= 1.\n", optarg); return EXIT_FAILURE; } rateLimit = static_cast(argVal); fprintf(stderr, "Rate limiting to %d POST codes per second.\n", argVal); break; } case 'v': verbose = true; break; default: usage(argv[0]); return EXIT_FAILURE; } } auto bus = sdbusplus::bus::new_default(); #ifdef ENABLE_IPMI_SNOOP std::cout << "Verbose = " << verbose << std::endl; int ret = postCodeIpmiHandler(ipmiSnoopObject, snoopDbus, bus, host); if (ret < 0) { fprintf(stderr, "Error in postCodeIpmiHandler\n"); return ret; } return 0; #endif bool deferSignals = true; // Add systemd object manager. sdbusplus::server::manager_t snoopdManager(bus, snoopObject); PostReporter reporter(bus, snoopObject, deferSignals); reporter.emit_object_added(); bus.request_name(snoopDbus); // Create sdevent and add IO source try { sdeventplus::Event event = sdeventplus::Event::get_default(); std::optional reporterSource; if (postFd > 0) { reporter.rateLimit = rateLimit; reporterSource.emplace( event, postFd, EPOLLIN, std::bind_front(PostCodeEventHandler, &reporter)); } // Enable bus to handle incoming IO and bus events auto intCb = [](sdeventplus::source::Signal& source, const struct signalfd_siginfo*) { source.get_event().exit(0); }; stdplus::signal::block(SIGINT); sdeventplus::source::Signal(event, SIGINT, intCb).set_floating(true); stdplus::signal::block(SIGTERM); sdeventplus::source::Signal(event, SIGTERM, std::move(intCb)) .set_floating(true); return sdeventplus::utility::loopWithBus(event, bus); } catch (const std::exception& e) { fprintf(stderr, "%s\n", e.what()); } if (postFd > -1) { close(postFd); } return 0; }