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