diff options
Diffstat (limited to 'src/libcamera/device_enumerator_udev.cpp')
-rw-r--r-- | src/libcamera/device_enumerator_udev.cpp | 135 |
1 files changed, 131 insertions, 4 deletions
diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp index 86f6ca18..40853e77 100644 --- a/src/libcamera/device_enumerator_udev.cpp +++ b/src/libcamera/device_enumerator_udev.cpp @@ -7,6 +7,10 @@ #include "device_enumerator_udev.h" +#include <algorithm> +#include <list> +#include <map> + #include <fcntl.h> #include <libudev.h> #include <string.h> @@ -17,6 +21,7 @@ #include <libcamera/event_notifier.h> #include "log.h" +#include "media_device.h" namespace libcamera { @@ -57,9 +62,40 @@ int DeviceEnumeratorUdev::init() if (ret < 0) return ret; + ret = udev_monitor_filter_add_match_subsystem_devtype(monitor_, "video4linux", + nullptr); + if (ret < 0) + return ret; + return 0; } +int DeviceEnumeratorUdev::addUdevDevice(struct udev_device *dev) +{ + const char *subsystem = udev_device_get_subsystem(dev); + if (!subsystem) + return -ENODEV; + + if (!strcmp(subsystem, "media")) { + std::shared_ptr<MediaDevice> media = + createDevice(udev_device_get_devnode(dev)); + if (!media) + return -ENODEV; + + int ret = populateMediaDevice(media); + if (ret == 0) + addDevice(media); + return 0; + } + + if (!strcmp(subsystem, "video4linux")) { + addV4L2Device(udev_device_get_devnum(dev)); + return 0; + } + + return -ENODEV; +} + int DeviceEnumeratorUdev::enumerate() { struct udev_enumerate *udev_enum = nullptr; @@ -74,6 +110,14 @@ int DeviceEnumeratorUdev::enumerate() if (ret < 0) goto done; + ret = udev_enumerate_add_match_subsystem(udev_enum, "video4linux"); + if (ret < 0) + goto done; + + ret = udev_enumerate_add_match_is_initialized(udev_enum); + if (ret < 0) + goto done; + ret = udev_enumerate_scan_devices(udev_enum); if (ret < 0) goto done; @@ -102,10 +146,12 @@ int DeviceEnumeratorUdev::enumerate() goto done; } - addDevice(devnode); - + ret = addUdevDevice(dev); udev_device_unref(dev); + if (ret < 0) + break; } + done: udev_enumerate_unref(udev_enum); if (ret < 0) @@ -122,6 +168,43 @@ done: return 0; } +int DeviceEnumeratorUdev::populateMediaDevice(const std::shared_ptr<MediaDevice> &media) +{ + unsigned int pendingNodes = 0; + int ret; + + /* Associate entities to device node paths. */ + for (MediaEntity *entity : media->entities()) { + if (entity->deviceMajor() == 0 && entity->deviceMinor() == 0) + continue; + + std::string deviceNode = lookupDeviceNode(entity->deviceMajor(), + entity->deviceMinor()); + dev_t devnum = makedev(entity->deviceMajor(), + entity->deviceMinor()); + + /* Take device from orphan list first, if it is in the list. */ + if (std::find(orphans_.begin(), orphans_.end(), devnum) != orphans_.end()) { + if (deviceNode.empty()) + return -EINVAL; + + ret = entity->setDeviceNode(deviceNode); + if (ret) + return ret; + + orphans_.remove(devnum); + continue; + } + + deps_[media].push_back(devnum); + devnumToDevice_[devnum] = media; + devnumToEntity_[devnum] = entity; + pendingNodes++; + } + + return pendingNodes; +} + std::string DeviceEnumeratorUdev::lookupDeviceNode(int major, int minor) { struct udev_device *device; @@ -143,6 +226,48 @@ std::string DeviceEnumeratorUdev::lookupDeviceNode(int major, int minor) return deviceNode; } +/** + * \brief Add a V4L2 device to the media device that it belongs to + * \param[in] devnum major:minor number of V4L2 device to add, as a dev_t + * + * Add V4L2 device identified by \a devnum to the MediaDevice that it belongs + * to, if such a MediaDevice has been created. Otherwise add the V4L2 device + * to the orphan list. If the V4L2 device is added to a MediaDevice, and it is + * the last V4L2 device that the MediaDevice needs, then the MediaDevice is + * added to the DeviceEnumerator, where it is available for pipeline handlers. + * + * \return 0 on success or a negative error code otherwise + */ +int DeviceEnumeratorUdev::addV4L2Device(dev_t devnum) +{ + MediaEntity *entity = devnumToEntity_[devnum]; + if (!entity) { + orphans_.push_back(devnum); + return 0; + } + + std::string deviceNode = lookupDeviceNode(entity->deviceMajor(), + entity->deviceMinor()); + if (deviceNode.empty()) + return -EINVAL; + + int ret = entity->setDeviceNode(deviceNode); + if (ret) + return ret; + + std::shared_ptr<MediaDevice> media = devnumToDevice_[devnum]; + deps_[media].remove(devnum); + devnumToDevice_.erase(devnum); + devnumToEntity_.erase(devnum); + + if (deps_[media].empty()) { + addDevice(media); + deps_.erase(media); + } + + return 0; +} + void DeviceEnumeratorUdev::udevNotify(EventNotifier *notifier) { struct udev_device *dev = udev_monitor_receive_device(monitor_); @@ -153,9 +278,11 @@ void DeviceEnumeratorUdev::udevNotify(EventNotifier *notifier) << action << " device " << udev_device_get_devnode(dev); if (action == "add") { - addDevice(deviceNode); + addUdevDevice(dev); } else if (action == "remove") { - removeDevice(deviceNode); + const char *subsystem = udev_device_get_subsystem(dev); + if (subsystem && !strcmp(subsystem, "media")) + removeDevice(deviceNode); } udev_device_unref(dev); |