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