xref: /openbmc/smbios-mdr/src/cpuinfo_main.cpp (revision 703a1856)
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