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(uint64_t&, ssize_t)> procPostCode; 48 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 */ 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 }).set_floating(true); 122 return true; 123 } 124 125 /* 126 * Split input code into multiple 2 bytes PCC code, If the PCC code prefix 127 * matches the check code, store each PCC code in aspeedPCCBuffer, or clear 128 * aspeedPCCBuffer if the prefix does not match. 129 * 130 * Each PCC code contains one byte of port number (MSB) and another byte of 131 * partial postcode (LSB). To get a complete postcode, the PCC code should 132 * followed the sequence of 0x40AA, 0x41BB, 0x42CC & 0x43DD. When 133 * aspeedPCCBuffer contains enough PCC codes, the postcode will be assigned as 134 * 0xDDCCBBAA. 135 */ 136 bool aspeedPCC(uint64_t& code, ssize_t readb) 137 { 138 // Size of data coming from the PCC hardware 139 constexpr size_t pccSize = sizeof(uint16_t); 140 // Required PCC count of a full postcode, if codeSize is 8 bytes, it means 141 // it require 4 PCC codes in correct sequence to get a complete postcode. 142 const size_t fullPostPCCCount = codeSize / pccSize; 143 // A PCC buffer for storing PCC code in sequence. 144 static std::vector<uint16_t> aspeedPCCBuffer; 145 constexpr uint16_t firstPCCPortNumber = 0x4000; 146 constexpr uint16_t pccPortNumberMask = 0xFF00; 147 constexpr uint16_t pccPostCodeMask = 0x00FF; 148 constexpr uint8_t byteShift = 8; 149 150 uint16_t* codePtr = reinterpret_cast<uint16_t*>(&code); 151 152 for (size_t i = 0; i < (readb / pccSize); i++) 153 { 154 uint16_t checkCode = firstPCCPortNumber + 155 ((aspeedPCCBuffer.size() % fullPostPCCCount) 156 << byteShift); 157 158 if (checkCode == (codePtr[i] & pccPortNumberMask)) 159 { 160 aspeedPCCBuffer.emplace_back(codePtr[i]); 161 } 162 else 163 { 164 aspeedPCCBuffer.clear(); 165 166 // keep the PCC code if codePtr[i] matches with 0x40XX as first PCC 167 // code in buffer. 168 if ((codePtr[i] & pccPortNumberMask) == firstPCCPortNumber) 169 { 170 aspeedPCCBuffer.emplace_back(codePtr[i]); 171 } 172 } 173 } 174 175 if (aspeedPCCBuffer.size() < fullPostPCCCount) 176 { 177 // not receive full postcode yet. 178 return false; 179 } 180 181 // Remove the prefix bytes and combine the partial postcodes together. 182 code = 0; 183 for (size_t i = 0; i < fullPostPCCCount; i++) 184 { 185 code |= static_cast<uint64_t>(aspeedPCCBuffer[i] & pccPostCodeMask) 186 << (byteShift * i); 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 */ 198 void PostCodeEventHandler(PostReporter* reporter, sdeventplus::source::IO& s, 199 int postFd, uint32_t) 200 { 201 uint64_t code = 0; 202 ssize_t readb; 203 204 while ((readb = read(postFd, &code, codeSize)) > 0) 205 { 206 if (procPostCode && procPostCode(code, readb) == false) 207 { 208 return; 209 } 210 211 code = le64toh(code); 212 if (verbose) 213 { 214 fprintf(stderr, "Code: 0x%" PRIx64 "\n", code); 215 } 216 // HACK: Always send property changed signal even for the same code 217 // since we are single threaded, external users will never see the 218 // first value. 219 reporter->value(std::make_tuple(~code, secondary_post_code_t{}), true); 220 reporter->value(std::make_tuple(code, secondary_post_code_t{})); 221 222 // read depends on old data being cleared since it doens't always read 223 // the full code size 224 code = 0; 225 226 if (rateLimit(*reporter, s)) 227 { 228 return; 229 } 230 } 231 232 if (readb < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 233 { 234 return; 235 } 236 237 /* Read failure. */ 238 if (readb == 0) 239 { 240 fprintf(stderr, "Unexpected EOF reading postcode\n"); 241 } 242 else 243 { 244 fprintf(stderr, "Failed to read postcode: %s\n", strerror(errno)); 245 } 246 s.get_event().exit(1); 247 } 248 249 /* 250 * TODO(venture): this only listens one of the possible snoop ports, but 251 * doesn't share the namespace. 252 * 253 * This polls() the lpc snoop character device and it owns the dbus object 254 * whose value is the latest port 80h value. 255 */ 256 int main(int argc, char* argv[]) 257 { 258 int postFd = -1; 259 unsigned int rateLimit = 0; 260 261 int opt; 262 263 std::vector<std::string> host; 264 265 // clang-format off 266 static const struct option long_options[] = { 267 #ifdef ENABLE_IPMI_SNOOP 268 {"host", optional_argument, NULL, 'h'}, 269 #else 270 {"device", optional_argument, NULL, 'd'}, 271 {"rate-limit", optional_argument, NULL, 'r'}, 272 {"bytes", required_argument, NULL, 'b'}, 273 #endif 274 {"verbose", no_argument, NULL, 'v'}, 275 {0, 0, 0, 0} 276 }; 277 // clang-format on 278 279 constexpr const char* optstring = 280 #ifdef ENABLE_IPMI_SNOOP 281 "h:" 282 #else 283 "d:r:b:" 284 #endif 285 "v"; 286 287 while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1) 288 { 289 switch (opt) 290 { 291 case 0: 292 break; 293 case 'h': 294 { 295 std::string_view instances = optarg; 296 size_t pos = 0; 297 298 while ((pos = instances.find(" ")) != std::string::npos) 299 { 300 host.emplace_back(instances.substr(0, pos)); 301 instances.remove_prefix(pos + 1); 302 } 303 host.emplace_back(instances); 304 break; 305 } 306 case 'b': 307 { 308 codeSize = atoi(optarg); 309 310 if (codeSize < 1 || codeSize > 8) 311 { 312 fprintf(stderr, 313 "Invalid POST code size '%s'. Must be " 314 "an integer from 1 to 8.\n", 315 optarg); 316 exit(EXIT_FAILURE); 317 } 318 break; 319 } 320 case 'd': 321 if (std::string(optarg).starts_with("/dev/aspeed-lpc-pcc")) 322 { 323 procPostCode = aspeedPCC; 324 } 325 326 postFd = open(optarg, O_NONBLOCK); 327 if (postFd < 0) 328 { 329 fprintf(stderr, "Unable to open: %s\n", optarg); 330 return -1; 331 } 332 break; 333 case 'r': 334 { 335 int argVal = -1; 336 try 337 { 338 argVal = std::stoi(optarg); 339 } 340 catch (...) 341 {} 342 343 if (argVal < 1) 344 { 345 fprintf(stderr, "Invalid rate limit '%s'. Must be >= 1.\n", 346 optarg); 347 return EXIT_FAILURE; 348 } 349 350 rateLimit = static_cast<unsigned int>(argVal); 351 fprintf(stderr, "Rate limiting to %d POST codes per second.\n", 352 argVal); 353 break; 354 } 355 case 'v': 356 verbose = true; 357 break; 358 default: 359 usage(argv[0]); 360 return EXIT_FAILURE; 361 } 362 } 363 364 auto bus = sdbusplus::bus::new_default(); 365 366 #ifdef ENABLE_IPMI_SNOOP 367 std::cout << "Verbose = " << verbose << std::endl; 368 int ret = postCodeIpmiHandler(ipmiSnoopObject, snoopDbus, bus, host); 369 if (ret < 0) 370 { 371 fprintf(stderr, "Error in postCodeIpmiHandler\n"); 372 return ret; 373 } 374 return 0; 375 #endif 376 377 bool deferSignals = true; 378 379 // Add systemd object manager. 380 sdbusplus::server::manager_t snoopdManager(bus, snoopObject); 381 382 PostReporter reporter(bus, snoopObject, deferSignals); 383 reporter.emit_object_added(); 384 bus.request_name(snoopDbus); 385 386 // Create sdevent and add IO source 387 try 388 { 389 sdeventplus::Event event = sdeventplus::Event::get_default(); 390 std::optional<sdeventplus::source::IO> reporterSource; 391 if (postFd > 0) 392 { 393 reporter.rateLimit = rateLimit; 394 reporterSource.emplace( 395 event, postFd, EPOLLIN, 396 std::bind_front(PostCodeEventHandler, &reporter)); 397 } 398 // Enable bus to handle incoming IO and bus events 399 auto intCb = [](sdeventplus::source::Signal& source, 400 const struct signalfd_siginfo*) { 401 source.get_event().exit(0); 402 }; 403 stdplus::signal::block(SIGINT); 404 sdeventplus::source::Signal(event, SIGINT, intCb).set_floating(true); 405 stdplus::signal::block(SIGTERM); 406 sdeventplus::source::Signal(event, SIGTERM, std::move(intCb)) 407 .set_floating(true); 408 return sdeventplus::utility::loopWithBus(event, bus); 409 } 410 catch (const std::exception& e) 411 { 412 fprintf(stderr, "%s\n", e.what()); 413 } 414 415 if (postFd > -1) 416 { 417 close(postFd); 418 } 419 420 return 0; 421 } 422