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 <iostream> 22 #include <sdbusplus/asio/connection.hpp> 23 #include <sdbusplus/asio/object_server.hpp> 24 #include <sdbusplus/bus/match.hpp> 25 #include <string> 26 #include <utility> 27 28 extern "C" { 29 #include <i2c/smbus.h> 30 #include <linux/i2c-dev.h> 31 } 32 33 constexpr const char* configType = 34 "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD"; 35 36 constexpr size_t scanRateSeconds = 5; 37 constexpr size_t maxDrives = 8; // only 1 byte alloted 38 39 boost::asio::io_context io; 40 auto conn = std::make_shared<sdbusplus::asio::connection>(io); 41 sdbusplus::asio::object_server objServer(conn); 42 43 static std::string zeroPad(const uint8_t val) 44 { 45 std::ostringstream version; 46 version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val); 47 return version.str(); 48 } 49 50 struct Drive 51 { 52 Drive(size_t driveIndex, bool isPresent, bool isOperational, bool nvme, 53 bool rebuilding) : 54 isNvme(nvme) 55 { 56 constexpr const char* basePath = 57 "/xyz/openbmc_project/inventory/item/drive/Drive_"; 58 itemIface = objServer.add_interface( 59 basePath + std::to_string(driveIndex), inventory::interface); 60 itemIface->register_property("Present", isPresent); 61 itemIface->register_property("PrettyName", 62 "Drive " + std::to_string(driveIndex)); 63 itemIface->initialize(); 64 operationalIface = objServer.add_interface( 65 itemIface->get_object_path(), 66 "xyz.openbmc_project.State.Decorator.OperationalStatus"); 67 operationalIface->register_property("Functional", isOperational); 68 operationalIface->initialize(); 69 rebuildingIface = objServer.add_interface( 70 itemIface->get_object_path(), "xyz.openbmc_project.State.Drive"); 71 rebuildingIface->register_property("Rebuilding", rebuilding); 72 rebuildingIface->initialize(); 73 } 74 ~Drive() 75 { 76 objServer.remove_interface(itemIface); 77 objServer.remove_interface(operationalIface); 78 objServer.remove_interface(rebuildingIface); 79 } 80 81 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface; 82 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface; 83 std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface; 84 bool isNvme; 85 }; 86 87 struct Backplane 88 { 89 90 Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn, 91 const std::string& nameIn) : 92 bus(busIn), 93 address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn), 94 timer(std::make_shared<boost::asio::steady_timer>(io)) 95 { 96 } 97 void run() 98 { 99 file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR); 100 if (file < 0) 101 { 102 std::cerr << "unable to open bus " << bus << "\n"; 103 return; 104 } 105 106 if (ioctl(file, I2C_SLAVE_FORCE, address) < 0) 107 { 108 std::cerr << "unable to set address to " << address << "\n"; 109 return; 110 } 111 112 if (!getPresent()) 113 { 114 std::cerr << "Cannot detect CPLD\n"; 115 return; 116 } 117 118 getBootVer(bootVer); 119 getFPGAVer(fpgaVer); 120 getSecurityRev(securityRev); 121 std::string dbusName = boost::replace_all_copy(name, " ", "_"); 122 hsbpItemIface = objServer.add_interface( 123 "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName, 124 inventory::interface); 125 hsbpItemIface->register_property("Present", true); 126 hsbpItemIface->register_property("PrettyName", name); 127 hsbpItemIface->initialize(); 128 129 versionIface = 130 objServer.add_interface(hsbpItemIface->get_object_path(), 131 "xyz.openbmc_project.Software.Version"); 132 versionIface->register_property("Version", zeroPad(bootVer) + "." + 133 zeroPad(fpgaVer) + "." + 134 zeroPad(securityRev)); 135 versionIface->register_property( 136 "Purpose", 137 std::string( 138 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP")); 139 versionIface->initialize(); 140 getPresence(presence); 141 getIFDET(ifdet); 142 143 createDrives(); 144 145 runTimer(); 146 } 147 148 void runTimer() 149 { 150 timer->expires_after(std::chrono::seconds(scanRateSeconds)); 151 timer->async_wait([this](boost::system::error_code ec) { 152 if (ec == boost::asio::error::operation_aborted) 153 { 154 // we're being destroyed 155 return; 156 } 157 else if (ec) 158 { 159 std::cerr << "timer error " << ec.message() << "\n"; 160 return; 161 } 162 uint8_t curPresence = 0; 163 uint8_t curIFDET = 0; 164 uint8_t curFailed = 0; 165 uint8_t curRebuild = 0; 166 167 getPresence(curPresence); 168 getIFDET(curIFDET); 169 getFailed(curFailed); 170 getRebuild(curRebuild); 171 172 if (curPresence != presence || curIFDET != ifdet || 173 curFailed != failed || curRebuild != rebuilding) 174 { 175 presence = curPresence; 176 ifdet = curIFDET; 177 failed = curFailed; 178 rebuilding = curRebuild; 179 updateDrives(); 180 } 181 runTimer(); 182 }); 183 } 184 185 void createDrives() 186 { 187 uint8_t nvme = ifdet ^ presence; 188 for (size_t ii = 0; ii < maxDrives; ii++) 189 { 190 bool isNvme = nvme & (1 << ii); 191 bool isPresent = isNvme || (presence & (1 << ii)); 192 bool isFailed = !isPresent || failed & (1 << ii); 193 bool isRebuilding = !isPresent && (rebuilding & (1 << ii)); 194 195 // +1 to convert from 0 based to 1 based 196 size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1; 197 drives.emplace_back(driveIndex, isPresent, !isFailed, isNvme, 198 isRebuilding); 199 } 200 } 201 202 void updateDrives() 203 { 204 205 uint8_t nvme = ifdet ^ presence; 206 for (size_t ii = 0; ii < maxDrives; ii++) 207 { 208 bool isNvme = nvme & (1 << ii); 209 bool isPresent = isNvme || (presence & (1 << ii)); 210 bool isFailed = !isPresent || (failed & (1 << ii)); 211 bool isRebuilding = isPresent && (rebuilding & (1 << ii)); 212 213 Drive& drive = drives[ii]; 214 drive.isNvme = isNvme; 215 drive.itemIface->set_property("Present", isPresent); 216 drive.operationalIface->set_property("Functional", !isFailed); 217 drive.rebuildingIface->set_property("Rebuilding", isRebuilding); 218 } 219 } 220 221 bool getPresent() 222 { 223 present = i2c_smbus_read_byte(file) >= 0; 224 return present; 225 } 226 227 bool getTypeID(uint8_t& val) 228 { 229 constexpr uint8_t addr = 2; 230 int ret = i2c_smbus_read_byte_data(file, addr); 231 if (ret < 0) 232 { 233 std::cerr << "Error " << __FUNCTION__ << "\n"; 234 return false; 235 } 236 val = static_cast<uint8_t>(ret); 237 return true; 238 } 239 240 bool getBootVer(uint8_t& val) 241 { 242 constexpr uint8_t addr = 3; 243 int ret = i2c_smbus_read_byte_data(file, addr); 244 if (ret < 0) 245 { 246 std::cerr << "Error " << __FUNCTION__ << "\n"; 247 return false; 248 } 249 val = static_cast<uint8_t>(ret); 250 return true; 251 } 252 253 bool getFPGAVer(uint8_t& val) 254 { 255 constexpr uint8_t addr = 4; 256 int ret = i2c_smbus_read_byte_data(file, addr); 257 if (ret < 0) 258 { 259 std::cerr << "Error " << __FUNCTION__ << "\n"; 260 return false; 261 } 262 val = static_cast<uint8_t>(ret); 263 return true; 264 } 265 266 bool getSecurityRev(uint8_t& val) 267 { 268 constexpr uint8_t addr = 5; 269 int ret = i2c_smbus_read_byte_data(file, addr); 270 if (ret < 0) 271 { 272 std::cerr << "Error " << __FUNCTION__ << "\n"; 273 return false; 274 } 275 val = static_cast<uint8_t>(ret); 276 return true; 277 } 278 279 bool getPresence(uint8_t& val) 280 { 281 // NVMe drives do not assert PRSNTn, and as such do not get reported as 282 // PRESENT in this register 283 284 constexpr uint8_t addr = 8; 285 286 int ret = i2c_smbus_read_byte_data(file, addr); 287 if (ret < 0) 288 { 289 std::cerr << "Error " << __FUNCTION__ << "\n"; 290 return false; 291 } 292 // presence is inverted 293 val = static_cast<uint8_t>(~ret); 294 return true; 295 } 296 297 bool getIFDET(uint8_t& val) 298 { 299 // This register is a bitmap of parallel GPIO pins connected to the 300 // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert 301 // IFDETn low when they are inserted into the HSBP.This register, in 302 // combination with the PRESENCE register, are used by the BMC to detect 303 // the presence of NVMe drives. 304 305 constexpr uint8_t addr = 9; 306 307 int ret = i2c_smbus_read_byte_data(file, addr); 308 if (ret < 0) 309 { 310 std::cerr << "Error " << __FUNCTION__ << "\n"; 311 return false; 312 } 313 // ifdet is inverted 314 val = static_cast<uint8_t>(~ret); 315 return true; 316 } 317 318 bool getFailed(uint8_t& val) 319 { 320 constexpr uint8_t addr = 0xC; 321 int ret = i2c_smbus_read_byte_data(file, addr); 322 if (ret < 0) 323 { 324 std::cerr << "Error " << __FUNCTION__ << "\n"; 325 return false; 326 } 327 val = static_cast<uint8_t>(ret); 328 return true; 329 } 330 331 bool getRebuild(uint8_t& val) 332 { 333 constexpr uint8_t addr = 0xD; 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 val = static_cast<uint8_t>(ret); 341 return true; 342 } 343 344 ~Backplane() 345 { 346 objServer.remove_interface(hsbpItemIface); 347 objServer.remove_interface(versionIface); 348 if (file >= 0) 349 { 350 close(file); 351 } 352 } 353 354 size_t bus; 355 size_t address; 356 size_t backplaneIndex; 357 std::string name; 358 std::shared_ptr<boost::asio::steady_timer> timer; 359 bool present = false; 360 uint8_t typeId = 0; 361 uint8_t bootVer = 0; 362 uint8_t fpgaVer = 0; 363 uint8_t securityRev = 0; 364 uint8_t funSupported = 0; 365 uint8_t presence = 0; 366 uint8_t ifdet = 0; 367 uint8_t failed = 0; 368 uint8_t rebuilding = 0; 369 370 int file = -1; 371 372 std::string type; 373 374 std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface; 375 std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface; 376 377 std::vector<Drive> drives; 378 }; 379 380 std::unordered_map<std::string, Backplane> backplanes; 381 382 void populate() 383 { 384 conn->async_method_call( 385 [](const boost::system::error_code ec, const GetSubTreeType& subtree) { 386 if (ec) 387 { 388 std::cerr << "Error contacting mapper " << ec.message() << "\n"; 389 return; 390 } 391 for (const auto& [path, objDict] : subtree) 392 { 393 if (objDict.empty()) 394 { 395 continue; 396 } 397 398 const std::string& owner = objDict.begin()->first; 399 conn->async_method_call( 400 [path](const boost::system::error_code ec2, 401 const boost::container::flat_map< 402 std::string, BasicVariantType>& resp) { 403 if (ec2) 404 { 405 std::cerr << "Error Getting Config " 406 << ec2.message() << "\n"; 407 return; 408 } 409 backplanes.clear(); 410 std::optional<size_t> bus; 411 std::optional<size_t> address; 412 std::optional<size_t> backplaneIndex; 413 std::optional<std::string> name; 414 for (const auto& [key, value] : resp) 415 { 416 if (key == "Bus") 417 { 418 bus = std::get<uint64_t>(value); 419 } 420 else if (key == "Address") 421 { 422 address = std::get<uint64_t>(value); 423 } 424 else if (key == "Index") 425 { 426 backplaneIndex = std::get<uint64_t>(value); 427 } 428 else if (key == "Name") 429 { 430 name = std::get<std::string>(value); 431 } 432 } 433 if (!bus || !address || !name || !backplaneIndex) 434 { 435 std::cerr << "Illegal configuration at " << path 436 << "\n"; 437 return; 438 } 439 const auto& [backplane, status] = backplanes.emplace( 440 *name, 441 Backplane(*bus, *address, *backplaneIndex, *name)); 442 backplane->second.run(); 443 }, 444 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 445 configType); 446 } 447 }, 448 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 449 0, std::array<const char*, 1>{configType}); 450 } 451 452 int main() 453 { 454 boost::asio::steady_timer callbackTimer(io); 455 456 conn->request_name("xyz.openbmc_project.HsbpManager"); 457 458 sdbusplus::bus::match::match match( 459 *conn, 460 "type='signal',member='PropertiesChanged',arg0='" + 461 std::string(configType) + "'", 462 [&callbackTimer](sdbusplus::message::message&) { 463 callbackTimer.expires_after(std::chrono::seconds(2)); 464 callbackTimer.async_wait([](const boost::system::error_code ec) { 465 if (ec == boost::asio::error::operation_aborted) 466 { 467 // timer was restarted 468 return; 469 } 470 else if (ec) 471 { 472 std::cerr << "Timer error" << ec.message() << "\n"; 473 return; 474 } 475 populate(); 476 }); 477 }); 478 479 io.post([]() { populate(); }); 480 io.run(); 481 } 482