xref: /openbmc/smbios-mdr/src/cpuinfo_main.cpp (revision 1d73dccc89f0bb9d1dce3543e5af6b3e3087d5f4)
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  
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  #include <boost/container/flat_map.hpp>
28  
29  #include <iostream>
30  #include <list>
31  #include <optional>
32  #include <sstream>
33  #include <string>
34  
35  extern "C"
36  {
37  #include <i2c/smbus.h>
38  #include <linux/i2c-dev.h>
39  }
40  
41  #if PECI_ENABLED
42  #include "speed_select.hpp"
43  
44  #include <peci.h>
45  #endif
46  
47  #include <phosphor-logging/log.hpp>
48  #include <sdbusplus/asio/object_server.hpp>
49  
50  namespace cpu_info
51  {
52  static constexpr bool debug = false;
53  static constexpr const char* assetInterfaceName =
54      "xyz.openbmc_project.Inventory.Decorator.Asset";
55  static constexpr const char* cpuProcessName =
56      "xyz.openbmc_project.Smbios.MDR_V2";
57  
58  // constants for reading SSPEC or QDF string from PIROM
59  // Currently, they are the same for platforms with Ice Lake
60  static constexpr uint8_t defaultI2cBus = 13;
61  static constexpr uint8_t defaultI2cSlaveAddr0 = 0x50;
62  static constexpr uint8_t sspecRegAddr = 0xd;
63  static constexpr uint8_t sspecSize = 6;
64  
65  using CPUInfoMap = boost::container::flat_map<size_t, std::shared_ptr<CPUInfo>>;
66  
67  static CPUInfoMap cpuInfoMap = {};
68  
69  /**
70   * Simple aggregate to define an external D-Bus property which needs to be set
71   * by this application.
72   */
73  struct CpuProperty
74  {
75      std::string object;
76      std::string interface;
77      std::string name;
78      std::string value;
79  };
80  
81  /**
82   * List of properties we want to set on other D-Bus objects. This list is kept
83   * around so that if any target objects are removed+re-added, then we can set
84   * the values again.
85   */
86  static std::list<CpuProperty> propertiesToSet;
87  
logStream(int cpu)88  static std::ostream& logStream(int cpu)
89  {
90      return std::cerr << "[CPU " << cpu << "] ";
91  }
92  
93  static void
94      setCpuProperty(const std::shared_ptr<sdbusplus::asio::connection>& conn,
95                     size_t cpu, const std::string& interface,
96                     const std::string& propName, const std::string& propVal);
97  static void
98      setDbusProperty(const std::shared_ptr<sdbusplus::asio::connection>& conn,
99                      size_t cpu, const CpuProperty& newProp);
100  static void createCpuUpdatedMatch(
101      const std::shared_ptr<sdbusplus::asio::connection>& conn, size_t cpu);
102  
103  static std::optional<std::string>
readSSpec(uint8_t bus,uint8_t slaveAddr,uint8_t regAddr,size_t count)104      readSSpec(uint8_t bus, uint8_t slaveAddr, uint8_t regAddr, size_t count)
105  {
106      unsigned long funcs = 0;
107      std::string devPath = "/dev/i2c-" + std::to_string(bus);
108  
109      int fd = ::open(devPath.c_str(), O_RDWR);
110      if (fd < 0)
111      {
112          phosphor::logging::log<phosphor::logging::level::ERR>(
113              "Error in open!",
114              phosphor::logging::entry("PATH=%s", devPath.c_str()),
115              phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr));
116          return std::nullopt;
117      }
118  
119      if (::ioctl(fd, I2C_FUNCS, &funcs) < 0)
120      {
121          phosphor::logging::log<phosphor::logging::level::ERR>(
122              "Error in I2C_FUNCS!",
123              phosphor::logging::entry("PATH=%s", devPath.c_str()),
124              phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr));
125          ::close(fd);
126          return std::nullopt;
127      }
128  
129      if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA))
130      {
131          phosphor::logging::log<phosphor::logging::level::ERR>(
132              "i2c bus does not support read!",
133              phosphor::logging::entry("PATH=%s", devPath.c_str()),
134              phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr));
135          ::close(fd);
136          return std::nullopt;
137      }
138  
139      if (::ioctl(fd, I2C_SLAVE_FORCE, slaveAddr) < 0)
140      {
141          phosphor::logging::log<phosphor::logging::level::ERR>(
142              "Error in I2C_SLAVE_FORCE!",
143              phosphor::logging::entry("PATH=%s", devPath.c_str()),
144              phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr));
145          ::close(fd);
146          return std::nullopt;
147      }
148  
149      int value = 0;
150      std::string sspec;
151      sspec.reserve(count);
152  
153      for (size_t i = 0; i < count; i++)
154      {
155          value = ::i2c_smbus_read_byte_data(fd, regAddr + i);
156          if (value < 0)
157          {
158              phosphor::logging::log<phosphor::logging::level::ERR>(
159                  "Error in i2c read!",
160                  phosphor::logging::entry("PATH=%s", devPath.c_str()),
161                  phosphor::logging::entry("SLAVEADDR=0x%x", slaveAddr));
162              ::close(fd);
163              return std::nullopt;
164          }
165          if (!std::isprint(static_cast<unsigned char>(value)))
166          {
167              phosphor::logging::log<phosphor::logging::level::ERR>(
168                  "Non printable value in sspec, ignored.");
169              continue;
170          }
171          // sspec always starts with S,
172          // if not assume it is QDF string which starts at offset 2
173          if (i == 0 && static_cast<unsigned char>(value) != 'S')
174          {
175              i = 1;
176              continue;
177          }
178          sspec.push_back(static_cast<unsigned char>(value));
179      }
180      ::close(fd);
181  
182      if (sspec.size() < 4)
183      {
184          return std::nullopt;
185      }
186  
187      return sspec;
188  }
189  
190  /**
191   * Higher level SSpec logic.
192   * This handles retrying the PIROM reads until two subsequent reads are
193   * successful and return matching data. When we have confidence that the data
194   * read is correct, then set the property on D-Bus.
195   */
tryReadSSpec(const std::shared_ptr<sdbusplus::asio::connection> & conn,size_t cpuIndex)196  static void tryReadSSpec(
197      const std::shared_ptr<sdbusplus::asio::connection>& conn, size_t cpuIndex)
198  {
199      static int failedReads = 0;
200  
201      auto cpuInfoIt = cpuInfoMap.find(cpuIndex);
202      if (cpuInfoIt == cpuInfoMap.end())
203      {
204          return;
205      }
206      auto cpuInfo = cpuInfoIt->second;
207  
208      std::optional<std::string> newSSpec =
209          readSSpec(cpuInfo->i2cBus, cpuInfo->i2cDevice, sspecRegAddr, sspecSize);
210      logStream(cpuInfo->id) << "SSpec read status: "
211                             << static_cast<bool>(newSSpec) << "\n";
212      if (newSSpec && newSSpec == cpuInfo->sSpec)
213      {
214          setCpuProperty(conn, cpuInfo->id, assetInterfaceName, "Model",
215                         *newSSpec);
216          return;
217      }
218  
219      // If this read failed, back off for a little longer so that hopefully the
220      // transient condition affecting PIROM reads will pass, but give up after
221      // several consecutive failures. But if this read looked OK, try again
222      // sooner to confirm it.
223      int retrySeconds;
224      if (newSSpec)
225      {
226          retrySeconds = 1;
227          failedReads = 0;
228          cpuInfo->sSpec = *newSSpec;
229      }
230      else
231      {
232          retrySeconds = 5;
233          if (++failedReads > 10)
234          {
235              logStream(cpuInfo->id) << "PIROM Read failed too many times\n";
236              return;
237          }
238      }
239  
240      auto sspecTimer = std::make_shared<boost::asio::steady_timer>(
241          conn->get_io_context(), std::chrono::seconds(retrySeconds));
242      sspecTimer->async_wait(
243          [sspecTimer, conn, cpuIndex](boost::system::error_code ec) {
244              if (ec)
245              {
246                  return;
247              }
248              tryReadSSpec(conn, cpuIndex);
249          });
250  }
251  
252  /**
253   * Add a D-Bus property to the global list, and attempt to set it by calling
254   * `setDbusProperty`.
255   *
256   * @param[in,out]   conn        D-Bus connection.
257   * @param[in]       cpu         1-based CPU index.
258   * @param[in]       interface   Interface to set.
259   * @param[in]       propName    Property to set.
260   * @param[in]       propVal     Value to set.
261   */
262  static void
setCpuProperty(const std::shared_ptr<sdbusplus::asio::connection> & conn,size_t cpu,const std::string & interface,const std::string & propName,const std::string & propVal)263      setCpuProperty(const std::shared_ptr<sdbusplus::asio::connection>& conn,
264                     size_t cpu, const std::string& interface,
265                     const std::string& propName, const std::string& propVal)
266  {
267      // cpuId from configuration is one based as
268      // dbus object path used by smbios is 0 based
269      const std::string objectPath = cpuPath + std::to_string(cpu - 1);
270  
271      // Can switch to emplace_back if you define a CpuProperty constructor.
272      propertiesToSet.push_back(
273          CpuProperty{objectPath, interface, propName, propVal});
274  
275      setDbusProperty(conn, cpu, propertiesToSet.back());
276  }
277  
278  /**
279   * Set a D-Bus property which is already contained in the global list, and also
280   * setup a D-Bus match to make sure the target property stays correct.
281   *
282   * @param[in,out]   conn    D-Bus connection.
283   * @param[in]       cpu     1-baesd CPU index.
284   * @param[in]       newProp Property to set.
285   */
286  static void
setDbusProperty(const std::shared_ptr<sdbusplus::asio::connection> & conn,size_t cpu,const CpuProperty & newProp)287      setDbusProperty(const std::shared_ptr<sdbusplus::asio::connection>& conn,
288                      size_t cpu, const CpuProperty& newProp)
289  {
290      createCpuUpdatedMatch(conn, cpu);
291      conn->async_method_call(
292          [](const boost::system::error_code ec) {
293              if (ec)
294              {
295                  phosphor::logging::log<phosphor::logging::level::ERR>(
296                      "Cannot set CPU property!");
297                  return;
298              }
299          },
300          cpuProcessName, newProp.object.c_str(),
301          "org.freedesktop.DBus.Properties", "Set", newProp.interface,
302          newProp.name, std::variant<std::string>{newProp.value});
303  }
304  
305  /**
306   * Set up a D-Bus match (if one does not already exist) to watch for any new
307   * interfaces on the cpu object. When new interfaces are added, re-send all
308   * properties targeting that object/interface.
309   *
310   * @param[in,out]   conn    D-Bus connection.
311   * @param[in]       cpu     1-based CPU index.
312   */
createCpuUpdatedMatch(const std::shared_ptr<sdbusplus::asio::connection> & conn,size_t cpu)313  static void createCpuUpdatedMatch(
314      const std::shared_ptr<sdbusplus::asio::connection>& conn, size_t cpu)
315  {
316      static boost::container::flat_map<size_t,
317                                        std::unique_ptr<sdbusplus::bus::match_t>>
318          cpuUpdatedMatch;
319  
320      if (cpuUpdatedMatch[cpu])
321      {
322          return;
323      }
324  
325      const std::string objectPath = cpuPath + std::to_string(cpu - 1);
326  
327      cpuUpdatedMatch.insert_or_assign(
328          cpu,
329          std::make_unique<sdbusplus::bus::match_t>(
330              static_cast<sdbusplus::bus_t&>(*conn),
331              sdbusplus::bus::match::rules::interfacesAdded() +
332                  sdbusplus::bus::match::rules::argNpath(0, objectPath.c_str()),
333              [conn, cpu](sdbusplus::message_t& msg) {
334                  sdbusplus::message::object_path objectName;
335                  boost::container::flat_map<
336                      std::string,
337                      boost::container::flat_map<
338                          std::string, std::variant<std::string, uint64_t>>>
339                      msgData;
340  
341                  msg.read(objectName, msgData);
342  
343                  // Go through all the property changes, and retry all of them
344                  // targeting this object/interface which was just added.
345                  for (const CpuProperty& prop : propertiesToSet)
346                  {
347                      if (prop.object == objectName &&
348                          msgData.contains(prop.interface))
349                      {
350                          setDbusProperty(conn, cpu, prop);
351                      }
352                  }
353              }));
354  }
355  
356  #if PECI_ENABLED
getPPIN(boost::asio::io_service & io,const std::shared_ptr<sdbusplus::asio::connection> & conn,const size_t & cpu)357  static void getPPIN(boost::asio::io_service& io,
358                      const std::shared_ptr<sdbusplus::asio::connection>& conn,
359                      const size_t& cpu)
360  {
361      if (cpuInfoMap.find(cpu) == cpuInfoMap.end() || cpuInfoMap[cpu] == nullptr)
362      {
363          std::cerr << "No information found for cpu " << cpu << "\n";
364          return;
365      }
366  
367      std::shared_ptr<CPUInfo> cpuInfo = cpuInfoMap[cpu];
368  
369      if (cpuInfo->id != cpu)
370      {
371          std::cerr << "Incorrect CPU id " << (unsigned)cpuInfo->id << " expect "
372                    << cpu << "\n";
373          return;
374      }
375  
376      uint8_t cpuAddr = cpuInfo->peciAddr;
377  
378      uint8_t cc = 0;
379      CPUModel model{};
380      uint8_t stepping = 0;
381  
382      // Wait for POST to complete to ensure that BIOS has time to enable the
383      // PPIN. Before BIOS enables it, we would get a 0x90 CC on PECI.
384      if (hostState != HostState::postComplete ||
385          peci_GetCPUID(cpuAddr, &model, &stepping, &cc) != PECI_CC_SUCCESS)
386      {
387          // Start the PECI check loop
388          auto waitTimer = std::make_shared<boost::asio::steady_timer>(io);
389          waitTimer->expires_after(
390              std::chrono::seconds(cpu_info::peciCheckInterval));
391  
392          waitTimer->async_wait(
393              [waitTimer, &io, conn, cpu](const boost::system::error_code& ec) {
394                  if (ec)
395                  {
396                      // operation_aborted is expected if timer is canceled
397                      // before completion.
398                      if (ec != boost::asio::error::operation_aborted)
399                      {
400                          phosphor::logging::log<phosphor::logging::level::ERR>(
401                              "info update timer async_wait failed ",
402                              phosphor::logging::entry("EC=0x%x", ec.value()));
403                      }
404                      return;
405                  }
406                  getPPIN(io, conn, cpu);
407              });
408          return;
409      }
410  
411      switch (model)
412      {
413          case iceLake:
414          case iceLakeD:
415          case sapphireRapids:
416          case emeraldRapids:
417          case graniteRapids:
418          case graniteRapidsD:
419          case sierraForest:
420          {
421              // PPIN can be read through PCS 19
422              static constexpr uint8_t u8Size = 4; // default to a DWORD
423              static constexpr uint8_t u8PPINPkgIndex = 19;
424              static constexpr uint16_t u16PPINPkgParamHigh = 2;
425              static constexpr uint16_t u16PPINPkgParamLow = 1;
426              uint64_t cpuPPIN = 0;
427              uint32_t u32PkgValue = 0;
428  
429              int ret =
430                  peci_RdPkgConfig(cpuAddr, u8PPINPkgIndex, u16PPINPkgParamLow,
431                                   u8Size, (uint8_t*)&u32PkgValue, &cc);
432              if (0 != ret)
433              {
434                  phosphor::logging::log<phosphor::logging::level::ERR>(
435                      "peci read package config failed at address",
436                      phosphor::logging::entry("PECIADDR=0x%x",
437                                               (unsigned)cpuAddr),
438                      phosphor::logging::entry("CC=0x%x", cc));
439                  u32PkgValue = 0;
440              }
441  
442              cpuPPIN = u32PkgValue;
443              ret = peci_RdPkgConfig(cpuAddr, u8PPINPkgIndex, u16PPINPkgParamHigh,
444                                     u8Size, (uint8_t*)&u32PkgValue, &cc);
445              if (0 != ret)
446              {
447                  phosphor::logging::log<phosphor::logging::level::ERR>(
448                      "peci read package config failed at address",
449                      phosphor::logging::entry("PECIADDR=0x%x",
450                                               (unsigned)cpuAddr),
451                      phosphor::logging::entry("CC=0x%x", cc));
452                  cpuPPIN = 0;
453                  u32PkgValue = 0;
454              }
455  
456              cpuPPIN |= static_cast<uint64_t>(u32PkgValue) << 32;
457  
458              // set SerialNumber if cpuPPIN is valid
459              if (0 != cpuPPIN)
460              {
461                  std::stringstream stream;
462                  stream << std::hex << cpuPPIN;
463                  std::string serialNumber(stream.str());
464                  cpuInfo->publishUUID(*conn, serialNumber);
465              }
466              break;
467          }
468          default:
469              phosphor::logging::log<phosphor::logging::level::INFO>(
470                  "in-compatible cpu for cpu asset info");
471              break;
472      }
473  }
474  #endif
475  
476  /**
477   * Get cpu and pirom address
478   */
479  static void
getCpuAddress(boost::asio::io_service & io,const std::shared_ptr<sdbusplus::asio::connection> & conn,const std::string & service,const std::string & object,const std::string & interface)480      getCpuAddress(boost::asio::io_service& io,
481                    const std::shared_ptr<sdbusplus::asio::connection>& conn,
482                    const std::string& service, const std::string& object,
483                    const std::string& interface)
484  {
485      conn->async_method_call(
486          [&io, conn](boost::system::error_code ec,
487                      const boost::container::flat_map<
488                          std::string,
489                          std::variant<std::string, uint64_t, uint32_t, uint16_t,
490                                       std::vector<std::string>>>& properties) {
491              const uint64_t* value = nullptr;
492              std::optional<uint8_t> peciAddress;
493              uint8_t i2cBus = defaultI2cBus;
494              std::optional<uint8_t> i2cDevice;
495              std::optional<size_t> cpu;
496  
497              if (ec)
498              {
499                  std::cerr << "DBUS response error " << ec.value() << ": "
500                            << ec.message() << "\n";
501                  return;
502              }
503  
504              for (const auto& property : properties)
505              {
506                  std::cerr << "property " << property.first << "\n";
507                  if (property.first == "Address")
508                  {
509                      value = std::get_if<uint64_t>(&property.second);
510                      if (value != nullptr)
511                      {
512                          peciAddress = static_cast<uint8_t>(*value);
513                      }
514                  }
515                  if (property.first == "CpuID")
516                  {
517                      value = std::get_if<uint64_t>(&property.second);
518                      if (value != nullptr)
519                      {
520                          cpu = static_cast<size_t>(*value);
521                      }
522                  }
523                  if (property.first == "PiromI2cAddress")
524                  {
525                      value = std::get_if<uint64_t>(&property.second);
526                      if (value != nullptr)
527                      {
528                          i2cDevice = static_cast<uint8_t>(*value);
529                      }
530                  }
531                  if (property.first == "PiromI2cBus")
532                  {
533                      value = std::get_if<uint64_t>(&property.second);
534                      if (value != nullptr)
535                      {
536                          i2cBus = static_cast<uint8_t>(*value);
537                      }
538                  }
539              }
540  
541              if (!cpu || !peciAddress)
542              {
543                  return;
544              }
545  
546              if (!i2cDevice)
547              {
548                  i2cDevice = defaultI2cSlaveAddr0 + *cpu - 1;
549              }
550  
551              auto key = cpuInfoMap.find(*cpu);
552  
553              if (key != cpuInfoMap.end())
554              {
555                  cpuInfoMap.erase(key);
556              }
557  
558              cpuInfoMap.emplace(*cpu,
559                                 std::make_shared<CPUInfo>(*cpu, *peciAddress,
560                                                           i2cBus, *i2cDevice));
561  
562              tryReadSSpec(conn, *cpu);
563  
564  #if PECI_ENABLED
565              getPPIN(io, conn, *cpu);
566  #endif
567          },
568          service, object, "org.freedesktop.DBus.Properties", "GetAll",
569          interface);
570  }
571  
572  /**
573   * D-Bus client: to get platform specific configs
574   */
getCpuConfiguration(boost::asio::io_service & io,const std::shared_ptr<sdbusplus::asio::connection> & conn,sdbusplus::asio::object_server & objServer)575  static void getCpuConfiguration(
576      boost::asio::io_service& io,
577      const std::shared_ptr<sdbusplus::asio::connection>& conn,
578      sdbusplus::asio::object_server& objServer)
579  {
580      // Get the Cpu configuration
581      // In case it's not available, set a match for it
582      static std::unique_ptr<sdbusplus::bus::match_t> cpuConfigMatch =
583          std::make_unique<sdbusplus::bus::match_t>(
584              *conn,
585              "type='signal',interface='org.freedesktop.DBus.Properties',member='"
586              "PropertiesChanged',arg0='xyz.openbmc_project."
587              "Configuration.XeonCPU'",
588              [&io, conn, &objServer](sdbusplus::message_t& /* msg */) {
589                  std::cerr << "get cpu configuration match\n";
590                  static boost::asio::steady_timer filterTimer(io);
591                  filterTimer.expires_after(
592                      std::chrono::seconds(configCheckInterval));
593  
594                  filterTimer.async_wait(
595                      [&io, conn,
596                       &objServer](const boost::system::error_code& ec) {
597                          if (ec == boost::asio::error::operation_aborted)
598                          {
599                              return; // we're being canceled
600                          }
601                          else if (ec)
602                          {
603                              std::cerr << "Error: " << ec.message() << "\n";
604                              return;
605                          }
606                          getCpuConfiguration(io, conn, objServer);
607                      });
608              });
609  
610      conn->async_method_call(
611          [&io, conn](
612              boost::system::error_code ec,
613              const std::vector<std::pair<
614                  std::string,
615                  std::vector<std::pair<std::string, std::vector<std::string>>>>>&
616                  subtree) {
617              if constexpr (debug)
618                  std::cerr << "async_method_call callback\n";
619  
620              if (ec)
621              {
622                  std::cerr << "error with async_method_call\n";
623                  return;
624              }
625              if (subtree.empty())
626              {
627                  // No config data yet, so wait for the match
628                  return;
629              }
630  
631              for (const auto& object : subtree)
632              {
633                  for (const auto& service : object.second)
634                  {
635                      getCpuAddress(io, conn, service.first, object.first,
636                                    "xyz.openbmc_project.Configuration.XeonCPU");
637                  }
638              }
639              if constexpr (debug)
640                  std::cerr << "getCpuConfiguration callback complete\n";
641  
642              return;
643          },
644          "xyz.openbmc_project.ObjectMapper",
645          "/xyz/openbmc_project/object_mapper",
646          "xyz.openbmc_project.ObjectMapper", "GetSubTree",
647          "/xyz/openbmc_project/", 0,
648          std::array<const char*, 1>{
649              "xyz.openbmc_project.Configuration.XeonCPU"});
650  }
651  
652  } // namespace cpu_info
653  
main()654  int main()
655  {
656      // setup connection to dbus
657      boost::asio::io_service& io = cpu_info::dbus::getIOContext();
658      std::shared_ptr<sdbusplus::asio::connection> conn =
659          cpu_info::dbus::getConnection();
660  
661      // CPUInfo Object
662      conn->request_name(cpu_info::cpuInfoObject);
663      sdbusplus::asio::object_server server =
664          sdbusplus::asio::object_server(conn);
665      sdbusplus::bus_t& bus = static_cast<sdbusplus::bus_t&>(*conn);
666      sdbusplus::server::manager_t objManager(bus,
667                                              "/xyz/openbmc_project/inventory");
668  
669      cpu_info::hostStateSetup(conn);
670  
671  #if PECI_ENABLED
672      cpu_info::sst::init();
673  #endif
674  
675      // shared_ptr conn is global for the service
676      // const reference of conn is passed to async calls
677      cpu_info::getCpuConfiguration(io, conn, server);
678  
679      io.run();
680  
681      return 0;
682  }
683