migrate usb device detection Move the usb device detection logic that's a patch in entity manager to pcie_bifurcation to keep all bifurcation related code in a common place. Based on - https://gbmc.googlesource.com/meta-gbmc-staging/+/e1bac3c2109b9f79449616e3c5da0b06a6974932/recipes-phosphor/configuration/entity-manager/0005-Add-USB-Device-discovery-daemon.patch Google-Bug-Id: 281642829 Change-Id: I79af52f715914bd494e360d30ae241cb4d104014 Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/meson.build b/meson.build index 74acf79..3138663 100644 --- a/meson.build +++ b/meson.build
@@ -16,10 +16,17 @@ nlohmann_json = nlohmann_json.as_system('system') endif +sdbusplus_dep = dependency('sdbusplus') pcie_bifurcation_pre = [ - dependency('sdbusplus'), + sdbusplus_dep, meson.get_compiler('cpp').find_library('i2c'), nlohmann_json, ] -subdir('fru') \ No newline at end of file +if get_option('fru').allowed() + subdir('fru') +endif + +if get_option('usb').allowed() + subdir('usb') +endif
diff --git a/meson_options.txt b/meson_options.txt index 0fc2767..abfbedc 100644 --- a/meson_options.txt +++ b/meson_options.txt
@@ -1 +1,17 @@ option('tests', type: 'feature', description: 'Build tests') +option( + 'fru', + type: 'feature', + description: 'Enable PCIe bifurcation via reading the fru eeprom.', +) +option( + 'usb', + type: 'feature', + description: 'Enable PCIe bifurcation via detect USB devices', +) +option( + 'usb-device-config', + type: 'string', + value: '/usr/share/usb-device/config.json', + description: 'USB Device configuration json file.', +) \ No newline at end of file
diff --git a/usb/meson.build b/usb/meson.build new file mode 100644 index 0000000..540f71a --- /dev/null +++ b/usb/meson.build
@@ -0,0 +1,32 @@ +configure_file( + copy: true, + input: 'xyz.openbmc_project.UsbDevice.service', + install: true, + install_dir: dependency('systemd').get_variable( + 'systemdsystemunitdir', + pkgconfig_define: ['prefix', get_option('prefix')], + ), + output: 'xyz.openbmc_project.UsbDevice.service', +) + +boost = dependency('boost') + +conf_data = configuration_data() +conf_data.set_quoted('USB_DEVICE_CONFIG', get_option('usb-device-config')) +conf_h = configure_file(output: 'config.h', configuration: conf_data) +executable( + 'usb-device', + 'usb_device.cpp', + conf_h, + dependencies: [ + boost, + nlohmann_json, + sdbusplus_dep, + dependency('threads'), + dependency('stdplus'), + ], + link_args: ['-ludev'], + implicit_include_directories: false, + include_directories: include_directories('.'), + install: true, +) \ No newline at end of file
diff --git a/usb/usb_device.cpp b/usb/usb_device.cpp new file mode 100644 index 0000000..a3d636c --- /dev/null +++ b/usb/usb_device.cpp
@@ -0,0 +1,427 @@ +#include "config.h" + +#include <libudev.h> +#include <locale.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#include <boost/algorithm/string/replace.hpp> +#include <boost/asio/io_service.hpp> +#include <boost/asio/steady_timer.hpp> +#include <nlohmann/json.hpp> +#include <sdbusplus/asio/connection.hpp> +#include <sdbusplus/asio/object_server.hpp> +#include <stdplus/print.hpp> + +#include <chrono> +#include <fstream> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +constexpr bool DEBUG = false; +constexpr std::chrono::duration<int64_t, std::milli> INITIAL_WAITTIME = + std::chrono::milliseconds(10); +constexpr std::chrono::duration<int64_t> WAITTIME = std::chrono::seconds(2); +constexpr std::chrono::duration<int64_t> INITIAL_TIMEOUT = + std::chrono::minutes(10); +constexpr std::chrono::duration<int64_t> DEBUG_SLEEP_TIME = + std::chrono::seconds(10); + +struct UsbDevice +{ + std::string path; + std::string SysPath; + std::string vendorId; + std::string productId; + std::string manufacturer; + std::string product; + std::string kernel; + std::string busnum; + std::string configuration; +}; + +// VIP, PID, CONFIGURATION, USB ID -> I2C Bus +using UsbMapping = std::unordered_map< + std::string, + std::unordered_map< + std::string, + std::unordered_map<std::string, + std::unordered_map<std::string, uint8_t>>>>; + +class ManageUsbDevices +{ + public: + ManageUsbDevices() : init(true) + { + startTime = std::chrono::system_clock::now(); + + std::ifstream configJson(USB_DEVICE_CONFIG); + if (configJson.is_open()) + { + auto data = nlohmann::json::parse(configJson, nullptr, false); + + try + { + usbMapping = data.get<UsbMapping>(); + } + catch (nlohmann::json::parse_error& ex) + { + stdplus::print(stderr, "failed to parese usb config: {}\n", + ex.what()); + } + } + } + + /** + * @brief This function enumerates the already connected TTY Devices and + * extracts the udev properties + */ + bool EnumerateTtyDevices() + { + return EnumerateDevices("tty"); + } + + /** + * @brief This function enumerates the already connected USB Devices and + * extracts the udev properties + */ + bool EnumerateUsbDevices() + { + return EnumerateDevices("usb"); + } + + /** + * @brief Removes the DBus interface created when the usb device is removed + * so that the object path of the corresponding device is also removed from + * the DBus + */ + bool RemoveIfaceForNode( + const std::string& devNode, + const std::shared_ptr<sdbusplus::asio::object_server>& objectServer); + + void MonitorUsbDeviceHandler( + const std::shared_ptr<boost::asio::steady_timer>& timer, + const std::shared_ptr<sdbusplus::asio::object_server>& objectServer, + const boost::system::error_code& ec); + /** + * @brief Continuously monitors the file descriptors using Udev library to + * see if any new USB device is connected or any old USB device is + * disconnected. For a new device a new DBus interface is created. + */ + void MonitorUsbDevice( + const std::shared_ptr<boost::asio::steady_timer>& timer, + const std::shared_ptr<sdbusplus::asio::object_server>& objectServer); + + std::vector<struct UsbDevice> usbDevices; + std::unordered_map<std::string, + std::shared_ptr<sdbusplus::asio::dbus_interface>> + ifacePtrMap; + + UsbMapping usbMapping; + + private: + bool EnumerateDevices(std::string_view type); + + void QueueMonitoring( + const std::shared_ptr<boost::asio::steady_timer>& timer, + const std::shared_ptr<sdbusplus::asio::object_server>& objectServer) + { + if (init && + std::chrono::system_clock::now() - startTime > INITIAL_TIMEOUT) + { + init = false; + } + timer->expires_from_now(init ? INITIAL_WAITTIME : WAITTIME); + timer->async_wait( + [this, timer, objectServer](const boost::system::error_code& ec) { + MonitorUsbDeviceHandler(timer, objectServer, ec); + }); + } + + std::string GetSysAttrValue(udev_device* dev, std::string_view key) + { + const char* value = udev_device_get_sysattr_value(dev, key.data()); + + if (value == nullptr) + { + return std::string(); + } + + return std::string(value); + } + + int fd; + udev_monitor* monitor; + bool init; + std::chrono::system_clock::time_point startTime; +}; + +bool ManageUsbDevices::EnumerateDevices(std::string_view type) +{ + struct udev* udev; + struct udev_enumerate* enumerate; + struct udev_list_entry *devices, *devListEntry; + struct udev_device* dev; + + udev = udev_new(); + if (!udev) + { + stdplus::print(stderr, "Can't create udev- USB Device Probe failed\n"); + return false; + } + + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, type.data()); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(devListEntry, devices) + { + struct UsbDevice device; + std::string path = udev_list_entry_get_name(devListEntry); + dev = udev_device_new_from_syspath(udev, path.c_str()); + if (!dev) + { + // Not a USB device skip + continue; + } + + device.path = path; + auto sysPath = udev_device_get_devnode(dev); + device.SysPath = sysPath == nullptr ? "" : std::string(sysPath); + if (DEBUG) + { + if (device.SysPath.empty()) + { + stdplus::print(stderr, "Did not find Device Node Path\n"); + } + else + { + stdplus::print(stderr, "Device Node Path: {}\n", + device.SysPath); + } + } + device.vendorId = GetSysAttrValue(dev, "idVendor"); + device.productId = GetSysAttrValue(dev, "idProduct"); + device.kernel = (sdbusplus::message::object_path() / + path.substr(path.find_last_of("/"))) + .str; + device.busnum = GetSysAttrValue(dev, "busnum"); + device.manufacturer = GetSysAttrValue(dev, "manufacturer"); + device.product = GetSysAttrValue(dev, "product"); + device.configuration = GetSysAttrValue(dev, "configuration"); + + if (DEBUG) + { + stdplus::print(stderr, "device.path: {}\n", device.path); + stdplus::print(stderr, "device.SysPath: {}\n", device.SysPath); + stdplus::print(stderr, "device.kernel: {}\n", device.kernel); + stdplus::print(stderr, "device.busnum: {}\n", device.busnum); + stdplus::print(stderr, "device.vendorId: {}\n", device.vendorId); + stdplus::print(stderr, "device.productId: {}\n", device.productId); + stdplus::print(stderr, "device.manufacturer: {}\n", + device.manufacturer); + stdplus::print(stderr, "device.product: {}\n", device.product); + } + udev_device_unref(dev); + + if (!device.vendorId.empty()) + { + usbDevices.emplace_back(device); + } + } + /* Free the enumerator object */ + udev_enumerate_unref(enumerate); + + udev_unref(udev); + return true; +} + +bool ManageUsbDevices::RemoveIfaceForNode( + const std::string& devNode, + const std::shared_ptr<sdbusplus::asio::object_server>& objectServer) +{ + auto it = ifacePtrMap.find(devNode); + if (it != ifacePtrMap.end()) + { + std::shared_ptr<sdbusplus::asio::dbus_interface> iface = it->second; + objectServer->remove_interface(iface); + stdplus::print(stderr, "USB device removed with dev node: {}\n", + devNode); + ifacePtrMap.erase(devNode); + return true; + } + return false; +} + +void ManageUsbDevices::MonitorUsbDeviceHandler( + const std::shared_ptr<boost::asio::steady_timer>& timer, + const std::shared_ptr<sdbusplus::asio::object_server>& objectServer, + const boost::system::error_code& ec) +{ + if (ec == boost::asio::error::operation_aborted) + { + stdplus::print(stderr, "MonitorUsbDeviceHandler aborted: {}\n", + ec.what()); + return; + } + + struct udev_device* dev = udev_monitor_receive_device(monitor); + while (dev != nullptr) + { + auto node = udev_device_get_devnode(dev); + std::string action = udev_device_get_action(dev); + std::string path = udev_device_get_syspath(dev); + + if (node == nullptr) + { + QueueMonitoring(timer, objectServer); + return; + } + + if (action == "remove") + { + // Remove object path + if (!RemoveIfaceForNode(node, objectServer)) + { + stdplus::print(stderr, "No node found for removal\n"); + } + } + else if (action == "add") + { + std::string vendorId = GetSysAttrValue(dev, "idVendor"); + std::string productId = GetSysAttrValue(dev, "idProduct"); + std::string kernel = (sdbusplus::message::object_path() / + path.substr(path.find_last_of("/"))) + .str; + std::string product = GetSysAttrValue(dev, "product"); + std::string configuration = GetSysAttrValue(dev, "configuration"); + + if (DEBUG) + { + // if you want to see when a device reboots, the old + // DBus object path is removed and creates a new DBus + // object path. 10 seconds is a random number + std::this_thread::sleep_for(DEBUG_SLEEP_TIME); + } + + sdbusplus::message::object_path objPath( + "/xyz/openbmc_project/devices/" + kernel); + std::shared_ptr<sdbusplus::asio::dbus_interface> iface = + objectServer->add_interface(objPath, + "xyz.openbmc_project.UsbDevice"); + iface->register_property( + "PRODUCT", product, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property( + "VID", vendorId, sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property( + "PID", productId, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property( + "CONFIGURATION", configuration, + sdbusplus::asio::PropertyPermission::readOnly); + + iface->register_property( + "UDEVID", objPath.filename().substr(1), + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property( + "USBPORT", node == nullptr ? std::string() : node, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property( + "BUS", + usbMapping[vendorId][productId][product] + [objPath.filename().substr(1)], + sdbusplus::asio::PropertyPermission::readOnly); + + iface->initialize(); + ifacePtrMap.emplace(node, std::move(iface)); + } + udev_device_unref(dev); + dev = udev_monitor_receive_device(monitor); + } + + QueueMonitoring(timer, objectServer); +} + +void ManageUsbDevices::MonitorUsbDevice( + const std::shared_ptr<boost::asio::steady_timer>& timer, + const std::shared_ptr<sdbusplus::asio::object_server>& objectServer) +{ + struct udev* udev; + + udev = udev_new(); + if (!udev) + { + stdplus::print(stderr, "Can't create udev- USB Device Probe failed\n"); + return; + } + + monitor = udev_monitor_new_from_netlink(udev, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(monitor, "usb", + "usb_device"); + udev_monitor_enable_receiving(monitor); + stdplus::print(stderr, "Beginning to monitor...\n"); + + fd = udev_monitor_get_fd(monitor); + QueueMonitoring(timer, objectServer); +} + +int main() +{ + ManageUsbDevices manager; + if (!manager.EnumerateUsbDevices()) + { + stdplus::print(stderr, "Enumerating USB devices failed\n"); + } + if (!manager.EnumerateTtyDevices()) + { + stdplus::print(stderr, "Enumerating TTY devices failed\n"); + } + + stdplus::print(stderr, "Adding devices...\n"); + boost::asio::io_context io; + auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); + auto objectServer = + std::make_shared<sdbusplus::asio::object_server>(systemBus, + /*skipManager=*/true); + objectServer->add_manager("/xyz/openbmc_project/devices"); + for (auto device : manager.usbDevices) + { + sdbusplus::message::object_path objPath( + "/xyz/openbmc_project/devices/" + device.kernel); + std::shared_ptr<sdbusplus::asio::dbus_interface> iface = + objectServer->add_interface(objPath, + "xyz.openbmc_project.UsbDevice"); + iface->register_property("PRODUCT", device.product, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property("VID", device.vendorId, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property("PID", device.productId, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property("CONFIGURATION", device.configuration, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property("UDEVID", objPath.filename().substr(1), + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property("USBPORT", device.SysPath, + sdbusplus::asio::PropertyPermission::readOnly); + iface->register_property( + "BUS", + manager.usbMapping[device.vendorId][device.productId] + [device.product][objPath.filename().substr(1)], + sdbusplus::asio::PropertyPermission::readOnly); + + iface->initialize(); + + manager.ifacePtrMap.emplace(device.SysPath, std::move(iface)); + } + systemBus->request_name("xyz.openbmc_project.usbdevice"); + + // Monitor + auto monitorTimer = std::make_shared<boost::asio::steady_timer>(io); + manager.MonitorUsbDevice(monitorTimer, objectServer); + io.run(); + return 0; +}
diff --git a/usb/xyz.openbmc_project.UsbDevice.service b/usb/xyz.openbmc_project.UsbDevice.service new file mode 100644 index 0000000..c0e80a6 --- /dev/null +++ b/usb/xyz.openbmc_project.UsbDevice.service
@@ -0,0 +1,10 @@ +[Unit] +Description=USB Device + +[Service] +ExecStart=/usr/bin/usb-device +Restart=always +Type=simple + +[Install] +WantedBy=multi-user.target