| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| /* |
| * Copyright (C) 2018-2019, Google Inc. |
| * |
| * device_enumerator_udev.cpp - udev-based device enumerator |
| */ |
| |
| #include "device_enumerator_udev.h" |
| |
| #include <algorithm> |
| #include <fcntl.h> |
| #include <libudev.h> |
| #include <list> |
| #include <map> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/sysmacros.h> |
| #include <unistd.h> |
| |
| #include <libcamera/event_notifier.h> |
| |
| #include "log.h" |
| #include "media_device.h" |
| |
| namespace libcamera { |
| |
| LOG_DECLARE_CATEGORY(DeviceEnumerator) |
| |
| DeviceEnumeratorUdev::DeviceEnumeratorUdev() |
| : udev_(nullptr) |
| { |
| } |
| |
| DeviceEnumeratorUdev::~DeviceEnumeratorUdev() |
| { |
| delete notifier_; |
| |
| if (monitor_) |
| udev_monitor_unref(monitor_); |
| if (udev_) |
| udev_unref(udev_); |
| } |
| |
| int DeviceEnumeratorUdev::init() |
| { |
| int ret; |
| |
| if (udev_) |
| return -EBUSY; |
| |
| udev_ = udev_new(); |
| if (!udev_) |
| return -ENODEV; |
| |
| monitor_ = udev_monitor_new_from_netlink(udev_, "udev"); |
| if (!monitor_) |
| return -ENODEV; |
| |
| ret = udev_monitor_filter_add_match_subsystem_devtype(monitor_, "media", |
| nullptr); |
| 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::unique_ptr<MediaDevice> media = |
| createDevice(udev_device_get_devnode(dev)); |
| if (!media) |
| return -ENODEV; |
| |
| DependencyMap deps; |
| int ret = populateMediaDevice(media.get(), &deps); |
| if (ret < 0) { |
| LOG(DeviceEnumerator, Warning) |
| << "Failed to populate media device " |
| << media->deviceNode() |
| << " (" << media->driver() << "), skipping"; |
| return ret; |
| } |
| |
| if (!deps.empty()) { |
| LOG(DeviceEnumerator, Debug) |
| << "Defer media device " << media->deviceNode() |
| << " due to " << deps.size() |
| << " missing dependencies"; |
| |
| pending_.emplace_back(std::move(media), std::move(deps)); |
| MediaDeviceDeps *mediaDeps = &pending_.back(); |
| for (const auto &dep : mediaDeps->deps_) |
| devMap_[dep.first] = mediaDeps; |
| |
| return 0; |
| } |
| |
| addDevice(std::move(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; |
| struct udev_list_entry *ents, *ent; |
| int ret; |
| |
| udev_enum = udev_enumerate_new(udev_); |
| if (!udev_enum) |
| return -ENOMEM; |
| |
| ret = udev_enumerate_add_match_subsystem(udev_enum, "media"); |
| 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; |
| |
| ents = udev_enumerate_get_list_entry(udev_enum); |
| if (!ents) |
| goto done; |
| |
| udev_list_entry_foreach(ent, ents) { |
| struct udev_device *dev; |
| const char *devnode; |
| const char *syspath = udev_list_entry_get_name(ent); |
| |
| dev = udev_device_new_from_syspath(udev_, syspath); |
| if (!dev) { |
| LOG(DeviceEnumerator, Warning) |
| << "Failed to get device for '" |
| << syspath << "', skipping"; |
| continue; |
| } |
| |
| devnode = udev_device_get_devnode(dev); |
| if (!devnode) { |
| udev_device_unref(dev); |
| LOG(DeviceEnumerator, Warning) |
| << "Failed to get device node for '" |
| << syspath << "', skipping"; |
| continue; |
| } |
| |
| if (addUdevDevice(dev) < 0) |
| LOG(DeviceEnumerator, Warning) |
| << "Failed to add device for '" |
| << syspath << "', skipping"; |
| |
| udev_device_unref(dev); |
| } |
| |
| done: |
| udev_enumerate_unref(udev_enum); |
| if (ret < 0) |
| return ret; |
| |
| ret = udev_monitor_enable_receiving(monitor_); |
| if (ret < 0) |
| return ret; |
| |
| int fd = udev_monitor_get_fd(monitor_); |
| notifier_ = new EventNotifier(fd, EventNotifier::Read); |
| notifier_->activated.connect(this, &DeviceEnumeratorUdev::udevNotify); |
| |
| return 0; |
| } |
| |
| int DeviceEnumeratorUdev::populateMediaDevice(MediaDevice *media, DependencyMap *deps) |
| { |
| std::set<dev_t> children; |
| |
| /* Associate entities to device node paths. */ |
| for (MediaEntity *entity : media->entities()) { |
| if (entity->deviceMajor() == 0 && entity->deviceMinor() == 0) |
| continue; |
| |
| dev_t devnum = makedev(entity->deviceMajor(), |
| entity->deviceMinor()); |
| |
| /* |
| * If the devnum isn't in the orphans list, add it to the unmet |
| * dependencies. |
| */ |
| if (orphans_.find(devnum) == orphans_.end()) { |
| (*deps)[devnum].push_back(entity); |
| continue; |
| } |
| |
| /* |
| * Otherwise take it from the orphans list. Don't remove the |
| * entry from the list yet as other entities in this media |
| * device may need the same device. |
| */ |
| std::string deviceNode = lookupDeviceNode(devnum); |
| if (deviceNode.empty()) |
| return -EINVAL; |
| |
| int ret = entity->setDeviceNode(deviceNode); |
| if (ret) |
| return ret; |
| |
| children.insert(devnum); |
| } |
| |
| /* Remove all found children from the orphans list. */ |
| for (auto it = orphans_.begin(), last = orphans_.end(); it != last;) { |
| if (children.find(*it) != children.end()) |
| it = orphans_.erase(it); |
| else |
| ++it; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * \brief Lookup device node path from device number |
| * \param[in] devnum The device number |
| * |
| * Translate a device number given as \a devnum to a device node path. |
| * |
| * \return The device node path on success, or an empty string if the lookup |
| * fails |
| */ |
| std::string DeviceEnumeratorUdev::lookupDeviceNode(dev_t devnum) |
| { |
| struct udev_device *device; |
| const char *name; |
| std::string deviceNode = std::string(); |
| |
| device = udev_device_new_from_devnum(udev_, 'c', devnum); |
| if (!device) |
| return std::string(); |
| |
| name = udev_device_get_devnode(device); |
| if (name) |
| deviceNode = name; |
| |
| udev_device_unref(device); |
| |
| 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) |
| { |
| /* |
| * If the devnum doesn't belong to any media device, add it to the |
| * orphans list. |
| */ |
| auto it = devMap_.find(devnum); |
| if (it == devMap_.end()) { |
| orphans_.insert(devnum); |
| return 0; |
| } |
| |
| /* |
| * Set the device node for all entities matching the devnum. Multiple |
| * entities can share the same device node, for instance for V4L2 M2M |
| * devices. |
| */ |
| std::string deviceNode = lookupDeviceNode(devnum); |
| if (deviceNode.empty()) |
| return -EINVAL; |
| |
| MediaDeviceDeps *deps = it->second; |
| for (MediaEntity *entity : deps->deps_[devnum]) { |
| int ret = entity->setDeviceNode(deviceNode); |
| if (ret) |
| return ret; |
| } |
| |
| /* |
| * Remove the devnum from the unmet dependencies for this media device. |
| * If no more dependency is unmet, add the media device to the |
| * enumerator. |
| */ |
| deps->deps_.erase(devnum); |
| |
| if (deps->deps_.empty()) { |
| LOG(DeviceEnumerator, Debug) |
| << "All dependencies for media device " |
| << deps->media_->deviceNode() << " found"; |
| addDevice(std::move(deps->media_)); |
| pending_.remove(*deps); |
| } |
| |
| return 0; |
| } |
| |
| void DeviceEnumeratorUdev::udevNotify(EventNotifier *notifier) |
| { |
| struct udev_device *dev = udev_monitor_receive_device(monitor_); |
| std::string action(udev_device_get_action(dev)); |
| std::string deviceNode(udev_device_get_devnode(dev)); |
| |
| LOG(DeviceEnumerator, Debug) |
| << action << " device " << udev_device_get_devnode(dev); |
| |
| if (action == "add") { |
| addUdevDevice(dev); |
| } else if (action == "remove") { |
| const char *subsystem = udev_device_get_subsystem(dev); |
| if (subsystem && !strcmp(subsystem, "media")) |
| removeDevice(deviceNode); |
| } |
| |
| udev_device_unref(dev); |
| } |
| |
| } /* namespace libcamera */ |