1 /* 2 // Copyright (c) 2020 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 "cpuinfo.hpp" 18 #include "cpuinfo_utils.hpp" 19 #include "speed_select.hpp" 20 21 #include <errno.h> 22 #include <fcntl.h> 23 #include <stdio.h> 24 #include <sys/ioctl.h> 25 26 #include <boost/asio/io_service.hpp> 27 #include <boost/asio/steady_timer.hpp> 28 29 #include <iostream> 30 #include <optional> 31 #include <sstream> 32 #include <string> 33 34 extern "C" 35 { 36 #include <i2c/smbus.h> 37 #include <linux/i2c-dev.h> 38 } 39 40 #include <peci.h> 41 42 #include <phosphor-logging/log.hpp> 43 #include <sdbusplus/asio/object_server.hpp> 44 45 namespace phosphor 46 { 47 namespace cpu_info 48 { 49 static constexpr bool debug = false; 50 static constexpr const char* cpuInterfaceName = 51 "xyz.openbmc_project.Inventory.Decorator.Asset"; 52 static constexpr const char* cpuProcessName = 53 "xyz.openbmc_project.Smbios.MDR_V2"; 54 55 // constants for reading SSPEC or QDF string from PIROM 56 // Currently, they are the same for platforms with icx 57 static constexpr uint8_t defaultI2cBus = 13; 58 static constexpr uint8_t defaultI2cSlaveAddr0 = 0x50; 59 static constexpr uint8_t sspecRegAddr = 0xd; 60 static constexpr uint8_t sspecSize = 6; 61 62 using CPUInfoMap = boost::container::flat_map<size_t, std::shared_ptr<CPUInfo>>; 63 64 static CPUInfoMap cpuInfoMap = {}; 65 66 static boost::container::flat_map<size_t, 67 std::unique_ptr<sdbusplus::bus::match_t>> 68 cpuUpdatedMatch = {}; 69 70 static std::optional<std::string> readSSpec(uint8_t bus, uint8_t slaveAddr, 71 uint8_t regAddr, size_t count) 72 { 73 unsigned long funcs = 0; 74 std::string devPath = "/dev/i2c-" + std::to_string(bus); 75 76 int fd = ::open(devPath.c_str(), O_RDWR); 77 if (fd < 0) 78 { 79 phosphor::logging::log<phosphor::logging::level::ERR>( 80 "Error in open!", 81 phosphor::logging::entry("PATH=%s", devPath.c_str()), 82 phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); 83 return std::nullopt; 84 } 85 86 if (::ioctl(fd, I2C_FUNCS, &funcs) < 0) 87 { 88 phosphor::logging::log<phosphor::logging::level::ERR>( 89 "Error in I2C_FUNCS!", 90 phosphor::logging::entry("PATH=%s", devPath.c_str()), 91 phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); 92 ::close(fd); 93 return std::nullopt; 94 } 95 96 if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA)) 97 { 98 phosphor::logging::log<phosphor::logging::level::ERR>( 99 "i2c bus does not support read!", 100 phosphor::logging::entry("PATH=%s", devPath.c_str()), 101 phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); 102 ::close(fd); 103 return std::nullopt; 104 } 105 106 if (::ioctl(fd, I2C_SLAVE_FORCE, slaveAddr) < 0) 107 { 108 phosphor::logging::log<phosphor::logging::level::ERR>( 109 "Error in I2C_SLAVE_FORCE!", 110 phosphor::logging::entry("PATH=%s", devPath.c_str()), 111 phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); 112 ::close(fd); 113 return std::nullopt; 114 } 115 116 int value = 0; 117 std::string sspec; 118 sspec.reserve(count); 119 120 for (size_t i = 0; i < count; i++) 121 { 122 value = ::i2c_smbus_read_byte_data(fd, regAddr + i); 123 if (value < 0) 124 { 125 phosphor::logging::log<phosphor::logging::level::ERR>( 126 "Error in i2c read!", 127 phosphor::logging::entry("PATH=%s", devPath.c_str()), 128 phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); 129 ::close(fd); 130 return std::nullopt; 131 } 132 if (!std::isprint(static_cast<unsigned char>(value))) 133 { 134 phosphor::logging::log<phosphor::logging::level::ERR>( 135 "Non printable value in sspec, ignored."); 136 continue; 137 } 138 // sspec always starts with S, 139 // if not assume it is QDF string which starts at offset 2 140 if (i == 0 && static_cast<unsigned char>(value) != 'S') 141 { 142 i = 1; 143 continue; 144 } 145 sspec.push_back(static_cast<unsigned char>(value)); 146 } 147 ::close(fd); 148 return sspec; 149 } 150 151 static void setAssetProperty( 152 const std::shared_ptr<sdbusplus::asio::connection>& conn, const int& cpu, 153 const std::vector<std::pair<std::string, std::string>>& propValues) 154 { 155 // cpuId from configuration is one based as 156 // dbus object path used by smbios is 0 based 157 const std::string objectPath = cpuPath + std::to_string(cpu - 1); 158 for (const auto& prop : propValues) 159 { 160 conn->async_method_call( 161 [](const boost::system::error_code ec) { 162 if (ec) 163 { 164 phosphor::logging::log<phosphor::logging::level::ERR>( 165 "Cannot get CPU property!"); 166 return; 167 } 168 }, 169 cpuProcessName, objectPath.c_str(), 170 "org.freedesktop.DBus.Properties", "Set", cpuInterfaceName, 171 prop.first.c_str(), std::variant<std::string>{prop.second}); 172 } 173 } 174 175 static void createCpuUpdatedMatch( 176 const std::shared_ptr<sdbusplus::asio::connection>& conn, const int cpu, 177 const std::vector<std::pair<std::string, std::string>>& propValues) 178 { 179 if (cpuUpdatedMatch[cpu]) 180 { 181 return; 182 } 183 184 const std::string objectPath = cpuPath + std::to_string(cpu - 1); 185 186 cpuUpdatedMatch.insert_or_assign( 187 cpu, 188 std::make_unique<sdbusplus::bus::match::match>( 189 static_cast<sdbusplus::bus::bus&>(*conn), 190 sdbusplus::bus::match::rules::interfacesAdded() + 191 sdbusplus::bus::match::rules::argNpath(0, objectPath.c_str()), 192 [conn, cpu, propValues](sdbusplus::message::message& msg) { 193 sdbusplus::message::object_path objectName; 194 boost::container::flat_map< 195 std::string, 196 boost::container::flat_map< 197 std::string, std::variant<std::string, uint64_t>>> 198 msgData; 199 200 msg.read(objectName, msgData); 201 202 // Check for xyz.openbmc_project.Inventory.Item.Cpu 203 // interface match 204 const auto& intfFound = msgData.find(cpuInterfaceName); 205 if (msgData.end() != intfFound) 206 { 207 setAssetProperty(conn, cpu, propValues); 208 } 209 })); 210 } 211 212 static void 213 getProcessorInfo(boost::asio::io_service& io, 214 const std::shared_ptr<sdbusplus::asio::connection>& conn, 215 const size_t& cpu) 216 { 217 if (cpuInfoMap.find(cpu) == cpuInfoMap.end() || cpuInfoMap[cpu] == nullptr) 218 { 219 std::cerr << "No information found for cpu " << cpu << "\n"; 220 return; 221 } 222 223 if (cpuInfoMap[cpu]->id != cpu) 224 { 225 std::cerr << "Incorrect CPU id " << (unsigned)cpuInfoMap[cpu]->id 226 << " expect " << cpu << "\n"; 227 return; 228 } 229 230 uint8_t cpuAddr = cpuInfoMap[cpu]->peciAddr; 231 uint8_t i2cBus = cpuInfoMap[cpu]->i2cBus; 232 uint8_t i2cDevice = cpuInfoMap[cpu]->i2cDevice; 233 234 uint8_t cc = 0; 235 CPUModel model{}; 236 uint8_t stepping = 0; 237 238 if (peci_GetCPUID(cpuAddr, &model, &stepping, &cc) != PECI_CC_SUCCESS) 239 { 240 // Start the PECI check loop 241 auto waitTimer = std::make_shared<boost::asio::steady_timer>(io); 242 waitTimer->expires_after( 243 std::chrono::seconds(phosphor::cpu_info::peciCheckInterval)); 244 245 waitTimer->async_wait( 246 [waitTimer, &io, conn, cpu](const boost::system::error_code& ec) { 247 if (ec) 248 { 249 // operation_aborted is expected if timer is canceled 250 // before completion. 251 if (ec != boost::asio::error::operation_aborted) 252 { 253 phosphor::logging::log<phosphor::logging::level::ERR>( 254 "info update timer async_wait failed ", 255 phosphor::logging::entry("EC=0x%x", ec.value())); 256 } 257 return; 258 } 259 getProcessorInfo(io, conn, cpu); 260 }); 261 return; 262 } 263 264 switch (model) 265 { 266 case icx: 267 { 268 // PPIN can be read through PCS 19 269 static constexpr uint8_t u8Size = 4; // default to a DWORD 270 static constexpr uint8_t u8PPINPkgIndex = 19; 271 static constexpr uint16_t u16PPINPkgParamHigh = 2; 272 static constexpr uint16_t u16PPINPkgParamLow = 1; 273 uint64_t cpuPPIN = 0; 274 uint32_t u32PkgValue = 0; 275 276 int ret = 277 peci_RdPkgConfig(cpuAddr, u8PPINPkgIndex, u16PPINPkgParamLow, 278 u8Size, (uint8_t*)&u32PkgValue, &cc); 279 if (0 != ret) 280 { 281 phosphor::logging::log<phosphor::logging::level::ERR>( 282 "peci read package config failed at address", 283 phosphor::logging::entry("PECIADDR=0x%x", 284 (unsigned)cpuAddr), 285 phosphor::logging::entry("CC=0x%x", cc)); 286 u32PkgValue = 0; 287 } 288 289 cpuPPIN = u32PkgValue; 290 ret = peci_RdPkgConfig(cpuAddr, u8PPINPkgIndex, u16PPINPkgParamHigh, 291 u8Size, (uint8_t*)&u32PkgValue, &cc); 292 if (0 != ret) 293 { 294 phosphor::logging::log<phosphor::logging::level::ERR>( 295 "peci read package config failed at address", 296 phosphor::logging::entry("PECIADDR=0x%x", 297 (unsigned)cpuAddr), 298 phosphor::logging::entry("CC=0x%x", cc)); 299 cpuPPIN = 0; 300 u32PkgValue = 0; 301 } 302 303 cpuPPIN |= static_cast<uint64_t>(u32PkgValue) << 32; 304 305 std::vector<std::pair<std::string, std::string>> values; 306 307 // set SerialNumber if cpuPPIN is valid 308 if (0 != cpuPPIN) 309 { 310 std::stringstream stream; 311 stream << std::hex << cpuPPIN; 312 std::string serialNumber(stream.str()); 313 // cpuInfo->serialNumber(serialNumber); 314 values.emplace_back( 315 std::make_pair("SerialNumber", serialNumber)); 316 } 317 318 std::optional<std::string> sspec = 319 readSSpec(i2cBus, i2cDevice, sspecRegAddr, sspecSize); 320 321 // cpuInfo->model(sspec.value_or("")); 322 values.emplace_back(std::make_pair("Model", sspec.value_or(""))); 323 324 /// \todo in followup patch 325 // CPUInfo is created by this service 326 // update the below logic, which is needed because smbios 327 // service creates the cpu object 328 createCpuUpdatedMatch(conn, cpu, values); 329 setAssetProperty(conn, cpu, values); 330 break; 331 } 332 default: 333 phosphor::logging::log<phosphor::logging::level::INFO>( 334 "in-compatible cpu for cpu asset info"); 335 break; 336 } 337 } 338 339 /** 340 * Get cpu and pirom address 341 */ 342 static void 343 getCpuAddress(boost::asio::io_service& io, 344 const std::shared_ptr<sdbusplus::asio::connection>& conn, 345 const std::string& service, const std::string& object, 346 const std::string& interface) 347 { 348 conn->async_method_call( 349 [&io, conn](boost::system::error_code ec, 350 const boost::container::flat_map< 351 std::string, 352 std::variant<std::string, uint64_t, uint32_t, uint16_t, 353 std::vector<std::string>>>& properties) { 354 const uint64_t* value = nullptr; 355 uint8_t peciAddress = 0; 356 uint8_t i2cBus = defaultI2cBus; 357 uint8_t i2cDevice; 358 bool i2cDeviceFound = false; 359 size_t cpu = 0; 360 361 if (ec) 362 { 363 std::cerr << "DBUS response error " << ec.value() << ": " 364 << ec.message() << "\n"; 365 return; 366 } 367 368 for (const auto& property : properties) 369 { 370 std::cerr << "property " << property.first << "\n"; 371 if (property.first == "Address") 372 { 373 value = std::get_if<uint64_t>(&property.second); 374 if (value != nullptr) 375 { 376 peciAddress = static_cast<uint8_t>(*value); 377 } 378 } 379 if (property.first == "CpuID") 380 { 381 value = std::get_if<uint64_t>(&property.second); 382 if (value != nullptr) 383 { 384 cpu = static_cast<size_t>(*value); 385 } 386 } 387 if (property.first == "PiromI2cAddress") 388 { 389 value = std::get_if<uint64_t>(&property.second); 390 if (value != nullptr) 391 { 392 i2cDevice = static_cast<uint8_t>(*value); 393 i2cDeviceFound = true; 394 } 395 } 396 if (property.first == "PiromI2cBus") 397 { 398 value = std::get_if<uint64_t>(&property.second); 399 if (value != nullptr) 400 { 401 i2cBus = static_cast<uint8_t>(*value); 402 } 403 } 404 } 405 406 ///\todo replace this with present + power state 407 if (cpu != 0 && peciAddress != 0) 408 { 409 if (!i2cDeviceFound) 410 { 411 i2cDevice = defaultI2cSlaveAddr0 + cpu - 1; 412 } 413 cpuInfoMap.insert_or_assign( 414 cpu, std::make_shared<CPUInfo>(cpu, peciAddress, i2cBus, 415 i2cDevice)); 416 417 getProcessorInfo(io, conn, cpu); 418 } 419 }, 420 service, object, "org.freedesktop.DBus.Properties", "GetAll", 421 interface); 422 } 423 424 /** 425 * D-Bus client: to get platform specific configs 426 */ 427 static void getCpuConfiguration( 428 boost::asio::io_service& io, 429 const std::shared_ptr<sdbusplus::asio::connection>& conn, 430 sdbusplus::asio::object_server& objServer) 431 { 432 // Get the Cpu configuration 433 // In case it's not available, set a match for it 434 static std::unique_ptr<sdbusplus::bus::match::match> cpuConfigMatch = 435 std::make_unique<sdbusplus::bus::match::match>( 436 *conn, 437 "type='signal',interface='org.freedesktop.DBus.Properties',member='" 438 "PropertiesChanged',arg0='xyz.openbmc_project." 439 "Configuration.XeonCPU'", 440 [&io, conn, &objServer](sdbusplus::message::message& msg) { 441 std::cerr << "get cpu configuration match\n"; 442 static boost::asio::steady_timer filterTimer(io); 443 filterTimer.expires_after( 444 std::chrono::seconds(configCheckInterval)); 445 446 filterTimer.async_wait( 447 [&io, conn, 448 &objServer](const boost::system::error_code& ec) { 449 if (ec == boost::asio::error::operation_aborted) 450 { 451 return; // we're being canceled 452 } 453 else if (ec) 454 { 455 std::cerr << "Error: " << ec.message() << "\n"; 456 return; 457 } 458 getCpuConfiguration(io, conn, objServer); 459 }); 460 }); 461 462 conn->async_method_call( 463 [&io, conn]( 464 boost::system::error_code ec, 465 const std::vector<std::pair< 466 std::string, 467 std::vector<std::pair<std::string, std::vector<std::string>>>>>& 468 subtree) { 469 if constexpr (debug) 470 std::cerr << "async_method_call callback\n"; 471 472 if (ec) 473 { 474 std::cerr << "error with async_method_call\n"; 475 return; 476 } 477 if (subtree.empty()) 478 { 479 // No config data yet, so wait for the match 480 return; 481 } 482 483 for (const auto& object : subtree) 484 { 485 for (const auto& service : object.second) 486 { 487 getCpuAddress(io, conn, service.first, object.first, 488 "xyz.openbmc_project.Configuration.XeonCPU"); 489 } 490 } 491 if constexpr (debug) 492 std::cerr << "getCpuConfiguration callback complete\n"; 493 494 return; 495 }, 496 "xyz.openbmc_project.ObjectMapper", 497 "/xyz/openbmc_project/object_mapper", 498 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 499 "/xyz/openbmc_project/", 0, 500 std::array<const char*, 1>{ 501 "xyz.openbmc_project.Configuration.XeonCPU"}); 502 } 503 504 } // namespace cpu_info 505 } // namespace phosphor 506 507 int main(int argc, char* argv[]) 508 { 509 // setup connection to dbus 510 boost::asio::io_service io; 511 std::shared_ptr<sdbusplus::asio::connection> conn = 512 std::make_shared<sdbusplus::asio::connection>(io); 513 514 // CPUInfo Object 515 conn->request_name(phosphor::cpu_info::cpuInfoObject); 516 sdbusplus::asio::object_server server = 517 sdbusplus::asio::object_server(conn); 518 sdbusplus::bus::bus& bus = static_cast<sdbusplus::bus::bus&>(*conn); 519 sdbusplus::server::manager::manager objManager( 520 bus, "/xyz/openbmc_project/inventory"); 521 522 cpu_info::hostStateSetup(conn); 523 524 cpu_info::sst::init(io, conn); 525 526 // shared_ptr conn is global for the service 527 // const reference of conn is passed to async calls 528 phosphor::cpu_info::getCpuConfiguration(io, conn, server); 529 530 io.run(); 531 532 return 0; 533 } 534