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