blob: a41cbcf084885c647fd386df2524de489ba3e847 [file] [log] [blame]
Jason M. Billsd1e40602019-05-09 11:43:51 -07001/*
2// Copyright (c) 2019 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 "peci_pcie.hpp"
18
19#include "pciDeviceClass.hpp"
20#include "pciVendors.hpp"
21
Jason M. Billsbce86a62020-10-08 16:03:47 -070022#include <boost/asio/io_service.hpp>
23#include <boost/asio/steady_timer.hpp>
Jason M. Billsd1e40602019-05-09 11:43:51 -070024#include <boost/container/flat_map.hpp>
25#include <boost/container/flat_set.hpp>
26#include <sdbusplus/asio/object_server.hpp>
27
28#include <iomanip>
29#include <iostream>
30#include <set>
31#include <sstream>
32
33namespace peci_pcie
34{
35static boost::container::flat_map<
36 int, boost::container::flat_map<
37 int, boost::container::flat_map<
38 int, std::shared_ptr<sdbusplus::asio::dbus_interface>>>>
39 pcieDeviceDBusMap;
40
41namespace function
42{
43static constexpr char const* functionTypeName = "FunctionType";
44static constexpr char const* deviceClassName = "DeviceClass";
45static constexpr char const* vendorIdName = "VendorId";
46static constexpr char const* deviceIdName = "DeviceId";
47static constexpr char const* classCodeName = "ClassCode";
48static constexpr char const* revisionIdName = "RevisionId";
49static constexpr char const* subsystemIdName = "SubsystemId";
50static constexpr char const* subsystemVendorIdName = "SubsystemVendorId";
51} // namespace function
52} // namespace peci_pcie
53
Jason M. Bills5d049732019-10-25 15:55:15 -070054struct CPUInfo
Jason M. Billsd1e40602019-05-09 11:43:51 -070055{
Jason M. Bills5d049732019-10-25 15:55:15 -070056 size_t addr;
57 bool skipCpuBuses;
58 boost::container::flat_set<size_t> cpuBusNums;
59};
60
61// PECI Client Address Map
62static void getClientAddrMap(std::vector<CPUInfo>& cpuInfo)
63{
64 for (size_t i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++)
Jason M. Billsd1e40602019-05-09 11:43:51 -070065 {
66 if (peci_Ping(i) == PECI_CC_SUCCESS)
67 {
Jason M. Bills5d049732019-10-25 15:55:15 -070068 cpuInfo.emplace_back(CPUInfo{i, false, {}});
69 }
70 }
71}
72
73// Get CPU PCIe Bus Numbers
74static void getCPUBusNums(std::vector<CPUInfo>& cpuInfo)
75{
76 for (CPUInfo& cpu : cpuInfo)
77 {
78 uint8_t cc = 0;
79 CPUModel model{};
80 uint8_t stepping = 0;
81 if (peci_GetCPUID(cpu.addr, &model, &stepping, &cc) != PECI_CC_SUCCESS)
82 {
83 std::cerr << "Cannot get CPUID!\n";
84 continue;
85 }
86
87 switch (model)
88 {
89 case skx:
90 {
91 // Get the assigned CPU bus numbers from CPUBUSNO and CPUBUSNO1
92 // (B(0) D8 F2 offsets CCh and D0h)
93 uint32_t cpuBusNum = 0;
94 if (peci_RdPCIConfigLocal(cpu.addr, 0, 8, 2, 0xCC, 4,
95 (uint8_t*)&cpuBusNum,
96 &cc) != PECI_CC_SUCCESS)
97 {
98 continue;
99 }
100 uint32_t cpuBusNum1 = 0;
101 if (peci_RdPCIConfigLocal(cpu.addr, 0, 8, 2, 0xD0, 4,
102 (uint8_t*)&cpuBusNum1,
103 &cc) != PECI_CC_SUCCESS)
104 {
105 continue;
106 }
107
108 // Add the CPU bus numbers to the set for this CPU
109 while (cpuBusNum)
110 {
111 // Get the LSB
112 size_t busNum = cpuBusNum & 0xFF;
113 cpu.cpuBusNums.insert(busNum);
114 // Shift right by one byte
115 cpuBusNum >>= 8;
116 }
117 while (cpuBusNum1)
118 {
119 // Get the LSB
120 size_t busNum = cpuBusNum1 & 0xFF;
121 cpu.cpuBusNums.insert(busNum);
122 // Shift right by one byte
Zev Weiss9fa54b52020-09-02 21:20:33 +0000123 cpuBusNum1 >>= 8;
Jason M. Bills5d049732019-10-25 15:55:15 -0700124 }
125 cpu.skipCpuBuses = true;
126 }
Jason M. Billsd1e40602019-05-09 11:43:51 -0700127 }
128 }
129}
130
131static bool isPECIAvailable(void)
132{
Jason M. Bills5d049732019-10-25 15:55:15 -0700133 for (size_t i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++)
Jason M. Billsd1e40602019-05-09 11:43:51 -0700134 {
Jason M. Bills5d049732019-10-25 15:55:15 -0700135 if (peci_Ping(i) == PECI_CC_SUCCESS)
136 {
137 return true;
138 }
Jason M. Billsd1e40602019-05-09 11:43:51 -0700139 }
Jason M. Bills5d049732019-10-25 15:55:15 -0700140 return false;
Jason M. Billsd1e40602019-05-09 11:43:51 -0700141}
142
143static bool getDataFromPCIeConfig(const int& clientAddr, const int& bus,
144 const int& dev, const int& func,
145 const int& offset, const int& size,
146 uint32_t& pciData)
147{
148 // PECI RdPCIConfig() currently only supports 4 byte reads, so adjust
149 // the offset and size to get the right data
150 static constexpr const int pciReadSize = 4;
151 int mod = offset % pciReadSize;
152 int pciOffset = offset - mod;
153 if (mod + size > pciReadSize)
154 {
155 return false;
156 }
157
158 std::array<uint8_t, pciReadSize> data;
159 uint8_t cc;
160 int ret = peci_RdPCIConfig(clientAddr, // CPU Address
161 bus, // PCI Bus
162 dev, // PCI Device
163 func, // PCI Function
164 pciOffset, // PCI Offset
165 data.data(), // PCI Read Data
166 &cc); // PECI Completion Code
167
168 if (ret != PECI_CC_SUCCESS || cc != PECI_DEV_CC_SUCCESS)
169 {
170 return false;
171 }
172
173 // Now build the requested data into a single number
174 pciData = 0;
175 for (int i = mod; i < mod + size; i++)
176 {
177 pciData |= data[i] << 8 * (i - mod);
178 }
179
180 return true;
181}
182
183static std::string getStringFromPCIeConfig(const int& clientAddr,
184 const int& bus, const int& dev,
185 const int& func, const int& offset,
186 const int& size)
187{
188 // Get the requested data
189 uint32_t data = 0;
190 if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, offset, size, data))
191 {
192 return std::string();
193 }
194
195 // And convert it to a string
196 std::stringstream dataStream;
197 dataStream << "0x" << std::hex << std::setfill('0') << std::setw(size * 2)
198 << data;
199 return dataStream.str();
200}
201
202static std::string getVendorName(const int& clientAddr, const int& bus,
203 const int& dev)
204{
205 static constexpr const int vendorIDOffset = 0x00;
206 static constexpr const int vendorIDSize = 2;
207
208 // Get the header type register from function 0
209 uint32_t vendorID = 0;
210 if (!getDataFromPCIeConfig(clientAddr, bus, dev, 0, vendorIDOffset,
211 vendorIDSize, vendorID))
212 {
213 return std::string();
214 }
215 // Get the vendor name or use Other if it doesn't exist
216 return pciVendors.try_emplace(vendorID, otherVendor).first->second;
217}
218
219static std::string getDeviceClass(const int& clientAddr, const int& bus,
220 const int& dev, const int& func)
221{
222 static constexpr const int baseClassOffset = 0x0b;
223 static constexpr const int baseClassSize = 1;
224
225 // Get the Device Base Class
226 uint32_t baseClass = 0;
227 if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, baseClassOffset,
228 baseClassSize, baseClass))
229 {
230 return std::string();
231 }
232 // Get the base class name or use Other if it doesn't exist
233 return pciDeviceClasses.try_emplace(baseClass, otherClass).first->second;
234}
235
236static bool isMultiFunction(const int& clientAddr, const int& bus,
237 const int& dev)
238{
239 static constexpr const int headerTypeOffset = 0x0e;
240 static constexpr const int headerTypeSize = 1;
241 static constexpr const int multiFuncBit = 1 << 7;
242
243 // Get the header type register from function 0
244 uint32_t headerType = 0;
245 if (!getDataFromPCIeConfig(clientAddr, bus, dev, 0, headerTypeOffset,
246 headerTypeSize, headerType))
247 {
248 return false;
249 }
250 // Check if it's a multifunction device
251 if (headerType & multiFuncBit)
252 {
253 return true;
254 }
255 return false;
256}
257
258static bool pcieFunctionExists(const int& clientAddr, const int& bus,
259 const int& dev, const int& func)
260{
261 constexpr const int pciIDOffset = 0;
262 constexpr const int pciIDSize = 4;
263 uint32_t pciID = 0;
264 if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, pciIDOffset,
265 pciIDSize, pciID))
266 {
267 return false;
268 }
269
270 // if VID and DID are all 0s or 1s, then the device doesn't exist
271 if (pciID == 0x00000000 || pciID == 0xFFFFFFFF)
272 {
273 return false;
274 }
275
276 return true;
277}
278
279static bool pcieDeviceExists(const int& clientAddr, const int& bus,
280 const int& dev)
281{
282 // Check if this device exists by checking function 0
283 return (pcieFunctionExists(clientAddr, bus, dev, 0));
284}
285
286static void setPCIeProperty(const int& clientAddr, const int& bus,
287 const int& dev, const std::string& propertyName,
288 const std::string& propertyValue)
289{
290 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
291 peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev];
292
293 if (iface->is_initialized())
294 {
295 iface->set_property(propertyName, propertyValue);
296 }
297 else
298 {
299 iface->register_property(propertyName, propertyValue);
300 }
301}
302
303static void setDefaultPCIeFunctionProperties(const int& clientAddr,
304 const int& bus, const int& dev,
305 const int& func)
306{
307 // Set the function-specific properties
308 static constexpr const std::array functionProperties{
309 peci_pcie::function::functionTypeName,
310 peci_pcie::function::deviceClassName,
311 peci_pcie::function::vendorIdName,
312 peci_pcie::function::deviceIdName,
313 peci_pcie::function::classCodeName,
314 peci_pcie::function::revisionIdName,
315 peci_pcie::function::subsystemIdName,
316 peci_pcie::function::subsystemVendorIdName,
317 };
318
319 for (const char* name : functionProperties)
320 {
321 setPCIeProperty(clientAddr, bus, dev,
322 "Function" + std::to_string(func) + std::string(name),
323 std::string());
324 }
325}
326
327static void setPCIeFunctionProperties(const int& clientAddr, const int& bus,
328 const int& dev, const int& func)
329{
330 // Set the function type always to physical for now
331 setPCIeProperty(clientAddr, bus, dev,
332 "Function" + std::to_string(func) +
333 std::string(peci_pcie::function::functionTypeName),
334 "Physical");
335
336 // Set the function Device Class
337 setPCIeProperty(clientAddr, bus, dev,
338 "Function" + std::to_string(func) +
339 std::string(peci_pcie::function::deviceClassName),
340 getDeviceClass(clientAddr, bus, dev, func));
341
342 // Get PCI Function Properties that come from PCI config with the following
343 // offset and size info
344 static constexpr const std::array pciConfigInfo{
345 std::tuple<const char*, int, int>{peci_pcie::function::vendorIdName, 0,
346 2},
347 std::tuple<const char*, int, int>{peci_pcie::function::deviceIdName, 2,
348 2},
349 std::tuple<const char*, int, int>{peci_pcie::function::classCodeName, 9,
350 3},
351 std::tuple<const char*, int, int>{peci_pcie::function::revisionIdName,
352 8, 1},
353 std::tuple<const char*, int, int>{peci_pcie::function::subsystemIdName,
354 0x2e, 2},
355 std::tuple<const char*, int, int>{
356 peci_pcie::function::subsystemVendorIdName, 0x2c, 2}};
357
358 for (const auto& [name, offset, size] : pciConfigInfo)
359 {
360 setPCIeProperty(
361 clientAddr, bus, dev,
362 "Function" + std::to_string(func) + std::string(name),
363 getStringFromPCIeConfig(clientAddr, bus, dev, func, offset, size));
364 }
365}
366
367static void setPCIeDeviceProperties(const int& clientAddr, const int& bus,
368 const int& dev)
369{
370 // Set the device manufacturer
371 setPCIeProperty(clientAddr, bus, dev, "Manufacturer",
372 getVendorName(clientAddr, bus, dev));
373
374 // Set the device type
375 constexpr char const* deviceTypeName = "DeviceType";
376 if (isMultiFunction(clientAddr, bus, dev))
377 {
378 setPCIeProperty(clientAddr, bus, dev, deviceTypeName, "MultiFunction");
379 }
380 else
381 {
382 setPCIeProperty(clientAddr, bus, dev, deviceTypeName, "SingleFunction");
383 }
384}
385
386static void updatePCIeDevice(const int& clientAddr, const int& bus,
387 const int& dev)
388{
389 setPCIeDeviceProperties(clientAddr, bus, dev);
390
391 // Walk through and populate the functions for this device
392 for (int func = 0; func < peci_pcie::maxPCIFunctions; func++)
393 {
394 if (pcieFunctionExists(clientAddr, bus, dev, func))
395 {
396 // Set the properties for this function
397 setPCIeFunctionProperties(clientAddr, bus, dev, func);
398 }
399 else
400 {
401 // Set default properties for unused functions
402 setDefaultPCIeFunctionProperties(clientAddr, bus, dev, func);
403 }
404 }
405}
406
407static void addPCIeDevice(sdbusplus::asio::object_server& objServer,
408 const int& clientAddr, const int& cpu, const int& bus,
409 const int& dev)
410{
411 std::string pathName = std::string(peci_pcie::peciPCIePath) + "/S" +
412 std::to_string(cpu) + "B" + std::to_string(bus) +
413 "D" + std::to_string(dev);
414 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
415 objServer.add_interface(pathName, peci_pcie::peciPCIeDeviceInterface);
416 peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev] = iface;
417
418 // Update the properties for the new device
419 updatePCIeDevice(clientAddr, bus, dev);
420
421 iface->initialize();
422}
423
424static void removePCIeDevice(sdbusplus::asio::object_server& objServer,
425 const int& clientAddr, const int& bus,
426 const int& dev)
427{
428 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
429 peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev];
430
431 objServer.remove_interface(iface);
432
433 peci_pcie::pcieDeviceDBusMap[clientAddr][bus].erase(dev);
434 if (peci_pcie::pcieDeviceDBusMap[clientAddr][bus].empty())
435 {
436 peci_pcie::pcieDeviceDBusMap[clientAddr].erase(bus);
437 }
438 if (peci_pcie::pcieDeviceDBusMap[clientAddr].empty())
439 {
440 peci_pcie::pcieDeviceDBusMap.erase(clientAddr);
441 }
442}
443
444static bool pcieDeviceInDBusMap(const int& clientAddr, const int& bus,
445 const int& dev)
446{
447 if (auto clientAddrIt = peci_pcie::pcieDeviceDBusMap.find(clientAddr);
448 clientAddrIt != peci_pcie::pcieDeviceDBusMap.end())
449 {
450 if (auto busIt = clientAddrIt->second.find(bus);
451 busIt != clientAddrIt->second.end())
452 {
453 if (auto devIt = busIt->second.find(dev);
454 devIt != busIt->second.end())
455 {
456 if (devIt->second)
457 {
458 return true;
459 }
460 }
461 }
462 }
463 return false;
464}
465
Jason M. Bills5d049732019-10-25 15:55:15 -0700466static void scanNextPCIeDevice(boost::asio::io_service& io,
467 sdbusplus::asio::object_server& objServer,
468 std::vector<CPUInfo>& cpuInfo, int cpu, int bus,
469 int dev);
470
Jason M. Billsd1e40602019-05-09 11:43:51 -0700471static void scanPCIeDevice(boost::asio::io_service& io,
472 sdbusplus::asio::object_server& objServer,
Jason M. Bills5d049732019-10-25 15:55:15 -0700473 std::vector<CPUInfo>& cpuInfo, int cpu, int bus,
Jason M. Billsd1e40602019-05-09 11:43:51 -0700474 int dev)
475{
Jason M. Bills5d049732019-10-25 15:55:15 -0700476 // Check if this is a CPU bus that we should skip
477 if (cpuInfo[cpu].skipCpuBuses && cpuInfo[cpu].cpuBusNums.count(bus))
Jason M. Billsd1e40602019-05-09 11:43:51 -0700478 {
Jason M. Bills5d049732019-10-25 15:55:15 -0700479 std::cerr << "Skipping CPU " << cpu << " Bus Number " << bus << "\n";
480 // Skip all the devices on this bus
481 dev = peci_pcie::maxPCIDevices;
482 scanNextPCIeDevice(io, objServer, cpuInfo, cpu, bus, dev);
483 return;
484 }
485
486 if (pcieDeviceExists(cpuInfo[cpu].addr, bus, dev))
487 {
488 if (pcieDeviceInDBusMap(cpuInfo[cpu].addr, bus, dev))
Jason M. Billsd1e40602019-05-09 11:43:51 -0700489 {
490 // This device is already in D-Bus, so update it
Jason M. Bills5d049732019-10-25 15:55:15 -0700491 updatePCIeDevice(cpuInfo[cpu].addr, bus, dev);
Jason M. Billsd1e40602019-05-09 11:43:51 -0700492 }
493 else
494 {
495 // This device is not in D-Bus, so add it
Jason M. Bills5d049732019-10-25 15:55:15 -0700496 addPCIeDevice(objServer, cpuInfo[cpu].addr, cpu, bus, dev);
Jason M. Billsd1e40602019-05-09 11:43:51 -0700497 }
498 }
499 else
500 {
501 // If PECI is not available, then stop scanning
502 if (!isPECIAvailable())
503 {
504 return;
505 }
506
Jason M. Bills5d049732019-10-25 15:55:15 -0700507 if (pcieDeviceInDBusMap(cpuInfo[cpu].addr, bus, dev))
Jason M. Billsd1e40602019-05-09 11:43:51 -0700508 {
509 // This device is in D-Bus, so remove it
Jason M. Bills5d049732019-10-25 15:55:15 -0700510 removePCIeDevice(objServer, cpuInfo[cpu].addr, bus, dev);
Jason M. Billsd1e40602019-05-09 11:43:51 -0700511 }
512 }
Jason M. Bills5d049732019-10-25 15:55:15 -0700513 scanNextPCIeDevice(io, objServer, cpuInfo, cpu, bus, dev);
514}
Jason M. Billsd1e40602019-05-09 11:43:51 -0700515
Jason M. Bills5d049732019-10-25 15:55:15 -0700516static void scanNextPCIeDevice(boost::asio::io_service& io,
517 sdbusplus::asio::object_server& objServer,
518 std::vector<CPUInfo>& cpuInfo, int cpu, int bus,
519 int dev)
520{
Jason M. Billsd1e40602019-05-09 11:43:51 -0700521 // PCIe Device scan completed, so move to the next device
522 if (++dev >= peci_pcie::maxPCIDevices)
523 {
524 // All devices scanned, so move to the next bus
525 dev = 0;
526 if (++bus >= peci_pcie::maxPCIBuses)
527 {
528 // All buses scanned, so move to the next CPU
529 bus = 0;
Jason M. Bills5d049732019-10-25 15:55:15 -0700530 if (++cpu >= cpuInfo.size())
Jason M. Billsd1e40602019-05-09 11:43:51 -0700531 {
532 // All CPUs scanned, so we're done
533 return;
534 }
535 }
536 }
Jason M. Bills5d049732019-10-25 15:55:15 -0700537 boost::asio::post(io, [&io, &objServer, cpuInfo, cpu, bus, dev]() mutable {
538 scanPCIeDevice(io, objServer, cpuInfo, cpu, bus, dev);
Jason M. Billsd1e40602019-05-09 11:43:51 -0700539 });
540}
541
542static void peciAvailableCheck(boost::asio::steady_timer& peciWaitTimer,
543 boost::asio::io_service& io,
544 sdbusplus::asio::object_server& objServer)
545{
Jason M. Bills5d049732019-10-25 15:55:15 -0700546 static bool lastPECIState = false;
Jason M. Billsd1e40602019-05-09 11:43:51 -0700547 bool peciAvailable = isPECIAvailable();
548 if (peciAvailable && !lastPECIState)
549 {
550 lastPECIState = true;
551
552 static boost::asio::steady_timer pcieTimeout(io);
553 constexpr const int pcieWaitTime = 60;
554 pcieTimeout.expires_after(std::chrono::seconds(pcieWaitTime));
555 pcieTimeout.async_wait(
556 [&io, &objServer](const boost::system::error_code& ec) {
557 if (ec)
558 {
559 // operation_aborted is expected if timer is canceled
560 // before completion.
561 if (ec != boost::asio::error::operation_aborted)
562 {
563 std::cerr << "PECI PCIe async_wait failed " << ec;
564 }
565 return;
566 }
567 // get the PECI client address list
Jason M. Bills5d049732019-10-25 15:55:15 -0700568 std::vector<CPUInfo> cpuInfo;
569 getClientAddrMap(cpuInfo);
570 // get the CPU Bus Numbers to skip
571 getCPUBusNums(cpuInfo);
Jason M. Billsd1e40602019-05-09 11:43:51 -0700572 // scan PCIe starting from CPU 0, Bus 0, Device 0
Jason M. Bills5d049732019-10-25 15:55:15 -0700573 scanPCIeDevice(io, objServer, cpuInfo, 0, 0, 0);
Jason M. Billsd1e40602019-05-09 11:43:51 -0700574 });
575 }
576 else if (!peciAvailable && lastPECIState)
577 {
578 lastPECIState = false;
579 }
580
581 peciWaitTimer.expires_after(
582 std::chrono::seconds(peci_pcie::peciCheckInterval));
583 peciWaitTimer.async_wait([&peciWaitTimer, &io,
584 &objServer](const boost::system::error_code& ec) {
585 if (ec)
586 {
587 // operation_aborted is expected if timer is canceled
588 // before completion.
589 if (ec != boost::asio::error::operation_aborted)
590 {
591 std::cerr << "PECI Available Check async_wait failed " << ec;
592 }
593 return;
594 }
595 peciAvailableCheck(peciWaitTimer, io, objServer);
596 });
597}
598
599int main(int argc, char* argv[])
600{
601 // setup connection to dbus
602 boost::asio::io_service io;
603 std::shared_ptr<sdbusplus::asio::connection> conn =
604 std::make_shared<sdbusplus::asio::connection>(io);
605
606 // PECI PCIe Object
607 conn->request_name(peci_pcie::peciPCIeObject);
608 sdbusplus::asio::object_server server =
609 sdbusplus::asio::object_server(conn);
610
611 // Start the PECI check loop
612 boost::asio::steady_timer peciWaitTimer(
613 io, std::chrono::seconds(peci_pcie::peciCheckInterval));
614 peciWaitTimer.async_wait([&peciWaitTimer, &io,
615 &server](const boost::system::error_code& ec) {
616 if (ec)
617 {
618 // operation_aborted is expected if timer is canceled
619 // before completion.
620 if (ec != boost::asio::error::operation_aborted)
621 {
622 std::cerr << "PECI Available Check async_wait failed " << ec;
623 }
624 return;
625 }
626 peciAvailableCheck(peciWaitTimer, io, server);
627 });
628
629 io.run();
630
631 return 0;
632}