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