| /* |
| // 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; |
| } |