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 <algorithm> 20 #include <bitset> 21 #include <boost/algorithm/string/replace.hpp> 22 #include <boost/asio/posix/stream_descriptor.hpp> 23 #include <boost/asio/steady_timer.hpp> 24 #include <boost/container/flat_set.hpp> 25 #include <filesystem> 26 #include <forward_list> 27 #include <fstream> 28 #include <gpiod.hpp> 29 #include <iostream> 30 #include <list> 31 #include <sdbusplus/asio/connection.hpp> 32 #include <sdbusplus/asio/object_server.hpp> 33 #include <sdbusplus/bus/match.hpp> 34 #include <string> 35 #include <utility> 36 37 extern "C" { 38 #include <i2c/smbus.h> 39 #include <linux/i2c-dev.h> 40 } 41 42 /****************************************************************************/ 43 /******************** Global Constants/Type Declarations ********************/ 44 /****************************************************************************/ 45 constexpr const char* hsbpCpldInft = 46 "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD"; 47 constexpr const char* hsbpConfigIntf = 48 "xyz.openbmc_project.Configuration.HSBPConfiguration"; 49 constexpr const char* nvmeIntf = "xyz.openbmc_project.Inventory.Item.NVMe"; 50 constexpr const char* busName = "xyz.openbmc_project.HsbpManager"; 51 52 constexpr size_t scanRateSeconds = 5; 53 constexpr size_t maxDrives = 8; // only 1 byte alloted 54 55 using NvmeMapping = std::vector<std::string>; 56 /***************************** End of Section *******************************/ 57 58 /****************************************************************************/ 59 /**************************** Enums Definitions *****************************/ 60 /****************************************************************************/ 61 enum class AppState : uint8_t 62 { 63 idle, 64 loadingHsbpConfig, 65 hsbpConfigLoaded, 66 loadingComponents, 67 componentsLoaded, 68 loadingBackplanes, 69 backplanesLoaded, 70 loadingDrives, 71 drivesLoaded 72 }; 73 74 enum class BlinkPattern : uint8_t 75 { 76 off = 0x0, 77 error = 0x2, 78 terminate = 0x3 79 }; 80 /***************************** End of Section *******************************/ 81 82 /****************************************************************************/ 83 /************ HSBP Configuration related struct/class Definitions ***********/ 84 /****************************************************************************/ 85 struct HsbpConfig 86 { 87 size_t rootBus; 88 std::vector<std::string> supportedHsbps; 89 std::unordered_map<std::string, NvmeMapping> hsbpNvmeMap; 90 std::vector<std::string> clockBufferTypes; 91 std::vector<std::string> ioExpanderTypes; 92 93 void clearConfig() 94 { 95 rootBus = -1; 96 supportedHsbps.clear(); 97 hsbpNvmeMap.clear(); 98 clockBufferTypes.clear(); 99 ioExpanderTypes.clear(); 100 } 101 }; 102 103 class ClockBuffer 104 { 105 size_t bus; 106 size_t address; 107 std::string modeOfOperation; 108 size_t outCtrlBaseAddr; 109 size_t outCtrlByteCount; 110 std::unordered_map<std::string, std::vector<std::string>> byteMap; 111 std::string name; 112 std::string type; 113 114 int file = -1; 115 bool initialized = false; 116 117 void initialize() 118 { 119 /* Execute below operation only when mode of operation is SMBus. By 120 * default the clock buffer is configured to follow OE pin output, so we 121 * need to set the output value to 0 to disable the clock outputs. If 122 * mode of operation is IO, then the IO value will determine the 123 * disable/enable of clock output */ 124 if (modeOfOperation == "SMBus") 125 { 126 if (file < 0) 127 { 128 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), 129 O_RDWR | O_CLOEXEC); 130 if (file < 0) 131 { 132 std::cerr << "ClockBuffer : \"" << name 133 << "\" - Unable to open bus : " << bus << "\n"; 134 return; 135 } 136 } 137 138 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) 139 { 140 std::cerr << "ClockBuffer : \"" << name 141 << "\" - Unable to set address to " << address 142 << "\n"; 143 return; 144 } 145 146 for (uint8_t i = 0; i < outCtrlByteCount; i++) 147 { 148 std::string byteName = "Byte" + std::to_string(i); 149 150 auto byte = byteMap.find(byteName); 151 if (byte == byteMap.end()) 152 { 153 std::cerr << "ClockBuffer : \"" << name 154 << "\" - Byte map error ! Unable to find " 155 << byteName << "\n"; 156 return; 157 } 158 159 /* Get current value of output control register */ 160 int read = i2c_smbus_read_byte_data( 161 file, static_cast<uint8_t>(outCtrlBaseAddr + i)); 162 if (read < 0) 163 { 164 std::cerr << "ClockBuffer : \"" << name 165 << "\" - Error: Unable to read data from clock " 166 "buffer register\n"; 167 return; 168 } 169 170 std::bitset<8> currByte(read); 171 bool writeRequired = false; 172 173 /* Set zero only at bit position that we have a NVMe drive (i.e. 174 * ignore where byteMap is "-"). We do not want to touch other 175 * bits */ 176 for (uint8_t bit = 0; bit < 8; bit++) 177 { 178 if (byte->second.at(bit) != "-") 179 { 180 writeRequired = true; 181 currByte.reset(bit); 182 } 183 } 184 185 if (writeRequired) 186 { 187 int ret = i2c_smbus_write_byte_data( 188 file, static_cast<uint8_t>(outCtrlBaseAddr + i), 189 static_cast<uint8_t>(currByte.to_ulong())); 190 191 if (ret < 0) 192 { 193 std::cerr 194 << "ClockBuffer : \"" << name 195 << "\" - Error: Unable to write data to clock " 196 "buffer register\n"; 197 return; 198 } 199 } 200 } 201 } 202 initialized = true; 203 std::cerr << "ClockBuffer : \"" << name << "\" initialized\n"; 204 } 205 206 public: 207 ClockBuffer( 208 size_t busIn, size_t addressIn, std::string& modeOfOperationIn, 209 size_t outCtrlBaseAddrIn, size_t outCtrlByteCountIn, 210 std::unordered_map<std::string, std::vector<std::string>>& byteMapIn, 211 std::string& nameIn, std::string& typeIn) : 212 bus(busIn), 213 address(addressIn), modeOfOperation(std::move(modeOfOperationIn)), 214 outCtrlBaseAddr(outCtrlBaseAddrIn), 215 outCtrlByteCount(outCtrlByteCountIn), byteMap(std::move(byteMapIn)), 216 name(std::move(nameIn)), type(std::move(typeIn)) 217 { 218 initialize(); 219 } 220 221 bool isInitialized() 222 { 223 if (!initialized) 224 { 225 /* There was an issue with the initialization of this component. Try 226 * to invoke initialization again */ 227 initialize(); 228 } 229 return initialized; 230 } 231 232 std::string getName() 233 { 234 return name; 235 } 236 237 bool enableDisableClock(std::forward_list<std::string>& nvmeDrivesInserted, 238 std::forward_list<std::string>& nvmeDrivesRemoved) 239 { 240 if (modeOfOperation != "SMBus") 241 { 242 /* The clock is enabled using IO expander. No action needed from 243 * here */ 244 return true; 245 } 246 247 if (nvmeDrivesInserted.empty() && nvmeDrivesRemoved.empty()) 248 { 249 /* There are no drives to update */ 250 return true; 251 } 252 253 for (uint8_t i = 0; i < outCtrlByteCount; i++) 254 { 255 std::string byteName = "Byte" + std::to_string(i); 256 257 auto byte = byteMap.find(byteName); 258 if (byte == byteMap.end()) 259 { 260 std::cerr << "ClockBuffer : \"" << name 261 << "\" - Byte map error ! Unable to find " << byteName 262 << "\n"; 263 return false; 264 } 265 266 /* Get current value of output control register */ 267 int read = i2c_smbus_read_byte_data( 268 file, static_cast<uint8_t>(outCtrlBaseAddr + i)); 269 if (read < 0) 270 { 271 std::cerr << "ClockBuffer : \"" << name 272 << "\" - Error: Unable to read data from clock " 273 "buffer register\n"; 274 return false; 275 } 276 277 std::bitset<8> currByte(read); 278 bool writeRequired = false; 279 280 /* Set the bit if the NVMe drive is found in nvmeDrivesInserted, and 281 * reset the bit if found in nvmeDrivesRemoved */ 282 for (uint8_t bit = 0; bit < 8; bit++) 283 { 284 /* The remove function returns number of elements removed from 285 * list indicating the presence of the drive and also removing 286 * it form the list */ 287 if (nvmeDrivesInserted.remove(byte->second.at(bit))) 288 { 289 writeRequired = true; 290 currByte.set(bit); 291 continue; 292 } 293 294 if (nvmeDrivesRemoved.remove(byte->second.at(bit))) 295 { 296 writeRequired = true; 297 currByte.reset(bit); 298 } 299 } 300 301 if (!writeRequired) 302 { 303 /* No Write is required as there are no changes */ 304 continue; 305 } 306 307 int ret = i2c_smbus_write_byte_data( 308 file, static_cast<uint8_t>(outCtrlBaseAddr + i), 309 static_cast<uint8_t>(currByte.to_ulong())); 310 if (ret < 0) 311 { 312 std::cerr << "ClockBuffer : \"" << name 313 << "\" - Error: Unable to write data to clock " 314 "buffer register\n"; 315 return false; 316 } 317 } 318 319 return true; 320 } 321 322 ~ClockBuffer() 323 { 324 if (file >= 0) 325 { 326 close(file); 327 } 328 } 329 }; 330 331 class IoExpander 332 { 333 size_t bus; 334 size_t address; 335 size_t confIORegAddr; 336 size_t outCtrlBaseAddr; 337 size_t outCtrlByteCount; 338 std::unordered_map<std::string, std::vector<std::string>> ioMap; 339 std::string name; 340 std::string type; 341 342 int file = -1; 343 bool initialized = false; 344 345 void initialize() 346 { 347 /* Initialize the IO expander Control register to configure the IO ports 348 * as outputs and set the output to low by default */ 349 if (file < 0) 350 { 351 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), 352 O_RDWR | O_CLOEXEC); 353 if (file < 0) 354 { 355 std::cerr << "IoExpander : " << name 356 << " - Unable to open bus : " << bus << "\n"; 357 return; 358 } 359 } 360 361 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) 362 { 363 std::cerr << "IoExpander : \"" << name 364 << "\" - Unable to set address to " << address << "\n"; 365 return; 366 } 367 368 for (uint8_t i = 0; i < outCtrlByteCount; i++) 369 { 370 std::string ioName = "IO" + std::to_string(i); 371 372 auto io = ioMap.find(ioName); 373 if (io == ioMap.end()) 374 { 375 std::cerr << "IoExpander : \"" << name 376 << "\" - IO map error ! Unable to find " << ioName 377 << "\n"; 378 return; 379 } 380 381 /* Get current value of IO configuration register */ 382 int read1 = i2c_smbus_read_byte_data( 383 file, static_cast<uint8_t>(confIORegAddr + i)); 384 if (read1 < 0) 385 { 386 std::cerr << "IoExpander : \"" << name 387 << "\" - Error: Unable to read data from io expander " 388 "IO control register\n"; 389 return; 390 } 391 392 /* Get current value of IO Ouput register */ 393 int read2 = i2c_smbus_read_byte_data( 394 file, static_cast<uint8_t>(confIORegAddr + i)); 395 if (read2 < 0) 396 { 397 std::cerr << "IoExpander : \"" << name 398 << "\" - Error: Unable to read data from io expander " 399 "IO output register\n"; 400 return; 401 } 402 403 bool writeRequired = false; 404 std::bitset<8> currCtrlVal(read1); 405 std::bitset<8> currOutVal(read2); 406 407 /* Set zero only at bit position that we have a NVMe drive (i.e. 408 * ignore where ioMap is "-"). We do not want to touch other 409 * bits */ 410 for (uint8_t bit = 0; bit < 8; bit++) 411 { 412 if (io->second.at(bit) != "-") 413 { 414 writeRequired = true; 415 currCtrlVal.reset(bit); 416 currOutVal.reset(bit); 417 } 418 } 419 420 if (writeRequired) 421 { 422 int ret1 = i2c_smbus_write_byte_data( 423 file, static_cast<uint8_t>(confIORegAddr + i), 424 static_cast<uint8_t>(currCtrlVal.to_ulong())); 425 if (ret1 < 0) 426 { 427 std::cerr 428 << "IoExpander : \"" << name 429 << "\" - Error: Unable to write data to IO expander " 430 "IO control register\n"; 431 return; 432 } 433 434 int ret2 = i2c_smbus_write_byte_data( 435 file, static_cast<uint8_t>(outCtrlBaseAddr + i), 436 static_cast<uint8_t>(currOutVal.to_ulong())); 437 if (ret2 < 0) 438 { 439 std::cerr 440 << "IoExpander : \"" << name 441 << "\" - Error: Unable to write data to IO expander " 442 "IO output register\n"; 443 return; 444 } 445 } 446 } 447 initialized = true; 448 std::cerr << "IoExpander : \"" << name << "\" initialized\n"; 449 } 450 451 public: 452 IoExpander( 453 size_t busIn, size_t addressIn, size_t confIORegAddrIn, 454 size_t outCtrlBaseAddrIn, size_t outCtrlByteCountIn, 455 std::unordered_map<std::string, std::vector<std::string>>& ioMapIn, 456 std::string& nameIn, std::string& typeIn) : 457 bus(busIn), 458 address(addressIn), confIORegAddr(confIORegAddrIn), 459 outCtrlBaseAddr(outCtrlBaseAddrIn), 460 outCtrlByteCount(outCtrlByteCountIn), ioMap(std::move(ioMapIn)), 461 name(std::move(nameIn)), type(std::move(typeIn)) 462 { 463 initialize(); 464 } 465 466 bool isInitialized() 467 { 468 if (!initialized) 469 { 470 /* There was an issue with the initialization of this component. Try 471 * to invoke initialization again */ 472 initialize(); 473 } 474 return initialized; 475 } 476 477 std::string getName() 478 { 479 return name; 480 } 481 482 bool enableDisableOuput(std::forward_list<std::string>& nvmeDrivesInserted, 483 std::forward_list<std::string>& nvmeDrivesRemoved) 484 { 485 if (nvmeDrivesInserted.empty() && nvmeDrivesRemoved.empty()) 486 { 487 /* There are no drives to update */ 488 return true; 489 } 490 491 for (uint8_t i = 0; i < outCtrlByteCount; i++) 492 { 493 std::string ioName = "IO" + std::to_string(i); 494 495 auto io = ioMap.find(ioName); 496 if (io == ioMap.end()) 497 { 498 std::cerr << "IoExpander : \"" << name 499 << "\" - IO map error ! Unable to find " << ioName 500 << "\n"; 501 return false; 502 } 503 504 /* Get current value of IO output register */ 505 int read = i2c_smbus_read_byte_data( 506 file, static_cast<uint8_t>(outCtrlBaseAddr + i)); 507 if (read < 0) 508 { 509 std::cerr << "IoExpander : \"" << name 510 << "\" - Error: Unable to read data from io expander " 511 "register\n"; 512 return false; 513 } 514 515 std::bitset<8> currVal(read); 516 bool writeRequired = false; 517 518 /* Set the bit if the NVMe drive is found in nvmeDrivesInserted, and 519 * reset the bit if found in nvmeDrivesRemoved */ 520 for (uint8_t bit = 0; bit < 8; bit++) 521 { 522 /* The remove function returns number of elements removed from 523 * list indicating the presence of the drive and also removing 524 * it form the list */ 525 if (nvmeDrivesInserted.remove(io->second.at(bit))) 526 { 527 writeRequired = true; 528 currVal.set(bit); 529 continue; 530 } 531 532 if (nvmeDrivesRemoved.remove(io->second.at(bit))) 533 { 534 writeRequired = true; 535 currVal.reset(bit); 536 } 537 } 538 539 if (!writeRequired) 540 { 541 /* No Write is required as there are no changes */ 542 continue; 543 } 544 545 int ret = i2c_smbus_write_byte_data( 546 file, static_cast<uint8_t>(outCtrlBaseAddr + i), 547 static_cast<uint8_t>(currVal.to_ulong())); 548 if (ret < 0) 549 { 550 std::cerr << "IoExpander : \"" << name 551 << "\" - Error: Unable to write data to IO expander " 552 "register\n"; 553 return false; 554 } 555 } 556 557 return true; 558 } 559 560 ~IoExpander() 561 { 562 if (file >= 0) 563 { 564 close(file); 565 } 566 } 567 }; 568 /***************************** End of Section *******************************/ 569 570 /****************************************************************************/ 571 /*********************** Global Variables Declarations **********************/ 572 /****************************************************************************/ 573 /* State os Application */ 574 static AppState appState = AppState::idle; 575 576 /* Configuration and Components */ 577 static HsbpConfig hsbpConfig; 578 std::forward_list<ClockBuffer> clockBuffers; 579 std::forward_list<IoExpander> ioExpanders; 580 581 /* Boost IO context and Dbus variables */ 582 boost::asio::io_context io; 583 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 584 sdbusplus::asio::object_server objServer(conn); 585 586 /* GPIO Lines and GPIO Event Descriptors */ 587 static gpiod::line nvmeLvc3AlertLine; 588 static boost::asio::posix::stream_descriptor nvmeLvc3AlertEvent(io); 589 /***************************** End of Section *******************************/ 590 591 /****************************************************************************/ 592 /********** HSBP Backplane related struct and Global definitions ************/ 593 /****************************************************************************/ 594 struct Mux 595 { 596 Mux(size_t busIn, size_t addressIn, size_t channelsIn, size_t indexIn) : 597 bus(busIn), address(addressIn), channels(channelsIn), index(indexIn) 598 { 599 } 600 size_t bus; 601 size_t address; 602 size_t channels; 603 size_t index; 604 605 // to sort in the flat set 606 bool operator<(const Mux& rhs) const 607 { 608 return index < rhs.index; 609 } 610 }; 611 612 struct Led : std::enable_shared_from_this<Led> 613 { 614 // led pattern addresses start at 0x10 615 Led(const std::string& path, size_t index, int fd) : 616 address(static_cast<uint8_t>(index + 0x10)), file(fd), 617 ledInterface(objServer.add_interface(path, ledGroup::interface)) 618 { 619 if (index >= maxDrives) 620 { 621 throw std::runtime_error("Invalid drive index"); 622 } 623 624 if (!set(BlinkPattern::off)) 625 { 626 std::cerr << "Cannot initialize LED " << path << "\n"; 627 } 628 } 629 630 // this has to be called outside the constructor for shared_from_this to 631 // work 632 void createInterface(void) 633 { 634 ledInterface->register_property( 635 ledGroup::asserted, false, 636 [weakRef{weak_from_this()}](const bool req, bool& val) { 637 auto self = weakRef.lock(); 638 if (!self) 639 { 640 return 0; 641 } 642 if (req == val) 643 { 644 return 1; 645 } 646 647 if (!isPowerOn()) 648 { 649 std::cerr << "Can't change blink state when power is off\n"; 650 throw std::runtime_error( 651 "Can't change blink state when power is off"); 652 } 653 BlinkPattern pattern = 654 req ? BlinkPattern::error : BlinkPattern::terminate; 655 if (!self->set(pattern)) 656 { 657 std::cerr << "Can't change blink pattern\n"; 658 throw std::runtime_error("Cannot set blink pattern"); 659 } 660 val = req; 661 return 1; 662 }); 663 ledInterface->initialize(); 664 } 665 666 virtual ~Led() 667 { 668 objServer.remove_interface(ledInterface); 669 } 670 671 bool set(BlinkPattern pattern) 672 { 673 int ret = i2c_smbus_write_byte_data(file, address, 674 static_cast<uint8_t>(pattern)); 675 return ret >= 0; 676 } 677 678 uint8_t address; 679 int file; 680 std::shared_ptr<sdbusplus::asio::dbus_interface> ledInterface; 681 }; 682 683 struct Drive 684 { 685 Drive(std::string driveName, bool present, bool isOperational, bool nvme, 686 bool rebuilding) : 687 isNvme(nvme), 688 isPresent(present), name(driveName) 689 { 690 constexpr const char* basePath = 691 "/xyz/openbmc_project/inventory/item/drive/"; 692 itemIface = 693 objServer.add_interface(basePath + driveName, inventory::interface); 694 itemIface->register_property("Present", isPresent); 695 itemIface->register_property("PrettyName", driveName); 696 itemIface->initialize(); 697 operationalIface = objServer.add_interface( 698 itemIface->get_object_path(), 699 "xyz.openbmc_project.State.Decorator.OperationalStatus"); 700 701 operationalIface->register_property( 702 "Functional", isOperational, 703 [this](const bool req, bool& property) { 704 if (!isPresent) 705 { 706 return 0; 707 } 708 if (property == req) 709 { 710 return 1; 711 } 712 property = req; 713 if (req) 714 { 715 clearFailed(); 716 return 1; 717 } 718 markFailed(); 719 return 1; 720 }); 721 722 operationalIface->initialize(); 723 rebuildingIface = objServer.add_interface( 724 itemIface->get_object_path(), "xyz.openbmc_project.State.Drive"); 725 rebuildingIface->register_property("Rebuilding", rebuilding); 726 rebuildingIface->initialize(); 727 driveIface = 728 objServer.add_interface(itemIface->get_object_path(), 729 "xyz.openbmc_project.Inventory.Item.Drive"); 730 driveIface->initialize(); 731 associations = objServer.add_interface(itemIface->get_object_path(), 732 association::interface); 733 associations->register_property("Associations", 734 std::vector<Association>{}); 735 associations->initialize(); 736 737 if (isPresent && (!isOperational || rebuilding)) 738 { 739 markFailed(); 740 } 741 } 742 virtual ~Drive() 743 { 744 objServer.remove_interface(itemIface); 745 objServer.remove_interface(operationalIface); 746 objServer.remove_interface(rebuildingIface); 747 objServer.remove_interface(assetIface); 748 objServer.remove_interface(driveIface); 749 objServer.remove_interface(associations); 750 } 751 752 void removeAsset() 753 { 754 objServer.remove_interface(assetIface); 755 assetIface = nullptr; 756 } 757 758 void createAsset( 759 const boost::container::flat_map<std::string, std::string>& data) 760 { 761 if (assetIface != nullptr) 762 { 763 return; 764 } 765 assetIface = objServer.add_interface( 766 itemIface->get_object_path(), 767 "xyz.openbmc_project.Inventory.Decorator.Asset"); 768 for (const auto& [key, value] : data) 769 { 770 assetIface->register_property(key, value); 771 if (key == "SerialNumber") 772 { 773 serialNumber = value; 774 serialNumberInitialized = true; 775 } 776 } 777 assetIface->initialize(); 778 } 779 780 void markFailed(void) 781 { 782 // todo: maybe look this up via mapper 783 constexpr const char* globalInventoryPath = 784 "/xyz/openbmc_project/CallbackManager"; 785 786 if (!isPresent) 787 { 788 return; 789 } 790 791 operationalIface->set_property("Functional", false); 792 std::vector<Association> warning = { 793 {"", "warning", globalInventoryPath}}; 794 associations->set_property("Associations", warning); 795 logDriveError("Drive " + name); 796 } 797 798 void clearFailed(void) 799 { 800 operationalIface->set_property("Functional", true); 801 associations->set_property("Associations", std::vector<Association>{}); 802 } 803 804 void setPresent(bool set) 805 { 806 // nvme drives get detected by their fru 807 if (set == isPresent) 808 { 809 return; 810 } 811 itemIface->set_property("Present", set); 812 isPresent = set; 813 } 814 815 void logPresent() 816 { 817 if (isNvme && !serialNumberInitialized) 818 { 819 // wait until NVMe asset is updated to include the serial number 820 // from the NVMe drive 821 return; 822 } 823 824 if (!isPresent && loggedPresent) 825 { 826 loggedPresent = false; 827 logDeviceRemoved("Drive", name, serialNumber); 828 serialNumber = "N/A"; 829 serialNumberInitialized = false; 830 removeAsset(); 831 } 832 else if (isPresent && !loggedPresent) 833 { 834 loggedPresent = true; 835 logDeviceAdded("Drive", name, serialNumber); 836 } 837 } 838 839 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface; 840 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface; 841 std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface; 842 std::shared_ptr<sdbusplus::asio::dbus_interface> assetIface; 843 std::shared_ptr<sdbusplus::asio::dbus_interface> driveIface; 844 std::shared_ptr<sdbusplus::asio::dbus_interface> associations; 845 846 bool isNvme; 847 bool isPresent; 848 std::string name; 849 std::string serialNumber = "N/A"; 850 bool serialNumberInitialized = false; 851 bool loggedPresent = false; 852 }; 853 854 struct Backplane : std::enable_shared_from_this<Backplane> 855 { 856 857 Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn, 858 const std::string& nameIn) : 859 bus(busIn), 860 address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn), 861 timer(boost::asio::steady_timer(io)), 862 muxes(std::make_shared<boost::container::flat_set<Mux>>()) 863 { 864 } 865 void populateAsset(const std::string& rootPath, const std::string& busname) 866 { 867 conn->async_method_call( 868 [assetIface{assetInterface}]( 869 const boost::system::error_code ec, 870 const boost::container::flat_map< 871 std::string, std::variant<std::string>>& values) mutable { 872 if (ec) 873 { 874 std::cerr 875 << "Error getting asset tag from HSBP configuration\n"; 876 877 return; 878 } 879 for (const auto& [key, value] : values) 880 { 881 const std::string* ptr = std::get_if<std::string>(&value); 882 if (ptr == nullptr) 883 { 884 std::cerr << key << " Invalid type!\n"; 885 continue; 886 } 887 assetIface->register_property(key, *ptr); 888 } 889 assetIface->initialize(); 890 }, 891 busname, rootPath, "org.freedesktop.DBus.Properties", "GetAll", 892 assetTag); 893 } 894 895 static std::string zeroPad(const uint8_t val) 896 { 897 std::ostringstream version; 898 version << std::setw(2) << std::setfill('0') 899 << static_cast<size_t>(val); 900 return version.str(); 901 } 902 903 void run(const std::string& rootPath, const std::string& busname) 904 { 905 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), 906 O_RDWR | O_CLOEXEC); 907 if (file < 0) 908 { 909 std::cerr << "unable to open bus " << bus << "\n"; 910 return; 911 } 912 913 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) 914 { 915 std::cerr << "unable to set address to " << address << "\n"; 916 return; 917 } 918 919 if (!getPresent()) 920 { 921 std::cerr << "Cannot detect CPLD\n"; 922 return; 923 } 924 925 getBootVer(bootVer); 926 getFPGAVer(fpgaVer); 927 getSecurityRev(securityRev); 928 std::string dbusName = boost::replace_all_copy(name, " ", "_"); 929 hsbpItemIface = objServer.add_interface( 930 "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName, 931 inventory::interface); 932 hsbpItemIface->register_property("Present", true); 933 hsbpItemIface->register_property("PrettyName", name); 934 hsbpItemIface->initialize(); 935 936 storageInterface = objServer.add_interface( 937 hsbpItemIface->get_object_path(), 938 "xyz.openbmc_project.Inventory.Item.StorageController"); 939 storageInterface->initialize(); 940 941 assetInterface = 942 objServer.add_interface(hsbpItemIface->get_object_path(), assetTag); 943 944 versionIface = 945 objServer.add_interface("/xyz/openbmc_project/software/" + dbusName, 946 "xyz.openbmc_project.Software.Version"); 947 versionIface->register_property("Version", zeroPad(bootVer) + "." + 948 zeroPad(fpgaVer) + "." + 949 zeroPad(securityRev)); 950 versionIface->register_property( 951 "Purpose", 952 std::string( 953 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP")); 954 versionIface->initialize(); 955 956 activationIface = 957 objServer.add_interface("/xyz/openbmc_project/software/" + dbusName, 958 "xyz.openbmc_project.Software.Activation"); 959 activationIface->register_property( 960 "Activation", 961 std::string( 962 "xyz.openbmc_project.Software.Activation.Activations.Active")); 963 activationIface->register_property( 964 "RequestedActivation", 965 std::string("xyz.openbmc_project.Software.Activation." 966 "RequestedActivations.None")); 967 activationIface->initialize(); 968 969 getPresence(presence); 970 getIFDET(ifdet); 971 972 populateAsset(rootPath, busname); 973 974 createDrives(); 975 976 runTimer(); 977 } 978 979 void runTimer() 980 { 981 timer.expires_after(std::chrono::seconds(scanRateSeconds)); 982 timer.async_wait([weak{std::weak_ptr<Backplane>(shared_from_this())}]( 983 boost::system::error_code ec) { 984 auto self = weak.lock(); 985 if (!self) 986 { 987 return; 988 } 989 if (ec == boost::asio::error::operation_aborted) 990 { 991 // we're being destroyed 992 return; 993 } 994 else if (ec) 995 { 996 std::cerr << "timer error " << ec.message() << "\n"; 997 return; 998 } 999 1000 if (!isPowerOn()) 1001 { 1002 // can't access hsbp when power is off 1003 self->runTimer(); 1004 return; 1005 } 1006 1007 self->getPresence(self->presence); 1008 self->getIFDET(self->ifdet); 1009 self->getFailed(self->failed); 1010 self->getRebuild(self->rebuilding); 1011 1012 self->updateDrives(); 1013 self->runTimer(); 1014 }); 1015 } 1016 1017 void createDrives() 1018 { 1019 for (size_t ii = 0; ii < maxDrives; ii++) 1020 { 1021 uint8_t driveSlot = (1 << ii); 1022 bool isNvme = ((ifdet & driveSlot) && !(presence & driveSlot)); 1023 bool isPresent = isNvme || (presence & driveSlot); 1024 bool isFailed = !isPresent || failed & driveSlot; 1025 bool isRebuilding = !isPresent && (rebuilding & driveSlot); 1026 1027 // +1 to convert from 0 based to 1 based 1028 std::string driveName = boost::replace_all_copy(name, " ", "_") + 1029 "_Drive_" + std::to_string(ii + 1); 1030 Drive& drive = drives.emplace_back(driveName, isPresent, !isFailed, 1031 isNvme, isRebuilding); 1032 std::shared_ptr<Led> led = leds.emplace_back(std::make_shared<Led>( 1033 drive.itemIface->get_object_path(), ii, file)); 1034 led->createInterface(); 1035 } 1036 } 1037 1038 void updateDrives() 1039 { 1040 size_t ii = 0; 1041 1042 for (auto it = drives.begin(); it != drives.end(); it++, ii++) 1043 { 1044 uint8_t driveSlot = (1 << ii); 1045 bool isNvme = ((ifdet & driveSlot) && !(presence & driveSlot)); 1046 bool isPresent = isNvme || (presence & driveSlot); 1047 bool isFailed = !isPresent || (failed & driveSlot); 1048 bool isRebuilding = isPresent && (rebuilding & driveSlot); 1049 1050 it->isNvme = isNvme; 1051 it->setPresent(isPresent); 1052 it->logPresent(); 1053 1054 it->rebuildingIface->set_property("Rebuilding", isRebuilding); 1055 if (isFailed || isRebuilding) 1056 { 1057 it->markFailed(); 1058 } 1059 else 1060 { 1061 it->clearFailed(); 1062 } 1063 } 1064 } 1065 1066 bool getPresent() 1067 { 1068 present = i2c_smbus_read_byte(file) >= 0; 1069 return present; 1070 } 1071 1072 bool getTypeID(uint8_t& val) 1073 { 1074 constexpr uint8_t addr = 2; 1075 int ret = i2c_smbus_read_byte_data(file, addr); 1076 if (ret < 0) 1077 { 1078 std::cerr << "Error " << __FUNCTION__ << "\n"; 1079 return false; 1080 } 1081 val = static_cast<uint8_t>(ret); 1082 return true; 1083 } 1084 1085 bool getBootVer(uint8_t& val) 1086 { 1087 constexpr uint8_t addr = 3; 1088 int ret = i2c_smbus_read_byte_data(file, addr); 1089 if (ret < 0) 1090 { 1091 std::cerr << "Error " << __FUNCTION__ << "\n"; 1092 return false; 1093 } 1094 val = static_cast<uint8_t>(ret); 1095 return true; 1096 } 1097 1098 bool getFPGAVer(uint8_t& val) 1099 { 1100 constexpr uint8_t addr = 4; 1101 int ret = i2c_smbus_read_byte_data(file, addr); 1102 if (ret < 0) 1103 { 1104 std::cerr << "Error " << __FUNCTION__ << "\n"; 1105 return false; 1106 } 1107 val = static_cast<uint8_t>(ret); 1108 return true; 1109 } 1110 1111 bool getSecurityRev(uint8_t& val) 1112 { 1113 constexpr uint8_t addr = 5; 1114 int ret = i2c_smbus_read_byte_data(file, addr); 1115 if (ret < 0) 1116 { 1117 std::cerr << "Error " << __FUNCTION__ << "\n"; 1118 return false; 1119 } 1120 val = static_cast<uint8_t>(ret); 1121 return true; 1122 } 1123 1124 bool getPresence(uint8_t& val) 1125 { 1126 // NVMe drives do not assert PRSNTn, and as such do not get reported as 1127 // PRESENT in this register 1128 1129 constexpr uint8_t addr = 8; 1130 1131 int ret = i2c_smbus_read_byte_data(file, addr); 1132 if (ret < 0) 1133 { 1134 std::cerr << "Error " << __FUNCTION__ << "\n"; 1135 return false; 1136 } 1137 // presence is inverted 1138 val = static_cast<uint8_t>(~ret); 1139 return true; 1140 } 1141 1142 bool getIFDET(uint8_t& val) 1143 { 1144 // This register is a bitmap of parallel GPIO pins connected to the 1145 // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert 1146 // IFDETn low when they are inserted into the HSBP.This register, in 1147 // combination with the PRESENCE register, are used by the BMC to detect 1148 // the presence of NVMe drives. 1149 1150 constexpr uint8_t addr = 9; 1151 1152 int ret = i2c_smbus_read_byte_data(file, addr); 1153 if (ret < 0) 1154 { 1155 std::cerr << "Error " << __FUNCTION__ << "\n"; 1156 return false; 1157 } 1158 // ifdet is inverted 1159 val = static_cast<uint8_t>(~ret); 1160 return true; 1161 } 1162 1163 bool getFailed(uint8_t& val) 1164 { 1165 constexpr uint8_t addr = 0xC; 1166 int ret = i2c_smbus_read_byte_data(file, addr); 1167 if (ret < 0) 1168 { 1169 std::cerr << "Error " << __FUNCTION__ << "\n"; 1170 return false; 1171 } 1172 val = static_cast<uint8_t>(ret); 1173 return true; 1174 } 1175 1176 bool getRebuild(uint8_t& val) 1177 { 1178 constexpr uint8_t addr = 0xD; 1179 int ret = i2c_smbus_read_byte_data(file, addr); 1180 if (ret < 0) 1181 { 1182 std::cerr << "Error " << __FUNCTION__ << " " << strerror(ret) 1183 << "\n"; 1184 return false; 1185 } 1186 val = static_cast<uint8_t>(ret); 1187 return true; 1188 } 1189 1190 bool getInsertedAndRemovedNvmeDrives( 1191 std::forward_list<std::string>& nvmeDrivesInserted, 1192 std::forward_list<std::string>& nvmeDrivesRemoved) 1193 { 1194 /* Get the current drives status */ 1195 std::bitset<8> currDriveStatus; 1196 uint8_t nPresence; 1197 uint8_t nIfdet; 1198 1199 if (!getPresence(nPresence) || !getIFDET(nIfdet)) 1200 { 1201 /* Error getting value. Return */ 1202 std::cerr << "Backplane " << name 1203 << " failed to get drive status\n"; 1204 return false; 1205 } 1206 1207 std::string dbusHsbpName = boost::replace_all_copy(name, " ", "_"); 1208 auto nvmeMap = hsbpConfig.hsbpNvmeMap.find(dbusHsbpName); 1209 if (nvmeMap == hsbpConfig.hsbpNvmeMap.end()) 1210 { 1211 std::cerr << "Couldn't get the NVMe Map for the backplane : " 1212 << name << "\n"; 1213 return false; 1214 } 1215 1216 /* NVMe drives do not assert PRSNTn, and as such do not get reported in 1217 * "presence" register, but assert ifdet low. This implies for a NVMe 1218 * drive to be present, corresponding precense bit has to be 0 and idfet 1219 * has to be 1 (as the values of these regosters are negated: check 1220 * getPresence() and getIfdet() functions) */ 1221 for (uint8_t bit = 0; bit < 8; bit++) 1222 { 1223 if ((nPresence & (1U << bit)) == 0) 1224 { 1225 if (nIfdet & (1U << bit)) 1226 { 1227 currDriveStatus.set(bit); 1228 } 1229 } 1230 } 1231 1232 /* Determine Inserted and Removed Drives 1233 * Prev Bit | Curr Bit | Status 1234 * 0 | 0 | No Change 1235 * 0 | 1 | Inserted 1236 * 1 | 0 | Removed 1237 * 1 | 1 | No Change 1238 */ 1239 for (uint8_t index = 0; index < 8; index++) 1240 { 1241 /* Inserted */ 1242 if (!prevDriveStatus.test(index) && currDriveStatus.test(index)) 1243 { 1244 nvmeDrivesInserted.emplace_front(nvmeMap->second.at(index)); 1245 std::cerr << name << " : " << nvmeDrivesInserted.front() 1246 << " Inserted !\n"; 1247 } 1248 1249 /* Removed */ 1250 else if (prevDriveStatus.test(index) && 1251 !currDriveStatus.test(index)) 1252 { 1253 nvmeDrivesRemoved.emplace_front(nvmeMap->second.at(index)); 1254 std::cerr << name << " : " << nvmeDrivesRemoved.front() 1255 << " Removed !\n"; 1256 } 1257 } 1258 1259 prevDriveStatus = currDriveStatus; 1260 return true; 1261 } 1262 1263 virtual ~Backplane() 1264 { 1265 timer.cancel(); 1266 objServer.remove_interface(hsbpItemIface); 1267 objServer.remove_interface(versionIface); 1268 objServer.remove_interface(storageInterface); 1269 objServer.remove_interface(assetInterface); 1270 objServer.remove_interface(activationIface); 1271 if (file >= 0) 1272 { 1273 close(file); 1274 } 1275 } 1276 1277 size_t bus; 1278 size_t address; 1279 size_t backplaneIndex; 1280 std::string name; 1281 boost::asio::steady_timer timer; 1282 bool present = false; 1283 uint8_t typeId = 0; 1284 uint8_t bootVer = 0; 1285 uint8_t fpgaVer = 0; 1286 uint8_t securityRev = 0; 1287 uint8_t funSupported = 0; 1288 uint8_t presence = 0; 1289 uint8_t ifdet = 0; 1290 uint8_t failed = 0; 1291 uint8_t rebuilding = 0; 1292 std::bitset<8> prevDriveStatus; 1293 1294 int file = -1; 1295 1296 std::string type; 1297 1298 std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface; 1299 std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface; 1300 std::shared_ptr<sdbusplus::asio::dbus_interface> storageInterface; 1301 std::shared_ptr<sdbusplus::asio::dbus_interface> assetInterface; 1302 std::shared_ptr<sdbusplus::asio::dbus_interface> activationIface; 1303 std::list<Drive> drives; 1304 std::vector<std::shared_ptr<Led>> leds; 1305 std::shared_ptr<boost::container::flat_set<Mux>> muxes; 1306 }; 1307 1308 /* Global HSBP backplanes and NVMe drives collection */ 1309 std::unordered_map<std::string, std::shared_ptr<Backplane>> backplanes; 1310 std::list<Drive> ownerlessDrives; // drives without a backplane 1311 /***************************** End of Section *******************************/ 1312 1313 /****************************************************************************/ 1314 /***************** Miscellaneous Class/Function Definitions *****************/ 1315 /****************************************************************************/ 1316 /* The purpose of this class is to sync the code flow. Often times there could 1317 * be multiple dbus calls which are async, and upon completely finishing all 1318 * Dbus calls, we need to call next function, or handle the error. 1319 * When an object of this class goes out of scope, the respective handlers 1320 * will be called */ 1321 class AsyncCallbackHandler 1322 { 1323 bool errorOccurred = false; 1324 std::function<void()> onSuccess = nullptr; 1325 std::function<void()> onError = nullptr; 1326 1327 public: 1328 explicit AsyncCallbackHandler(std::function<void()> onSuccessIn, 1329 std::function<void()> onErrorIn) : 1330 onSuccess(std::move(onSuccessIn)), 1331 onError(std::move(onErrorIn)) 1332 { 1333 } 1334 1335 void setError() 1336 { 1337 errorOccurred = true; 1338 } 1339 1340 ~AsyncCallbackHandler() 1341 { 1342 /* If error occurred flag was set, execute the error handler */ 1343 if (errorOccurred) 1344 { 1345 /* Check if Error Handler is defined */ 1346 if (onError) 1347 { 1348 onError(); 1349 } 1350 1351 return; 1352 } 1353 1354 /* If Success Handler is present, execute Success Handler */ 1355 if (onSuccess) 1356 { 1357 onSuccess(); 1358 } 1359 } 1360 }; 1361 1362 void stopHsbpManager() 1363 { 1364 std::cerr << __FUNCTION__ << ": Stopping hsbp-manager\n"; 1365 appState = AppState::idle; 1366 hsbpConfig.clearConfig(); 1367 clockBuffers.clear(); 1368 ioExpanders.clear(); 1369 backplanes.clear(); 1370 1371 io.stop(); 1372 } 1373 /***************************** End of Section *******************************/ 1374 1375 /****************************************************************************/ 1376 /********* HSBP clock enable/disable related Function Definitions ***********/ 1377 /****************************************************************************/ 1378 void updateHsbpClocks(std::forward_list<std::string>& nvmeDrivesInserted, 1379 std::forward_list<std::string>& nvmeDrivesRemoved) 1380 { 1381 if (appState < AppState::backplanesLoaded) 1382 { 1383 std::cerr << "HSBP not initialized ! Cancelling Clock Update ! \n"; 1384 return; 1385 } 1386 1387 std::cerr << "Updating HSBP drive clocks ...\n"; 1388 1389 /* Loop through all clock buffers and try to update the clocks (this will be 1390 * done if the mode of operation of the clock buffer is SMBus) */ 1391 for (auto& clockBuffer : clockBuffers) 1392 { 1393 if (!clockBuffer.enableDisableClock(nvmeDrivesInserted, 1394 nvmeDrivesRemoved)) 1395 { 1396 std::cerr << "Error Occurred while setting the clock in \"" 1397 << clockBuffer.getName() << "\"\n"; 1398 } 1399 } 1400 1401 /* If there are drives yet to be updated, check all the IO Expanders in case 1402 * they are mapped to the drives and enable the respective IO */ 1403 if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty()) 1404 { 1405 for (auto& ioExpander : ioExpanders) 1406 { 1407 if (!ioExpander.enableDisableOuput(nvmeDrivesInserted, 1408 nvmeDrivesRemoved)) 1409 { 1410 std::cerr << "Error Occurred while setting the IO in \"" 1411 << ioExpander.getName() << "\"\n"; 1412 } 1413 } 1414 } 1415 1416 /* If there are drives still left, then one or more drives clock 1417 * enable/diable failed. There is a possibility of improper mapping or 1418 * current communication with the device failed */ 1419 if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty()) 1420 { 1421 std::cerr << "Critical Error !!!\nMapping issue detected !\n"; 1422 1423 if (!nvmeDrivesInserted.empty()) 1424 { 1425 std::cerr << "The clock enable failed for : "; 1426 for (auto& nvme1 : nvmeDrivesInserted) 1427 { 1428 std::cerr << nvme1 << ", "; 1429 } 1430 std::cerr << "\n"; 1431 } 1432 1433 if (!nvmeDrivesRemoved.empty()) 1434 { 1435 std::cerr << "The clock disable failed for : "; 1436 for (auto& nvme1 : nvmeDrivesRemoved) 1437 { 1438 std::cerr << nvme1 << ", "; 1439 } 1440 std::cerr << "\n"; 1441 } 1442 } 1443 } 1444 1445 void scanHsbpDrives(bool& hsbpDriveScanInProgress) 1446 { 1447 std::cerr << __FUNCTION__ << ": Scanning HSBP drives status ...\n"; 1448 1449 /* List variables to store the drives Inserted/Removed */ 1450 std::forward_list<std::string> nvmeDrivesInserted; 1451 std::forward_list<std::string> nvmeDrivesRemoved; 1452 1453 /* Loop through each backplane present and get the list of inserted/removed 1454 * drives */ 1455 for (auto& [name, backplane] : backplanes) 1456 { 1457 backplane->getInsertedAndRemovedNvmeDrives(nvmeDrivesInserted, 1458 nvmeDrivesRemoved); 1459 } 1460 1461 if (!nvmeDrivesInserted.empty() || !nvmeDrivesRemoved.empty()) 1462 { 1463 updateHsbpClocks(nvmeDrivesInserted, nvmeDrivesRemoved); 1464 } 1465 1466 std::cerr << __FUNCTION__ << ": Scanning HSBP drives Completed\n"; 1467 1468 hsbpDriveScanInProgress = false; 1469 } 1470 1471 void checkHsbpDrivesStatus() 1472 { 1473 static bool hsbpDriveScanInProgress = false; 1474 static bool hsbpDriveRescanInQueue = false; 1475 1476 if (appState < AppState::backplanesLoaded) 1477 { 1478 std::cerr << __FUNCTION__ 1479 << ": HSBP not initialized ! Cancelling scan of HSBP drives " 1480 "status ! \n"; 1481 return; 1482 } 1483 1484 if (hsbpDriveScanInProgress) 1485 { 1486 /* Scan and Clock Update already in progress. Try again after sometime. 1487 * This event can occur due to the GPIO interrupt */ 1488 std::cerr << __FUNCTION__ 1489 << ": HSBP Drives Scan is already in progress\n"; 1490 if (hsbpDriveRescanInQueue) 1491 { 1492 /* There is already a Re-Scan in queue. No need to create multiple 1493 * rescans */ 1494 return; 1495 } 1496 1497 hsbpDriveRescanInQueue = true; 1498 1499 std::cerr << __FUNCTION__ << ": Queuing the Scan \n"; 1500 1501 auto driveScanTimer = std::make_shared<boost::asio::steady_timer>(io); 1502 driveScanTimer->expires_after(std::chrono::seconds(1)); 1503 driveScanTimer->async_wait( 1504 [driveScanTimer](const boost::system::error_code ec) { 1505 if (ec == boost::asio::error::operation_aborted) 1506 { 1507 // Timer was Aborted 1508 return; 1509 } 1510 else if (ec) 1511 { 1512 std::cerr << "driveScanTimer: Timer error" << ec.message() 1513 << "\n"; 1514 return; 1515 } 1516 hsbpDriveRescanInQueue = false; 1517 checkHsbpDrivesStatus(); 1518 }); 1519 1520 return; 1521 } 1522 1523 hsbpDriveScanInProgress = true; 1524 1525 /* Post the scan to IO queue and return from here. This enables capturing 1526 * next GPIO event if any */ 1527 boost::asio::post(io, []() { scanHsbpDrives(hsbpDriveScanInProgress); }); 1528 } 1529 /***************************** End of Section *******************************/ 1530 1531 /****************************************************************************/ 1532 /********** Backplanes and NVMe drives related Function Definitions *********/ 1533 /****************************************************************************/ 1534 static size_t getDriveCount() 1535 { 1536 size_t count = 0; 1537 for (const auto& [key, backplane] : backplanes) 1538 { 1539 count += backplane->drives.size(); 1540 } 1541 return count + ownerlessDrives.size(); 1542 } 1543 1544 void updateAssets() 1545 { 1546 appState = AppState::loadingDrives; 1547 1548 /* Setup a callback to be called once the assets are populated completely or 1549 * fallback to error handler */ 1550 auto drivesLoadedCallback = std::make_shared<AsyncCallbackHandler>( 1551 []() { 1552 appState = AppState::drivesLoaded; 1553 std::cerr << "Drives Updated !\n"; 1554 }, 1555 []() { 1556 // TODO: Handle this error if needed 1557 appState = AppState::backplanesLoaded; 1558 std::cerr << "An error occured ! Drives load failed \n"; 1559 }); 1560 1561 conn->async_method_call( 1562 [drivesLoadedCallback](const boost::system::error_code ec, 1563 const GetSubTreeType& subtree) { 1564 if (ec) 1565 { 1566 std::cerr << __FUNCTION__ << ": Error contacting mapper " 1567 << ec.message() << "\n"; 1568 drivesLoadedCallback->setError(); 1569 return; 1570 } 1571 1572 // drives may get an owner during this, or we might disover more 1573 // drives 1574 ownerlessDrives.clear(); 1575 for (const auto& [path, objDict] : subtree) 1576 { 1577 if (objDict.empty()) 1578 { 1579 continue; 1580 } 1581 1582 const std::string& owner = objDict.begin()->first; 1583 // we export this interface too 1584 if (owner == busName) 1585 { 1586 continue; 1587 } 1588 if (std::find(objDict.begin()->second.begin(), 1589 objDict.begin()->second.end(), 1590 assetTag) == objDict.begin()->second.end()) 1591 { 1592 // no asset tag to associate to 1593 continue; 1594 } 1595 1596 conn->async_method_call( 1597 [path, drivesLoadedCallback]( 1598 const boost::system::error_code ec2, 1599 const boost::container::flat_map< 1600 std::string, std::variant<uint64_t, std::string>>& 1601 values) { 1602 if (ec2) 1603 { 1604 std::cerr << __FUNCTION__ 1605 << ": Error Getting Config " 1606 << ec2.message() << " " 1607 << "\n"; 1608 drivesLoadedCallback->setError(); 1609 return; 1610 } 1611 auto findBus = values.find("Bus"); 1612 1613 if (findBus == values.end()) 1614 { 1615 std::cerr << __FUNCTION__ 1616 << ": Illegal interface at " << path 1617 << "\n"; 1618 drivesLoadedCallback->setError(); 1619 return; 1620 } 1621 1622 // find the mux bus and addr 1623 size_t muxBus = static_cast<size_t>( 1624 std::get<uint64_t>(findBus->second)); 1625 std::filesystem::path muxPath = 1626 "/sys/bus/i2c/devices/i2c-" + 1627 std::to_string(muxBus) + "/mux_device"; 1628 if (!std::filesystem::is_symlink(muxPath)) 1629 { 1630 std::cerr << path << " mux does not exist\n"; 1631 drivesLoadedCallback->setError(); 1632 return; 1633 } 1634 1635 // we should be getting something of the form 7-0052 1636 // for bus 7 addr 52 1637 std::string fname = 1638 std::filesystem::read_symlink(muxPath).filename(); 1639 auto findDash = fname.find('-'); 1640 1641 if (findDash == std::string::npos || 1642 findDash + 1 >= fname.size()) 1643 { 1644 std::cerr << path << " mux path invalid\n"; 1645 drivesLoadedCallback->setError(); 1646 return; 1647 } 1648 1649 std::string busStr = fname.substr(0, findDash); 1650 std::string muxStr = fname.substr(findDash + 1); 1651 1652 size_t bus = static_cast<size_t>(std::stoi(busStr)); 1653 size_t addr = 1654 static_cast<size_t>(std::stoi(muxStr, nullptr, 16)); 1655 size_t muxIndex = 0; 1656 1657 // find the channel of the mux the drive is on 1658 std::ifstream nameFile("/sys/bus/i2c/devices/i2c-" + 1659 std::to_string(muxBus) + 1660 "/name"); 1661 if (!nameFile) 1662 { 1663 std::cerr << __FUNCTION__ 1664 << ": Unable to open name file of bus " 1665 << muxBus << "\n"; 1666 drivesLoadedCallback->setError(); 1667 return; 1668 } 1669 1670 std::string nameStr; 1671 std::getline(nameFile, nameStr); 1672 1673 // file is of the form "i2c-4-mux (chan_id 1)", get chan 1674 // assume single digit chan 1675 const std::string prefix = "chan_id "; 1676 size_t findId = nameStr.find(prefix); 1677 if (findId == std::string::npos || 1678 findId + 1 >= nameStr.size()) 1679 { 1680 std::cerr << __FUNCTION__ 1681 << ": Illegal name file on bus " << muxBus 1682 << "\n"; 1683 } 1684 1685 std::string indexStr = 1686 nameStr.substr(findId + prefix.size(), 1); 1687 1688 size_t driveIndex = std::stoi(indexStr); 1689 1690 boost::container::flat_map<std::string, std::string> 1691 assetInventory; 1692 const std::array<const char*, 4> assetKeys = { 1693 "PartNumber", "SerialNumber", "Manufacturer", 1694 "Model"}; 1695 for (const auto& [key, value] : values) 1696 { 1697 if (std::find(assetKeys.begin(), assetKeys.end(), 1698 key) == assetKeys.end()) 1699 { 1700 continue; 1701 } 1702 assetInventory[key] = std::get<std::string>(value); 1703 } 1704 1705 Backplane* parent = nullptr; 1706 for (auto& [name, backplane] : backplanes) 1707 { 1708 muxIndex = 0; 1709 for (const Mux& mux : *(backplane->muxes)) 1710 { 1711 if (bus == mux.bus && addr == mux.address) 1712 { 1713 parent = backplane.get(); 1714 break; 1715 } 1716 muxIndex += mux.channels; 1717 } 1718 if (parent) 1719 { 1720 /* Found the backplane. No need to proceed 1721 * further */ 1722 break; 1723 } 1724 } 1725 1726 // assume its a M.2 or something without a hsbp 1727 if (parent == nullptr) 1728 { 1729 std::string driveName = 1730 "Drive_" + std::to_string(getDriveCount() + 1); 1731 auto& drive = ownerlessDrives.emplace_back( 1732 driveName, true, true, true, false); 1733 drive.createAsset(assetInventory); 1734 return; 1735 } 1736 1737 driveIndex += muxIndex; 1738 1739 if (parent->drives.size() <= driveIndex) 1740 { 1741 std::cerr << __FUNCTION__ 1742 << ": Illegal drive index at " << path 1743 << " " << driveIndex << "\n"; 1744 drivesLoadedCallback->setError(); 1745 return; 1746 } 1747 auto it = parent->drives.begin(); 1748 std::advance(it, driveIndex); 1749 1750 it->createAsset(assetInventory); 1751 }, 1752 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1753 "" /*all interface items*/); 1754 } 1755 }, 1756 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 1757 0, std::array<const char*, 1>{nvmeIntf}); 1758 } 1759 1760 void populateMuxes(std::shared_ptr<boost::container::flat_set<Mux>> muxes, 1761 std::string& rootPath) 1762 { 1763 const static std::array<const std::string, 4> muxTypes = { 1764 "xyz.openbmc_project.Configuration.PCA9543Mux", 1765 "xyz.openbmc_project.Configuration.PCA9544Mux", 1766 "xyz.openbmc_project.Configuration.PCA9545Mux", 1767 "xyz.openbmc_project.Configuration.PCA9546Mux"}; 1768 1769 conn->async_method_call( 1770 [muxes](const boost::system::error_code ec, 1771 const GetSubTreeType& subtree) { 1772 if (ec) 1773 { 1774 std::cerr << __FUNCTION__ << ": Error contacting mapper " 1775 << ec.message() << "\n"; 1776 return; 1777 } 1778 size_t index = 0; // as we use a flat map, these are sorted 1779 for (const auto& [path, objDict] : subtree) 1780 { 1781 if (objDict.empty() || objDict.begin()->second.empty()) 1782 { 1783 continue; 1784 } 1785 1786 const std::string& owner = objDict.begin()->first; 1787 const std::vector<std::string>& interfaces = 1788 objDict.begin()->second; 1789 1790 const std::string* interface = nullptr; 1791 for (const std::string& iface : interfaces) 1792 { 1793 if (std::find(muxTypes.begin(), muxTypes.end(), iface) != 1794 muxTypes.end()) 1795 { 1796 interface = &iface; 1797 break; 1798 } 1799 } 1800 1801 if (interface == nullptr) 1802 { 1803 std::cerr << __FUNCTION__ << ": Cannot get mux type\n"; 1804 continue; 1805 } 1806 1807 conn->async_method_call( 1808 [path, muxes, index]( 1809 const boost::system::error_code ec2, 1810 const boost::container::flat_map< 1811 std::string, 1812 std::variant<uint64_t, std::vector<std::string>>>& 1813 values) { 1814 if (ec2) 1815 { 1816 std::cerr << __FUNCTION__ 1817 << ": Error Getting Config " 1818 << ec2.message() << "\n"; 1819 return; 1820 } 1821 auto findBus = values.find("Bus"); 1822 auto findAddress = values.find("Address"); 1823 auto findChannelNames = values.find("ChannelNames"); 1824 if (findBus == values.end() || 1825 findAddress == values.end()) 1826 { 1827 std::cerr << __FUNCTION__ 1828 << ": Illegal configuration at " << path 1829 << "\n"; 1830 return; 1831 } 1832 size_t bus = static_cast<size_t>( 1833 std::get<uint64_t>(findBus->second)); 1834 size_t address = static_cast<size_t>( 1835 std::get<uint64_t>(findAddress->second)); 1836 std::vector<std::string> channels = 1837 std::get<std::vector<std::string>>( 1838 findChannelNames->second); 1839 muxes->emplace(bus, address, channels.size(), index); 1840 }, 1841 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1842 *interface); 1843 index++; 1844 } 1845 }, 1846 mapper::busName, mapper::path, mapper::interface, mapper::subtree, 1847 rootPath, 1, muxTypes); 1848 } 1849 1850 void populateHsbpBackplanes( 1851 const std::shared_ptr<AsyncCallbackHandler>& backplanesLoadedCallback) 1852 { 1853 std::cerr << __FUNCTION__ << ": Scanning Backplanes ...\n"; 1854 appState = AppState::loadingBackplanes; 1855 backplanes.clear(); 1856 1857 conn->async_method_call( 1858 [backplanesLoadedCallback](const boost::system::error_code ec, 1859 const GetSubTreeType& subtree) { 1860 if (ec) 1861 { 1862 std::cerr << __FUNCTION__ << ": Error contacting mapper " 1863 << ec.message() << "\n"; 1864 backplanesLoadedCallback->setError(); 1865 return; 1866 } 1867 1868 if (subtree.empty()) 1869 { 1870 /* There wer no HSBP's detected. set teh state back to 1871 * componentsLoaded so that on backplane match event, the 1872 * process can start again */ 1873 appState = AppState::componentsLoaded; 1874 std::cerr << __FUNCTION__ << ": No HSBPs Detected....\n"; 1875 return; 1876 } 1877 1878 for (const auto& [path, objDict] : subtree) 1879 { 1880 if (objDict.empty()) 1881 { 1882 std::cerr << __FUNCTION__ 1883 << ": Subtree data " 1884 "corrupted !\n"; 1885 backplanesLoadedCallback->setError(); 1886 return; 1887 } 1888 1889 const std::string& owner = objDict.begin()->first; 1890 conn->async_method_call( 1891 [backplanesLoadedCallback, path, 1892 owner](const boost::system::error_code ec2, 1893 const boost::container::flat_map< 1894 std::string, BasicVariantType>& resp) { 1895 if (ec2) 1896 { 1897 std::cerr << __FUNCTION__ 1898 << ": Error Getting Config " 1899 << ec2.message() << "\n"; 1900 backplanesLoadedCallback->setError(); 1901 return; 1902 } 1903 std::optional<size_t> bus; 1904 std::optional<size_t> address; 1905 std::optional<size_t> backplaneIndex; 1906 std::optional<std::string> name; 1907 for (const auto& [key, value] : resp) 1908 { 1909 if (key == "Bus") 1910 { 1911 bus = std::get<uint64_t>(value); 1912 } 1913 else if (key == "Address") 1914 { 1915 address = std::get<uint64_t>(value); 1916 } 1917 else if (key == "Index") 1918 { 1919 backplaneIndex = std::get<uint64_t>(value); 1920 } 1921 else if (key == "Name") 1922 { 1923 name = std::get<std::string>(value); 1924 } 1925 } 1926 if (!bus || !address || !name || !backplaneIndex) 1927 { 1928 std::cerr << __FUNCTION__ 1929 << ": Illegal configuration at " << path 1930 << "\n"; 1931 backplanesLoadedCallback->setError(); 1932 return; 1933 } 1934 std::string parentPath = 1935 std::filesystem::path(path).parent_path(); 1936 const auto& [backplane, status] = backplanes.emplace( 1937 *name, std::make_shared<Backplane>( 1938 *bus, *address, *backplaneIndex, *name)); 1939 backplane->second->run(parentPath, owner); 1940 populateMuxes(backplane->second->muxes, parentPath); 1941 }, 1942 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1943 hsbpCpldInft); 1944 } 1945 }, 1946 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 1947 0, std::array<const char*, 1>{hsbpCpldInft}); 1948 } 1949 1950 void setUpBackplanesAndDrives() 1951 { 1952 static bool backplanesScanInProgress = false; 1953 static bool backplanesRescanInQueue = false; 1954 1955 if (appState < AppState::componentsLoaded) 1956 { 1957 std::cerr << __FUNCTION__ 1958 << ": Components are not initialized ! Cancelling scan of " 1959 "Backplanes ! \n"; 1960 return; 1961 } 1962 1963 if (backplanesScanInProgress) 1964 { 1965 std::cerr << __FUNCTION__ 1966 << ": Backplanes Scan is already in progress\n"; 1967 if (backplanesRescanInQueue) 1968 { 1969 /* There is already a Re-Scan in queue. No need to create multiple 1970 * rescans */ 1971 return; 1972 } 1973 1974 backplanesRescanInQueue = true; 1975 1976 std::cerr << __FUNCTION__ << ": Queuing the Backplane Scan \n"; 1977 1978 auto backplaneScanTimer = 1979 std::make_shared<boost::asio::steady_timer>(io); 1980 backplaneScanTimer->expires_after(std::chrono::seconds(1)); 1981 backplaneScanTimer->async_wait( 1982 [backplaneScanTimer](const boost::system::error_code ec) { 1983 if (ec == boost::asio::error::operation_aborted) 1984 { 1985 // Timer was Aborted 1986 return; 1987 } 1988 else if (ec) 1989 { 1990 std::cerr << "backplaneScanTimer: Timer error" 1991 << ec.message() << "\n"; 1992 return; 1993 } 1994 backplanesRescanInQueue = false; 1995 setUpBackplanesAndDrives(); 1996 }); 1997 1998 return; 1999 } 2000 2001 backplanesScanInProgress = true; 2002 2003 /* Set Callback to be called once backplanes are populated to call 2004 * updateAssets() and checkHsbpDrivesStatus() or handle error scnenario */ 2005 auto backplanesLoadedCallback = std::make_shared<AsyncCallbackHandler>( 2006 []() { 2007 /* If no HSBP's were detected, the state changes to 2008 * componentsLoaded. Proceed further only if state was 2009 * loadingBackplanes */ 2010 if (appState != AppState::loadingBackplanes) 2011 { 2012 backplanesScanInProgress = false; 2013 return; 2014 } 2015 2016 /* If there is a ReScan in the Queue, dont proceed further. Load the 2017 * Backplanes again and then proceed further */ 2018 if (backplanesRescanInQueue) 2019 { 2020 backplanesScanInProgress = false; 2021 return; 2022 } 2023 2024 appState = AppState::backplanesLoaded; 2025 std::cerr << __FUNCTION__ << ": Backplanes Loaded...\n"; 2026 2027 checkHsbpDrivesStatus(); 2028 updateAssets(); 2029 backplanesScanInProgress = false; 2030 }, 2031 []() { 2032 /* Loading Backplanes is an important step. If the load failed due 2033 * to an error, stop the app so that restart cant be triggerred */ 2034 std::cerr << "Backplanes couldn't be loaded due to an error !...\n"; 2035 appState = AppState::idle; 2036 backplanesScanInProgress = false; 2037 stopHsbpManager(); 2038 }); 2039 2040 populateHsbpBackplanes(backplanesLoadedCallback); 2041 } 2042 2043 void setupBackplanesAndDrivesMatch() 2044 { 2045 static auto backplaneMatch = std::make_unique<sdbusplus::bus::match_t>( 2046 *conn, 2047 "sender='xyz.openbmc_project.EntityManager', type='signal', " 2048 "member='PropertiesChanged', " 2049 "interface='org.freedesktop.DBus.Properties', " 2050 "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" + 2051 std::string(hsbpCpldInft) + "'", 2052 [](sdbusplus::message_t& msg) { 2053 std::string intfName; 2054 boost::container::flat_map<std::string, BasicVariantType> values; 2055 msg.read(intfName, values); 2056 2057 /* This match will be triggered for each of the property being set 2058 * under the hsbpCpldInft interface. Call the loader only on one 2059 * property say "name". This will avoid multiple calls to populate 2060 * function 2061 */ 2062 for (const auto& [key, value] : values) 2063 { 2064 if (key == "Name") 2065 { 2066 /* This match will be triggered when ever there is a 2067 * addition/removal of HSBP backplane. At this stage, all 2068 * the HSBP's need to be populated again and also assets 2069 * have to be re-discovered. So, setting state to 2070 * componentsLoaded and calling setUpBackplanesAndDrives() 2071 * only if configuration and components loading was 2072 * completed */ 2073 if (appState < AppState::componentsLoaded) 2074 { 2075 /* Configuration is not loaded yet. Backplanes will be 2076 * loaded 2077 * once configuration and components are loaded. */ 2078 std::cerr << __FUNCTION__ 2079 << ": Discarding Backplane match\n"; 2080 return; 2081 } 2082 2083 appState = AppState::componentsLoaded; 2084 2085 /* We will call the function after a small delay to let all 2086 * the properties to be intialized */ 2087 auto backplaneTimer = 2088 std::make_shared<boost::asio::steady_timer>(io); 2089 backplaneTimer->expires_after(std::chrono::seconds(2)); 2090 backplaneTimer->async_wait( 2091 [backplaneTimer](const boost::system::error_code ec) { 2092 if (ec == boost::asio::error::operation_aborted) 2093 { 2094 return; 2095 } 2096 else if (ec) 2097 { 2098 std::cerr << "backplaneTimer: Timer error" 2099 << ec.message() << "\n"; 2100 return; 2101 } 2102 setUpBackplanesAndDrives(); 2103 }); 2104 } 2105 } 2106 }); 2107 2108 static auto drivesMatch = std::make_unique<sdbusplus::bus::match_t>( 2109 *conn, 2110 "sender='xyz.openbmc_project.EntityManager', type='signal', " 2111 "member='PropertiesChanged', " 2112 "interface='org.freedesktop.DBus.Properties', arg0='" + 2113 std::string(nvmeIntf) + "'", 2114 [](sdbusplus::message_t& msg) { 2115 std::string intfName; 2116 boost::container::flat_map<std::string, BasicVariantType> values; 2117 msg.read(intfName, values); 2118 2119 /* This match will be triggered for each of the property being set 2120 * under the nvmeIntf interface. Call the loader only on one 2121 * property say "name". This will avoid multiple calls to populate 2122 * function 2123 */ 2124 for (const auto& [key, value] : values) 2125 { 2126 if (key == "Name") 2127 { 2128 /* This match will be triggered when ever there is a 2129 * addition/removal of drives. At this stage only assets 2130 * have to be re-discovered. So, setting state to 2131 * backplanesLoaded and calling updateAssets() only if all 2132 * previous states are completed */ 2133 if (appState < AppState::backplanesLoaded) 2134 { 2135 /* Configuration is not loaded yet. Drives will be 2136 * loaded once 2137 * configuration, components and backplanes are loaded. 2138 */ 2139 std::cerr << __FUNCTION__ 2140 << ": Discarding Drive match\n"; 2141 return; 2142 } 2143 2144 appState = AppState::backplanesLoaded; 2145 2146 /* We will call the function after a small delay to let all 2147 * the properties to be intialized */ 2148 auto driveTimer = 2149 std::make_shared<boost::asio::steady_timer>(io); 2150 driveTimer->expires_after(std::chrono::seconds(2)); 2151 driveTimer->async_wait( 2152 [driveTimer](const boost::system::error_code ec) { 2153 if (ec == boost::asio::error::operation_aborted) 2154 { 2155 return; 2156 } 2157 else if (ec) 2158 { 2159 std::cerr << "driveTimer: Timer error" 2160 << ec.message() << "\n"; 2161 return; 2162 } 2163 updateAssets(); 2164 }); 2165 } 2166 } 2167 }); 2168 } 2169 /***************************** End of Section *******************************/ 2170 2171 /****************************************************************************/ 2172 /******************* Components related Function Definitions ****************/ 2173 /****************************************************************************/ 2174 bool verifyComponentsLoaded() 2175 { 2176 std::cerr << __FUNCTION__ << ": Verifying all Components...\n"; 2177 2178 /* Loop through all clock buffers */ 2179 for (auto& clockBuffer : clockBuffers) 2180 { 2181 if (!clockBuffer.isInitialized()) 2182 { 2183 std::cerr << "Critical Error: Initializing \"" 2184 << clockBuffer.getName() << "\" failed\n"; 2185 return false; 2186 } 2187 } 2188 2189 /* Loop through all IO Expanders */ 2190 for (auto& ioExpander : ioExpanders) 2191 { 2192 if (!ioExpander.isInitialized()) 2193 { 2194 std::cerr << "Critical Error: Initializing \"" 2195 << ioExpander.getName() << "\" failed\n"; 2196 return false; 2197 } 2198 } 2199 2200 std::cerr << __FUNCTION__ << ": Verifying Components Complete\n"; 2201 2202 return true; 2203 } 2204 /***************************** End of Section *******************************/ 2205 2206 /****************************************************************************/ 2207 /****************** IO expander related Function Definitions ****************/ 2208 /****************************************************************************/ 2209 void loadIoExpanderInfo( 2210 const std::shared_ptr<AsyncCallbackHandler>& componentsLoadedCallback) 2211 { 2212 appState = AppState::loadingComponents; 2213 2214 /* Clear global ioExpanders to start off */ 2215 ioExpanders.clear(); 2216 2217 conn->async_method_call( 2218 [componentsLoadedCallback](const boost::system::error_code ec, 2219 const GetSubTreeType& subtree) { 2220 if (ec) 2221 { 2222 std::cerr << __FUNCTION__ << ": Error contacting mapper " 2223 << ec.message() << "\n"; 2224 componentsLoadedCallback->setError(); 2225 return; 2226 } 2227 2228 for (auto& [path, objDict] : subtree) 2229 { 2230 2231 if (objDict.empty()) 2232 { 2233 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n"; 2234 componentsLoadedCallback->setError(); 2235 return; 2236 } 2237 2238 /* Ideally there would be only one element in objDict as only 2239 * one service exposes it and there would be only one interface 2240 * so it is safe to directly read them without loop */ 2241 const std::string& service = objDict.begin()->first; 2242 const std::string& intf = objDict.begin()->second.front(); 2243 2244 conn->async_method_call( 2245 [componentsLoadedCallback]( 2246 const boost::system::error_code er, 2247 const boost::container::flat_map< 2248 std::string, BasicVariantType>& resp) { 2249 if (er) 2250 { 2251 std::cerr << __FUNCTION__ 2252 << ": Error Getting " 2253 "Config " 2254 << er.message() << "\n"; 2255 componentsLoadedCallback->setError(); 2256 return; 2257 } 2258 2259 std::optional<uint64_t> bus; 2260 std::optional<uint64_t> address; 2261 std::optional<uint64_t> confIORegAddr; 2262 std::optional<uint64_t> outCtrlBaseAddr; 2263 std::optional<uint64_t> outCtrlByteCount; 2264 std::unordered_map<std::string, 2265 std::vector<std::string>> 2266 ioMap; 2267 std::optional<std::string> name; 2268 std::optional<std::string> type; 2269 2270 /* Loop through to get all IO Expander properties */ 2271 for (const auto& [key, value] : resp) 2272 { 2273 if (key == "Bus") 2274 { 2275 bus = std::get<uint64_t>(value); 2276 } 2277 else if (key == "Address") 2278 { 2279 address = std::get<uint64_t>(value); 2280 } 2281 else if (key == "ConfIORegAddr") 2282 { 2283 confIORegAddr = std::get<uint64_t>(value); 2284 } 2285 else if (key == "OutCtrlBaseAddr") 2286 { 2287 outCtrlBaseAddr = std::get<uint64_t>(value); 2288 } 2289 else if (key == "OutCtrlByteCount") 2290 { 2291 outCtrlByteCount = std::get<uint64_t>(value); 2292 } 2293 else if (key == "Name") 2294 { 2295 name = std::get<std::string>(value); 2296 } 2297 else if (key == "Type") 2298 { 2299 type = std::get<std::string>(value); 2300 } 2301 else if (key.starts_with("IO")) 2302 { 2303 std::optional<std::vector<std::string>> outList; 2304 outList = std::get<NvmeMapping>(value); 2305 if (!outList) 2306 { 2307 break; 2308 } 2309 ioMap.try_emplace(key, *outList); 2310 } 2311 } 2312 2313 /* Verify if all properties were defined */ 2314 if (!bus || !address || !confIORegAddr || 2315 !outCtrlBaseAddr || !outCtrlByteCount || !name) 2316 { 2317 std::cerr << __FUNCTION__ 2318 << ": Incomplete " 2319 "Clock Buffer definition !! \n"; 2320 componentsLoadedCallback->setError(); 2321 return; 2322 } 2323 2324 /* Check if we were able to get byteMap correctly */ 2325 if ((*outCtrlByteCount) != ioMap.size()) 2326 { 2327 std::cerr << "loadIoExpanderInfo(): Incomplete " 2328 "IO Map !! \n"; 2329 componentsLoadedCallback->setError(); 2330 return; 2331 } 2332 2333 /* Create IO expander object and add it to global 2334 * ioExpanders vector */ 2335 ioExpanders.emplace_front( 2336 *bus, *address, *confIORegAddr, *outCtrlBaseAddr, 2337 *outCtrlByteCount, ioMap, *name, *type); 2338 }, 2339 service, path, "org.freedesktop.DBus.Properties", "GetAll", 2340 intf); 2341 } 2342 }, 2343 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 2344 0, hsbpConfig.ioExpanderTypes); 2345 } 2346 /***************************** End of Section *******************************/ 2347 2348 /****************************************************************************/ 2349 /***************** Clock buffer related Function Definitions ****************/ 2350 /****************************************************************************/ 2351 void loadClockBufferInfo( 2352 const std::shared_ptr<AsyncCallbackHandler>& componentsLoadedCallback) 2353 { 2354 appState = AppState::loadingComponents; 2355 2356 /* Clear global clockBuffers to start off */ 2357 clockBuffers.clear(); 2358 2359 conn->async_method_call( 2360 [componentsLoadedCallback](const boost::system::error_code ec, 2361 const GetSubTreeType& subtree) { 2362 if (ec) 2363 { 2364 std::cerr << __FUNCTION__ << ": Error contacting mapper " 2365 << ec.message() << "\n"; 2366 componentsLoadedCallback->setError(); 2367 return; 2368 } 2369 2370 for (auto& [path, objDict] : subtree) 2371 { 2372 2373 if (objDict.empty()) 2374 { 2375 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n"; 2376 componentsLoadedCallback->setError(); 2377 return; 2378 } 2379 2380 /* Ideally there would be only one element in objDict as only 2381 * one service exposes it and there would be only one interface 2382 * so it is safe to directly read them without loop */ 2383 const std::string& service = objDict.begin()->first; 2384 const std::string& intf = objDict.begin()->second.front(); 2385 2386 conn->async_method_call( 2387 [componentsLoadedCallback]( 2388 const boost::system::error_code er, 2389 const boost::container::flat_map< 2390 std::string, BasicVariantType>& resp) { 2391 if (er) 2392 { 2393 std::cerr << __FUNCTION__ 2394 << ": Error Getting " 2395 "Config " 2396 << er.message() << "\n"; 2397 componentsLoadedCallback->setError(); 2398 return; 2399 } 2400 2401 std::optional<uint64_t> bus; 2402 std::optional<uint64_t> address; 2403 std::optional<std::string> mode; 2404 std::optional<uint64_t> outCtrlBaseAddr; 2405 std::optional<uint64_t> outCtrlByteCount; 2406 std::unordered_map<std::string, 2407 std::vector<std::string>> 2408 byteMap; 2409 std::optional<std::string> name; 2410 std::optional<std::string> type; 2411 2412 /* Loop through to get all Clock Buffer properties */ 2413 for (const auto& [key, value] : resp) 2414 { 2415 if (key == "Bus") 2416 { 2417 bus = std::get<uint64_t>(value); 2418 } 2419 else if (key == "Address") 2420 { 2421 address = std::get<uint64_t>(value); 2422 } 2423 else if (key == "Mode") 2424 { 2425 mode = std::get<std::string>(value); 2426 } 2427 else if (key == "OutCtrlBaseAddr") 2428 { 2429 outCtrlBaseAddr = std::get<uint64_t>(value); 2430 } 2431 else if (key == "OutCtrlByteCount") 2432 { 2433 outCtrlByteCount = std::get<uint64_t>(value); 2434 } 2435 else if (key == "Name") 2436 { 2437 name = std::get<std::string>(value); 2438 } 2439 else if (key == "Type") 2440 { 2441 type = std::get<std::string>(value); 2442 } 2443 else if (key.starts_with("Byte")) 2444 { 2445 std::optional<std::vector<std::string>> 2446 byteList; 2447 byteList = std::get<NvmeMapping>(value); 2448 if (!byteList) 2449 { 2450 break; 2451 } 2452 byteMap.try_emplace(key, *byteList); 2453 } 2454 } 2455 2456 /* Verify if all properties were defined */ 2457 if (!bus || !address || !mode || !outCtrlBaseAddr || 2458 !outCtrlByteCount || !name) 2459 { 2460 std::cerr << __FUNCTION__ 2461 << ": Incomplete " 2462 "Clock Buffer definition !! \n"; 2463 componentsLoadedCallback->setError(); 2464 return; 2465 } 2466 2467 /* Check if we were able to get byteMap correctly */ 2468 if ((*outCtrlByteCount) != byteMap.size()) 2469 { 2470 std::cerr << __FUNCTION__ 2471 << ": Incomplete " 2472 "Byte Map !! \n"; 2473 componentsLoadedCallback->setError(); 2474 return; 2475 } 2476 2477 /* Create clock buffer object and add it to global 2478 * clockBuffers vector */ 2479 clockBuffers.emplace_front( 2480 *bus, *address, *mode, *outCtrlBaseAddr, 2481 *outCtrlByteCount, byteMap, *name, *type); 2482 }, 2483 service, path, "org.freedesktop.DBus.Properties", "GetAll", 2484 intf); 2485 } 2486 }, 2487 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 2488 0, hsbpConfig.clockBufferTypes); 2489 } 2490 /***************************** End of Section *******************************/ 2491 2492 /****************************************************************************/ 2493 /***************** HSBP Config related Function Definitions *****************/ 2494 /****************************************************************************/ 2495 void loadHsbpConfig() 2496 { 2497 appState = AppState::loadingHsbpConfig; 2498 2499 conn->async_method_call( 2500 [](const boost::system::error_code ec, const GetSubTreeType& subtree) { 2501 if (ec) 2502 { 2503 std::cerr << __FUNCTION__ << ": Error contacting mapper " 2504 << ec.message() << "\n"; 2505 return; 2506 } 2507 2508 if (subtree.empty()) 2509 { 2510 /* Entity manager is either still loading the configuration or 2511 * failed to load. In either way, return from here as the dbus 2512 * match will take care */ 2513 std::cerr << __FUNCTION__ << ": No configuration detected !!\n"; 2514 return; 2515 } 2516 2517 /* There should be only one HSBP Configureation exposed */ 2518 if (subtree.size() != 1) 2519 { 2520 std::cerr << __FUNCTION__ 2521 << ": Multiple configurations " 2522 "detected !!\n"; 2523 /* Critical Error. Stop Application */ 2524 stopHsbpManager(); 2525 return; 2526 } 2527 2528 auto& path = subtree.begin()->first; 2529 auto& objDict = subtree.begin()->second; 2530 2531 if (objDict.empty()) 2532 { 2533 /* Critical Error. Stop Application */ 2534 std::cerr << __FUNCTION__ << ": Subtree data corrupted !\n"; 2535 stopHsbpManager(); 2536 return; 2537 } 2538 2539 const std::string& service = objDict.begin()->first; 2540 2541 conn->async_method_call( 2542 [](const boost::system::error_code er, 2543 const boost::container::flat_map<std::string, 2544 BasicVariantType>& resp) { 2545 if (er) 2546 { 2547 std::cerr << __FUNCTION__ << ": Error Getting Config " 2548 << er.message() << "\n"; 2549 /* Critical Error. Stop Application */ 2550 stopHsbpManager(); 2551 return; 2552 } 2553 2554 std::optional<uint64_t> rootI2cBus; 2555 std::optional<std::vector<std::string>> supportedHsbps; 2556 std::optional<std::vector<std::string>> clockBufferTypes; 2557 std::optional<std::vector<std::string>> ioExpanderTypes; 2558 2559 /* Loop through to get root i2c bus and list of supported 2560 * HSBPs */ 2561 for (const auto& [key, value] : resp) 2562 { 2563 if (key == "HsbpSupported") 2564 { 2565 supportedHsbps = 2566 std::get<std::vector<std::string>>(value); 2567 } 2568 else if (key == "RootI2cBus") 2569 { 2570 rootI2cBus = std::get<uint64_t>(value); 2571 } 2572 else if (key == "ClockBuffer") 2573 { 2574 clockBufferTypes = 2575 std::get<std::vector<std::string>>(value); 2576 } 2577 else if (key == "IoExpander") 2578 { 2579 ioExpanderTypes = 2580 std::get<std::vector<std::string>>(value); 2581 } 2582 } 2583 2584 /* Verify if i2c bus, supported HSBP's and clock buffers 2585 * were defined (IO Expanders are optional) */ 2586 if (!rootI2cBus || !supportedHsbps || !clockBufferTypes) 2587 { 2588 std::cerr << __FUNCTION__ 2589 << ": Incomplete HSBP " 2590 "configuration !! \n"; 2591 /* Critical Error. Stop Application */ 2592 stopHsbpManager(); 2593 return; 2594 } 2595 2596 /* Clear and Load all details to global hsbp configuration 2597 * variable */ 2598 hsbpConfig.clearConfig(); 2599 hsbpConfig.rootBus = *rootI2cBus; 2600 hsbpConfig.supportedHsbps = std::move(*supportedHsbps); 2601 2602 for (auto& clkBuffType : *clockBufferTypes) 2603 { 2604 hsbpConfig.clockBufferTypes.emplace_back( 2605 "xyz.openbmc_project.Configuration." + clkBuffType); 2606 } 2607 2608 if (ioExpanderTypes) 2609 { 2610 for (auto& ioCntrType : *ioExpanderTypes) 2611 { 2612 hsbpConfig.ioExpanderTypes.emplace_back( 2613 "xyz.openbmc_project.Configuration." + 2614 ioCntrType); 2615 } 2616 } 2617 2618 /* Loop through to get HSBP-NVME map and Components map 2619 * details */ 2620 uint8_t hsbpMapCount = 0; 2621 for (const auto& [key, value] : resp) 2622 { 2623 if (std::find(hsbpConfig.supportedHsbps.begin(), 2624 hsbpConfig.supportedHsbps.end(), 2625 key) != hsbpConfig.supportedHsbps.end()) 2626 { 2627 std::optional<std::vector<std::string>> hsbpMap; 2628 hsbpMap = std::get<NvmeMapping>(value); 2629 if (!hsbpMap) 2630 { 2631 break; 2632 } 2633 hsbpConfig.hsbpNvmeMap.try_emplace(key, *hsbpMap); 2634 hsbpMapCount++; 2635 } 2636 } 2637 2638 /* Check if we were able to get all the HSBP-NVMe maps */ 2639 if (hsbpConfig.supportedHsbps.size() != hsbpMapCount) 2640 { 2641 std::cerr << __FUNCTION__ 2642 << ": Incomplete HSBP Map " 2643 "details !! \n"; 2644 /* Critical Error. Stop Application */ 2645 stopHsbpManager(); 2646 return; 2647 } 2648 2649 /* HSBP configuration is loaded */ 2650 appState = AppState::hsbpConfigLoaded; 2651 std::cerr << "HSBP Config loaded !\n"; 2652 2653 /* Get Clock buffers and IO expander details. Create shared 2654 * object of AsyncCallbackHandler with success and error 2655 * callback */ 2656 auto componentsLoadedCallback = std::make_shared< 2657 AsyncCallbackHandler>( 2658 []() { 2659 /* Verify if all components were initialized without 2660 * errors */ 2661 if (!verifyComponentsLoaded()) 2662 { 2663 /* The application cannot proceed further as 2664 * components initialization failed. App needs 2665 * Restart */ 2666 appState = AppState::idle; 2667 std::cerr 2668 << "One or more Componenets initialization " 2669 "failed !! Restart Required !\n"; 2670 stopHsbpManager(); 2671 } 2672 2673 appState = AppState::componentsLoaded; 2674 setUpBackplanesAndDrives(); 2675 }, 2676 []() { 2677 /* The application cannot proceed further as 2678 * components load failed. App needs Restart */ 2679 appState = AppState::idle; 2680 std::cerr << "Loading Componenets failed !! " 2681 "Restart Required !\n"; 2682 stopHsbpManager(); 2683 }); 2684 2685 loadClockBufferInfo(componentsLoadedCallback); 2686 2687 if (ioExpanderTypes) 2688 { 2689 loadIoExpanderInfo(componentsLoadedCallback); 2690 } 2691 }, 2692 service, path, "org.freedesktop.DBus.Properties", "GetAll", 2693 hsbpConfigIntf); 2694 }, 2695 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 2696 0, std::array<const char*, 1>{hsbpConfigIntf}); 2697 } 2698 2699 void setupHsbpConfigMatch() 2700 { 2701 static auto hsbpConfigMatch = std::make_unique<sdbusplus::bus::match_t>( 2702 *conn, 2703 "sender='xyz.openbmc_project.EntityManager', type='signal', " 2704 "member='PropertiesChanged', " 2705 "interface='org.freedesktop.DBus.Properties', " 2706 "path_namespace='/xyz/openbmc_project/inventory/system/board', arg0='" + 2707 std::string(hsbpConfigIntf) + "'", 2708 [](sdbusplus::message_t& msg) { 2709 std::string intfName; 2710 boost::container::flat_map<std::string, BasicVariantType> values; 2711 msg.read(intfName, values); 2712 2713 /* This match will be triggered for each of the property being set 2714 * under the hsbpConfig interface. "HsbpSupported" is one of the 2715 * important property which will enable us to read other properties. 2716 * So, when the match event occurs for "HsbpSupported" property 2717 * being set, we will call "loadHsbpConfig()" If the control has 2718 * come here, its either the first initialization or entity-manager 2719 * reload. So, we will reset the state to uninitialized 2720 */ 2721 for (const auto& [key, value] : values) 2722 { 2723 if (key == "HsbpSupported") 2724 { 2725 /* Configuration change detected, change the state to stop 2726 * other processing */ 2727 appState = AppState::idle; 2728 2729 /* We will call the function after a small delay to let all 2730 * the properties to be intialized */ 2731 auto loadTimer = 2732 std::make_shared<boost::asio::steady_timer>(io); 2733 loadTimer->expires_after(std::chrono::seconds(1)); 2734 loadTimer->async_wait( 2735 [loadTimer](const boost::system::error_code ec) { 2736 if (ec == boost::asio::error::operation_aborted) 2737 { 2738 return; 2739 } 2740 else if (ec) 2741 { 2742 std::cerr << __FUNCTION__ << ": Timer error" 2743 << ec.message() << "\n"; 2744 if (hsbpConfig.supportedHsbps.empty()) 2745 { 2746 /* Critical Error as none of the 2747 * configuration was loaded and timer 2748 * failed. Stop the application */ 2749 stopHsbpManager(); 2750 } 2751 return; 2752 } 2753 loadHsbpConfig(); 2754 }); 2755 } 2756 } 2757 }); 2758 } 2759 /***************************** End of Section *******************************/ 2760 2761 /****************************************************************************/ 2762 /***************** GPIO Events related Function Definitions *****************/ 2763 /****************************************************************************/ 2764 static void nvmeLvc3AlertHandler() 2765 { 2766 /* If the state is not backplanesLoaded, we ignore the GPIO event as we 2767 * cannot communicate to the backplanes yet */ 2768 if (appState < AppState::backplanesLoaded) 2769 { 2770 std::cerr << __FUNCTION__ 2771 << ": HSBP not initialized ! Dropping the interrupt ! \n"; 2772 return; 2773 } 2774 2775 /* This GPIO event only indicates the addition or removal of drive to either 2776 * of CPU. The backplanes detected need to be scanned and detect which drive 2777 * has been added/removed and enable/diable clock accordingly */ 2778 gpiod::line_event gpioLineEvent = nvmeLvc3AlertLine.event_read(); 2779 2780 if (gpioLineEvent.event_type == gpiod::line_event::FALLING_EDGE) 2781 { 2782 /* Check for HSBP Drives status to determine if any new drive has been 2783 * added/removed and update clocks accordingly */ 2784 checkHsbpDrivesStatus(); 2785 } 2786 2787 nvmeLvc3AlertEvent.async_wait( 2788 boost::asio::posix::stream_descriptor::wait_read, 2789 [](const boost::system::error_code ec) { 2790 if (ec) 2791 { 2792 std::cerr << __FUNCTION__ 2793 << ": nvmealert event error: " << ec.message() 2794 << "\n"; 2795 } 2796 nvmeLvc3AlertHandler(); 2797 }); 2798 } 2799 2800 static bool hsbpRequestAlertGpioEvents( 2801 const std::string& name, const std::function<void()>& handler, 2802 gpiod::line& gpioLine, 2803 boost::asio::posix::stream_descriptor& gpioEventDescriptor) 2804 { 2805 // Find the GPIO line 2806 gpioLine = gpiod::find_line(name); 2807 if (!gpioLine) 2808 { 2809 std::cerr << __FUNCTION__ << ": Failed to find the " << name 2810 << " line\n"; 2811 return false; 2812 } 2813 2814 try 2815 { 2816 gpioLine.request( 2817 {"hsbp-manager", gpiod::line_request::EVENT_BOTH_EDGES, 0}); 2818 } 2819 catch (std::exception&) 2820 { 2821 std::cerr << __FUNCTION__ << ": Failed to request events for " << name 2822 << "\n"; 2823 return false; 2824 } 2825 2826 int gpioLineFd = gpioLine.event_get_fd(); 2827 if (gpioLineFd < 0) 2828 { 2829 std::cerr << __FUNCTION__ << ": Failed to get " << name << " fd\n"; 2830 return false; 2831 } 2832 2833 gpioEventDescriptor.assign(gpioLineFd); 2834 2835 gpioEventDescriptor.async_wait( 2836 boost::asio::posix::stream_descriptor::wait_read, 2837 [&name, handler](const boost::system::error_code ec) { 2838 if (ec) 2839 { 2840 std::cerr << __FUNCTION__ << ": " << name 2841 << " fd handler error: " << ec.message() << "\n"; 2842 return; 2843 } 2844 handler(); 2845 }); 2846 return true; 2847 } 2848 /***************************** End of Section *******************************/ 2849 2850 int main() 2851 { 2852 std::cerr << "******* Starting hsbp-manager *******\n"; 2853 2854 /* Set the Dbus name */ 2855 conn->request_name(busName); 2856 2857 std::shared_ptr<sdbusplus::asio::dbus_interface> storageIface; 2858 2859 /* Add interface for storage inventory */ 2860 storageIface = objServer.add_interface("/xyz/openbmc_project/inventory/item/storage/hsbp/1", 2861 "xyz.openbmc_project.inventory.item.storage"); 2862 2863 storageIface->initialize(); 2864 2865 /* HSBP initializtion flow: 2866 * 1. Register GPIO event callback on FM_SMB_BMC_NVME_LVC3_ALERT_N line 2867 * 2. Set up Dbus match for power - determine if host is up and running 2868 * or powered off 2869 * 3. Set up Dbus match for HSBP backplanes and Drives 2870 * 4. Load HSBP config exposed by entity manager 2871 * - Also setup a match to capture HSBP configuation in case 2872 * entity-manager restarts 2873 * 5. Load Clock buffer and IO expander (and other peripherals if any 2874 * related to HSBP functionality) 2875 * - Reload the info each time HSBP configuration is changed 2876 * 6. Populate all Backpanes (HSBP's) 2877 * 7. Load all NVMe drive's and associate with HSBP Backpane 2878 */ 2879 2880 /* Register GPIO Events on FM_SMB_BMC_NVME_LVC3_ALERT_N */ 2881 if (!hsbpRequestAlertGpioEvents("FM_SMB_BMC_NVME_LVC3_ALERT_N", 2882 nvmeLvc3AlertHandler, nvmeLvc3AlertLine, 2883 nvmeLvc3AlertEvent)) 2884 { 2885 std::cerr << __FUNCTION__ 2886 << ": error: Unable to monitor events on HSBP " 2887 "Alert line\n"; 2888 return -1; 2889 } 2890 2891 /* Setup Dbus-match for power */ 2892 setupPowerMatch(conn); 2893 2894 /* Setup Dbus-match for HSBP backplanes and Drives */ 2895 setupBackplanesAndDrivesMatch(); 2896 2897 /* Setup HSBP Config match and load config 2898 * In the event of entity-manager reboot, the match will help catch new 2899 * configuration. 2900 * In the event of hsbp-manager reboot, loadHsbpConfig will get all 2901 * config details and will take care of remaining config's to be 2902 * loaded 2903 */ 2904 setupHsbpConfigMatch(); 2905 loadHsbpConfig(); 2906 2907 io.run(); 2908 std::cerr << __FUNCTION__ << ": Aborting hsbp-manager !\n"; 2909 return -1; 2910 } 2911