1 /* 2 // Copyright (c) 2019 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 17 #include "utils.hpp" 18 19 #include <boost/algorithm/string/replace.hpp> 20 #include <boost/asio/steady_timer.hpp> 21 #include <filesystem> 22 #include <iostream> 23 #include <sdbusplus/asio/connection.hpp> 24 #include <sdbusplus/asio/object_server.hpp> 25 #include <sdbusplus/bus/match.hpp> 26 #include <string> 27 #include <utility> 28 29 extern "C" { 30 #include <i2c/smbus.h> 31 #include <linux/i2c-dev.h> 32 } 33 34 constexpr const char* configType = 35 "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD"; 36 37 constexpr size_t scanRateSeconds = 5; 38 constexpr size_t maxDrives = 8; // only 1 byte alloted 39 40 boost::asio::io_context io; 41 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 42 sdbusplus::asio::object_server objServer(conn); 43 44 static std::string zeroPad(const uint8_t val) 45 { 46 std::ostringstream version; 47 version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val); 48 return version.str(); 49 } 50 51 struct Mux 52 { 53 Mux(size_t busIn, size_t addressIn) : bus(busIn), address(addressIn) 54 { 55 } 56 size_t bus; 57 size_t address; 58 }; 59 struct Drive 60 { 61 Drive(size_t driveIndex, bool isPresent, bool isOperational, bool nvme, 62 bool rebuilding) : 63 isNvme(nvme) 64 { 65 constexpr const char* basePath = 66 "/xyz/openbmc_project/inventory/item/drive/Drive_"; 67 itemIface = objServer.add_interface( 68 basePath + std::to_string(driveIndex), inventory::interface); 69 itemIface->register_property("Present", isPresent); 70 itemIface->register_property("PrettyName", 71 "Drive " + std::to_string(driveIndex)); 72 itemIface->initialize(); 73 operationalIface = objServer.add_interface( 74 itemIface->get_object_path(), 75 "xyz.openbmc_project.State.Decorator.OperationalStatus"); 76 operationalIface->register_property("Functional", isOperational); 77 operationalIface->initialize(); 78 rebuildingIface = objServer.add_interface( 79 itemIface->get_object_path(), "xyz.openbmc_project.State.Drive"); 80 rebuildingIface->register_property("Rebuilding", rebuilding); 81 rebuildingIface->initialize(); 82 } 83 ~Drive() 84 { 85 objServer.remove_interface(itemIface); 86 objServer.remove_interface(operationalIface); 87 objServer.remove_interface(rebuildingIface); 88 objServer.remove_interface(associationIface); 89 } 90 91 void createAssociation(const std::string& path) 92 { 93 if (associationIface != nullptr) 94 { 95 return; 96 } 97 associationIface = objServer.add_interface( 98 itemIface->get_object_path(), 99 "xyz.openbmc_project.Association.Definitions"); 100 std::vector<Association> associations; 101 associations.emplace_back("inventory", "drive", path); 102 associationIface->register_property("Associations", associations); 103 associationIface->initialize(); 104 } 105 106 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface; 107 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface; 108 std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface; 109 std::shared_ptr<sdbusplus::asio::dbus_interface> associationIface; 110 bool isNvme; 111 }; 112 113 struct Backplane 114 { 115 116 Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn, 117 const std::string& nameIn) : 118 bus(busIn), 119 address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn), 120 timer(std::make_shared<boost::asio::steady_timer>(io)), 121 muxes(std::make_shared<std::vector<Mux>>()) 122 { 123 } 124 void run() 125 { 126 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR); 127 if (file < 0) 128 { 129 std::cerr << "unable to open bus " << bus << "\n"; 130 return; 131 } 132 133 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) 134 { 135 std::cerr << "unable to set address to " << address << "\n"; 136 return; 137 } 138 139 if (!getPresent()) 140 { 141 std::cerr << "Cannot detect CPLD\n"; 142 return; 143 } 144 145 getBootVer(bootVer); 146 getFPGAVer(fpgaVer); 147 getSecurityRev(securityRev); 148 std::string dbusName = boost::replace_all_copy(name, " ", "_"); 149 hsbpItemIface = objServer.add_interface( 150 "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName, 151 inventory::interface); 152 hsbpItemIface->register_property("Present", true); 153 hsbpItemIface->register_property("PrettyName", name); 154 hsbpItemIface->initialize(); 155 156 versionIface = 157 objServer.add_interface(hsbpItemIface->get_object_path(), 158 "xyz.openbmc_project.Software.Version"); 159 versionIface->register_property("Version", zeroPad(bootVer) + "." + 160 zeroPad(fpgaVer) + "." + 161 zeroPad(securityRev)); 162 versionIface->register_property( 163 "Purpose", 164 std::string( 165 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP")); 166 versionIface->initialize(); 167 getPresence(presence); 168 getIFDET(ifdet); 169 170 createDrives(); 171 172 runTimer(); 173 } 174 175 void runTimer() 176 { 177 timer->expires_after(std::chrono::seconds(scanRateSeconds)); 178 timer->async_wait([this](boost::system::error_code ec) { 179 if (ec == boost::asio::error::operation_aborted) 180 { 181 // we're being destroyed 182 return; 183 } 184 else if (ec) 185 { 186 std::cerr << "timer error " << ec.message() << "\n"; 187 return; 188 } 189 uint8_t curPresence = 0; 190 uint8_t curIFDET = 0; 191 uint8_t curFailed = 0; 192 uint8_t curRebuild = 0; 193 194 getPresence(curPresence); 195 getIFDET(curIFDET); 196 getFailed(curFailed); 197 getRebuild(curRebuild); 198 199 if (curPresence != presence || curIFDET != ifdet || 200 curFailed != failed || curRebuild != rebuilding) 201 { 202 presence = curPresence; 203 ifdet = curIFDET; 204 failed = curFailed; 205 rebuilding = curRebuild; 206 updateDrives(); 207 } 208 runTimer(); 209 }); 210 } 211 212 void createDrives() 213 { 214 uint8_t nvme = ifdet ^ presence; 215 for (size_t ii = 0; ii < maxDrives; ii++) 216 { 217 bool isNvme = nvme & (1 << ii); 218 bool isPresent = isNvme || (presence & (1 << ii)); 219 bool isFailed = !isPresent || failed & (1 << ii); 220 bool isRebuilding = !isPresent && (rebuilding & (1 << ii)); 221 222 // +1 to convert from 0 based to 1 based 223 size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1; 224 drives.emplace_back(driveIndex, isPresent, !isFailed, isNvme, 225 isRebuilding); 226 } 227 } 228 229 void updateDrives() 230 { 231 232 uint8_t nvme = ifdet ^ presence; 233 for (size_t ii = 0; ii < maxDrives; ii++) 234 { 235 bool isNvme = nvme & (1 << ii); 236 bool isPresent = isNvme || (presence & (1 << ii)); 237 bool isFailed = !isPresent || (failed & (1 << ii)); 238 bool isRebuilding = isPresent && (rebuilding & (1 << ii)); 239 240 Drive& drive = drives[ii]; 241 drive.isNvme = isNvme; 242 drive.itemIface->set_property("Present", isPresent); 243 drive.operationalIface->set_property("Functional", !isFailed); 244 drive.rebuildingIface->set_property("Rebuilding", isRebuilding); 245 } 246 } 247 248 bool getPresent() 249 { 250 present = i2c_smbus_read_byte(file) >= 0; 251 return present; 252 } 253 254 bool getTypeID(uint8_t& val) 255 { 256 constexpr uint8_t addr = 2; 257 int ret = i2c_smbus_read_byte_data(file, addr); 258 if (ret < 0) 259 { 260 std::cerr << "Error " << __FUNCTION__ << "\n"; 261 return false; 262 } 263 val = static_cast<uint8_t>(ret); 264 return true; 265 } 266 267 bool getBootVer(uint8_t& val) 268 { 269 constexpr uint8_t addr = 3; 270 int ret = i2c_smbus_read_byte_data(file, addr); 271 if (ret < 0) 272 { 273 std::cerr << "Error " << __FUNCTION__ << "\n"; 274 return false; 275 } 276 val = static_cast<uint8_t>(ret); 277 return true; 278 } 279 280 bool getFPGAVer(uint8_t& val) 281 { 282 constexpr uint8_t addr = 4; 283 int ret = i2c_smbus_read_byte_data(file, addr); 284 if (ret < 0) 285 { 286 std::cerr << "Error " << __FUNCTION__ << "\n"; 287 return false; 288 } 289 val = static_cast<uint8_t>(ret); 290 return true; 291 } 292 293 bool getSecurityRev(uint8_t& val) 294 { 295 constexpr uint8_t addr = 5; 296 int ret = i2c_smbus_read_byte_data(file, addr); 297 if (ret < 0) 298 { 299 std::cerr << "Error " << __FUNCTION__ << "\n"; 300 return false; 301 } 302 val = static_cast<uint8_t>(ret); 303 return true; 304 } 305 306 bool getPresence(uint8_t& val) 307 { 308 // NVMe drives do not assert PRSNTn, and as such do not get reported as 309 // PRESENT in this register 310 311 constexpr uint8_t addr = 8; 312 313 int ret = i2c_smbus_read_byte_data(file, addr); 314 if (ret < 0) 315 { 316 std::cerr << "Error " << __FUNCTION__ << "\n"; 317 return false; 318 } 319 // presence is inverted 320 val = static_cast<uint8_t>(~ret); 321 return true; 322 } 323 324 bool getIFDET(uint8_t& val) 325 { 326 // This register is a bitmap of parallel GPIO pins connected to the 327 // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert 328 // IFDETn low when they are inserted into the HSBP.This register, in 329 // combination with the PRESENCE register, are used by the BMC to detect 330 // the presence of NVMe drives. 331 332 constexpr uint8_t addr = 9; 333 334 int ret = i2c_smbus_read_byte_data(file, addr); 335 if (ret < 0) 336 { 337 std::cerr << "Error " << __FUNCTION__ << "\n"; 338 return false; 339 } 340 // ifdet is inverted 341 val = static_cast<uint8_t>(~ret); 342 return true; 343 } 344 345 bool getFailed(uint8_t& val) 346 { 347 constexpr uint8_t addr = 0xC; 348 int ret = i2c_smbus_read_byte_data(file, addr); 349 if (ret < 0) 350 { 351 std::cerr << "Error " << __FUNCTION__ << "\n"; 352 return false; 353 } 354 val = static_cast<uint8_t>(ret); 355 return true; 356 } 357 358 bool getRebuild(uint8_t& val) 359 { 360 constexpr uint8_t addr = 0xD; 361 int ret = i2c_smbus_read_byte_data(file, addr); 362 if (ret < 0) 363 { 364 std::cerr << "Error " << __FUNCTION__ << "\n"; 365 return false; 366 } 367 val = static_cast<uint8_t>(ret); 368 return true; 369 } 370 371 ~Backplane() 372 { 373 objServer.remove_interface(hsbpItemIface); 374 objServer.remove_interface(versionIface); 375 if (file >= 0) 376 { 377 close(file); 378 } 379 } 380 381 size_t bus; 382 size_t address; 383 size_t backplaneIndex; 384 std::string name; 385 std::shared_ptr<boost::asio::steady_timer> timer; 386 bool present = false; 387 uint8_t typeId = 0; 388 uint8_t bootVer = 0; 389 uint8_t fpgaVer = 0; 390 uint8_t securityRev = 0; 391 uint8_t funSupported = 0; 392 uint8_t presence = 0; 393 uint8_t ifdet = 0; 394 uint8_t failed = 0; 395 uint8_t rebuilding = 0; 396 397 int file = -1; 398 399 std::string type; 400 401 std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface; 402 std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface; 403 404 std::vector<Drive> drives; 405 std::shared_ptr<std::vector<Mux>> muxes; 406 }; 407 408 std::unordered_map<std::string, Backplane> backplanes; 409 410 void updateAssociations() 411 { 412 constexpr const char* driveType = 413 "xyz.openbmc_project.Inventory.Item.Drive"; 414 415 conn->async_method_call( 416 [](const boost::system::error_code ec, const GetSubTreeType& subtree) { 417 if (ec) 418 { 419 std::cerr << "Error contacting mapper " << ec.message() << "\n"; 420 return; 421 } 422 for (const auto& [path, objDict] : subtree) 423 { 424 if (objDict.empty()) 425 { 426 continue; 427 } 428 429 const std::string& owner = objDict.begin()->first; 430 conn->async_method_call( 431 [path](const boost::system::error_code ec2, 432 const boost::container::flat_map< 433 std::string, std::variant<uint64_t>>& values) { 434 if (ec2) 435 { 436 std::cerr << "Error Getting Config " 437 << ec2.message() << " " << __FUNCTION__ 438 << "\n"; 439 return; 440 } 441 auto findBus = values.find("Bus"); 442 auto findIndex = values.find("Index"); 443 444 if (findBus == values.end() || 445 findIndex == values.end()) 446 { 447 std::cerr << "Illegal interface at " << path 448 << "\n"; 449 return; 450 } 451 452 size_t muxBus = static_cast<size_t>( 453 std::get<uint64_t>(findBus->second)); 454 size_t driveIndex = static_cast<size_t>( 455 std::get<uint64_t>(findIndex->second)); 456 std::filesystem::path muxPath = 457 "/sys/bus/i2c/devices/i2c-" + 458 std::to_string(muxBus) + "/mux_device"; 459 if (!std::filesystem::is_symlink(muxPath)) 460 { 461 std::cerr << path << " mux does not exist\n"; 462 return; 463 } 464 465 // we should be getting something of the form 7-0052 for 466 // bus 7 addr 52 467 std::string fname = 468 std::filesystem::read_symlink(muxPath).filename(); 469 auto findDash = fname.find('-'); 470 471 if (findDash == std::string::npos || 472 findDash + 1 >= fname.size()) 473 { 474 std::cerr << path << " mux path invalid\n"; 475 return; 476 } 477 478 std::string busStr = fname.substr(0, findDash); 479 std::string muxStr = fname.substr(findDash + 1); 480 481 size_t bus = static_cast<size_t>(std::stoi(busStr)); 482 size_t addr = 483 static_cast<size_t>(std::stoi(muxStr, nullptr, 16)); 484 Backplane* parent = nullptr; 485 for (auto& [name, backplane] : backplanes) 486 { 487 for (const Mux& mux : *(backplane.muxes)) 488 { 489 if (bus == mux.bus && addr == mux.address) 490 { 491 parent = &backplane; 492 break; 493 } 494 } 495 } 496 if (parent == nullptr) 497 { 498 std::cerr << "Failed to find mux at bus " << bus 499 << ", addr " << addr << "\n"; 500 return; 501 } 502 if (parent->drives.size() <= driveIndex) 503 { 504 505 std::cerr << "Illegal drive index at " << path 506 << " " << driveIndex << "\n"; 507 return; 508 } 509 Drive& drive = parent->drives[driveIndex]; 510 drive.createAssociation(path); 511 }, 512 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 513 "xyz.openbmc_project.Inventory.Item.Drive"); 514 } 515 }, 516 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 517 0, std::array<const char*, 1>{driveType}); 518 } 519 520 void populateMuxes(std::shared_ptr<std::vector<Mux>> muxes, 521 std::string& rootPath) 522 { 523 const static std::array<const std::string, 4> muxTypes = { 524 "xyz.openbmc_project.Configuration.PCA9543Mux", 525 "xyz.openbmc_project.Configuration.PCA9544Mux", 526 "xyz.openbmc_project.Configuration.PCA9545Mux", 527 "xyz.openbmc_project.Configuration.PCA9546Mux"}; 528 conn->async_method_call( 529 [muxes](const boost::system::error_code ec, 530 const GetSubTreeType& subtree) { 531 if (ec) 532 { 533 std::cerr << "Error contacting mapper " << ec.message() << "\n"; 534 return; 535 } 536 std::shared_ptr<std::function<void()>> callback = 537 std::make_shared<std::function<void()>>( 538 []() { updateAssociations(); }); 539 for (const auto& [path, objDict] : subtree) 540 { 541 if (objDict.empty() || objDict.begin()->second.empty()) 542 { 543 continue; 544 } 545 546 const std::string& owner = objDict.begin()->first; 547 const std::vector<std::string>& interfaces = 548 objDict.begin()->second; 549 550 const std::string* interface = nullptr; 551 for (const std::string& iface : interfaces) 552 { 553 if (std::find(muxTypes.begin(), muxTypes.end(), iface) != 554 muxTypes.end()) 555 { 556 interface = &iface; 557 break; 558 } 559 } 560 if (interface == nullptr) 561 { 562 std::cerr << "Cannot get mux type\n"; 563 continue; 564 } 565 566 conn->async_method_call( 567 [path, muxes, callback]( 568 const boost::system::error_code ec2, 569 const boost::container::flat_map< 570 std::string, std::variant<uint64_t>>& values) { 571 if (ec2) 572 { 573 std::cerr << "Error Getting Config " 574 << ec2.message() << " " << __FUNCTION__ 575 << "\n"; 576 return; 577 } 578 auto findBus = values.find("Bus"); 579 auto findAddress = values.find("Address"); 580 if (findBus == values.end() || 581 findAddress == values.end()) 582 { 583 std::cerr << "Illegal configuration at " << path 584 << "\n"; 585 return; 586 } 587 size_t bus = static_cast<size_t>( 588 std::get<uint64_t>(findBus->second)); 589 size_t address = static_cast<size_t>( 590 std::get<uint64_t>(findAddress->second)); 591 muxes->emplace_back(bus, address); 592 if (callback.use_count() == 1) 593 { 594 (*callback)(); 595 } 596 }, 597 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 598 *interface); 599 } 600 }, 601 mapper::busName, mapper::path, mapper::interface, mapper::subtree, 602 rootPath, 1, muxTypes); 603 } 604 605 void populate() 606 { 607 conn->async_method_call( 608 [](const boost::system::error_code ec, const GetSubTreeType& subtree) { 609 if (ec) 610 { 611 std::cerr << "Error contacting mapper " << ec.message() << "\n"; 612 return; 613 } 614 for (const auto& [path, objDict] : subtree) 615 { 616 if (objDict.empty()) 617 { 618 continue; 619 } 620 621 const std::string& owner = objDict.begin()->first; 622 conn->async_method_call( 623 [path](const boost::system::error_code ec2, 624 const boost::container::flat_map< 625 std::string, BasicVariantType>& resp) { 626 if (ec2) 627 { 628 std::cerr << "Error Getting Config " 629 << ec2.message() << "\n"; 630 return; 631 } 632 backplanes.clear(); 633 std::optional<size_t> bus; 634 std::optional<size_t> address; 635 std::optional<size_t> backplaneIndex; 636 std::optional<std::string> name; 637 for (const auto& [key, value] : resp) 638 { 639 if (key == "Bus") 640 { 641 bus = std::get<uint64_t>(value); 642 } 643 else if (key == "Address") 644 { 645 address = std::get<uint64_t>(value); 646 } 647 else if (key == "Index") 648 { 649 backplaneIndex = std::get<uint64_t>(value); 650 } 651 else if (key == "Name") 652 { 653 name = std::get<std::string>(value); 654 } 655 } 656 if (!bus || !address || !name || !backplaneIndex) 657 { 658 std::cerr << "Illegal configuration at " << path 659 << "\n"; 660 return; 661 } 662 std::string parentPath = 663 std::filesystem::path(path).parent_path(); 664 const auto& [backplane, status] = backplanes.emplace( 665 *name, 666 Backplane(*bus, *address, *backplaneIndex, *name)); 667 backplane->second.run(); 668 populateMuxes(backplane->second.muxes, parentPath); 669 }, 670 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 671 configType); 672 } 673 }, 674 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 675 0, std::array<const char*, 1>{configType}); 676 } 677 678 int main() 679 { 680 boost::asio::steady_timer callbackTimer(io); 681 682 conn->request_name("xyz.openbmc_project.HsbpManager"); 683 684 sdbusplus::bus::match::match match( 685 *conn, 686 "type='signal',member='PropertiesChanged',arg0='" + 687 std::string(configType) + "'", 688 [&callbackTimer](sdbusplus::message::message&) { 689 callbackTimer.expires_after(std::chrono::seconds(2)); 690 callbackTimer.async_wait([](const boost::system::error_code ec) { 691 if (ec == boost::asio::error::operation_aborted) 692 { 693 // timer was restarted 694 return; 695 } 696 else if (ec) 697 { 698 std::cerr << "Timer error" << ec.message() << "\n"; 699 return; 700 } 701 populate(); 702 }); 703 }); 704 705 sdbusplus::bus::match::match drive( 706 *conn, 707 "type='signal',member='PropertiesChanged',arg0='xyz.openbmc_project." 708 "Inventory.Item.Drive'", 709 [&callbackTimer](sdbusplus::message::message&) { 710 callbackTimer.expires_after(std::chrono::seconds(2)); 711 callbackTimer.async_wait([](const boost::system::error_code ec) { 712 if (ec == boost::asio::error::operation_aborted) 713 { 714 // timer was restarted 715 return; 716 } 717 else if (ec) 718 { 719 std::cerr << "Timer error" << ec.message() << "\n"; 720 return; 721 } 722 populate(); 723 }); 724 }); 725 726 io.post([]() { populate(); }); 727 io.run(); 728 } 729