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