1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 3 4 #include "../utils.hpp" 5 #include "fru_utils.hpp" 6 7 #include <fcntl.h> 8 #include <sys/inotify.h> 9 #include <sys/ioctl.h> 10 11 #include <boost/asio/io_context.hpp> 12 #include <boost/asio/steady_timer.hpp> 13 #include <boost/container/flat_map.hpp> 14 #include <nlohmann/json.hpp> 15 #include <phosphor-logging/lg2.hpp> 16 #include <sdbusplus/asio/connection.hpp> 17 #include <sdbusplus/asio/object_server.hpp> 18 19 #include <array> 20 #include <cerrno> 21 #include <charconv> 22 #include <chrono> 23 #include <ctime> 24 #include <filesystem> 25 #include <fstream> 26 #include <functional> 27 #include <future> 28 #include <iomanip> 29 #include <iostream> 30 #include <limits> 31 #include <map> 32 #include <optional> 33 #include <regex> 34 #include <set> 35 #include <sstream> 36 #include <string> 37 #include <thread> 38 #include <utility> 39 #include <variant> 40 #include <vector> 41 42 extern "C" 43 { 44 #include <i2c/smbus.h> 45 #include <linux/i2c-dev.h> 46 } 47 48 namespace fs = std::filesystem; 49 constexpr size_t maxFruSize = 512; 50 constexpr size_t maxEepromPageIndex = 255; 51 constexpr size_t busTimeoutSeconds = 10; 52 53 constexpr const char* blocklistPath = PACKAGE_DIR "blacklist.json"; 54 55 const static constexpr char* baseboardFruLocation = 56 "/etc/fru/baseboard.fru.bin"; 57 58 const static constexpr char* i2CDevLocation = "/dev"; 59 60 constexpr const char* fruDevice16BitDetectMode = FRU_DEVICE_16BITDETECTMODE; 61 62 // TODO Refactor these to not be globals 63 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) 64 static boost::container::flat_map<size_t, std::optional<std::set<size_t>>> 65 busBlocklist; 66 struct FindDevicesWithCallback; 67 68 static boost::container::flat_map< 69 std::pair<size_t, size_t>, std::shared_ptr<sdbusplus::asio::dbus_interface>> 70 foundDevices; 71 72 static boost::container::flat_map<size_t, std::set<size_t>> failedAddresses; 73 static boost::container::flat_map<size_t, std::set<size_t>> fruAddresses; 74 75 boost::asio::io_context io; 76 // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) 77 78 bool updateFRUProperty( 79 const std::string& updatePropertyReq, uint32_t bus, uint32_t address, 80 const std::string& propertyName, 81 boost::container::flat_map< 82 std::pair<size_t, size_t>, 83 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap, 84 size_t& unknownBusObjectCount, const bool& powerIsOn, 85 sdbusplus::asio::object_server& objServer, 86 std::shared_ptr<sdbusplus::asio::connection>& systemBus); 87 88 // Given a bus/address, produce the path in sysfs for an eeprom. 89 static std::string getEepromPath(size_t bus, size_t address) 90 { 91 std::stringstream output; 92 output << "/sys/bus/i2c/devices/" << bus << "-" << std::right 93 << std::setfill('0') << std::setw(4) << std::hex << address 94 << "/eeprom"; 95 return output.str(); 96 } 97 98 static bool hasEepromFile(size_t bus, size_t address) 99 { 100 auto path = getEepromPath(bus, address); 101 try 102 { 103 return fs::exists(path); 104 } 105 catch (...) 106 { 107 return false; 108 } 109 } 110 111 static int64_t readFromEeprom(int fd, off_t offset, size_t len, uint8_t* buf) 112 { 113 auto result = lseek(fd, offset, SEEK_SET); 114 if (result < 0) 115 { 116 std::cerr << "failed to seek\n"; 117 return -1; 118 } 119 120 return read(fd, buf, len); 121 } 122 123 static int busStrToInt(const std::string_view busName) 124 { 125 auto findBus = busName.rfind('-'); 126 if (findBus == std::string::npos) 127 { 128 return -1; 129 } 130 std::string_view num = busName.substr(findBus + 1); 131 int val = 0; 132 bool fullMatch = false; 133 fromCharsWrapper(num, val, fullMatch); 134 return val; 135 } 136 137 static int getRootBus(size_t bus) 138 { 139 auto ec = std::error_code(); 140 auto path = std::filesystem::read_symlink( 141 std::filesystem::path( 142 "/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/mux_device"), 143 ec); 144 if (ec) 145 { 146 return -1; 147 } 148 149 std::string filename = path.filename(); 150 auto findBus = filename.find('-'); 151 if (findBus == std::string::npos) 152 { 153 return -1; 154 } 155 return std::stoi(filename.substr(0, findBus)); 156 } 157 158 static bool isMuxBus(size_t bus) 159 { 160 auto ec = std::error_code(); 161 auto isSymlink = 162 is_symlink(std::filesystem::path("/sys/bus/i2c/devices/i2c-" + 163 std::to_string(bus) + "/mux_device"), 164 ec); 165 return (!ec && isSymlink); 166 } 167 168 static void makeProbeInterface(size_t bus, size_t address, 169 sdbusplus::asio::object_server& objServer) 170 { 171 if (isMuxBus(bus)) 172 { 173 return; // the mux buses are random, no need to publish 174 } 175 auto [it, success] = foundDevices.emplace( 176 std::make_pair(bus, address), 177 objServer.add_interface( 178 "/xyz/openbmc_project/FruDevice/" + std::to_string(bus) + "_" + 179 std::to_string(address), 180 "xyz.openbmc_project.Inventory.Item.I2CDevice")); 181 if (!success) 182 { 183 return; // already added 184 } 185 it->second->register_property("Bus", bus); 186 it->second->register_property("Address", address); 187 it->second->initialize(); 188 } 189 190 // Issue an I2C transaction to first write to_target_buf_len bytes,then read 191 // from_target_buf_len bytes. 192 static int i2cSmbusWriteThenRead( 193 int file, uint16_t address, uint8_t* toTargetBuf, uint8_t toTargetBufLen, 194 uint8_t* fromTargetBuf, uint8_t fromTargetBufLen) 195 { 196 if (toTargetBuf == nullptr || toTargetBufLen == 0 || 197 fromTargetBuf == nullptr || fromTargetBufLen == 0) 198 { 199 return -1; 200 } 201 202 constexpr size_t smbusWriteThenReadMsgCount = 2; 203 std::array<struct i2c_msg, smbusWriteThenReadMsgCount> msgs{}; 204 struct i2c_rdwr_ioctl_data rdwr{}; 205 206 msgs[0].addr = address; 207 msgs[0].flags = 0; 208 msgs[0].len = toTargetBufLen; 209 msgs[0].buf = toTargetBuf; 210 msgs[1].addr = address; 211 msgs[1].flags = I2C_M_RD; 212 msgs[1].len = fromTargetBufLen; 213 msgs[1].buf = fromTargetBuf; 214 215 rdwr.msgs = msgs.data(); 216 rdwr.nmsgs = msgs.size(); 217 218 int ret = ioctl(file, I2C_RDWR, &rdwr); 219 220 return (ret == static_cast<int>(msgs.size())) ? msgs[1].len : -1; 221 } 222 223 static int64_t readData(bool is16bit, bool isBytewise, int file, 224 uint16_t address, off_t offset, size_t len, 225 uint8_t* buf) 226 { 227 if (!is16bit) 228 { 229 if (!isBytewise) 230 { 231 return i2c_smbus_read_i2c_block_data( 232 file, static_cast<uint8_t>(offset), len, buf); 233 } 234 235 std::span<uint8_t> bufspan{buf, len}; 236 for (size_t i = 0; i < len; i++) 237 { 238 int byte = i2c_smbus_read_byte_data( 239 file, static_cast<uint8_t>(offset + i)); 240 if (byte < 0) 241 { 242 return static_cast<int64_t>(byte); 243 } 244 bufspan[i] = static_cast<uint8_t>(byte); 245 } 246 return static_cast<int64_t>(len); 247 } 248 249 offset = htobe16(offset); 250 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 251 uint8_t* u8Offset = reinterpret_cast<uint8_t*>(&offset); 252 return i2cSmbusWriteThenRead(file, address, u8Offset, 2, buf, len); 253 } 254 255 // Mode_1: 256 // -------- 257 // Please refer to document docs/address_size_detection_modes.md for 258 // more details and explanations. 259 static std::optional<bool> isDevice16BitMode1(int file) 260 { 261 // Set the higher data word address bits to 0. It's safe on 8-bit 262 // addressing EEPROMs because it doesn't write any actual data. 263 int ret = i2c_smbus_write_byte(file, 0); 264 if (ret < 0) 265 { 266 return std::nullopt; 267 } 268 269 /* Get first byte */ 270 int byte1 = i2c_smbus_read_byte_data(file, 0); 271 if (byte1 < 0) 272 { 273 return std::nullopt; 274 } 275 /* Read 7 more bytes, it will read same first byte in case of 276 * 8 bit but it will read next byte in case of 16 bit 277 */ 278 for (int i = 0; i < 7; i++) 279 { 280 int byte2 = i2c_smbus_read_byte_data(file, 0); 281 if (byte2 < 0) 282 { 283 return std::nullopt; 284 } 285 if (byte2 != byte1) 286 { 287 return true; 288 } 289 } 290 return false; 291 } 292 293 // Mode_2: 294 // -------- 295 // Please refer to document docs/address_size_detection_modes.md for 296 // more details and explanations. 297 static std::optional<bool> isDevice16BitMode2(int file, uint16_t address) 298 { 299 uint8_t first = 0; 300 uint8_t cur = 0; 301 uint16_t v = 0; 302 int ret = 0; 303 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 304 uint8_t* p = reinterpret_cast<uint8_t*>(&v); 305 306 /* 307 * Write 2 bytes byte0 = 0, byte1 = {0..7} and then subsequent read byte 308 * It will read same first byte in case of 8 bit but 309 * it will read next byte in case of 16 bit 310 */ 311 for (int i = 0; i < 8; i++) 312 { 313 v = htobe16(i); 314 315 ret = i2cSmbusWriteThenRead(file, address, p, 2, &cur, 1); 316 if (ret < 0) 317 { 318 return std::nullopt; 319 } 320 321 if (i == 0) 322 { 323 first = cur; 324 } 325 326 if (first != cur) 327 { 328 return true; 329 } 330 } 331 return false; 332 } 333 334 static std::optional<bool> isDevice16Bit(int file, uint16_t address) 335 { 336 std::string mode(fruDevice16BitDetectMode); 337 338 if (mode == "MODE_2") 339 { 340 return isDevice16BitMode2(file, address); 341 } 342 343 return isDevice16BitMode1(file); 344 } 345 346 // TODO: This code is very similar to the non-eeprom version and can be merged 347 // with some tweaks. 348 static std::vector<uint8_t> processEeprom(int bus, int address) 349 { 350 auto path = getEepromPath(bus, address); 351 352 int file = open(path.c_str(), O_RDONLY); 353 if (file < 0) 354 { 355 std::cerr << "Unable to open eeprom file: " << path << "\n"; 356 return {}; 357 } 358 359 std::string errorMessage = "eeprom at " + std::to_string(bus) + 360 " address " + std::to_string(address); 361 auto readFunc = [file](off_t offset, size_t length, uint8_t* outbuf) { 362 return readFromEeprom(file, offset, length, outbuf); 363 }; 364 FRUReader reader(std::move(readFunc)); 365 std::pair<std::vector<uint8_t>, bool> pair = 366 readFRUContents(reader, errorMessage); 367 368 close(file); 369 return pair.first; 370 } 371 372 std::set<size_t> findI2CEeproms(int i2cBus, 373 const std::shared_ptr<DeviceMap>& devices) 374 { 375 std::set<size_t> foundList; 376 377 std::string path = "/sys/bus/i2c/devices/i2c-" + std::to_string(i2cBus); 378 379 // For each file listed under the i2c device 380 // NOTE: This should be faster than just checking for each possible address 381 // path. 382 auto ec = std::error_code(); 383 for (const auto& p : fs::directory_iterator(path, ec)) 384 { 385 if (ec) 386 { 387 std::cerr << "directory_iterator err " << ec.message() << "\n"; 388 break; 389 } 390 const std::string node = p.path().string(); 391 std::smatch m; 392 bool found = 393 std::regex_match(node, m, std::regex(".+\\d+-([0-9abcdef]+$)")); 394 395 if (!found) 396 { 397 continue; 398 } 399 if (m.size() != 2) 400 { 401 std::cerr << "regex didn't capture\n"; 402 continue; 403 } 404 405 std::ssub_match subMatch = m[1]; 406 std::string addressString = subMatch.str(); 407 std::string_view addressStringView(addressString); 408 409 size_t address = 0; 410 std::from_chars(addressStringView.begin(), addressStringView.end(), 411 address, 16); 412 413 const std::string eeprom = node + "/eeprom"; 414 415 try 416 { 417 if (!fs::exists(eeprom)) 418 { 419 continue; 420 } 421 } 422 catch (...) 423 { 424 continue; 425 } 426 427 // There is an eeprom file at this address, it may have invalid 428 // contents, but we found it. 429 foundList.insert(address); 430 431 std::vector<uint8_t> device = processEeprom(i2cBus, address); 432 if (!device.empty()) 433 { 434 devices->emplace(address, device); 435 } 436 } 437 438 return foundList; 439 } 440 441 int getBusFRUs(int file, int first, int last, int bus, 442 std::shared_ptr<DeviceMap> devices, const bool& powerIsOn, 443 sdbusplus::asio::object_server& objServer) 444 { 445 std::future<int> future = std::async(std::launch::async, [&]() { 446 // NOTE: When reading the devices raw on the bus, it can interfere with 447 // the driver's ability to operate, therefore read eeproms first before 448 // scanning for devices without drivers. Several experiments were run 449 // and it was determined that if there were any devices on the bus 450 // before the eeprom was hit and read, the eeprom driver wouldn't open 451 // while the bus device was open. An experiment was not performed to see 452 // if this issue was resolved if the i2c bus device was closed, but 453 // hexdumps of the eeprom later were successful. 454 455 // Scan for i2c eeproms loaded on this bus. 456 std::set<size_t> skipList = findI2CEeproms(bus, devices); 457 std::set<size_t>& failedItems = failedAddresses[bus]; 458 std::set<size_t>& foundItems = fruAddresses[bus]; 459 foundItems.clear(); 460 461 auto busFind = busBlocklist.find(bus); 462 if (busFind != busBlocklist.end()) 463 { 464 if (busFind->second != std::nullopt) 465 { 466 for (const auto& address : *(busFind->second)) 467 { 468 skipList.insert(address); 469 } 470 } 471 } 472 473 std::set<size_t>* rootFailures = nullptr; 474 int rootBus = getRootBus(bus); 475 476 if (rootBus >= 0) 477 { 478 auto rootBusFind = busBlocklist.find(rootBus); 479 if (rootBusFind != busBlocklist.end()) 480 { 481 if (rootBusFind->second != std::nullopt) 482 { 483 for (const auto& rootAddress : *(rootBusFind->second)) 484 { 485 skipList.insert(rootAddress); 486 } 487 } 488 } 489 rootFailures = &(failedAddresses[rootBus]); 490 foundItems = fruAddresses[rootBus]; 491 } 492 493 constexpr int startSkipTargetAddr = 0; 494 constexpr int endSkipTargetAddr = 12; 495 496 for (int ii = first; ii <= last; ii++) 497 { 498 if (foundItems.contains(ii)) 499 { 500 continue; 501 } 502 if (skipList.contains(ii)) 503 { 504 continue; 505 } 506 // skipping since no device is present in this range 507 if (ii >= startSkipTargetAddr && ii <= endSkipTargetAddr) 508 { 509 continue; 510 } 511 // Set target address 512 if (ioctl(file, I2C_SLAVE, ii) < 0) 513 { 514 std::cerr << "device at bus " << bus << " address " << ii 515 << " busy\n"; 516 continue; 517 } 518 // probe 519 if (i2c_smbus_read_byte(file) < 0) 520 { 521 continue; 522 } 523 524 lg2::debug("something at bus {BUS}, addr {ADDR}", "BUS", bus, 525 "ADDR", ii); 526 527 makeProbeInterface(bus, ii, objServer); 528 529 if (failedItems.contains(ii)) 530 { 531 // if we failed to read it once, unlikely we can read it later 532 continue; 533 } 534 535 if (rootFailures != nullptr) 536 { 537 if (rootFailures->contains(ii)) 538 { 539 continue; 540 } 541 } 542 543 /* Check for Device type if it is 8 bit or 16 bit */ 544 std::optional<bool> is16Bit = isDevice16Bit(file, ii); 545 if (!is16Bit.has_value()) 546 { 547 std::cerr << "failed to read bus " << bus << " address " << ii 548 << "\n"; 549 if (powerIsOn) 550 { 551 failedItems.insert(ii); 552 } 553 continue; 554 } 555 bool is16BitBool{*is16Bit}; 556 557 auto readFunc = [is16BitBool, file, 558 ii](off_t offset, size_t length, uint8_t* outbuf) { 559 return readData(is16BitBool, false, file, ii, offset, length, 560 outbuf); 561 }; 562 FRUReader reader(std::move(readFunc)); 563 std::string errorMessage = 564 "bus " + std::to_string(bus) + " address " + std::to_string(ii); 565 std::pair<std::vector<uint8_t>, bool> pair = 566 readFRUContents(reader, errorMessage); 567 const bool foundHeader = pair.second; 568 569 if (!foundHeader && !is16BitBool) 570 { 571 // certain FRU eeproms require bytewise reading. 572 // otherwise garbage is read. e.g. SuperMicro PWS 920P-SQ 573 574 auto readFunc = 575 [is16BitBool, file, 576 ii](off_t offset, size_t length, uint8_t* outbuf) { 577 return readData(is16BitBool, true, file, ii, offset, 578 length, outbuf); 579 }; 580 FRUReader readerBytewise(std::move(readFunc)); 581 pair = readFRUContents(readerBytewise, errorMessage); 582 } 583 584 if (pair.first.empty()) 585 { 586 continue; 587 } 588 589 devices->emplace(ii, pair.first); 590 fruAddresses[bus].insert(ii); 591 } 592 return 1; 593 }); 594 std::future_status status = 595 future.wait_for(std::chrono::seconds(busTimeoutSeconds)); 596 if (status == std::future_status::timeout) 597 { 598 std::cerr << "Error reading bus " << bus << "\n"; 599 if (powerIsOn) 600 { 601 busBlocklist[bus] = std::nullopt; 602 } 603 close(file); 604 return -1; 605 } 606 607 close(file); 608 return future.get(); 609 } 610 611 void loadBlocklist(const char* path) 612 { 613 std::ifstream blocklistStream(path); 614 if (!blocklistStream.good()) 615 { 616 // File is optional. 617 std::cerr << "Cannot open blocklist file.\n\n"; 618 return; 619 } 620 621 nlohmann::json data = 622 nlohmann::json::parse(blocklistStream, nullptr, false); 623 if (data.is_discarded()) 624 { 625 std::cerr << "Illegal blocklist file detected, cannot validate JSON, " 626 "exiting\n"; 627 std::exit(EXIT_FAILURE); 628 } 629 630 // It's expected to have at least one field, "buses" that is an array of the 631 // buses by integer. Allow for future options to exclude further aspects, 632 // such as specific addresses or ranges. 633 if (data.type() != nlohmann::json::value_t::object) 634 { 635 std::cerr << "Illegal blocklist, expected to read dictionary\n"; 636 std::exit(EXIT_FAILURE); 637 } 638 639 // If buses field is missing, that's fine. 640 if (data.count("buses") == 1) 641 { 642 // Parse the buses array after a little validation. 643 auto buses = data.at("buses"); 644 if (buses.type() != nlohmann::json::value_t::array) 645 { 646 // Buses field present but invalid, therefore this is an error. 647 std::cerr << "Invalid contents for blocklist buses field\n"; 648 std::exit(EXIT_FAILURE); 649 } 650 651 // Catch exception here for type mis-match. 652 try 653 { 654 for (const auto& busIterator : buses) 655 { 656 // If bus and addresses field are missing, that's fine. 657 if (busIterator.contains("bus") && 658 busIterator.contains("addresses")) 659 { 660 auto busData = busIterator.at("bus"); 661 auto bus = busData.get<size_t>(); 662 663 auto addressData = busIterator.at("addresses"); 664 auto addresses = 665 addressData.get<std::set<std::string_view>>(); 666 667 auto& block = busBlocklist[bus].emplace(); 668 for (const auto& address : addresses) 669 { 670 size_t addressInt = 0; 671 bool fullMatch = false; 672 fromCharsWrapper(address.substr(2), addressInt, 673 fullMatch, 16); 674 block.insert(addressInt); 675 } 676 } 677 else 678 { 679 busBlocklist[busIterator.get<size_t>()] = std::nullopt; 680 } 681 } 682 } 683 catch (const nlohmann::detail::type_error& e) 684 { 685 // Type mis-match is a critical error. 686 std::cerr << "Invalid bus type: " << e.what() << "\n"; 687 std::exit(EXIT_FAILURE); 688 } 689 } 690 } 691 692 static void findI2CDevices(const std::vector<fs::path>& i2cBuses, 693 BusMap& busmap, const bool& powerIsOn, 694 sdbusplus::asio::object_server& objServer) 695 { 696 for (const auto& i2cBus : i2cBuses) 697 { 698 int bus = busStrToInt(i2cBus.string()); 699 700 if (bus < 0) 701 { 702 std::cerr << "Cannot translate " << i2cBus << " to int\n"; 703 continue; 704 } 705 auto busFind = busBlocklist.find(bus); 706 if (busFind != busBlocklist.end()) 707 { 708 if (busFind->second == std::nullopt) 709 { 710 continue; // Skip blocked busses. 711 } 712 } 713 int rootBus = getRootBus(bus); 714 auto rootBusFind = busBlocklist.find(rootBus); 715 if (rootBusFind != busBlocklist.end()) 716 { 717 if (rootBusFind->second == std::nullopt) 718 { 719 continue; 720 } 721 } 722 723 auto file = open(i2cBus.c_str(), O_RDWR); 724 if (file < 0) 725 { 726 std::cerr << "unable to open i2c device " << i2cBus.string() 727 << "\n"; 728 continue; 729 } 730 unsigned long funcs = 0; 731 732 if (ioctl(file, I2C_FUNCS, &funcs) < 0) 733 { 734 std::cerr 735 << "Error: Could not get the adapter functionality matrix bus " 736 << bus << "\n"; 737 close(file); 738 continue; 739 } 740 if (((funcs & I2C_FUNC_SMBUS_READ_BYTE) == 0U) || 741 ((I2C_FUNC_SMBUS_READ_I2C_BLOCK) == 0)) 742 { 743 std::cerr << "Error: Can't use SMBus Receive Byte command bus " 744 << bus << "\n"; 745 close(file); 746 continue; 747 } 748 auto& device = busmap[bus]; 749 device = std::make_shared<DeviceMap>(); 750 751 // i2cdetect by default uses the range 0x03 to 0x77, as 752 // this is what we have tested with, use this range. Could be 753 // changed in future. 754 lg2::debug("Scanning bus {BUS}", "BUS", bus); 755 756 // fd is closed in this function in case the bus locks up 757 getBusFRUs(file, 0x03, 0x77, bus, device, powerIsOn, objServer); 758 759 lg2::debug("Done scanning bus {BUS}", "BUS", bus); 760 } 761 } 762 763 // this class allows an async response after all i2c devices are discovered 764 struct FindDevicesWithCallback : 765 std::enable_shared_from_this<FindDevicesWithCallback> 766 { 767 FindDevicesWithCallback(const std::vector<fs::path>& i2cBuses, 768 BusMap& busmap, const bool& powerIsOn, 769 sdbusplus::asio::object_server& objServer, 770 std::function<void()>&& callback) : 771 _i2cBuses(i2cBuses), _busMap(busmap), _powerIsOn(powerIsOn), 772 _objServer(objServer), _callback(std::move(callback)) 773 {} 774 ~FindDevicesWithCallback() 775 { 776 _callback(); 777 } 778 void run() 779 { 780 findI2CDevices(_i2cBuses, _busMap, _powerIsOn, _objServer); 781 } 782 783 const std::vector<fs::path>& _i2cBuses; 784 BusMap& _busMap; 785 const bool& _powerIsOn; 786 sdbusplus::asio::object_server& _objServer; 787 std::function<void()> _callback; 788 }; 789 790 void addFruObjectToDbus( 791 std::vector<uint8_t>& device, 792 boost::container::flat_map< 793 std::pair<size_t, size_t>, 794 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap, 795 uint32_t bus, uint32_t address, size_t& unknownBusObjectCount, 796 const bool& powerIsOn, sdbusplus::asio::object_server& objServer, 797 std::shared_ptr<sdbusplus::asio::connection>& systemBus) 798 { 799 boost::container::flat_map<std::string, std::string> formattedFRU; 800 801 std::optional<std::string> optionalProductName = getProductName( 802 device, formattedFRU, bus, address, unknownBusObjectCount); 803 if (!optionalProductName) 804 { 805 std::cerr << "getProductName failed. product name is empty.\n"; 806 return; 807 } 808 809 std::string productName = 810 "/xyz/openbmc_project/FruDevice/" + optionalProductName.value(); 811 812 std::optional<int> index = findIndexForFRU(dbusInterfaceMap, productName); 813 if (index.has_value()) 814 { 815 productName += "_"; 816 productName += std::to_string(++(*index)); 817 } 818 819 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 820 objServer.add_interface(productName, "xyz.openbmc_project.FruDevice"); 821 dbusInterfaceMap[std::pair<size_t, size_t>(bus, address)] = iface; 822 823 for (auto& property : formattedFRU) 824 { 825 std::regex_replace(property.second.begin(), property.second.begin(), 826 property.second.end(), nonAsciiRegex, "_"); 827 if (property.second.empty()) 828 { 829 continue; 830 } 831 std::string key = 832 std::regex_replace(property.first, nonAsciiRegex, "_"); 833 834 // Allow FRU field update if ENABLE_FRU_UPDATE_PROPERTY is set. 835 if (isFieldEditable(property.first)) 836 { 837 std::string propertyName = property.first; 838 iface->register_property( 839 key, property.second + '\0', 840 [bus, address, propertyName, &dbusInterfaceMap, 841 &unknownBusObjectCount, &powerIsOn, &objServer, 842 &systemBus](const std::string& req, std::string& resp) { 843 if (strcmp(req.c_str(), resp.c_str()) != 0) 844 { 845 // call the method which will update 846 if (updateFRUProperty(req, bus, address, propertyName, 847 dbusInterfaceMap, 848 unknownBusObjectCount, powerIsOn, 849 objServer, systemBus)) 850 { 851 resp = req; 852 } 853 else 854 { 855 throw std::invalid_argument( 856 "FRU property update failed."); 857 } 858 } 859 return 1; 860 }); 861 } 862 else if (!iface->register_property(key, property.second + '\0')) 863 { 864 std::cerr << "illegal key: " << key << "\n"; 865 } 866 lg2::debug("parsed FRU property: {FIRST}: {SECOND}", "FIRST", 867 property.first, "SECOND", property.second); 868 } 869 870 // baseboard will be 0, 0 871 iface->register_property("BUS", bus); 872 iface->register_property("ADDRESS", address); 873 874 iface->initialize(); 875 } 876 877 static bool readBaseboardFRU(std::vector<uint8_t>& baseboardFRU) 878 { 879 // try to read baseboard fru from file 880 std::ifstream baseboardFRUFile(baseboardFruLocation, std::ios::binary); 881 if (baseboardFRUFile.good()) 882 { 883 baseboardFRUFile.seekg(0, std::ios_base::end); 884 size_t fileSize = static_cast<size_t>(baseboardFRUFile.tellg()); 885 baseboardFRU.resize(fileSize); 886 baseboardFRUFile.seekg(0, std::ios_base::beg); 887 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 888 char* charOffset = reinterpret_cast<char*>(baseboardFRU.data()); 889 baseboardFRUFile.read(charOffset, fileSize); 890 } 891 else 892 { 893 return false; 894 } 895 return true; 896 } 897 898 bool writeFRU(uint8_t bus, uint8_t address, const std::vector<uint8_t>& fru) 899 { 900 boost::container::flat_map<std::string, std::string> tmp; 901 if (fru.size() > maxFruSize) 902 { 903 std::cerr << "Invalid fru.size() during writeFRU\n"; 904 return false; 905 } 906 // verify legal fru by running it through fru parsing logic 907 if (formatIPMIFRU(fru, tmp) != resCodes::resOK) 908 { 909 std::cerr << "Invalid fru format during writeFRU\n"; 910 return false; 911 } 912 // baseboard fru 913 if (bus == 0 && address == 0) 914 { 915 std::ofstream file(baseboardFruLocation, std::ios_base::binary); 916 if (!file.good()) 917 { 918 std::cerr << "Error opening file " << baseboardFruLocation << "\n"; 919 throw DBusInternalError(); 920 return false; 921 } 922 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 923 const char* charOffset = reinterpret_cast<const char*>(fru.data()); 924 file.write(charOffset, fru.size()); 925 return file.good(); 926 } 927 928 if (hasEepromFile(bus, address)) 929 { 930 auto path = getEepromPath(bus, address); 931 off_t offset = 0; 932 933 int eeprom = open(path.c_str(), O_RDWR | O_CLOEXEC); 934 if (eeprom < 0) 935 { 936 std::cerr << "unable to open i2c device " << path << "\n"; 937 throw DBusInternalError(); 938 return false; 939 } 940 941 std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{}; 942 std::string errorMessage = "eeprom at " + std::to_string(bus) + 943 " address " + std::to_string(address); 944 auto readFunc = [eeprom](off_t offset, size_t length, uint8_t* outbuf) { 945 return readFromEeprom(eeprom, offset, length, outbuf); 946 }; 947 FRUReader reader(std::move(readFunc)); 948 949 if (!findFRUHeader(reader, errorMessage, blockData, offset)) 950 { 951 offset = 0; 952 } 953 954 if (lseek(eeprom, offset, SEEK_SET) < 0) 955 { 956 std::cerr << "Unable to seek to offset " << offset 957 << " in device: " << path << "\n"; 958 close(eeprom); 959 throw DBusInternalError(); 960 } 961 962 ssize_t writtenBytes = write(eeprom, fru.data(), fru.size()); 963 if (writtenBytes < 0) 964 { 965 std::cerr << "unable to write to i2c device " << path << "\n"; 966 close(eeprom); 967 throw DBusInternalError(); 968 return false; 969 } 970 971 close(eeprom); 972 return true; 973 } 974 975 std::string i2cBus = "/dev/i2c-" + std::to_string(bus); 976 977 int file = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC); 978 if (file < 0) 979 { 980 std::cerr << "unable to open i2c device " << i2cBus << "\n"; 981 throw DBusInternalError(); 982 return false; 983 } 984 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) 985 { 986 std::cerr << "unable to set device address\n"; 987 close(file); 988 throw DBusInternalError(); 989 return false; 990 } 991 992 constexpr const size_t retryMax = 2; 993 uint16_t index = 0; 994 size_t retries = retryMax; 995 while (index < fru.size()) 996 { 997 if (((index != 0U) && ((index % (maxEepromPageIndex + 1)) == 0)) && 998 (retries == retryMax)) 999 { 1000 // The 4K EEPROM only uses the A2 and A1 device address bits 1001 // with the third bit being a memory page address bit. 1002 if (ioctl(file, I2C_SLAVE_FORCE, ++address) < 0) 1003 { 1004 std::cerr << "unable to set device address\n"; 1005 close(file); 1006 throw DBusInternalError(); 1007 return false; 1008 } 1009 } 1010 1011 if (i2c_smbus_write_byte_data(file, static_cast<uint8_t>(index), 1012 fru[index]) < 0) 1013 { 1014 if ((retries--) == 0U) 1015 { 1016 std::cerr << "error writing fru: " << strerror(errno) << "\n"; 1017 close(file); 1018 throw DBusInternalError(); 1019 return false; 1020 } 1021 } 1022 else 1023 { 1024 retries = retryMax; 1025 index++; 1026 } 1027 // most eeproms require 5-10ms between writes 1028 std::this_thread::sleep_for(std::chrono::milliseconds(10)); 1029 } 1030 close(file); 1031 return true; 1032 } 1033 1034 void rescanOneBus( 1035 BusMap& busmap, uint16_t busNum, 1036 boost::container::flat_map< 1037 std::pair<size_t, size_t>, 1038 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap, 1039 bool dbusCall, size_t& unknownBusObjectCount, const bool& powerIsOn, 1040 sdbusplus::asio::object_server& objServer, 1041 std::shared_ptr<sdbusplus::asio::connection>& systemBus) 1042 { 1043 for (auto device = foundDevices.begin(); device != foundDevices.end();) 1044 { 1045 if (device->first.first == static_cast<size_t>(busNum)) 1046 { 1047 objServer.remove_interface(device->second); 1048 device = foundDevices.erase(device); 1049 } 1050 else 1051 { 1052 device++; 1053 } 1054 } 1055 1056 fs::path busPath = fs::path("/dev/i2c-" + std::to_string(busNum)); 1057 if (!fs::exists(busPath)) 1058 { 1059 if (dbusCall) 1060 { 1061 std::cerr << "Unable to access i2c bus " << static_cast<int>(busNum) 1062 << "\n"; 1063 throw std::invalid_argument("Invalid Bus."); 1064 } 1065 return; 1066 } 1067 1068 std::vector<fs::path> i2cBuses; 1069 i2cBuses.emplace_back(busPath); 1070 1071 auto scan = std::make_shared<FindDevicesWithCallback>( 1072 i2cBuses, busmap, powerIsOn, objServer, 1073 [busNum, &busmap, &dbusInterfaceMap, &unknownBusObjectCount, &powerIsOn, 1074 &objServer, &systemBus]() { 1075 for (auto busIface = dbusInterfaceMap.begin(); 1076 busIface != dbusInterfaceMap.end();) 1077 { 1078 if (busIface->first.first == static_cast<size_t>(busNum)) 1079 { 1080 objServer.remove_interface(busIface->second); 1081 busIface = dbusInterfaceMap.erase(busIface); 1082 } 1083 else 1084 { 1085 busIface++; 1086 } 1087 } 1088 auto found = busmap.find(busNum); 1089 if (found == busmap.end() || found->second == nullptr) 1090 { 1091 return; 1092 } 1093 for (auto& device : *(found->second)) 1094 { 1095 addFruObjectToDbus(device.second, dbusInterfaceMap, 1096 static_cast<uint32_t>(busNum), device.first, 1097 unknownBusObjectCount, powerIsOn, objServer, 1098 systemBus); 1099 } 1100 }); 1101 scan->run(); 1102 } 1103 1104 void rescanBusses( 1105 BusMap& busmap, 1106 boost::container::flat_map< 1107 std::pair<size_t, size_t>, 1108 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap, 1109 size_t& unknownBusObjectCount, const bool& powerIsOn, 1110 sdbusplus::asio::object_server& objServer, 1111 std::shared_ptr<sdbusplus::asio::connection>& systemBus) 1112 { 1113 static boost::asio::steady_timer timer(io); 1114 timer.expires_after(std::chrono::seconds(1)); 1115 1116 // setup an async wait in case we get flooded with requests 1117 timer.async_wait([&](const boost::system::error_code& ec) { 1118 if (ec == boost::asio::error::operation_aborted) 1119 { 1120 return; 1121 } 1122 1123 if (ec) 1124 { 1125 std::cerr << "Error in timer: " << ec.message() << "\n"; 1126 return; 1127 } 1128 1129 auto devDir = fs::path("/dev/"); 1130 std::vector<fs::path> i2cBuses; 1131 1132 boost::container::flat_map<size_t, fs::path> busPaths; 1133 if (!getI2cDevicePaths(devDir, busPaths)) 1134 { 1135 std::cerr << "unable to find i2c devices\n"; 1136 return; 1137 } 1138 1139 for (const auto& busPath : busPaths) 1140 { 1141 i2cBuses.emplace_back(busPath.second); 1142 } 1143 1144 busmap.clear(); 1145 for (auto& [pair, interface] : foundDevices) 1146 { 1147 objServer.remove_interface(interface); 1148 } 1149 foundDevices.clear(); 1150 1151 auto scan = std::make_shared<FindDevicesWithCallback>( 1152 i2cBuses, busmap, powerIsOn, objServer, [&]() { 1153 for (auto& busIface : dbusInterfaceMap) 1154 { 1155 objServer.remove_interface(busIface.second); 1156 } 1157 1158 dbusInterfaceMap.clear(); 1159 unknownBusObjectCount = 0; 1160 1161 // todo, get this from a more sensable place 1162 std::vector<uint8_t> baseboardFRU; 1163 if (readBaseboardFRU(baseboardFRU)) 1164 { 1165 // If no device on i2c bus 0, the insertion will happen. 1166 auto bus0 = 1167 busmap.try_emplace(0, std::make_shared<DeviceMap>()); 1168 bus0.first->second->emplace(0, baseboardFRU); 1169 } 1170 for (auto& devicemap : busmap) 1171 { 1172 for (auto& device : *devicemap.second) 1173 { 1174 addFruObjectToDbus(device.second, dbusInterfaceMap, 1175 devicemap.first, device.first, 1176 unknownBusObjectCount, powerIsOn, 1177 objServer, systemBus); 1178 } 1179 } 1180 }); 1181 scan->run(); 1182 }); 1183 } 1184 1185 // Details with example of Asset Tag Update 1186 // To find location of Product Info Area asset tag as per FRU specification 1187 // 1. Find product Info area starting offset (*8 - as header will be in 1188 // multiple of 8 bytes). 1189 // 2. Skip 3 bytes of product info area (like format version, area length, 1190 // and language code). 1191 // 3. Traverse manufacturer name, product name, product version, & product 1192 // serial number, by reading type/length code to reach the Asset Tag. 1193 // 4. Update the Asset Tag, reposition the product Info area in multiple of 1194 // 8 bytes. Update the Product area length and checksum. 1195 1196 bool updateFRUProperty( 1197 const std::string& updatePropertyReq, uint32_t bus, uint32_t address, 1198 const std::string& propertyName, 1199 boost::container::flat_map< 1200 std::pair<size_t, size_t>, 1201 std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap, 1202 size_t& unknownBusObjectCount, const bool& powerIsOn, 1203 sdbusplus::asio::object_server& objServer, 1204 std::shared_ptr<sdbusplus::asio::connection>& systemBus) 1205 { 1206 size_t updatePropertyReqLen = updatePropertyReq.length(); 1207 if (updatePropertyReqLen == 1 || updatePropertyReqLen > 63) 1208 { 1209 std::cerr 1210 << "FRU field data cannot be of 1 char or more than 63 chars. " 1211 "Invalid Length " 1212 << updatePropertyReqLen << "\n"; 1213 return false; 1214 } 1215 1216 std::vector<uint8_t> fruData; 1217 1218 if (!getFruData(fruData, bus, address)) 1219 { 1220 std::cerr << "Failure getting FRU Data \n"; 1221 return false; 1222 } 1223 1224 struct FruArea fruAreaParams{}; 1225 1226 if (!findFruAreaLocationAndField(fruData, propertyName, fruAreaParams)) 1227 { 1228 std::cerr << "findFruAreaLocationAndField failed \n"; 1229 return false; 1230 } 1231 1232 std::vector<uint8_t> restFRUAreaFieldsData; 1233 if (!copyRestFRUArea(fruData, propertyName, fruAreaParams, 1234 restFRUAreaFieldsData)) 1235 { 1236 std::cerr << "copyRestFRUArea failed \n"; 1237 return false; 1238 } 1239 1240 // Push post update fru areas if any 1241 unsigned int nextFRUAreaLoc = 0; 1242 for (fruAreas nextFRUArea = fruAreas::fruAreaInternal; 1243 nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea) 1244 { 1245 unsigned int fruAreaLoc = 1246 fruData[getHeaderAreaFieldOffset(nextFRUArea)] * fruBlockSize; 1247 if ((fruAreaLoc > fruAreaParams.restFieldsEnd) && 1248 ((nextFRUAreaLoc == 0) || (fruAreaLoc < nextFRUAreaLoc))) 1249 { 1250 nextFRUAreaLoc = fruAreaLoc; 1251 } 1252 } 1253 std::vector<uint8_t> restFRUAreasData; 1254 if (nextFRUAreaLoc != 0U) 1255 { 1256 std::copy_n(fruData.begin() + nextFRUAreaLoc, 1257 fruData.size() - nextFRUAreaLoc, 1258 std::back_inserter(restFRUAreasData)); 1259 } 1260 1261 // check FRU area size 1262 size_t fruAreaDataSize = 1263 ((fruAreaParams.updateFieldLoc - fruAreaParams.start + 1) + 1264 restFRUAreaFieldsData.size()); 1265 size_t fruAreaAvailableSize = fruAreaParams.size - fruAreaDataSize; 1266 if ((updatePropertyReqLen + 1) > fruAreaAvailableSize) 1267 { 1268 #ifdef ENABLE_FRU_AREA_RESIZE 1269 size_t newFRUAreaSize = fruAreaDataSize + updatePropertyReqLen + 1; 1270 // round size to 8-byte blocks 1271 newFRUAreaSize = 1272 ((newFRUAreaSize - 1) / fruBlockSize + 1) * fruBlockSize; 1273 size_t newFRUDataSize = 1274 fruData.size() + newFRUAreaSize - fruAreaParams.size; 1275 fruData.resize(newFRUDataSize); 1276 fruAreaParams.size = newFRUAreaSize; 1277 fruAreaParams.end = fruAreaParams.start + fruAreaParams.size; 1278 #else 1279 std::cerr << "FRU field length: " << updatePropertyReqLen + 1 1280 << " should not be greater than available FRU area size: " 1281 << fruAreaAvailableSize << "\n"; 1282 return false; 1283 #endif // ENABLE_FRU_AREA_RESIZE 1284 } 1285 1286 // write new requested property field length and data 1287 constexpr uint8_t newTypeLenMask = 0xC0; 1288 fruData[fruAreaParams.updateFieldLoc] = 1289 static_cast<uint8_t>(updatePropertyReqLen | newTypeLenMask); 1290 fruAreaParams.updateFieldLoc++; 1291 std::copy(updatePropertyReq.begin(), updatePropertyReq.end(), 1292 fruData.begin() + fruAreaParams.updateFieldLoc); 1293 1294 // Copy remaining data to main fru area - post updated fru field vector 1295 fruAreaParams.restFieldsLoc = 1296 fruAreaParams.updateFieldLoc + updatePropertyReqLen; 1297 size_t fruAreaDataEnd = 1298 fruAreaParams.restFieldsLoc + restFRUAreaFieldsData.size(); 1299 1300 std::copy(restFRUAreaFieldsData.begin(), restFRUAreaFieldsData.end(), 1301 fruData.begin() + fruAreaParams.restFieldsLoc); 1302 1303 // Update final fru with new fru area length and checksum 1304 unsigned int nextFRUAreaNewLoc = updateFRUAreaLenAndChecksum( 1305 fruData, fruAreaParams.start, fruAreaDataEnd, fruAreaParams.end); 1306 1307 #ifdef ENABLE_FRU_AREA_RESIZE 1308 ++nextFRUAreaNewLoc; 1309 ssize_t nextFRUAreaOffsetDiff = 1310 (nextFRUAreaNewLoc - nextFRUAreaLoc) / fruBlockSize; 1311 // Append rest FRU Areas if size changed and there were other sections after 1312 // updated one 1313 if ((nextFRUAreaOffsetDiff != 0) && (nextFRUAreaLoc != 0U)) 1314 { 1315 std::copy(restFRUAreasData.begin(), restFRUAreasData.end(), 1316 fruData.begin() + nextFRUAreaNewLoc); 1317 // Update Common Header 1318 for (fruAreas nextFRUArea = fruAreas::fruAreaInternal; 1319 nextFRUArea <= fruAreas::fruAreaMultirecord; ++nextFRUArea) 1320 { 1321 unsigned int fruAreaOffsetField = 1322 getHeaderAreaFieldOffset(nextFRUArea); 1323 size_t curFRUAreaOffset = fruData[fruAreaOffsetField]; 1324 if (curFRUAreaOffset > fruAreaParams.end) 1325 { 1326 fruData[fruAreaOffsetField] = static_cast<int8_t>( 1327 curFRUAreaOffset + nextFRUAreaOffsetDiff); 1328 } 1329 } 1330 // Calculate new checksum 1331 std::vector<uint8_t> headerFRUData; 1332 std::copy_n(fruData.begin(), 7, std::back_inserter(headerFRUData)); 1333 size_t checksumVal = calculateChecksum(headerFRUData); 1334 fruData[7] = static_cast<uint8_t>(checksumVal); 1335 // fill zeros if FRU Area size decreased 1336 if (nextFRUAreaOffsetDiff < 0) 1337 { 1338 std::fill(fruData.begin() + nextFRUAreaNewLoc + 1339 restFRUAreasData.size(), 1340 fruData.end(), 0); 1341 } 1342 } 1343 #else 1344 // this is to avoid "unused variable" warning 1345 (void)nextFRUAreaNewLoc; 1346 #endif // ENABLE_FRU_AREA_RESIZE 1347 if (fruData.empty()) 1348 { 1349 return false; 1350 } 1351 1352 if (!writeFRU(static_cast<uint8_t>(bus), static_cast<uint8_t>(address), 1353 fruData)) 1354 { 1355 return false; 1356 } 1357 1358 // Rescan the bus so that GetRawFru dbus-call fetches updated values 1359 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn, 1360 objServer, systemBus); 1361 return true; 1362 } 1363 1364 int main() 1365 { 1366 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 1367 sdbusplus::asio::object_server objServer(systemBus); 1368 1369 static size_t unknownBusObjectCount = 0; 1370 static bool powerIsOn = false; 1371 auto devDir = fs::path("/dev/"); 1372 auto matchString = std::string(R"(i2c-\d+$)"); 1373 std::vector<fs::path> i2cBuses; 1374 1375 if (!findFiles(devDir, matchString, i2cBuses)) 1376 { 1377 std::cerr << "unable to find i2c devices\n"; 1378 return 1; 1379 } 1380 1381 // check for and load blocklist with initial buses. 1382 loadBlocklist(blocklistPath); 1383 1384 systemBus->request_name("xyz.openbmc_project.FruDevice"); 1385 1386 // this is a map with keys of pair(bus number, address) and values of 1387 // the object on dbus 1388 boost::container::flat_map<std::pair<size_t, size_t>, 1389 std::shared_ptr<sdbusplus::asio::dbus_interface>> 1390 dbusInterfaceMap; 1391 1392 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 1393 objServer.add_interface("/xyz/openbmc_project/FruDevice", 1394 "xyz.openbmc_project.FruDeviceManager"); 1395 1396 iface->register_method("ReScan", [&]() { 1397 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn, 1398 objServer, systemBus); 1399 }); 1400 1401 iface->register_method("ReScanBus", [&](uint16_t bus) { 1402 rescanOneBus(busMap, bus, dbusInterfaceMap, true, unknownBusObjectCount, 1403 powerIsOn, objServer, systemBus); 1404 }); 1405 1406 iface->register_method("GetRawFru", getFRUInfo); 1407 1408 iface->register_method( 1409 "WriteFru", [&](const uint16_t bus, const uint8_t address, 1410 const std::vector<uint8_t>& data) { 1411 if (!writeFRU(bus, address, data)) 1412 { 1413 throw std::invalid_argument("Invalid Arguments."); 1414 return; 1415 } 1416 // schedule rescan on success 1417 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, 1418 powerIsOn, objServer, systemBus); 1419 }); 1420 iface->initialize(); 1421 1422 std::function<void(sdbusplus::message_t & message)> eventHandler = 1423 [&](sdbusplus::message_t& message) { 1424 std::string objectName; 1425 boost::container::flat_map< 1426 std::string, 1427 std::variant<std::string, bool, int64_t, uint64_t, double>> 1428 values; 1429 message.read(objectName, values); 1430 auto findState = values.find("CurrentHostState"); 1431 if (findState != values.end()) 1432 { 1433 if (std::get<std::string>(findState->second) == 1434 "xyz.openbmc_project.State.Host.HostState.Running") 1435 { 1436 powerIsOn = true; 1437 } 1438 } 1439 1440 if (powerIsOn) 1441 { 1442 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, 1443 powerIsOn, objServer, systemBus); 1444 } 1445 }; 1446 1447 sdbusplus::bus::match_t powerMatch = sdbusplus::bus::match_t( 1448 static_cast<sdbusplus::bus_t&>(*systemBus), 1449 "type='signal',interface='org.freedesktop.DBus.Properties',path='/xyz/" 1450 "openbmc_project/state/" 1451 "host0',arg0='xyz.openbmc_project.State.Host'", 1452 eventHandler); 1453 1454 int fd = inotify_init(); 1455 inotify_add_watch(fd, i2CDevLocation, IN_CREATE | IN_MOVED_TO | IN_DELETE); 1456 std::array<char, 4096> readBuffer{}; 1457 // monitor for new i2c devices 1458 boost::asio::posix::stream_descriptor dirWatch(io, fd); 1459 std::function<void(const boost::system::error_code, std::size_t)> 1460 watchI2cBusses = [&](const boost::system::error_code& ec, 1461 std::size_t bytesTransferred) { 1462 if (ec) 1463 { 1464 std::cout << "Callback Error " << ec << "\n"; 1465 return; 1466 } 1467 size_t index = 0; 1468 while ((index + sizeof(inotify_event)) <= bytesTransferred) 1469 { 1470 const char* p = &readBuffer[index]; 1471 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 1472 const auto* iEvent = reinterpret_cast<const inotify_event*>(p); 1473 switch (iEvent->mask) 1474 { 1475 case IN_CREATE: 1476 case IN_MOVED_TO: 1477 case IN_DELETE: 1478 { 1479 std::string_view name(&iEvent->name[0], iEvent->len); 1480 if (name.starts_with("i2c")) 1481 { 1482 int bus = busStrToInt(name); 1483 if (bus < 0) 1484 { 1485 std::cerr 1486 << "Could not parse bus " << name << "\n"; 1487 continue; 1488 } 1489 int rootBus = getRootBus(bus); 1490 if (rootBus >= 0) 1491 { 1492 rescanOneBus(busMap, 1493 static_cast<uint16_t>(rootBus), 1494 dbusInterfaceMap, false, 1495 unknownBusObjectCount, powerIsOn, 1496 objServer, systemBus); 1497 } 1498 rescanOneBus(busMap, static_cast<uint16_t>(bus), 1499 dbusInterfaceMap, false, 1500 unknownBusObjectCount, powerIsOn, 1501 objServer, systemBus); 1502 } 1503 } 1504 break; 1505 default: 1506 break; 1507 } 1508 index += sizeof(inotify_event) + iEvent->len; 1509 } 1510 1511 dirWatch.async_read_some(boost::asio::buffer(readBuffer), 1512 watchI2cBusses); 1513 }; 1514 1515 dirWatch.async_read_some(boost::asio::buffer(readBuffer), watchI2cBusses); 1516 // run the initial scan 1517 rescanBusses(busMap, dbusInterfaceMap, unknownBusObjectCount, powerIsOn, 1518 objServer, systemBus); 1519 1520 io.run(); 1521 return 0; 1522 } 1523