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 21 #include "lpcsnoop/snoop.hpp" 22 23 #include <endian.h> 24 #include <fcntl.h> 25 #include <getopt.h> 26 #include <sys/epoll.h> 27 #include <systemd/sd-event.h> 28 #include <unistd.h> 29 30 #include <cstdint> 31 #include <exception> 32 #include <functional> 33 #include <iostream> 34 #include <optional> 35 #include <sdeventplus/event.hpp> 36 #include <sdeventplus/source/event.hpp> 37 #include <sdeventplus/source/io.hpp> 38 #include <sdeventplus/source/signal.hpp> 39 #include <span> 40 #include <stdplus/signal.hpp> 41 #include <thread> 42 43 #ifdef ENABLE_IPMI_SNOOP 44 #include <xyz/openbmc_project/State/Boot/Raw/server.hpp> 45 #endif 46 47 static size_t codeSize = 1; /* Size of each POST code in bytes */ 48 const char* defaultHostInstances = "0"; 49 #ifdef ENABLE_IPMI_SNOOP 50 const uint8_t minPositionVal = 0; 51 const uint8_t maxPositionVal = 5; 52 #endif 53 54 #ifdef ENABLE_IPMI_SNOOP 55 std::vector<std::unique_ptr<IpmiPostReporter>> reporters; 56 #endif 57 58 #ifdef ENABLE_IPMI_SNOOP 59 void IpmiPostReporter::getSelectorPositionSignal(sdbusplus::bus::bus& bus) 60 { 61 size_t posVal = 0; 62 63 matchSignal = std::make_unique<sdbusplus::bus::match_t>( 64 bus, 65 sdbusplus::bus::match::rules::propertiesChanged(selectorObject, 66 selectorIface), 67 [&](sdbusplus::message::message& msg) { 68 std::string objectName; 69 std::map<std::string, Selector::PropertiesVariant> msgData; 70 msg.read(objectName, msgData); 71 72 auto valPropMap = msgData.find("Position"); 73 { 74 if (valPropMap == msgData.end()) 75 { 76 std::cerr << "Position property not found " << std::endl; 77 return; 78 } 79 80 posVal = std::get<size_t>(valPropMap->second); 81 82 if (posVal > minPositionVal && posVal < maxPositionVal) 83 { 84 std::tuple<uint64_t, secondary_post_code_t> postcodes = 85 reporters[posVal - 1]->value(); 86 uint64_t postcode = std::get<uint64_t>(postcodes); 87 88 // write postcode into seven segment display 89 if (postCodeDisplay(postcode) < 0) 90 { 91 fprintf(stderr, "Error in display the postcode\n"); 92 } 93 } 94 } 95 }); 96 } 97 #endif 98 99 static void usage(const char* name) 100 { 101 fprintf(stderr, 102 "Usage: %s [-d <DEVICE>]\n" 103 " -b, --bytes <SIZE> set POST code length to <SIZE> bytes. " 104 "Default is %zu\n" 105 " -d, --device <DEVICE> use <DEVICE> file.\n" 106 " -h, --host <host instances> . Default is '%s'\n" 107 " -v, --verbose Prints verbose information while running\n\n", 108 name, codeSize, defaultHostInstances); 109 } 110 111 /* 112 * Callback handling IO event from the POST code fd. i.e. there is new 113 * POST code available to read. 114 */ 115 void PostCodeEventHandler(PostReporter* reporter, bool verbose, 116 sdeventplus::source::IO& s, int postFd, uint32_t) 117 { 118 uint64_t code = 0; 119 ssize_t readb; 120 while ((readb = read(postFd, &code, codeSize)) > 0) 121 { 122 code = le64toh(code); 123 if (verbose) 124 { 125 fprintf(stderr, "Code: 0x%" PRIx64 "\n", code); 126 } 127 // HACK: Always send property changed signal even for the same code 128 // since we are single threaded, external users will never see the 129 // first value. 130 reporter->value(std::make_tuple(~code, secondary_post_code_t{}), true); 131 reporter->value(std::make_tuple(code, secondary_post_code_t{})); 132 133 // read depends on old data being cleared since it doens't always read 134 // the full code size 135 code = 0; 136 } 137 138 if (readb < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) 139 { 140 return; 141 } 142 143 /* Read failure. */ 144 if (readb == 0) 145 { 146 fprintf(stderr, "Unexpected EOF reading postcode\n"); 147 } 148 else 149 { 150 fprintf(stderr, "Failed to read postcode: %s\n", strerror(errno)); 151 } 152 s.get_event().exit(1); 153 } 154 155 #ifdef ENABLE_IPMI_SNOOP 156 // handle muti-host D-bus 157 int postCodeIpmiHandler(const std::string& snoopObject, 158 const std::string& snoopDbus, sdbusplus::bus::bus& bus, 159 std::span<std::string> host) 160 { 161 int ret = 0; 162 163 try 164 { 165 for (size_t iteration = 0; iteration < host.size(); iteration++) 166 { 167 std::string objPathInst = snoopObject + host[iteration]; 168 169 sdbusplus::server::manager_t m{bus, objPathInst.c_str()}; 170 171 /* Create a monitor object and let it do all the rest */ 172 reporters.emplace_back( 173 std::make_unique<IpmiPostReporter>(bus, objPathInst.c_str())); 174 175 reporters[iteration]->emit_object_added(); 176 } 177 178 bus.request_name(snoopDbus.c_str()); 179 reporters[0]->getSelectorPositionSignal(bus); 180 } 181 catch (const std::exception& e) 182 { 183 fprintf(stderr, "%s\n", e.what()); 184 } 185 186 // Configure seven segment dsiplay connected to GPIOs as output 187 ret = configGPIODirOutput(); 188 if (ret < 0) 189 { 190 fprintf(stderr, "Failed find the gpio line\n"); 191 } 192 193 while (true) 194 { 195 bus.process_discard(); 196 bus.wait(); 197 } 198 exit(EXIT_SUCCESS); 199 } 200 #endif 201 202 /* 203 * TODO(venture): this only listens one of the possible snoop ports, but 204 * doesn't share the namespace. 205 * 206 * This polls() the lpc snoop character device and it owns the dbus object 207 * whose value is the latest port 80h value. 208 */ 209 int main(int argc, char* argv[]) 210 { 211 212 #ifndef ENABLE_IPMI_SNOOP 213 int postFd = -1; 214 #endif 215 216 int opt; 217 bool verbose = false; 218 219 #ifdef ENABLE_IPMI_SNOOP 220 std::vector<std::string> host; 221 #endif 222 /* 223 * These string constants are only used in this method within this object 224 * and this object is the only object feeding into the final binary. 225 * 226 * If however, another object is added to this binary it would be proper 227 * to move these declarations to be global and extern to the other object. 228 */ 229 230 // clang-format off 231 static const struct option long_options[] = { 232 #ifdef ENABLE_IPMI_SNOOP 233 {"host", optional_argument, NULL, 'h'}, 234 #endif 235 {"bytes", required_argument, NULL, 'b'}, 236 #ifndef ENABLE_IPMI_SNOOP 237 {"device", optional_argument, NULL, 'd'}, 238 #endif 239 {"verbose", no_argument, NULL, 'v'}, 240 {0, 0, 0, 0} 241 }; 242 // clang-format on 243 244 while ((opt = getopt_long(argc, argv, "h:b:d:v", long_options, NULL)) != -1) 245 { 246 switch (opt) 247 { 248 case 0: 249 break; 250 #ifdef ENABLE_IPMI_SNOOP 251 case 'h': { 252 std::string_view instances = optarg; 253 size_t pos = 0; 254 255 while ((pos = instances.find(" ")) != std::string::npos) 256 { 257 host.emplace_back(instances.substr(0, pos)); 258 instances.remove_prefix(pos + 1); 259 } 260 host.emplace_back(instances); 261 break; 262 } 263 #endif 264 case 'b': { 265 codeSize = atoi(optarg); 266 267 if (codeSize < 1 || codeSize > 8) 268 { 269 fprintf(stderr, 270 "Invalid POST code size '%s'. Must be " 271 "an integer from 1 to 8.\n", 272 optarg); 273 exit(EXIT_FAILURE); 274 } 275 break; 276 } 277 #ifndef ENABLE_IPMI_SNOOP 278 case 'd': 279 280 postFd = open(optarg, O_NONBLOCK); 281 if (postFd < 0) 282 { 283 fprintf(stderr, "Unable to open: %s\n", optarg); 284 return -1; 285 } 286 break; 287 #endif 288 case 'v': 289 verbose = true; 290 break; 291 default: 292 usage(argv[0]); 293 } 294 } 295 296 auto bus = sdbusplus::bus::new_default(); 297 298 #ifdef ENABLE_IPMI_SNOOP 299 std::cout << "Verbose = " << verbose << std::endl; 300 int ret = postCodeIpmiHandler(ipmiSnoopObject, snoopDbus, bus, host); 301 if (ret < 0) 302 { 303 fprintf(stderr, "Error in postCodeIpmiHandler\n"); 304 return ret; 305 } 306 return 0; 307 #endif 308 309 #ifndef ENABLE_IPMI_SNOOP 310 int rc = 0; 311 312 bool deferSignals = true; 313 314 // Add systemd object manager. 315 sdbusplus::server::manager::manager snoopdManager(bus, snoopObject); 316 317 PostReporter reporter(bus, snoopObject, deferSignals); 318 reporter.emit_object_added(); 319 bus.request_name(snoopDbus); 320 321 // Create sdevent and add IO source 322 try 323 { 324 sdeventplus::Event event = sdeventplus::Event::get_default(); 325 std::optional<sdeventplus::source::IO> reporterSource; 326 if (postFd > 0) 327 { 328 reporterSource.emplace( 329 event, postFd, EPOLLIN | EPOLLET, 330 std::bind_front(PostCodeEventHandler, &reporter, verbose)); 331 } 332 // Enable bus to handle incoming IO and bus events 333 bool done = false; 334 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); 335 auto intCb = [&done](sdeventplus::source::Signal& source, 336 const struct signalfd_siginfo*) { 337 source.get_event().exit(0); 338 done = true; 339 }; 340 stdplus::signal::block(SIGINT); 341 sdeventplus::source::Signal(event, SIGINT, intCb).set_floating(true); 342 stdplus::signal::block(SIGTERM); 343 sdeventplus::source::Signal(event, SIGTERM, std::move(intCb)) 344 .set_floating(true); 345 346 while (!done) 347 { 348 // Process all outstanding bus events before running the loop. 349 // This prevents the sd-bus handling logic from leaking memory. 350 // TODO: Remove when upstream fixes this bug 351 while (bus.process_discard() > 0) 352 ; 353 354 // Run and never timeout 355 rc = event.run(std::nullopt); 356 } 357 } 358 catch (const std::exception& e) 359 { 360 fprintf(stderr, "%s\n", e.what()); 361 } 362 363 if (postFd > -1) 364 { 365 close(postFd); 366 } 367 368 return rc; 369 #endif 370 } 371