blob: 2e8135c2e46a2f2b85c1869216853a9da61c6a46 [file] [log] [blame]
Zhikui Ren18a5ab92020-09-01 21:35:20 -07001/*
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 Doman703a1852020-11-11 13:04:02 -080018#include "cpuinfo_utils.hpp"
Jonathan Doman94c94bf2020-10-05 23:25:45 -070019#include "speed_select.hpp"
Zhikui Ren18a5ab92020-09-01 21:35:20 -070020
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
Zhikui Ren6d3ad582020-09-11 21:25:59 -070029#include <iostream>
Zhikui Ren18a5ab92020-09-01 21:35:20 -070030#include <optional>
31#include <sstream>
32#include <string>
33
34extern "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
45namespace phosphor
46{
47namespace cpu_info
48{
Zhikui Ren6d3ad582020-09-11 21:25:59 -070049static constexpr bool debug = false;
Zhikui Ren18a5ab92020-09-01 21:35:20 -070050static constexpr const char* cpuInterfaceName =
51 "xyz.openbmc_project.Inventory.Decorator.Asset";
52static constexpr const char* cpuProcessName =
53 "xyz.openbmc_project.Smbios.MDR_V2";
54
Zhikui Ren6d3ad582020-09-11 21:25:59 -070055// constants for reading SSPEC or QDF string from PIROM
56// Currently, they are the same for platforms with icx
57static constexpr uint8_t defaultI2cBus = 13;
58static constexpr uint8_t defaultI2cSlaveAddr0 = 0x50;
59static constexpr uint8_t sspecRegAddr = 0xd;
60static constexpr uint8_t sspecSize = 6;
Zhikui Ren18a5ab92020-09-01 21:35:20 -070061
Zhikui Ren6d3ad582020-09-11 21:25:59 -070062using CPUInfoMap = boost::container::flat_map<size_t, std::shared_ptr<CPUInfo>>;
Zhikui Ren18a5ab92020-09-01 21:35:20 -070063
Zhikui Ren6d3ad582020-09-11 21:25:59 -070064static CPUInfoMap cpuInfoMap = {};
Zhikui Ren18a5ab92020-09-01 21:35:20 -070065
Zhikui Ren6d3ad582020-09-11 21:25:59 -070066static boost::container::flat_map<size_t,
67 std::unique_ptr<sdbusplus::bus::match_t>>
68 cpuUpdatedMatch = {};
Zhikui Ren18a5ab92020-09-01 21:35:20 -070069
70static 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 {
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700122 value = ::i2c_smbus_read_byte_data(fd, regAddr + i);
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700123 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 }
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700138 // 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 }
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700145 sspec.push_back(static_cast<unsigned char>(value));
146 }
147 ::close(fd);
148 return sspec;
149}
150
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700151static void setAssetProperty(
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700152 const std::shared_ptr<sdbusplus::asio::connection>& conn, const int& cpu,
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700153 const std::vector<std::pair<std::string, std::string>>& propValues)
154{
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700155 // 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);
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700158 for (const auto& prop : propValues)
159 {
160 conn->async_method_call(
161 [](const boost::system::error_code ec) {
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700162 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
175static void createCpuUpdatedMatch(
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700176 const std::shared_ptr<sdbusplus::asio::connection>& conn, const int cpu,
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700177 const std::vector<std::pair<std::string, std::string>>& propValues)
178{
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700179 if (cpuUpdatedMatch[cpu])
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700180 {
181 return;
182 }
183
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700184 const std::string objectPath = cpuPath + std::to_string(cpu - 1);
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700185
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700186 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;
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700199
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700200 msg.read(objectName, msgData);
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700201
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700202 // Check for xyz.openbmc_project.Inventory.Item.Cpu
203 // interface match
204 const auto& intfFound = msgData.find(cpuInterfaceName);
205 if (msgData.end() != intfFound)
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700206 {
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700207 setAssetProperty(conn, cpu, propValues);
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700208 }
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700209 }));
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700210}
211
212static void
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700213 getProcessorInfo(boost::asio::io_service& io,
214 const std::shared_ptr<sdbusplus::asio::connection>& conn,
215 const size_t& cpu)
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700216{
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700217 if (cpuInfoMap.find(cpu) == cpuInfoMap.end() || cpuInfoMap[cpu] == nullptr)
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700218 {
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700219 std::cerr << "No information found for cpu " << cpu << "\n";
220 return;
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700221 }
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700222
223 if (cpuInfoMap[cpu]->id != cpu)
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700224 {
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700225 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 */
342static 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
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700361 if (ec)
362 {
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700363 std::cerr << "DBUS response error " << ec.value() << ": "
364 << ec.message() << "\n";
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700365 return;
366 }
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700367
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 */
427static 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"});
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700502}
503
504} // namespace cpu_info
505} // namespace phosphor
506
507int 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
Jonathan Doman703a1852020-11-11 13:04:02 -0800522 cpu_info::hostStateSetup(conn);
523
Jonathan Doman94c94bf2020-10-05 23:25:45 -0700524 cpu_info::sst::init(io, conn);
525
Zhikui Ren6d3ad582020-09-11 21:25:59 -0700526 // 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);
Zhikui Ren18a5ab92020-09-01 21:35:20 -0700529
530 io.run();
531
532 return 0;
533}