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