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