blob: 8db1f894b3425b7652c51f8adb23e421467381d4 [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
22#include <boost/container/flat_map.hpp>
23#include <boost/container/flat_set.hpp>
24#include <sdbusplus/asio/object_server.hpp>
25
26#include <iomanip>
27#include <iostream>
28#include <set>
29#include <sstream>
30
31namespace peci_pcie
32{
33static boost::container::flat_map<
34 int, boost::container::flat_map<
35 int, boost::container::flat_map<
36 int, std::shared_ptr<sdbusplus::asio::dbus_interface>>>>
37 pcieDeviceDBusMap;
38
39namespace function
40{
41static constexpr char const* functionTypeName = "FunctionType";
42static constexpr char const* deviceClassName = "DeviceClass";
43static constexpr char const* vendorIdName = "VendorId";
44static constexpr char const* deviceIdName = "DeviceId";
45static constexpr char const* classCodeName = "ClassCode";
46static constexpr char const* revisionIdName = "RevisionId";
47static constexpr char const* subsystemIdName = "SubsystemId";
48static constexpr char const* subsystemVendorIdName = "SubsystemVendorId";
49} // namespace function
50} // namespace peci_pcie
51
52// PECI Client Address Map
53static void getClientAddrMap(std::vector<int>& clientAddrs)
54{
55 for (int i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++)
56 {
57 if (peci_Ping(i) == PECI_CC_SUCCESS)
58 {
59 clientAddrs.push_back(i);
60 }
61 }
62}
63
64static bool isPECIAvailable(void)
65{
66 std::vector<int> clientAddrs;
67 getClientAddrMap(clientAddrs);
68 if (clientAddrs.empty())
69 {
70 return false;
71 }
72 return true;
73}
74
75static bool getDataFromPCIeConfig(const int& clientAddr, const int& bus,
76 const int& dev, const int& func,
77 const int& offset, const int& size,
78 uint32_t& pciData)
79{
80 // PECI RdPCIConfig() currently only supports 4 byte reads, so adjust
81 // the offset and size to get the right data
82 static constexpr const int pciReadSize = 4;
83 int mod = offset % pciReadSize;
84 int pciOffset = offset - mod;
85 if (mod + size > pciReadSize)
86 {
87 return false;
88 }
89
90 std::array<uint8_t, pciReadSize> data;
91 uint8_t cc;
92 int ret = peci_RdPCIConfig(clientAddr, // CPU Address
93 bus, // PCI Bus
94 dev, // PCI Device
95 func, // PCI Function
96 pciOffset, // PCI Offset
97 data.data(), // PCI Read Data
98 &cc); // PECI Completion Code
99
100 if (ret != PECI_CC_SUCCESS || cc != PECI_DEV_CC_SUCCESS)
101 {
102 return false;
103 }
104
105 // Now build the requested data into a single number
106 pciData = 0;
107 for (int i = mod; i < mod + size; i++)
108 {
109 pciData |= data[i] << 8 * (i - mod);
110 }
111
112 return true;
113}
114
115static std::string getStringFromPCIeConfig(const int& clientAddr,
116 const int& bus, const int& dev,
117 const int& func, const int& offset,
118 const int& size)
119{
120 // Get the requested data
121 uint32_t data = 0;
122 if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, offset, size, data))
123 {
124 return std::string();
125 }
126
127 // And convert it to a string
128 std::stringstream dataStream;
129 dataStream << "0x" << std::hex << std::setfill('0') << std::setw(size * 2)
130 << data;
131 return dataStream.str();
132}
133
134static std::string getVendorName(const int& clientAddr, const int& bus,
135 const int& dev)
136{
137 static constexpr const int vendorIDOffset = 0x00;
138 static constexpr const int vendorIDSize = 2;
139
140 // Get the header type register from function 0
141 uint32_t vendorID = 0;
142 if (!getDataFromPCIeConfig(clientAddr, bus, dev, 0, vendorIDOffset,
143 vendorIDSize, vendorID))
144 {
145 return std::string();
146 }
147 // Get the vendor name or use Other if it doesn't exist
148 return pciVendors.try_emplace(vendorID, otherVendor).first->second;
149}
150
151static std::string getDeviceClass(const int& clientAddr, const int& bus,
152 const int& dev, const int& func)
153{
154 static constexpr const int baseClassOffset = 0x0b;
155 static constexpr const int baseClassSize = 1;
156
157 // Get the Device Base Class
158 uint32_t baseClass = 0;
159 if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, baseClassOffset,
160 baseClassSize, baseClass))
161 {
162 return std::string();
163 }
164 // Get the base class name or use Other if it doesn't exist
165 return pciDeviceClasses.try_emplace(baseClass, otherClass).first->second;
166}
167
168static bool isMultiFunction(const int& clientAddr, const int& bus,
169 const int& dev)
170{
171 static constexpr const int headerTypeOffset = 0x0e;
172 static constexpr const int headerTypeSize = 1;
173 static constexpr const int multiFuncBit = 1 << 7;
174
175 // Get the header type register from function 0
176 uint32_t headerType = 0;
177 if (!getDataFromPCIeConfig(clientAddr, bus, dev, 0, headerTypeOffset,
178 headerTypeSize, headerType))
179 {
180 return false;
181 }
182 // Check if it's a multifunction device
183 if (headerType & multiFuncBit)
184 {
185 return true;
186 }
187 return false;
188}
189
190static bool pcieFunctionExists(const int& clientAddr, const int& bus,
191 const int& dev, const int& func)
192{
193 constexpr const int pciIDOffset = 0;
194 constexpr const int pciIDSize = 4;
195 uint32_t pciID = 0;
196 if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, pciIDOffset,
197 pciIDSize, pciID))
198 {
199 return false;
200 }
201
202 // if VID and DID are all 0s or 1s, then the device doesn't exist
203 if (pciID == 0x00000000 || pciID == 0xFFFFFFFF)
204 {
205 return false;
206 }
207
208 return true;
209}
210
211static bool pcieDeviceExists(const int& clientAddr, const int& bus,
212 const int& dev)
213{
214 // Check if this device exists by checking function 0
215 return (pcieFunctionExists(clientAddr, bus, dev, 0));
216}
217
218static void setPCIeProperty(const int& clientAddr, const int& bus,
219 const int& dev, const std::string& propertyName,
220 const std::string& propertyValue)
221{
222 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
223 peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev];
224
225 if (iface->is_initialized())
226 {
227 iface->set_property(propertyName, propertyValue);
228 }
229 else
230 {
231 iface->register_property(propertyName, propertyValue);
232 }
233}
234
235static void setDefaultPCIeFunctionProperties(const int& clientAddr,
236 const int& bus, const int& dev,
237 const int& func)
238{
239 // Set the function-specific properties
240 static constexpr const std::array functionProperties{
241 peci_pcie::function::functionTypeName,
242 peci_pcie::function::deviceClassName,
243 peci_pcie::function::vendorIdName,
244 peci_pcie::function::deviceIdName,
245 peci_pcie::function::classCodeName,
246 peci_pcie::function::revisionIdName,
247 peci_pcie::function::subsystemIdName,
248 peci_pcie::function::subsystemVendorIdName,
249 };
250
251 for (const char* name : functionProperties)
252 {
253 setPCIeProperty(clientAddr, bus, dev,
254 "Function" + std::to_string(func) + std::string(name),
255 std::string());
256 }
257}
258
259static void setPCIeFunctionProperties(const int& clientAddr, const int& bus,
260 const int& dev, const int& func)
261{
262 // Set the function type always to physical for now
263 setPCIeProperty(clientAddr, bus, dev,
264 "Function" + std::to_string(func) +
265 std::string(peci_pcie::function::functionTypeName),
266 "Physical");
267
268 // Set the function Device Class
269 setPCIeProperty(clientAddr, bus, dev,
270 "Function" + std::to_string(func) +
271 std::string(peci_pcie::function::deviceClassName),
272 getDeviceClass(clientAddr, bus, dev, func));
273
274 // Get PCI Function Properties that come from PCI config with the following
275 // offset and size info
276 static constexpr const std::array pciConfigInfo{
277 std::tuple<const char*, int, int>{peci_pcie::function::vendorIdName, 0,
278 2},
279 std::tuple<const char*, int, int>{peci_pcie::function::deviceIdName, 2,
280 2},
281 std::tuple<const char*, int, int>{peci_pcie::function::classCodeName, 9,
282 3},
283 std::tuple<const char*, int, int>{peci_pcie::function::revisionIdName,
284 8, 1},
285 std::tuple<const char*, int, int>{peci_pcie::function::subsystemIdName,
286 0x2e, 2},
287 std::tuple<const char*, int, int>{
288 peci_pcie::function::subsystemVendorIdName, 0x2c, 2}};
289
290 for (const auto& [name, offset, size] : pciConfigInfo)
291 {
292 setPCIeProperty(
293 clientAddr, bus, dev,
294 "Function" + std::to_string(func) + std::string(name),
295 getStringFromPCIeConfig(clientAddr, bus, dev, func, offset, size));
296 }
297}
298
299static void setPCIeDeviceProperties(const int& clientAddr, const int& bus,
300 const int& dev)
301{
302 // Set the device manufacturer
303 setPCIeProperty(clientAddr, bus, dev, "Manufacturer",
304 getVendorName(clientAddr, bus, dev));
305
306 // Set the device type
307 constexpr char const* deviceTypeName = "DeviceType";
308 if (isMultiFunction(clientAddr, bus, dev))
309 {
310 setPCIeProperty(clientAddr, bus, dev, deviceTypeName, "MultiFunction");
311 }
312 else
313 {
314 setPCIeProperty(clientAddr, bus, dev, deviceTypeName, "SingleFunction");
315 }
316}
317
318static void updatePCIeDevice(const int& clientAddr, const int& bus,
319 const int& dev)
320{
321 setPCIeDeviceProperties(clientAddr, bus, dev);
322
323 // Walk through and populate the functions for this device
324 for (int func = 0; func < peci_pcie::maxPCIFunctions; func++)
325 {
326 if (pcieFunctionExists(clientAddr, bus, dev, func))
327 {
328 // Set the properties for this function
329 setPCIeFunctionProperties(clientAddr, bus, dev, func);
330 }
331 else
332 {
333 // Set default properties for unused functions
334 setDefaultPCIeFunctionProperties(clientAddr, bus, dev, func);
335 }
336 }
337}
338
339static void addPCIeDevice(sdbusplus::asio::object_server& objServer,
340 const int& clientAddr, const int& cpu, const int& bus,
341 const int& dev)
342{
343 std::string pathName = std::string(peci_pcie::peciPCIePath) + "/S" +
344 std::to_string(cpu) + "B" + std::to_string(bus) +
345 "D" + std::to_string(dev);
346 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
347 objServer.add_interface(pathName, peci_pcie::peciPCIeDeviceInterface);
348 peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev] = iface;
349
350 // Update the properties for the new device
351 updatePCIeDevice(clientAddr, bus, dev);
352
353 iface->initialize();
354}
355
356static void removePCIeDevice(sdbusplus::asio::object_server& objServer,
357 const int& clientAddr, const int& bus,
358 const int& dev)
359{
360 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
361 peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev];
362
363 objServer.remove_interface(iface);
364
365 peci_pcie::pcieDeviceDBusMap[clientAddr][bus].erase(dev);
366 if (peci_pcie::pcieDeviceDBusMap[clientAddr][bus].empty())
367 {
368 peci_pcie::pcieDeviceDBusMap[clientAddr].erase(bus);
369 }
370 if (peci_pcie::pcieDeviceDBusMap[clientAddr].empty())
371 {
372 peci_pcie::pcieDeviceDBusMap.erase(clientAddr);
373 }
374}
375
376static bool pcieDeviceInDBusMap(const int& clientAddr, const int& bus,
377 const int& dev)
378{
379 if (auto clientAddrIt = peci_pcie::pcieDeviceDBusMap.find(clientAddr);
380 clientAddrIt != peci_pcie::pcieDeviceDBusMap.end())
381 {
382 if (auto busIt = clientAddrIt->second.find(bus);
383 busIt != clientAddrIt->second.end())
384 {
385 if (auto devIt = busIt->second.find(dev);
386 devIt != busIt->second.end())
387 {
388 if (devIt->second)
389 {
390 return true;
391 }
392 }
393 }
394 }
395 return false;
396}
397
398static void scanPCIeDevice(boost::asio::io_service& io,
399 sdbusplus::asio::object_server& objServer,
400 std::vector<int> clientAddrs, int cpu, int bus,
401 int dev)
402{
403 if (pcieDeviceExists(clientAddrs[cpu], bus, dev))
404 {
405 if (pcieDeviceInDBusMap(clientAddrs[cpu], bus, dev))
406 {
407 // This device is already in D-Bus, so update it
408 updatePCIeDevice(clientAddrs[cpu], bus, dev);
409 }
410 else
411 {
412 // This device is not in D-Bus, so add it
413 addPCIeDevice(objServer, clientAddrs[cpu], cpu, bus, dev);
414 }
415 }
416 else
417 {
418 // If PECI is not available, then stop scanning
419 if (!isPECIAvailable())
420 {
421 return;
422 }
423
424 if (pcieDeviceInDBusMap(clientAddrs[cpu], bus, dev))
425 {
426 // This device is in D-Bus, so remove it
427 removePCIeDevice(objServer, clientAddrs[cpu], bus, dev);
428 }
429 }
430
431 // PCIe Device scan completed, so move to the next device
432 if (++dev >= peci_pcie::maxPCIDevices)
433 {
434 // All devices scanned, so move to the next bus
435 dev = 0;
436 if (++bus >= peci_pcie::maxPCIBuses)
437 {
438 // All buses scanned, so move to the next CPU
439 bus = 0;
440 if (++cpu >= clientAddrs.size())
441 {
442 // All CPUs scanned, so we're done
443 return;
444 }
445 }
446 }
447 boost::asio::post(io, [&io, &objServer, clientAddrs, cpu, bus, dev]() {
448 scanPCIeDevice(io, objServer, clientAddrs, cpu, bus, dev);
449 });
450}
451
452static void peciAvailableCheck(boost::asio::steady_timer& peciWaitTimer,
453 boost::asio::io_service& io,
454 sdbusplus::asio::object_server& objServer)
455{
456 static bool lastPECIState;
457 bool peciAvailable = isPECIAvailable();
458 if (peciAvailable && !lastPECIState)
459 {
460 lastPECIState = true;
461
462 static boost::asio::steady_timer pcieTimeout(io);
463 constexpr const int pcieWaitTime = 60;
464 pcieTimeout.expires_after(std::chrono::seconds(pcieWaitTime));
465 pcieTimeout.async_wait(
466 [&io, &objServer](const boost::system::error_code& ec) {
467 if (ec)
468 {
469 // operation_aborted is expected if timer is canceled
470 // before completion.
471 if (ec != boost::asio::error::operation_aborted)
472 {
473 std::cerr << "PECI PCIe async_wait failed " << ec;
474 }
475 return;
476 }
477 // get the PECI client address list
478 std::vector<int> clientAddrs;
479 getClientAddrMap(clientAddrs);
480 // scan PCIe starting from CPU 0, Bus 0, Device 0
481 scanPCIeDevice(io, objServer, clientAddrs, 0, 0, 0);
482 });
483 }
484 else if (!peciAvailable && lastPECIState)
485 {
486 lastPECIState = false;
487 }
488
489 peciWaitTimer.expires_after(
490 std::chrono::seconds(peci_pcie::peciCheckInterval));
491 peciWaitTimer.async_wait([&peciWaitTimer, &io,
492 &objServer](const boost::system::error_code& ec) {
493 if (ec)
494 {
495 // operation_aborted is expected if timer is canceled
496 // before completion.
497 if (ec != boost::asio::error::operation_aborted)
498 {
499 std::cerr << "PECI Available Check async_wait failed " << ec;
500 }
501 return;
502 }
503 peciAvailableCheck(peciWaitTimer, io, objServer);
504 });
505}
506
507int 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 // PECI PCIe Object
515 conn->request_name(peci_pcie::peciPCIeObject);
516 sdbusplus::asio::object_server server =
517 sdbusplus::asio::object_server(conn);
518
519 // Start the PECI check loop
520 boost::asio::steady_timer peciWaitTimer(
521 io, std::chrono::seconds(peci_pcie::peciCheckInterval));
522 peciWaitTimer.async_wait([&peciWaitTimer, &io,
523 &server](const boost::system::error_code& ec) {
524 if (ec)
525 {
526 // operation_aborted is expected if timer is canceled
527 // before completion.
528 if (ec != boost::asio::error::operation_aborted)
529 {
530 std::cerr << "PECI Available Check async_wait failed " << ec;
531 }
532 return;
533 }
534 peciAvailableCheck(peciWaitTimer, io, server);
535 });
536
537 io.run();
538
539 return 0;
540}