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