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