| Zhikui Ren | 18a5ab9 | 2020-09-01 21:35:20 -0700 | [diff] [blame] | 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" | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame^] | 18 | #include "speed_select.hpp" | 
| Zhikui Ren | 18a5ab9 | 2020-09-01 21:35:20 -0700 | [diff] [blame] | 19 |  | 
|  | 20 | #include <errno.h> | 
|  | 21 | #include <fcntl.h> | 
|  | 22 | #include <stdio.h> | 
|  | 23 | #include <sys/ioctl.h> | 
|  | 24 |  | 
|  | 25 | #include <boost/asio/io_service.hpp> | 
|  | 26 | #include <boost/asio/steady_timer.hpp> | 
|  | 27 |  | 
|  | 28 | #include <optional> | 
|  | 29 | #include <sstream> | 
|  | 30 | #include <string> | 
|  | 31 |  | 
|  | 32 | extern "C" | 
|  | 33 | { | 
|  | 34 | #include <i2c/smbus.h> | 
|  | 35 | #include <linux/i2c-dev.h> | 
|  | 36 | } | 
|  | 37 |  | 
|  | 38 | #include <peci.h> | 
|  | 39 |  | 
|  | 40 | #include <phosphor-logging/log.hpp> | 
|  | 41 | #include <sdbusplus/asio/object_server.hpp> | 
|  | 42 |  | 
|  | 43 | namespace phosphor | 
|  | 44 | { | 
|  | 45 | namespace cpu_info | 
|  | 46 | { | 
|  | 47 |  | 
| Zhikui Ren | 18a5ab9 | 2020-09-01 21:35:20 -0700 | [diff] [blame] | 48 | static constexpr const char* cpuInterfaceName = | 
|  | 49 | "xyz.openbmc_project.Inventory.Decorator.Asset"; | 
|  | 50 | static constexpr const char* cpuProcessName = | 
|  | 51 | "xyz.openbmc_project.Smbios.MDR_V2"; | 
|  | 52 |  | 
|  | 53 | struct ProcessorInfo | 
|  | 54 | { | 
|  | 55 | uint64_t ppin; | 
|  | 56 | std::string sspec; | 
|  | 57 | }; | 
|  | 58 |  | 
|  | 59 | using CPUMap = | 
|  | 60 | boost::container::flat_map<size_t, | 
|  | 61 | std::pair<int, std::shared_ptr<CPUInfo>>>; | 
|  | 62 |  | 
|  | 63 | static CPUMap cpuMap = {}; | 
|  | 64 |  | 
|  | 65 | static std::unique_ptr<sdbusplus::bus::match_t> cpuUpdatedMatch = nullptr; | 
|  | 66 |  | 
|  | 67 | static std::optional<std::string> readSSpec(uint8_t bus, uint8_t slaveAddr, | 
|  | 68 | uint8_t regAddr, size_t count) | 
|  | 69 | { | 
|  | 70 | unsigned long funcs = 0; | 
|  | 71 | std::string devPath = "/dev/i2c-" + std::to_string(bus); | 
|  | 72 |  | 
|  | 73 | int fd = ::open(devPath.c_str(), O_RDWR); | 
|  | 74 | if (fd < 0) | 
|  | 75 | { | 
|  | 76 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 77 | "Error in open!", | 
|  | 78 | phosphor::logging::entry("PATH=%s", devPath.c_str()), | 
|  | 79 | phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); | 
|  | 80 | return std::nullopt; | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | if (::ioctl(fd, I2C_FUNCS, &funcs) < 0) | 
|  | 84 | { | 
|  | 85 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 86 | "Error in I2C_FUNCS!", | 
|  | 87 | phosphor::logging::entry("PATH=%s", devPath.c_str()), | 
|  | 88 | phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); | 
|  | 89 | ::close(fd); | 
|  | 90 | return std::nullopt; | 
|  | 91 | } | 
|  | 92 |  | 
|  | 93 | if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA)) | 
|  | 94 | { | 
|  | 95 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 96 | "i2c bus does not support read!", | 
|  | 97 | phosphor::logging::entry("PATH=%s", devPath.c_str()), | 
|  | 98 | phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); | 
|  | 99 | ::close(fd); | 
|  | 100 | return std::nullopt; | 
|  | 101 | } | 
|  | 102 |  | 
|  | 103 | if (::ioctl(fd, I2C_SLAVE_FORCE, slaveAddr) < 0) | 
|  | 104 | { | 
|  | 105 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 106 | "Error in I2C_SLAVE_FORCE!", | 
|  | 107 | phosphor::logging::entry("PATH=%s", devPath.c_str()), | 
|  | 108 | phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); | 
|  | 109 | ::close(fd); | 
|  | 110 | return std::nullopt; | 
|  | 111 | } | 
|  | 112 |  | 
|  | 113 | int value = 0; | 
|  | 114 | std::string sspec; | 
|  | 115 | sspec.reserve(count); | 
|  | 116 |  | 
|  | 117 | for (size_t i = 0; i < count; i++) | 
|  | 118 | { | 
|  | 119 | value = ::i2c_smbus_read_byte_data(fd, regAddr++); | 
|  | 120 | if (value < 0) | 
|  | 121 | { | 
|  | 122 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 123 | "Error in i2c read!", | 
|  | 124 | phosphor::logging::entry("PATH=%s", devPath.c_str()), | 
|  | 125 | phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr)); | 
|  | 126 | ::close(fd); | 
|  | 127 | return std::nullopt; | 
|  | 128 | } | 
|  | 129 | if (!std::isprint(static_cast<unsigned char>(value))) | 
|  | 130 | { | 
|  | 131 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 132 | "Non printable value in sspec, ignored."); | 
|  | 133 | continue; | 
|  | 134 | } | 
|  | 135 | sspec.push_back(static_cast<unsigned char>(value)); | 
|  | 136 | } | 
|  | 137 | ::close(fd); | 
|  | 138 | return sspec; | 
|  | 139 | } | 
|  | 140 |  | 
|  | 141 | // PECI Client Address Map | 
|  | 142 | static void getPECIAddrMap(CPUMap& cpuMap) | 
|  | 143 | { | 
|  | 144 | int idx = 0; | 
|  | 145 | for (size_t i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++) | 
|  | 146 | { | 
|  | 147 | if (peci_Ping(i) == PECI_CC_SUCCESS) | 
|  | 148 | { | 
|  | 149 | cpuMap.emplace(std::make_pair(i, std::make_pair(idx, nullptr))); | 
|  | 150 | idx++; | 
|  | 151 | } | 
|  | 152 | } | 
|  | 153 | } | 
|  | 154 |  | 
|  | 155 | static std::shared_ptr<CPUInfo> | 
|  | 156 | createCPUInfo(std::shared_ptr<sdbusplus::asio::connection>& conn, | 
|  | 157 | const int& cpu) | 
|  | 158 | { | 
|  | 159 | std::string path = cpuPath + std::to_string(cpu); | 
|  | 160 | std::shared_ptr<CPUInfo> cpuInfo = std::make_shared<CPUInfo>( | 
|  | 161 | static_cast<sdbusplus::bus::bus&>(*conn), path); | 
|  | 162 | return cpuInfo; | 
|  | 163 | } | 
|  | 164 |  | 
|  | 165 | static void setAssetProperty( | 
|  | 166 | std::shared_ptr<sdbusplus::asio::connection>& conn, const int& cpu, | 
|  | 167 | const std::vector<std::pair<std::string, std::string>>& propValues) | 
|  | 168 | { | 
|  | 169 |  | 
|  | 170 | const std::string objectPath = cpuPath + std::to_string(cpu); | 
|  | 171 | for (const auto& prop : propValues) | 
|  | 172 | { | 
|  | 173 | conn->async_method_call( | 
|  | 174 | [](const boost::system::error_code ec) { | 
|  | 175 | // Use "Set" method to set the property value. | 
|  | 176 | if (ec) | 
|  | 177 | { | 
|  | 178 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 179 | "Cannot get CPU property!"); | 
|  | 180 | return; | 
|  | 181 | } | 
|  | 182 | }, | 
|  | 183 | cpuProcessName, objectPath.c_str(), | 
|  | 184 | "org.freedesktop.DBus.Properties", "Set", cpuInterfaceName, | 
|  | 185 | prop.first.c_str(), std::variant<std::string>{prop.second}); | 
|  | 186 | } | 
|  | 187 | } | 
|  | 188 |  | 
|  | 189 | static void createCpuUpdatedMatch( | 
|  | 190 | std::shared_ptr<sdbusplus::asio::connection>& conn, const int& cpu, | 
|  | 191 | const std::vector<std::pair<std::string, std::string>>& propValues) | 
|  | 192 | { | 
|  | 193 | if (cpuUpdatedMatch) | 
|  | 194 | { | 
|  | 195 | return; | 
|  | 196 | } | 
|  | 197 |  | 
|  | 198 | const std::string objectPath = cpuPath + std::to_string(cpu); | 
|  | 199 |  | 
|  | 200 | cpuUpdatedMatch = std::make_unique<sdbusplus::bus::match::match>( | 
|  | 201 | static_cast<sdbusplus::bus::bus&>(*conn), | 
|  | 202 | sdbusplus::bus::match::rules::interfacesAdded() + | 
|  | 203 | sdbusplus::bus::match::rules::argNpath(0, objectPath.c_str()), | 
|  | 204 | [&conn, cpu, propValues](sdbusplus::message::message& msg) { | 
| Jonathan Doman | a43eec8 | 2020-10-06 09:23:09 -0700 | [diff] [blame] | 205 | sdbusplus::message::object_path objectName; | 
| Zhikui Ren | 18a5ab9 | 2020-09-01 21:35:20 -0700 | [diff] [blame] | 206 | boost::container::flat_map< | 
|  | 207 | std::string, | 
|  | 208 | boost::container::flat_map<std::string, | 
|  | 209 | std::variant<std::string, uint64_t>>> | 
|  | 210 | msgData; | 
|  | 211 |  | 
|  | 212 | msg.read(objectName, msgData); | 
|  | 213 |  | 
|  | 214 | // Check for xyz.openbmc_project.Inventory.Item.Cpu | 
|  | 215 | // interface match | 
|  | 216 | auto intfFound = msgData.find(cpuInterfaceName); | 
|  | 217 | if (msgData.end() != intfFound) | 
|  | 218 | { | 
|  | 219 | setAssetProperty(conn, cpu, propValues); | 
|  | 220 | } | 
|  | 221 | }); | 
|  | 222 | } | 
|  | 223 |  | 
|  | 224 | // constants for reading QDF string from PIROM | 
|  | 225 | // Currently, they are the same for platforms with icx | 
|  | 226 | // \todo: move into configuration file to be more robust | 
|  | 227 | static constexpr uint8_t i2cBus = 13; | 
|  | 228 | static constexpr uint8_t slaveAddr0 = 0x50; | 
|  | 229 | static constexpr uint8_t regAddr = 0xf; | 
|  | 230 | static constexpr uint8_t sspecSize = 4; | 
|  | 231 |  | 
|  | 232 | static void getProcessorInfo(std::shared_ptr<sdbusplus::asio::connection>& conn, | 
|  | 233 | sdbusplus::asio::object_server& objServer, | 
|  | 234 | CPUMap& cpuMap) | 
|  | 235 | { | 
|  | 236 |  | 
|  | 237 | for (auto& cpu : cpuMap) | 
|  | 238 | { | 
|  | 239 | uint8_t cc = 0; | 
|  | 240 | CPUModel model{}; | 
|  | 241 | uint8_t stepping = 0; | 
|  | 242 |  | 
|  | 243 | /// \todo in a follwup patch | 
|  | 244 | // CPUInfo will be updated as the centrol place for CPU information | 
|  | 245 | // std::shared_ptr<CPUInfo> cpuInfo = | 
|  | 246 | //    createCPUInfo(conn, cpu.second.first); | 
|  | 247 | // cpu.second.second = cpuInfo; | 
|  | 248 |  | 
|  | 249 | if (peci_GetCPUID(cpu.first, &model, &stepping, &cc) != PECI_CC_SUCCESS) | 
|  | 250 | { | 
|  | 251 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 252 | "Cannot get CPUID!", | 
|  | 253 | phosphor::logging::entry("PECIADDR=0x%x", cpu.first)); | 
|  | 254 | continue; | 
|  | 255 | } | 
|  | 256 |  | 
|  | 257 | switch (model) | 
|  | 258 | { | 
|  | 259 | case icx: | 
|  | 260 | { | 
|  | 261 | // get processor ID | 
|  | 262 | static constexpr uint8_t u8Size = 4; // default to a DWORD | 
|  | 263 | static constexpr uint8_t u8PPINPkgIndex = 19; | 
|  | 264 | static constexpr uint16_t u16PPINPkgParamHigh = 2; | 
|  | 265 | static constexpr uint16_t u16PPINPkgParamLow = 1; | 
|  | 266 | uint64_t cpuPPIN = 0; | 
|  | 267 | uint32_t u32PkgValue = 0; | 
|  | 268 |  | 
|  | 269 | int ret = peci_RdPkgConfig(cpu.first, u8PPINPkgIndex, | 
|  | 270 | u16PPINPkgParamLow, u8Size, | 
|  | 271 | (uint8_t*)&u32PkgValue, &cc); | 
|  | 272 | if (0 != ret) | 
|  | 273 | { | 
|  | 274 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 275 | "peci read package config failed at address", | 
|  | 276 | phosphor::logging::entry("PECIADDR=0x%x", cpu.first), | 
|  | 277 | phosphor::logging::entry("CC=0x%x", cc)); | 
|  | 278 | u32PkgValue = 0; | 
|  | 279 | } | 
|  | 280 |  | 
|  | 281 | cpuPPIN = u32PkgValue; | 
|  | 282 | ret = peci_RdPkgConfig(cpu.first, u8PPINPkgIndex, | 
|  | 283 | u16PPINPkgParamHigh, u8Size, | 
|  | 284 | (uint8_t*)&u32PkgValue, &cc); | 
|  | 285 | if (0 != ret) | 
|  | 286 | { | 
|  | 287 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 288 | "peci read package config failed at address", | 
|  | 289 | phosphor::logging::entry("PECIADDR=0x%x", cpu.first), | 
|  | 290 | phosphor::logging::entry("CC=0x%x", cc)); | 
|  | 291 | cpuPPIN = 0; | 
|  | 292 | u32PkgValue = 0; | 
|  | 293 | } | 
|  | 294 |  | 
|  | 295 | cpuPPIN |= static_cast<uint64_t>(u32PkgValue) << 32; | 
|  | 296 |  | 
|  | 297 | std::vector<std::pair<std::string, std::string>> values; | 
|  | 298 |  | 
|  | 299 | // set SerialNumber if cpuPPIN is valid | 
|  | 300 | if (0 != cpuPPIN) | 
|  | 301 | { | 
|  | 302 | std::stringstream stream; | 
|  | 303 | stream << std::hex << cpuPPIN; | 
|  | 304 | std::string serialNumber(stream.str()); | 
|  | 305 | // cpuInfo->serialNumber(serialNumber); | 
|  | 306 | values.emplace_back( | 
|  | 307 | std::make_pair("SerialNumber", serialNumber)); | 
|  | 308 | } | 
|  | 309 |  | 
|  | 310 | // assuming the slaveAddress will be incrementing like peci | 
|  | 311 | // client address | 
|  | 312 | std::optional<std::string> sspec = readSSpec( | 
|  | 313 | i2cBus, static_cast<uint8_t>(slaveAddr0 + cpu.second.first), | 
|  | 314 | regAddr, sspecSize); | 
|  | 315 | // cpuInfo->model(sspec.value_or("")); | 
|  | 316 | values.emplace_back( | 
|  | 317 | std::make_pair("Model", sspec.value_or(""))); | 
|  | 318 |  | 
|  | 319 | /// \todo in followup patch | 
|  | 320 | // CPUInfo is created by this service | 
|  | 321 | // update the below logic, which is needed because smbios | 
|  | 322 | // service creates the cpu object | 
|  | 323 | createCpuUpdatedMatch(conn, cpu.second.first, values); | 
|  | 324 | setAssetProperty(conn, cpu.second.first, values); | 
|  | 325 | break; | 
|  | 326 | } | 
|  | 327 | default: | 
|  | 328 | phosphor::logging::log<phosphor::logging::level::INFO>( | 
|  | 329 | "in-compatible cpu for cpu asset info"); | 
|  | 330 | break; | 
|  | 331 | } | 
|  | 332 | } | 
|  | 333 | } | 
|  | 334 |  | 
|  | 335 | static bool isPECIAvailable(void) | 
|  | 336 | { | 
|  | 337 | for (size_t i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++) | 
|  | 338 | { | 
|  | 339 | if (peci_Ping(i) == PECI_CC_SUCCESS) | 
|  | 340 | { | 
|  | 341 | return true; | 
|  | 342 | } | 
|  | 343 | } | 
|  | 344 | return false; | 
|  | 345 | } | 
|  | 346 |  | 
|  | 347 | static void | 
|  | 348 | peciAvailableCheck(boost::asio::steady_timer& peciWaitTimer, | 
|  | 349 | boost::asio::io_service& io, | 
|  | 350 | std::shared_ptr<sdbusplus::asio::connection>& conn, | 
|  | 351 | sdbusplus::asio::object_server& objServer) | 
|  | 352 | { | 
|  | 353 | bool peciAvailable = isPECIAvailable(); | 
|  | 354 | if (peciAvailable) | 
|  | 355 | { | 
|  | 356 | // get the PECI client address list | 
|  | 357 | getPECIAddrMap(cpuMap); | 
|  | 358 | getProcessorInfo(conn, objServer, cpuMap); | 
|  | 359 | } | 
|  | 360 | if (!peciAvailable || !cpuMap.size()) | 
|  | 361 | { | 
|  | 362 | peciWaitTimer.expires_after( | 
|  | 363 | std::chrono::seconds(6 * peciCheckInterval)); | 
|  | 364 | peciWaitTimer.async_wait([&peciWaitTimer, &io, &conn, &objServer]( | 
|  | 365 | const boost::system::error_code& ec) { | 
|  | 366 | if (ec) | 
|  | 367 | { | 
|  | 368 | // operation_aborted is expected if timer is canceled | 
|  | 369 | // before completion. | 
|  | 370 | if (ec != boost::asio::error::operation_aborted) | 
|  | 371 | { | 
|  | 372 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 373 | "PECI Available Check async_wait failed", | 
|  | 374 | phosphor::logging::entry("EC=0x%x", ec.value())); | 
|  | 375 | } | 
|  | 376 | return; | 
|  | 377 | } | 
|  | 378 | peciAvailableCheck(peciWaitTimer, io, conn, objServer); | 
|  | 379 | }); | 
|  | 380 | } | 
|  | 381 | } | 
|  | 382 |  | 
|  | 383 | } // namespace cpu_info | 
|  | 384 | } // namespace phosphor | 
|  | 385 |  | 
|  | 386 | int main(int argc, char* argv[]) | 
|  | 387 | { | 
|  | 388 | // setup connection to dbus | 
|  | 389 | boost::asio::io_service io; | 
|  | 390 | std::shared_ptr<sdbusplus::asio::connection> conn = | 
|  | 391 | std::make_shared<sdbusplus::asio::connection>(io); | 
|  | 392 |  | 
|  | 393 | // CPUInfo Object | 
|  | 394 | conn->request_name(phosphor::cpu_info::cpuInfoObject); | 
|  | 395 | sdbusplus::asio::object_server server = | 
|  | 396 | sdbusplus::asio::object_server(conn); | 
|  | 397 | sdbusplus::bus::bus& bus = static_cast<sdbusplus::bus::bus&>(*conn); | 
|  | 398 | sdbusplus::server::manager::manager objManager( | 
|  | 399 | bus, "/xyz/openbmc_project/inventory"); | 
|  | 400 |  | 
| Jonathan Doman | 94c94bf | 2020-10-05 23:25:45 -0700 | [diff] [blame^] | 401 | cpu_info::sst::init(io, conn); | 
|  | 402 |  | 
| Zhikui Ren | 18a5ab9 | 2020-09-01 21:35:20 -0700 | [diff] [blame] | 403 | // Start the PECI check loop | 
|  | 404 | boost::asio::steady_timer peciWaitTimer( | 
|  | 405 | io, std::chrono::seconds(phosphor::cpu_info::peciCheckInterval)); | 
|  | 406 | peciWaitTimer.async_wait([&peciWaitTimer, &io, &conn, | 
|  | 407 | &server](const boost::system::error_code& ec) { | 
|  | 408 | if (ec) | 
|  | 409 | { | 
|  | 410 | // operation_aborted is expected if timer is canceled | 
|  | 411 | // before completion. | 
|  | 412 | if (ec != boost::asio::error::operation_aborted) | 
|  | 413 | { | 
|  | 414 | phosphor::logging::log<phosphor::logging::level::ERR>( | 
|  | 415 | "PECI Available Check async_wait failed ", | 
|  | 416 | phosphor::logging::entry("EC=0x%x", ec.value())); | 
|  | 417 | } | 
|  | 418 | return; | 
|  | 419 | } | 
|  | 420 | phosphor::cpu_info::peciAvailableCheck(peciWaitTimer, io, conn, server); | 
|  | 421 | }); | 
|  | 422 |  | 
|  | 423 | io.run(); | 
|  | 424 |  | 
|  | 425 | return 0; | 
|  | 426 | } |