1 /* 2 // Copyright (c) 2019 Intel Corporation 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 #include "utils.hpp" 18 19 #include <boost/algorithm/string/replace.hpp> 20 #include <boost/asio/steady_timer.hpp> 21 #include <boost/container/flat_set.hpp> 22 #include <filesystem> 23 #include <fstream> 24 #include <iostream> 25 #include <sdbusplus/asio/connection.hpp> 26 #include <sdbusplus/asio/object_server.hpp> 27 #include <sdbusplus/bus/match.hpp> 28 #include <string> 29 #include <utility> 30 31 extern "C" { 32 #include <i2c/smbus.h> 33 #include <linux/i2c-dev.h> 34 } 35 36 constexpr const char* configType = 37 "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD"; 38 constexpr const char* busName = "xyz.openbmc_project.HsbpManager"; 39 40 constexpr size_t scanRateSeconds = 5; 41 constexpr size_t maxDrives = 8; // only 1 byte alloted 42 43 boost::asio::io_context io; 44 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 45 sdbusplus::asio::object_server objServer(conn); 46 47 static std::string zeroPad(const uint8_t val) 48 { 49 std::ostringstream version; 50 version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val); 51 return version.str(); 52 } 53 54 struct Mux 55 { 56 Mux(size_t busIn, size_t addressIn, size_t channelsIn, size_t indexIn) : 57 bus(busIn), address(addressIn), channels(channelsIn), index(indexIn) 58 { 59 } 60 size_t bus; 61 size_t address; 62 size_t channels; 63 size_t index; 64 65 // to sort in the flat set 66 bool operator<(const Mux& rhs) const 67 { 68 return index < rhs.index; 69 } 70 }; 71 72 enum class BlinkPattern : uint8_t 73 { 74 off = 0x0, 75 error = 0x2, 76 terminate = 0x3 77 }; 78 79 struct Led : std::enable_shared_from_this<Led> 80 { 81 // led pattern addresses start at 0x10 82 Led(const std::string& path, size_t index, int fd) : 83 address(static_cast<uint8_t>(index + 0x10)), file(fd), 84 ledInterface(objServer.add_interface(path, ledGroup::interface)) 85 { 86 if (index >= maxDrives) 87 { 88 throw std::runtime_error("Invalid drive index"); 89 } 90 91 if (!set(BlinkPattern::off)) 92 { 93 std::cerr << "Cannot initialize LED " << path << "\n"; 94 } 95 } 96 97 // this has to be called outside the constructor for shared_from_this to 98 // work 99 void createInterface(void) 100 { 101 std::shared_ptr<Led> self = shared_from_this(); 102 103 ledInterface->register_property( 104 ledGroup::asserted, false, [self](const bool req, bool& val) { 105 if (req == val) 106 { 107 return 1; 108 } 109 110 if (!isPowerOn()) 111 { 112 std::cerr << "Can't change blink state when power is off\n"; 113 throw std::runtime_error( 114 "Can't change blink state when power is off"); 115 } 116 BlinkPattern pattern = 117 req ? BlinkPattern::error : BlinkPattern::terminate; 118 if (!self->set(pattern)) 119 { 120 std::cerr << "Can't change blink pattern\n"; 121 throw std::runtime_error("Cannot set blink pattern"); 122 } 123 val = req; 124 return 1; 125 }); 126 ledInterface->initialize(); 127 } 128 129 virtual ~Led() 130 { 131 objServer.remove_interface(ledInterface); 132 } 133 134 bool set(BlinkPattern pattern) 135 { 136 int ret = i2c_smbus_write_byte_data(file, address, 137 static_cast<uint8_t>(pattern)); 138 return ret >= 0; 139 } 140 141 uint8_t address; 142 int file; 143 std::shared_ptr<sdbusplus::asio::dbus_interface> ledInterface; 144 }; 145 146 struct Drive 147 { 148 Drive(size_t driveIndex, bool present, bool isOperational, bool nvme, 149 bool rebuilding) : 150 isNvme(nvme), 151 isPresent(present) 152 { 153 constexpr const char* basePath = 154 "/xyz/openbmc_project/inventory/item/drive/Drive_"; 155 itemIface = objServer.add_interface( 156 basePath + std::to_string(driveIndex), inventory::interface); 157 itemIface->register_property("Present", isPresent); 158 itemIface->register_property("PrettyName", 159 "Drive " + std::to_string(driveIndex)); 160 itemIface->initialize(); 161 operationalIface = objServer.add_interface( 162 itemIface->get_object_path(), 163 "xyz.openbmc_project.State.Decorator.OperationalStatus"); 164 165 operationalIface->register_property( 166 "Functional", isOperational, 167 [this](const bool req, bool& property) { 168 if (!isPresent) 169 { 170 return 0; 171 } 172 if (property == req) 173 { 174 return 1; 175 } 176 property = req; 177 if (req) 178 { 179 clearFailed(); 180 return 1; 181 } 182 markFailed(); 183 return 1; 184 }); 185 186 operationalIface->initialize(); 187 rebuildingIface = objServer.add_interface( 188 itemIface->get_object_path(), "xyz.openbmc_project.State.Drive"); 189 rebuildingIface->register_property("Rebuilding", rebuilding); 190 rebuildingIface->initialize(); 191 driveIface = 192 objServer.add_interface(itemIface->get_object_path(), 193 "xyz.openbmc_project.Inventory.Item.Drive"); 194 driveIface->initialize(); 195 associations = objServer.add_interface(itemIface->get_object_path(), 196 association::interface); 197 associations->register_property("Associations", 198 std::vector<Association>{}); 199 associations->initialize(); 200 201 if (isPresent && (!isOperational || rebuilding)) 202 { 203 markFailed(); 204 } 205 } 206 virtual ~Drive() 207 { 208 objServer.remove_interface(itemIface); 209 objServer.remove_interface(operationalIface); 210 objServer.remove_interface(rebuildingIface); 211 objServer.remove_interface(assetIface); 212 objServer.remove_interface(driveIface); 213 objServer.remove_interface(associations); 214 } 215 216 void createAsset( 217 const boost::container::flat_map<std::string, std::string>& data) 218 { 219 if (assetIface != nullptr) 220 { 221 return; 222 } 223 assetIface = objServer.add_interface( 224 itemIface->get_object_path(), 225 "xyz.openbmc_project.Inventory.Decorator.Asset"); 226 for (const auto& [key, value] : data) 227 { 228 assetIface->register_property(key, value); 229 } 230 assetIface->initialize(); 231 } 232 233 void markFailed(void) 234 { 235 // todo: maybe look this up via mapper 236 constexpr const char* globalInventoryPath = 237 "/xyz/openbmc_project/CallbackManager"; 238 239 if (!isPresent) 240 { 241 return; 242 } 243 244 operationalIface->set_property("Functional", false); 245 std::vector<Association> warning = { 246 {"", "warning", globalInventoryPath}}; 247 associations->set_property("Associations", warning); 248 } 249 250 void clearFailed(void) 251 { 252 operationalIface->set_property("Functional", true); 253 associations->set_property("Associations", std::vector<Association>{}); 254 } 255 256 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface; 257 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface; 258 std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface; 259 std::shared_ptr<sdbusplus::asio::dbus_interface> assetIface; 260 std::shared_ptr<sdbusplus::asio::dbus_interface> driveIface; 261 std::shared_ptr<sdbusplus::asio::dbus_interface> associations; 262 263 bool isNvme; 264 bool isPresent; 265 }; 266 267 struct Backplane 268 { 269 270 Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn, 271 const std::string& nameIn) : 272 bus(busIn), 273 address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn), 274 timer(std::make_shared<boost::asio::steady_timer>(io)), 275 muxes(std::make_shared<boost::container::flat_set<Mux>>()) 276 { 277 } 278 void run() 279 { 280 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), 281 O_RDWR | O_CLOEXEC); 282 if (file < 0) 283 { 284 std::cerr << "unable to open bus " << bus << "\n"; 285 return; 286 } 287 288 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) 289 { 290 std::cerr << "unable to set address to " << address << "\n"; 291 return; 292 } 293 294 if (!getPresent()) 295 { 296 std::cerr << "Cannot detect CPLD\n"; 297 return; 298 } 299 300 getBootVer(bootVer); 301 getFPGAVer(fpgaVer); 302 getSecurityRev(securityRev); 303 std::string dbusName = boost::replace_all_copy(name, " ", "_"); 304 hsbpItemIface = objServer.add_interface( 305 "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName, 306 inventory::interface); 307 hsbpItemIface->register_property("Present", true); 308 hsbpItemIface->register_property("PrettyName", name); 309 hsbpItemIface->initialize(); 310 311 versionIface = 312 objServer.add_interface(hsbpItemIface->get_object_path(), 313 "xyz.openbmc_project.Software.Version"); 314 versionIface->register_property("Version", zeroPad(bootVer) + "." + 315 zeroPad(fpgaVer) + "." + 316 zeroPad(securityRev)); 317 versionIface->register_property( 318 "Purpose", 319 std::string( 320 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP")); 321 versionIface->initialize(); 322 getPresence(presence); 323 getIFDET(ifdet); 324 325 createDrives(); 326 327 runTimer(); 328 } 329 330 void runTimer() 331 { 332 timer->expires_after(std::chrono::seconds(scanRateSeconds)); 333 timer->async_wait([this](boost::system::error_code ec) { 334 if (ec == boost::asio::error::operation_aborted) 335 { 336 // we're being destroyed 337 return; 338 } 339 else if (ec) 340 { 341 std::cerr << "timer error " << ec.message() << "\n"; 342 return; 343 } 344 345 if (!isPowerOn()) 346 { 347 // can't access hsbp when power is off 348 runTimer(); 349 return; 350 } 351 uint8_t curPresence = 0; 352 uint8_t curIFDET = 0; 353 uint8_t curFailed = 0; 354 uint8_t curRebuild = 0; 355 356 getPresence(curPresence); 357 getIFDET(curIFDET); 358 getFailed(curFailed); 359 getRebuild(curRebuild); 360 361 if (curPresence != presence || curIFDET != ifdet || 362 curFailed != failed || curRebuild != rebuilding) 363 { 364 presence = curPresence; 365 ifdet = curIFDET; 366 failed = curFailed; 367 rebuilding = curRebuild; 368 updateDrives(); 369 } 370 runTimer(); 371 }); 372 } 373 374 void createDrives() 375 { 376 uint8_t nvme = ifdet ^ presence; 377 for (size_t ii = 0; ii < maxDrives; ii++) 378 { 379 bool isNvme = nvme & (1 << ii); 380 bool isPresent = isNvme || (presence & (1 << ii)); 381 bool isFailed = !isPresent || failed & (1 << ii); 382 bool isRebuilding = !isPresent && (rebuilding & (1 << ii)); 383 384 // +1 to convert from 0 based to 1 based 385 size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1; 386 Drive& drive = drives.emplace_back(driveIndex, isPresent, !isFailed, 387 isNvme, isRebuilding); 388 std::shared_ptr<Led> led = leds.emplace_back(std::make_shared<Led>( 389 drive.itemIface->get_object_path(), ii, file)); 390 led->createInterface(); 391 } 392 } 393 394 void updateDrives() 395 { 396 397 uint8_t nvme = ifdet ^ presence; 398 size_t ii = 0; 399 400 for (auto it = drives.begin(); it != drives.end(); it++, ii++) 401 { 402 bool isNvme = nvme & (1 << ii); 403 bool isPresent = isNvme || (presence & (1 << ii)); 404 bool isFailed = !isPresent || (failed & (1 << ii)); 405 bool isRebuilding = isPresent && (rebuilding & (1 << ii)); 406 407 it->isNvme = isNvme; 408 it->itemIface->set_property("Present", isPresent); 409 it->isPresent = isPresent; 410 it->rebuildingIface->set_property("Rebuilding", isRebuilding); 411 if (isFailed || isRebuilding) 412 { 413 it->markFailed(); 414 } 415 else 416 { 417 it->clearFailed(); 418 } 419 } 420 } 421 422 bool getPresent() 423 { 424 present = i2c_smbus_read_byte(file) >= 0; 425 return present; 426 } 427 428 bool getTypeID(uint8_t& val) 429 { 430 constexpr uint8_t addr = 2; 431 int ret = i2c_smbus_read_byte_data(file, addr); 432 if (ret < 0) 433 { 434 std::cerr << "Error " << __FUNCTION__ << "\n"; 435 return false; 436 } 437 val = static_cast<uint8_t>(ret); 438 return true; 439 } 440 441 bool getBootVer(uint8_t& val) 442 { 443 constexpr uint8_t addr = 3; 444 int ret = i2c_smbus_read_byte_data(file, addr); 445 if (ret < 0) 446 { 447 std::cerr << "Error " << __FUNCTION__ << "\n"; 448 return false; 449 } 450 val = static_cast<uint8_t>(ret); 451 return true; 452 } 453 454 bool getFPGAVer(uint8_t& val) 455 { 456 constexpr uint8_t addr = 4; 457 int ret = i2c_smbus_read_byte_data(file, addr); 458 if (ret < 0) 459 { 460 std::cerr << "Error " << __FUNCTION__ << "\n"; 461 return false; 462 } 463 val = static_cast<uint8_t>(ret); 464 return true; 465 } 466 467 bool getSecurityRev(uint8_t& val) 468 { 469 constexpr uint8_t addr = 5; 470 int ret = i2c_smbus_read_byte_data(file, addr); 471 if (ret < 0) 472 { 473 std::cerr << "Error " << __FUNCTION__ << "\n"; 474 return false; 475 } 476 val = static_cast<uint8_t>(ret); 477 return true; 478 } 479 480 bool getPresence(uint8_t& val) 481 { 482 // NVMe drives do not assert PRSNTn, and as such do not get reported as 483 // PRESENT in this register 484 485 constexpr uint8_t addr = 8; 486 487 int ret = i2c_smbus_read_byte_data(file, addr); 488 if (ret < 0) 489 { 490 std::cerr << "Error " << __FUNCTION__ << "\n"; 491 return false; 492 } 493 // presence is inverted 494 val = static_cast<uint8_t>(~ret); 495 return true; 496 } 497 498 bool getIFDET(uint8_t& val) 499 { 500 // This register is a bitmap of parallel GPIO pins connected to the 501 // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert 502 // IFDETn low when they are inserted into the HSBP.This register, in 503 // combination with the PRESENCE register, are used by the BMC to detect 504 // the presence of NVMe drives. 505 506 constexpr uint8_t addr = 9; 507 508 int ret = i2c_smbus_read_byte_data(file, addr); 509 if (ret < 0) 510 { 511 std::cerr << "Error " << __FUNCTION__ << "\n"; 512 return false; 513 } 514 // ifdet is inverted 515 val = static_cast<uint8_t>(~ret); 516 return true; 517 } 518 519 bool getFailed(uint8_t& val) 520 { 521 constexpr uint8_t addr = 0xC; 522 int ret = i2c_smbus_read_byte_data(file, addr); 523 if (ret < 0) 524 { 525 std::cerr << "Error " << __FUNCTION__ << "\n"; 526 return false; 527 } 528 val = static_cast<uint8_t>(ret); 529 return true; 530 } 531 532 bool getRebuild(uint8_t& val) 533 { 534 constexpr uint8_t addr = 0xD; 535 int ret = i2c_smbus_read_byte_data(file, addr); 536 if (ret < 0) 537 { 538 std::cerr << "Error " << __FUNCTION__ << "\n"; 539 return false; 540 } 541 val = static_cast<uint8_t>(ret); 542 return true; 543 } 544 545 virtual ~Backplane() 546 { 547 objServer.remove_interface(hsbpItemIface); 548 objServer.remove_interface(versionIface); 549 if (file >= 0) 550 { 551 close(file); 552 } 553 } 554 555 size_t bus; 556 size_t address; 557 size_t backplaneIndex; 558 std::string name; 559 std::shared_ptr<boost::asio::steady_timer> timer; 560 bool present = false; 561 uint8_t typeId = 0; 562 uint8_t bootVer = 0; 563 uint8_t fpgaVer = 0; 564 uint8_t securityRev = 0; 565 uint8_t funSupported = 0; 566 uint8_t presence = 0; 567 uint8_t ifdet = 0; 568 uint8_t failed = 0; 569 uint8_t rebuilding = 0; 570 571 int file = -1; 572 573 std::string type; 574 575 std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface; 576 std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface; 577 578 std::list<Drive> drives; 579 std::vector<std::shared_ptr<Led>> leds; 580 std::shared_ptr<boost::container::flat_set<Mux>> muxes; 581 }; 582 583 std::unordered_map<std::string, Backplane> backplanes; 584 std::list<Drive> ownerlessDrives; // drives without a backplane 585 586 static size_t getDriveCount() 587 { 588 size_t count = 0; 589 for (const auto& [key, backplane] : backplanes) 590 { 591 count += backplane.drives.size(); 592 } 593 return count + ownerlessDrives.size(); 594 } 595 596 void updateAssets() 597 { 598 static constexpr const char* nvmeType = 599 "xyz.openbmc_project.Inventory.Item.NVMe"; 600 static const std::string assetTag = 601 "xyz.openbmc_project.Inventory.Decorator.Asset"; 602 603 conn->async_method_call( 604 [](const boost::system::error_code ec, const GetSubTreeType& subtree) { 605 if (ec) 606 { 607 std::cerr << "Error contacting mapper " << ec.message() << "\n"; 608 return; 609 } 610 611 // drives may get an owner during this, or we might disover more 612 // drives 613 ownerlessDrives.clear(); 614 for (const auto& [path, objDict] : subtree) 615 { 616 if (objDict.empty()) 617 { 618 continue; 619 } 620 621 const std::string& owner = objDict.begin()->first; 622 // we export this interface too 623 if (owner == busName) 624 { 625 continue; 626 } 627 if (std::find(objDict.begin()->second.begin(), 628 objDict.begin()->second.end(), 629 assetTag) == objDict.begin()->second.end()) 630 { 631 // no asset tag to associate to 632 continue; 633 } 634 635 conn->async_method_call( 636 [path](const boost::system::error_code ec2, 637 const boost::container::flat_map< 638 std::string, 639 std::variant<uint64_t, std::string>>& values) { 640 if (ec2) 641 { 642 std::cerr << "Error Getting Config " 643 << ec2.message() << " " << __FUNCTION__ 644 << "\n"; 645 return; 646 } 647 auto findBus = values.find("Bus"); 648 649 if (findBus == values.end()) 650 { 651 std::cerr << "Illegal interface at " << path 652 << "\n"; 653 return; 654 } 655 656 // find the mux bus and addr 657 size_t muxBus = static_cast<size_t>( 658 std::get<uint64_t>(findBus->second)); 659 std::filesystem::path muxPath = 660 "/sys/bus/i2c/devices/i2c-" + 661 std::to_string(muxBus) + "/mux_device"; 662 if (!std::filesystem::is_symlink(muxPath)) 663 { 664 std::cerr << path << " mux does not exist\n"; 665 return; 666 } 667 668 // we should be getting something of the form 7-0052 669 // for bus 7 addr 52 670 std::string fname = 671 std::filesystem::read_symlink(muxPath).filename(); 672 auto findDash = fname.find('-'); 673 674 if (findDash == std::string::npos || 675 findDash + 1 >= fname.size()) 676 { 677 std::cerr << path << " mux path invalid\n"; 678 return; 679 } 680 681 std::string busStr = fname.substr(0, findDash); 682 std::string muxStr = fname.substr(findDash + 1); 683 684 size_t bus = static_cast<size_t>(std::stoi(busStr)); 685 size_t addr = 686 static_cast<size_t>(std::stoi(muxStr, nullptr, 16)); 687 size_t muxIndex = 0; 688 689 // find the channel of the mux the drive is on 690 std::ifstream nameFile("/sys/bus/i2c/devices/i2c-" + 691 std::to_string(muxBus) + 692 "/name"); 693 if (!nameFile) 694 { 695 std::cerr << "Unable to open name file of bus " 696 << muxBus << "\n"; 697 return; 698 } 699 700 std::string nameStr; 701 std::getline(nameFile, nameStr); 702 703 // file is of the form "i2c-4-mux (chan_id 1)", get chan 704 // assume single digit chan 705 const std::string prefix = "chan_id "; 706 size_t findId = nameStr.find(prefix); 707 if (findId == std::string::npos || 708 findId + 1 >= nameStr.size()) 709 { 710 std::cerr << "Illegal name file on bus " << muxBus 711 << "\n"; 712 } 713 714 std::string indexStr = 715 nameStr.substr(findId + prefix.size(), 1); 716 717 size_t driveIndex = std::stoi(indexStr); 718 719 Backplane* parent = nullptr; 720 for (auto& [name, backplane] : backplanes) 721 { 722 muxIndex = 0; 723 for (const Mux& mux : *(backplane.muxes)) 724 { 725 if (bus == mux.bus && addr == mux.address) 726 { 727 parent = &backplane; 728 break; 729 } 730 muxIndex += mux.channels; 731 } 732 } 733 boost::container::flat_map<std::string, std::string> 734 assetInventory; 735 const std::array<const char*, 4> assetKeys = { 736 "PartNumber", "SerialNumber", "Manufacturer", 737 "Model"}; 738 for (const auto& [key, value] : values) 739 { 740 if (std::find(assetKeys.begin(), assetKeys.end(), 741 key) == assetKeys.end()) 742 { 743 continue; 744 } 745 assetInventory[key] = std::get<std::string>(value); 746 } 747 748 // assume its a M.2 or something without a hsbp 749 if (parent == nullptr) 750 { 751 auto& drive = ownerlessDrives.emplace_back( 752 getDriveCount() + 1, true, true, true, false); 753 drive.createAsset(assetInventory); 754 return; 755 } 756 757 driveIndex += muxIndex; 758 759 if (parent->drives.size() <= driveIndex) 760 { 761 std::cerr << "Illegal drive index at " << path 762 << " " << driveIndex << "\n"; 763 return; 764 } 765 auto it = parent->drives.begin(); 766 std::advance(it, driveIndex); 767 768 it->createAsset(assetInventory); 769 }, 770 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 771 "" /*all interface items*/); 772 } 773 }, 774 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 775 0, std::array<const char*, 1>{nvmeType}); 776 } 777 778 void populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes, 779 std::string& rootPath) 780 { 781 const static std::array<const std::string, 4> muxTypes = { 782 "xyz.openbmc_project.Configuration.PCA9543Mux", 783 "xyz.openbmc_project.Configuration.PCA9544Mux", 784 "xyz.openbmc_project.Configuration.PCA9545Mux", 785 "xyz.openbmc_project.Configuration.PCA9546Mux"}; 786 conn->async_method_call( 787 [muxes](const boost::system::error_code ec, 788 const GetSubTreeType& subtree) { 789 if (ec) 790 { 791 std::cerr << "Error contacting mapper " << ec.message() << "\n"; 792 return; 793 } 794 std::shared_ptr<std::function<void()>> callback = 795 std::make_shared<std::function<void()>>( 796 []() { updateAssets(); }); 797 size_t index = 0; // as we use a flat map, these are sorted 798 for (const auto& [path, objDict] : subtree) 799 { 800 if (objDict.empty() || objDict.begin()->second.empty()) 801 { 802 continue; 803 } 804 805 const std::string& owner = objDict.begin()->first; 806 const std::vector<std::string>& interfaces = 807 objDict.begin()->second; 808 809 const std::string* interface = nullptr; 810 for (const std::string& iface : interfaces) 811 { 812 if (std::find(muxTypes.begin(), muxTypes.end(), iface) != 813 muxTypes.end()) 814 { 815 interface = &iface; 816 break; 817 } 818 } 819 if (interface == nullptr) 820 { 821 std::cerr << "Cannot get mux type\n"; 822 continue; 823 } 824 825 conn->async_method_call( 826 [path, muxes, callback, index]( 827 const boost::system::error_code ec2, 828 const boost::container::flat_map< 829 std::string, 830 std::variant<uint64_t, std::vector<std::string>>>& 831 values) { 832 if (ec2) 833 { 834 std::cerr << "Error Getting Config " 835 << ec2.message() << " " << __FUNCTION__ 836 << "\n"; 837 return; 838 } 839 auto findBus = values.find("Bus"); 840 auto findAddress = values.find("Address"); 841 auto findChannelNames = values.find("ChannelNames"); 842 if (findBus == values.end() || 843 findAddress == values.end()) 844 { 845 std::cerr << "Illegal configuration at " << path 846 << "\n"; 847 return; 848 } 849 size_t bus = static_cast<size_t>( 850 std::get<uint64_t>(findBus->second)); 851 size_t address = static_cast<size_t>( 852 std::get<uint64_t>(findAddress->second)); 853 std::vector<std::string> channels = 854 std::get<std::vector<std::string>>( 855 findChannelNames->second); 856 muxes->emplace(bus, address, channels.size(), index); 857 if (callback.use_count() == 1) 858 { 859 (*callback)(); 860 } 861 }, 862 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 863 *interface); 864 index++; 865 } 866 }, 867 mapper::busName, mapper::path, mapper::interface, mapper::subtree, 868 rootPath, 1, muxTypes); 869 } 870 871 void populate() 872 { 873 conn->async_method_call( 874 [](const boost::system::error_code ec, const GetSubTreeType& subtree) { 875 if (ec) 876 { 877 std::cerr << "Error contacting mapper " << ec.message() << "\n"; 878 return; 879 } 880 for (const auto& [path, objDict] : subtree) 881 { 882 if (objDict.empty()) 883 { 884 continue; 885 } 886 887 const std::string& owner = objDict.begin()->first; 888 conn->async_method_call( 889 [path](const boost::system::error_code ec2, 890 const boost::container::flat_map< 891 std::string, BasicVariantType>& resp) { 892 if (ec2) 893 { 894 std::cerr << "Error Getting Config " 895 << ec2.message() << "\n"; 896 return; 897 } 898 backplanes.clear(); 899 std::optional<size_t> bus; 900 std::optional<size_t> address; 901 std::optional<size_t> backplaneIndex; 902 std::optional<std::string> name; 903 for (const auto& [key, value] : resp) 904 { 905 if (key == "Bus") 906 { 907 bus = std::get<uint64_t>(value); 908 } 909 else if (key == "Address") 910 { 911 address = std::get<uint64_t>(value); 912 } 913 else if (key == "Index") 914 { 915 backplaneIndex = std::get<uint64_t>(value); 916 } 917 else if (key == "Name") 918 { 919 name = std::get<std::string>(value); 920 } 921 } 922 if (!bus || !address || !name || !backplaneIndex) 923 { 924 std::cerr << "Illegal configuration at " << path 925 << "\n"; 926 return; 927 } 928 std::string parentPath = 929 std::filesystem::path(path).parent_path(); 930 const auto& [backplane, status] = backplanes.emplace( 931 *name, 932 Backplane(*bus, *address, *backplaneIndex, *name)); 933 backplane->second.run(); 934 populateMuxes(backplane->second.muxes, parentPath); 935 }, 936 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 937 configType); 938 } 939 }, 940 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 941 0, std::array<const char*, 1>{configType}); 942 } 943 944 int main() 945 { 946 boost::asio::steady_timer callbackTimer(io); 947 948 conn->request_name(busName); 949 950 sdbusplus::bus::match::match match( 951 *conn, 952 "type='signal',member='PropertiesChanged',arg0='" + 953 std::string(configType) + "'", 954 [&callbackTimer](sdbusplus::message::message&) { 955 callbackTimer.expires_after(std::chrono::seconds(2)); 956 callbackTimer.async_wait([](const boost::system::error_code ec) { 957 if (ec == boost::asio::error::operation_aborted) 958 { 959 // timer was restarted 960 return; 961 } 962 else if (ec) 963 { 964 std::cerr << "Timer error" << ec.message() << "\n"; 965 return; 966 } 967 populate(); 968 }); 969 }); 970 971 sdbusplus::bus::match::match drive( 972 *conn, 973 "type='signal',member='PropertiesChanged',arg0='xyz.openbmc_project." 974 "Inventory.Item.NVMe'", 975 [&callbackTimer](sdbusplus::message::message& message) { 976 callbackTimer.expires_after(std::chrono::seconds(2)); 977 if (message.get_sender() == conn->get_unique_name()) 978 { 979 return; 980 } 981 callbackTimer.async_wait([](const boost::system::error_code ec) { 982 if (ec == boost::asio::error::operation_aborted) 983 { 984 // timer was restarted 985 return; 986 } 987 else if (ec) 988 { 989 std::cerr << "Timer error" << ec.message() << "\n"; 990 return; 991 } 992 populate(); 993 }); 994 }); 995 996 auto iface = 997 objServer.add_interface("/xyz/openbmc_project/inventory/item/storage", 998 "xyz.openbmc_project.inventory.item.storage"); 999 1000 io.post([]() { populate(); }); 1001 setupPowerMatch(conn); 1002 io.run(); 1003 } 1004