Add a PECI PCIe daemon
This daemon is responsible for collecting PCIe information for
the system over PECI and sharing it with other components,
such as Redfish.
Change-Id: I3332cb2584a6f496a0f2162536b24fb20380ad1d
Signed-off-by: Jason M. Bills <jason.m.bills@intel.com>
diff --git a/src/peci_pcie.cpp b/src/peci_pcie.cpp
new file mode 100644
index 0000000..8db1f89
--- /dev/null
+++ b/src/peci_pcie.cpp
@@ -0,0 +1,540 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#include "peci_pcie.hpp"
+
+#include "pciDeviceClass.hpp"
+#include "pciVendors.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <iomanip>
+#include <iostream>
+#include <set>
+#include <sstream>
+
+namespace peci_pcie
+{
+static boost::container::flat_map<
+ int, boost::container::flat_map<
+ int, boost::container::flat_map<
+ int, std::shared_ptr<sdbusplus::asio::dbus_interface>>>>
+ pcieDeviceDBusMap;
+
+namespace function
+{
+static constexpr char const* functionTypeName = "FunctionType";
+static constexpr char const* deviceClassName = "DeviceClass";
+static constexpr char const* vendorIdName = "VendorId";
+static constexpr char const* deviceIdName = "DeviceId";
+static constexpr char const* classCodeName = "ClassCode";
+static constexpr char const* revisionIdName = "RevisionId";
+static constexpr char const* subsystemIdName = "SubsystemId";
+static constexpr char const* subsystemVendorIdName = "SubsystemVendorId";
+} // namespace function
+} // namespace peci_pcie
+
+// PECI Client Address Map
+static void getClientAddrMap(std::vector<int>& clientAddrs)
+{
+ for (int i = MIN_CLIENT_ADDR; i <= MAX_CLIENT_ADDR; i++)
+ {
+ if (peci_Ping(i) == PECI_CC_SUCCESS)
+ {
+ clientAddrs.push_back(i);
+ }
+ }
+}
+
+static bool isPECIAvailable(void)
+{
+ std::vector<int> clientAddrs;
+ getClientAddrMap(clientAddrs);
+ if (clientAddrs.empty())
+ {
+ return false;
+ }
+ return true;
+}
+
+static bool getDataFromPCIeConfig(const int& clientAddr, const int& bus,
+ const int& dev, const int& func,
+ const int& offset, const int& size,
+ uint32_t& pciData)
+{
+ // PECI RdPCIConfig() currently only supports 4 byte reads, so adjust
+ // the offset and size to get the right data
+ static constexpr const int pciReadSize = 4;
+ int mod = offset % pciReadSize;
+ int pciOffset = offset - mod;
+ if (mod + size > pciReadSize)
+ {
+ return false;
+ }
+
+ std::array<uint8_t, pciReadSize> data;
+ uint8_t cc;
+ int ret = peci_RdPCIConfig(clientAddr, // CPU Address
+ bus, // PCI Bus
+ dev, // PCI Device
+ func, // PCI Function
+ pciOffset, // PCI Offset
+ data.data(), // PCI Read Data
+ &cc); // PECI Completion Code
+
+ if (ret != PECI_CC_SUCCESS || cc != PECI_DEV_CC_SUCCESS)
+ {
+ return false;
+ }
+
+ // Now build the requested data into a single number
+ pciData = 0;
+ for (int i = mod; i < mod + size; i++)
+ {
+ pciData |= data[i] << 8 * (i - mod);
+ }
+
+ return true;
+}
+
+static std::string getStringFromPCIeConfig(const int& clientAddr,
+ const int& bus, const int& dev,
+ const int& func, const int& offset,
+ const int& size)
+{
+ // Get the requested data
+ uint32_t data = 0;
+ if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, offset, size, data))
+ {
+ return std::string();
+ }
+
+ // And convert it to a string
+ std::stringstream dataStream;
+ dataStream << "0x" << std::hex << std::setfill('0') << std::setw(size * 2)
+ << data;
+ return dataStream.str();
+}
+
+static std::string getVendorName(const int& clientAddr, const int& bus,
+ const int& dev)
+{
+ static constexpr const int vendorIDOffset = 0x00;
+ static constexpr const int vendorIDSize = 2;
+
+ // Get the header type register from function 0
+ uint32_t vendorID = 0;
+ if (!getDataFromPCIeConfig(clientAddr, bus, dev, 0, vendorIDOffset,
+ vendorIDSize, vendorID))
+ {
+ return std::string();
+ }
+ // Get the vendor name or use Other if it doesn't exist
+ return pciVendors.try_emplace(vendorID, otherVendor).first->second;
+}
+
+static std::string getDeviceClass(const int& clientAddr, const int& bus,
+ const int& dev, const int& func)
+{
+ static constexpr const int baseClassOffset = 0x0b;
+ static constexpr const int baseClassSize = 1;
+
+ // Get the Device Base Class
+ uint32_t baseClass = 0;
+ if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, baseClassOffset,
+ baseClassSize, baseClass))
+ {
+ return std::string();
+ }
+ // Get the base class name or use Other if it doesn't exist
+ return pciDeviceClasses.try_emplace(baseClass, otherClass).first->second;
+}
+
+static bool isMultiFunction(const int& clientAddr, const int& bus,
+ const int& dev)
+{
+ static constexpr const int headerTypeOffset = 0x0e;
+ static constexpr const int headerTypeSize = 1;
+ static constexpr const int multiFuncBit = 1 << 7;
+
+ // Get the header type register from function 0
+ uint32_t headerType = 0;
+ if (!getDataFromPCIeConfig(clientAddr, bus, dev, 0, headerTypeOffset,
+ headerTypeSize, headerType))
+ {
+ return false;
+ }
+ // Check if it's a multifunction device
+ if (headerType & multiFuncBit)
+ {
+ return true;
+ }
+ return false;
+}
+
+static bool pcieFunctionExists(const int& clientAddr, const int& bus,
+ const int& dev, const int& func)
+{
+ constexpr const int pciIDOffset = 0;
+ constexpr const int pciIDSize = 4;
+ uint32_t pciID = 0;
+ if (!getDataFromPCIeConfig(clientAddr, bus, dev, func, pciIDOffset,
+ pciIDSize, pciID))
+ {
+ return false;
+ }
+
+ // if VID and DID are all 0s or 1s, then the device doesn't exist
+ if (pciID == 0x00000000 || pciID == 0xFFFFFFFF)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+static bool pcieDeviceExists(const int& clientAddr, const int& bus,
+ const int& dev)
+{
+ // Check if this device exists by checking function 0
+ return (pcieFunctionExists(clientAddr, bus, dev, 0));
+}
+
+static void setPCIeProperty(const int& clientAddr, const int& bus,
+ const int& dev, const std::string& propertyName,
+ const std::string& propertyValue)
+{
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+ peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev];
+
+ if (iface->is_initialized())
+ {
+ iface->set_property(propertyName, propertyValue);
+ }
+ else
+ {
+ iface->register_property(propertyName, propertyValue);
+ }
+}
+
+static void setDefaultPCIeFunctionProperties(const int& clientAddr,
+ const int& bus, const int& dev,
+ const int& func)
+{
+ // Set the function-specific properties
+ static constexpr const std::array functionProperties{
+ peci_pcie::function::functionTypeName,
+ peci_pcie::function::deviceClassName,
+ peci_pcie::function::vendorIdName,
+ peci_pcie::function::deviceIdName,
+ peci_pcie::function::classCodeName,
+ peci_pcie::function::revisionIdName,
+ peci_pcie::function::subsystemIdName,
+ peci_pcie::function::subsystemVendorIdName,
+ };
+
+ for (const char* name : functionProperties)
+ {
+ setPCIeProperty(clientAddr, bus, dev,
+ "Function" + std::to_string(func) + std::string(name),
+ std::string());
+ }
+}
+
+static void setPCIeFunctionProperties(const int& clientAddr, const int& bus,
+ const int& dev, const int& func)
+{
+ // Set the function type always to physical for now
+ setPCIeProperty(clientAddr, bus, dev,
+ "Function" + std::to_string(func) +
+ std::string(peci_pcie::function::functionTypeName),
+ "Physical");
+
+ // Set the function Device Class
+ setPCIeProperty(clientAddr, bus, dev,
+ "Function" + std::to_string(func) +
+ std::string(peci_pcie::function::deviceClassName),
+ getDeviceClass(clientAddr, bus, dev, func));
+
+ // Get PCI Function Properties that come from PCI config with the following
+ // offset and size info
+ static constexpr const std::array pciConfigInfo{
+ std::tuple<const char*, int, int>{peci_pcie::function::vendorIdName, 0,
+ 2},
+ std::tuple<const char*, int, int>{peci_pcie::function::deviceIdName, 2,
+ 2},
+ std::tuple<const char*, int, int>{peci_pcie::function::classCodeName, 9,
+ 3},
+ std::tuple<const char*, int, int>{peci_pcie::function::revisionIdName,
+ 8, 1},
+ std::tuple<const char*, int, int>{peci_pcie::function::subsystemIdName,
+ 0x2e, 2},
+ std::tuple<const char*, int, int>{
+ peci_pcie::function::subsystemVendorIdName, 0x2c, 2}};
+
+ for (const auto& [name, offset, size] : pciConfigInfo)
+ {
+ setPCIeProperty(
+ clientAddr, bus, dev,
+ "Function" + std::to_string(func) + std::string(name),
+ getStringFromPCIeConfig(clientAddr, bus, dev, func, offset, size));
+ }
+}
+
+static void setPCIeDeviceProperties(const int& clientAddr, const int& bus,
+ const int& dev)
+{
+ // Set the device manufacturer
+ setPCIeProperty(clientAddr, bus, dev, "Manufacturer",
+ getVendorName(clientAddr, bus, dev));
+
+ // Set the device type
+ constexpr char const* deviceTypeName = "DeviceType";
+ if (isMultiFunction(clientAddr, bus, dev))
+ {
+ setPCIeProperty(clientAddr, bus, dev, deviceTypeName, "MultiFunction");
+ }
+ else
+ {
+ setPCIeProperty(clientAddr, bus, dev, deviceTypeName, "SingleFunction");
+ }
+}
+
+static void updatePCIeDevice(const int& clientAddr, const int& bus,
+ const int& dev)
+{
+ setPCIeDeviceProperties(clientAddr, bus, dev);
+
+ // Walk through and populate the functions for this device
+ for (int func = 0; func < peci_pcie::maxPCIFunctions; func++)
+ {
+ if (pcieFunctionExists(clientAddr, bus, dev, func))
+ {
+ // Set the properties for this function
+ setPCIeFunctionProperties(clientAddr, bus, dev, func);
+ }
+ else
+ {
+ // Set default properties for unused functions
+ setDefaultPCIeFunctionProperties(clientAddr, bus, dev, func);
+ }
+ }
+}
+
+static void addPCIeDevice(sdbusplus::asio::object_server& objServer,
+ const int& clientAddr, const int& cpu, const int& bus,
+ const int& dev)
+{
+ std::string pathName = std::string(peci_pcie::peciPCIePath) + "/S" +
+ std::to_string(cpu) + "B" + std::to_string(bus) +
+ "D" + std::to_string(dev);
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+ objServer.add_interface(pathName, peci_pcie::peciPCIeDeviceInterface);
+ peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev] = iface;
+
+ // Update the properties for the new device
+ updatePCIeDevice(clientAddr, bus, dev);
+
+ iface->initialize();
+}
+
+static void removePCIeDevice(sdbusplus::asio::object_server& objServer,
+ const int& clientAddr, const int& bus,
+ const int& dev)
+{
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+ peci_pcie::pcieDeviceDBusMap[clientAddr][bus][dev];
+
+ objServer.remove_interface(iface);
+
+ peci_pcie::pcieDeviceDBusMap[clientAddr][bus].erase(dev);
+ if (peci_pcie::pcieDeviceDBusMap[clientAddr][bus].empty())
+ {
+ peci_pcie::pcieDeviceDBusMap[clientAddr].erase(bus);
+ }
+ if (peci_pcie::pcieDeviceDBusMap[clientAddr].empty())
+ {
+ peci_pcie::pcieDeviceDBusMap.erase(clientAddr);
+ }
+}
+
+static bool pcieDeviceInDBusMap(const int& clientAddr, const int& bus,
+ const int& dev)
+{
+ if (auto clientAddrIt = peci_pcie::pcieDeviceDBusMap.find(clientAddr);
+ clientAddrIt != peci_pcie::pcieDeviceDBusMap.end())
+ {
+ if (auto busIt = clientAddrIt->second.find(bus);
+ busIt != clientAddrIt->second.end())
+ {
+ if (auto devIt = busIt->second.find(dev);
+ devIt != busIt->second.end())
+ {
+ if (devIt->second)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static void scanPCIeDevice(boost::asio::io_service& io,
+ sdbusplus::asio::object_server& objServer,
+ std::vector<int> clientAddrs, int cpu, int bus,
+ int dev)
+{
+ if (pcieDeviceExists(clientAddrs[cpu], bus, dev))
+ {
+ if (pcieDeviceInDBusMap(clientAddrs[cpu], bus, dev))
+ {
+ // This device is already in D-Bus, so update it
+ updatePCIeDevice(clientAddrs[cpu], bus, dev);
+ }
+ else
+ {
+ // This device is not in D-Bus, so add it
+ addPCIeDevice(objServer, clientAddrs[cpu], cpu, bus, dev);
+ }
+ }
+ else
+ {
+ // If PECI is not available, then stop scanning
+ if (!isPECIAvailable())
+ {
+ return;
+ }
+
+ if (pcieDeviceInDBusMap(clientAddrs[cpu], bus, dev))
+ {
+ // This device is in D-Bus, so remove it
+ removePCIeDevice(objServer, clientAddrs[cpu], bus, dev);
+ }
+ }
+
+ // PCIe Device scan completed, so move to the next device
+ if (++dev >= peci_pcie::maxPCIDevices)
+ {
+ // All devices scanned, so move to the next bus
+ dev = 0;
+ if (++bus >= peci_pcie::maxPCIBuses)
+ {
+ // All buses scanned, so move to the next CPU
+ bus = 0;
+ if (++cpu >= clientAddrs.size())
+ {
+ // All CPUs scanned, so we're done
+ return;
+ }
+ }
+ }
+ boost::asio::post(io, [&io, &objServer, clientAddrs, cpu, bus, dev]() {
+ scanPCIeDevice(io, objServer, clientAddrs, cpu, bus, dev);
+ });
+}
+
+static void peciAvailableCheck(boost::asio::steady_timer& peciWaitTimer,
+ boost::asio::io_service& io,
+ sdbusplus::asio::object_server& objServer)
+{
+ static bool lastPECIState;
+ bool peciAvailable = isPECIAvailable();
+ if (peciAvailable && !lastPECIState)
+ {
+ lastPECIState = true;
+
+ static boost::asio::steady_timer pcieTimeout(io);
+ constexpr const int pcieWaitTime = 60;
+ pcieTimeout.expires_after(std::chrono::seconds(pcieWaitTime));
+ pcieTimeout.async_wait(
+ [&io, &objServer](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled
+ // before completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "PECI PCIe async_wait failed " << ec;
+ }
+ return;
+ }
+ // get the PECI client address list
+ std::vector<int> clientAddrs;
+ getClientAddrMap(clientAddrs);
+ // scan PCIe starting from CPU 0, Bus 0, Device 0
+ scanPCIeDevice(io, objServer, clientAddrs, 0, 0, 0);
+ });
+ }
+ else if (!peciAvailable && lastPECIState)
+ {
+ lastPECIState = false;
+ }
+
+ peciWaitTimer.expires_after(
+ std::chrono::seconds(peci_pcie::peciCheckInterval));
+ peciWaitTimer.async_wait([&peciWaitTimer, &io,
+ &objServer](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled
+ // before completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "PECI Available Check async_wait failed " << ec;
+ }
+ return;
+ }
+ peciAvailableCheck(peciWaitTimer, io, objServer);
+ });
+}
+
+int main(int argc, char* argv[])
+{
+ // setup connection to dbus
+ boost::asio::io_service io;
+ std::shared_ptr<sdbusplus::asio::connection> conn =
+ std::make_shared<sdbusplus::asio::connection>(io);
+
+ // PECI PCIe Object
+ conn->request_name(peci_pcie::peciPCIeObject);
+ sdbusplus::asio::object_server server =
+ sdbusplus::asio::object_server(conn);
+
+ // Start the PECI check loop
+ boost::asio::steady_timer peciWaitTimer(
+ io, std::chrono::seconds(peci_pcie::peciCheckInterval));
+ peciWaitTimer.async_wait([&peciWaitTimer, &io,
+ &server](const boost::system::error_code& ec) {
+ if (ec)
+ {
+ // operation_aborted is expected if timer is canceled
+ // before completion.
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "PECI Available Check async_wait failed " << ec;
+ }
+ return;
+ }
+ peciAvailableCheck(peciWaitTimer, io, server);
+ });
+
+ io.run();
+
+ return 0;
+}