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