diff options
author | Paul Elder <paul.elder@ideasonboard.com> | 2019-09-01 23:53:02 -0400 |
---|---|---|
committer | Paul Elder <paul.elder@ideasonboard.com> | 2019-09-08 19:50:11 -0400 |
commit | 6e620349009de675250a7e3e753480b85a6df10b (patch) | |
tree | 3fa87d45d3518ba49906f0da4df0afb3bb95a30a /src/libcamera/device_enumerator_udev.cpp | |
parent | 19f85f43ff2ad4da255aebe8440a0aa6a3650632 (diff) |
libcamera: device_enumerator: fix udev media graph loading dependency
When a MediaDevice is enumerated and populated by the
DeviceEnumeratorUdev, there is a possibility that the member device
nodes of the media graph would not be ready (either not created, or
without proper permissions set by udev yet). The MediaDevice is still
passed up to the pipeline handler, where an attempt to access the device
nodes will fail in EPERM. This whole issue is especially likely to
happen when libcamera is run at system init time.
To fix this, we first split DeviceEnumerator::addDevice() into three
methods:
- createDevice() to simply create the MediaDevice
- populateMediaDevice() to populate the MediaDevice
- addDevice() to pass the MediaDevice up to the pipeline handler
DeviceEnumeratorSysfs calls these methods in succession, similar to what
it did before when they were all together as addDevice().
DeviceEnumeratorUdev additionally keeps a map of MediaDevices to a list
of pending device nodes (plus some other auxillary maps), and a simple
list of orphan device nodes. If a v4l device node is ready and there
does not exist any MediaDevice node for it, then it goes to the orphan
list, otherwise it is initialized and removed from the pending list of
the corresponding MediaDevice in the dependency map. When a MediaDevice
is populated via DeviceEnumeratorUdev::populateMediaDevice(), it first
checks the orphan list to see if the device nodes it needs are there,
otherwise it tries to initialize the device nodes and if it fails, then
it adds the device nodes it wants to its list in the dependency map.
This allows MediaDevice instances to be created and initialized properly
with udev when v4l device nodes in the media graph may not be ready when
the MediaDevice is populated.
Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
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); |