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