blob: 6c8c683eedd1ef236b75d730c770204ba9a2b3ad [file] [log] [blame] [edit]
#include "usb_monitor.hpp"
#include <stdplus/print.hpp>
#include <sstream>
#include <string_view>
namespace usbdevice
{
/**
* @brief Get the root hub path for a given USB device.
*
* This function walks up the udev device tree from the given usbDevice
* to find its root USB hub. The root hub is identified as a 'platform'
* subsystem device that is not itself a 'usb_device'.
*
* @param usbDevice A pointer to the udev_device representing the USB device.
* @return A string containing the syspath of the root USB hub, or an empty
* string if not found or if usbDevice is null.
*/
std::string getUsbHub(struct udev_device* usbDevice)
{
if (!usbDevice)
{
return "";
}
// To find the root hub, we walk up the device chain until we find the
// device that is a 'platform' subsystem device but not a 'usb_device'
// itself. This is the main controller like 'usb1' or 'usb2'.
struct udev_device* parent = usbDevice;
while ((parent = udev_device_get_parent(parent)) != NULL)
{
const char* subsystem = udev_device_get_subsystem(parent);
const char* devtype = udev_device_get_devtype(parent);
if (subsystem && strcmp(subsystem, "platform") == 0 &&
(!devtype || strcmp(devtype, "usb_device") != 0))
{
return udev_device_get_syspath(parent);
}
}
return "";
}
/**
* @brief Retrieves the value of a system attribute for a udev device.
*
* @param dev A pointer to the udev_device.
* @param key The name of the system attribute to retrieve.
* @return A string containing the value of the system attribute, or an empty
* string if the attribute is not found or dev is null.
*/
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);
}
UsbMonitor::UsbMonitor(Udev::UdevUniquePtr udevContext,
Udev::MonitorUniquePtr udevMonitor,
boost::asio::io_context& io) :
udevCtx(std::move(udevContext)), udevMonitor(std::move(udevMonitor)),
udevDescriptor(io)
{}
UsbMonitor::~UsbMonitor() {}
std::unique_ptr<UsbMonitor> UsbMonitor::create(boost::asio::io_context& io)
{
auto ctx = Udev::UdevUniquePtr(udev_new());
if (!ctx)
{
stdplus::println(stderr, "Failed to create udev context.");
return nullptr;
}
auto udevMonitor = Udev::MonitorUniquePtr(
udev_monitor_new_from_netlink(ctx.get(), "udev"));
if (!udevMonitor)
{
stdplus::println(stderr, "Failed to create udev monitor.");
return nullptr;
}
return std::unique_ptr<UsbMonitor>(
new UsbMonitor(std::move(ctx), std::move(udevMonitor), io));
}
std::vector<UsbInterface> UsbMonitor::getEndpoints()
{
int ret = 0;
Udev::EnumerateUniquePtr enumerate(udev_enumerate_new(udevCtx.get()));
if (!enumerate)
{
stdplus::println(stderr, "Failed to create udev enumeration context.");
return {};
}
ret = udev_enumerate_add_match_subsystem(enumerate.get(), "usb");
if (ret < 0)
{
stdplus::println(stderr,
"udev_enumerate_add_match_subsystem failed. Error: {}",
ret);
return {};
}
ret = udev_enumerate_add_match_property(enumerate.get(), "DEVTYPE",
"usb_interface");
if (ret < 0)
{
stdplus::println(
stderr, "udev_enumerate_add_match_property failed. Error: {}", ret);
return {};
}
ret = udev_enumerate_scan_devices(enumerate.get());
if (ret < 0)
{
stdplus::println(stderr,
"udev_enumerate_scan_devices failed. Error: {}", ret);
return {};
}
struct udev_list_entry* devices =
udev_enumerate_get_list_entry(enumerate.get());
if (!devices)
{
stdplus::println(stderr, "No USB interfaces found.");
return {};
}
struct udev_list_entry* devListEntry;
std::vector<UsbInterface> usbInfInfo;
udev_list_entry_foreach(devListEntry, devices)
{
const char* infPath = udev_list_entry_get_name(devListEntry);
Udev::DeviceUniquePtr usbInf(
udev_device_new_from_syspath(udevCtx.get(), infPath));
if (!usbInf)
{
continue;
}
std::optional<UsbInterface> devInfo =
processUsbInf(usbInf.get(), infPath);
if (devInfo.has_value())
{
usbInfInfo.push_back(std::move(*devInfo));
}
}
return usbInfInfo;
}
std::optional<UsbInterface> UsbMonitor::processUsbInf(udev_device* usbInf,
std::string_view infPath)
{
if (!usbInf)
{
return std::nullopt;
}
if (infPath.empty() || (infPath.find_last_of("/") == std::string::npos))
{
return std::nullopt;
}
udev_device* usbDevice = udev_device_get_parent_with_subsystem_devtype(
usbInf, "usb", "usb_device");
if (!usbDevice)
{
return std::nullopt;
}
struct UsbInterface device;
// Eg: 1-1.7. This includes the hub as well.
const char* port = udev_device_get_sysname(usbDevice);
if (port == nullptr)
{
return std::nullopt;
}
// Extract jsut the port from the path that includes USB hub.
// Eg: 1-1.7 Then 1 is the USB hub and 1.7 is the port.
std::string portPathOnly;
std::string fullPortPath(port);
size_t hyphenPos = fullPortPath.find('-');
// Check if a hyphen was found and it's not the last character
if (hyphenPos != std::string::npos && hyphenPos < fullPortPath.length() - 1)
{
// Extract the substring after the first hyphen
portPathOnly = fullPortPath.substr(hyphenPos + 1);
}
else
{
// If there's no hyphen (e.g., a root device), we will use the full name
portPathOnly = fullPortPath;
}
device.name = (sdbusplus::message::object_path() /
infPath.substr(infPath.find_last_of("/")))
.str;
// Device level properties
device.rootHubPath = getUsbHub(usbDevice);
device.vendorId = GetSysAttrValue(usbDevice, "idVendor");
device.productId = GetSysAttrValue(usbDevice, "idProduct");
device.manufacturer = GetSysAttrValue(usbDevice, "manufacturer");
device.product = GetSysAttrValue(usbDevice, "product");
device.configuration = GetSysAttrValue(usbDevice, "bConfigurationValue");
// Interface descriptor level properties
device.interfacePath = infPath;
device.port = portPathOnly;
device.interfaceString = GetSysAttrValue(usbInf, "iInterface");
device.interfaceNum = GetSysAttrValue(usbInf, "bInterfaceNumber");
device.interfaceClass = GetSysAttrValue(usbInf, "bInterfaceClass");
device.interfaceSubclass = GetSysAttrValue(usbInf, "bInterfaceSubClass");
device.interfaceProtocol = GetSysAttrValue(usbInf, "bInterfaceProtocol");
return device;
}
int UsbMonitor::monitor(AddCallback onAdd, RemoveCallback onRemove,
void* context)
{
int ret = udev_monitor_filter_add_match_subsystem_devtype(
udevMonitor.get(), "usb", "usb_interface");
if (ret < 0)
{
stdplus::println(
stderr,
"udev_monitor_filter_add_match_subsystem_devtype failed. Error: {}",
ret);
return ret;
}
ret = udev_monitor_enable_receiving(udevMonitor.get());
if (ret < 0)
{
stdplus::println(
stderr, "udev_monitor_enable_receiving failed. Error: {}", ret);
return ret;
}
int fd = udev_monitor_get_fd(udevMonitor.get());
if (fd < 0)
{
stdplus::println(stderr, "Failed to get udev monitor fd. Error: {}",
fd);
return fd;
}
onAddCallback = onAdd;
onRemoveCallback = onRemove;
cbContext = context;
try
{
udevDescriptor.assign(fd);
}
catch (const boost::system::system_error& e)
{
stdplus::println(stderr,
"Assign failure. Error code: {} Error message: {}",
e.code().value(), e.what());
return -e.code().value();
}
udevDescriptor.async_wait(boost::asio::posix::stream_descriptor::wait_read,
[this](const boost::system::error_code& error) {
this->handleUdevEvent(error);
});
return 0;
}
void UsbMonitor::handleUdevEvent(const boost::system::error_code& ec)
{
if (ec == boost::asio::error::operation_aborted)
{
stdplus::println(stderr, "handleUdevEvent aborted: {}", ec.what());
return;
}
// Handle other errors
if (ec)
{
stdplus::println(stderr, "handleUdevEvent error: {}", ec.message());
// Not re-arming incase of persistant errors.
// TODO: identify error cases where we can re-arm this again.
return;
}
if (onAddCallback == nullptr)
{
stdplus::println(stderr, "onAddCallback is null");
}
if (onRemoveCallback == nullptr)
{
stdplus::println(stderr, "onRemoveCallback is null");
}
while (onAddCallback && onRemoveCallback)
{
Udev::DeviceUniquePtr usbInf(
udev_monitor_receive_device(udevMonitor.get()));
if (!usbInf)
{
break;
}
const char* path = udev_device_get_syspath(usbInf.get());
if (path == nullptr)
{
stdplus::println(stderr, "Failed to get syspath for udev event");
continue;
}
const char* action = udev_device_get_action(usbInf.get());
if (action == nullptr)
{
stdplus::println(stderr, "Failed to get action for udev event");
continue;
}
if (strcmp(action, "remove") == 0)
{
onRemoveCallback(path, cbContext);
}
else if (strcmp(action, "add") == 0)
{
std::optional<UsbInterface> devInfo =
processUsbInf(usbInf.get(), path);
if (!devInfo.has_value())
{
stdplus::println(
stderr, "Failed to process newly added USB interface {}.",
path);
continue;
}
onAddCallback(*devInfo, cbContext);
}
}
// Re-arm the wait operation for the next event.
udevDescriptor.async_wait(boost::asio::posix::stream_descriptor::wait_read,
[this](const boost::system::error_code& error) {
this->handleUdevEvent(error);
});
}
} // namespace usbdevice