summaryrefslogtreecommitdiff
path: root/src/v4l2/v4l2_compat.cpp
blob: fd73381d34e92909c95e26ac56560f96335ae24c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * v4l2_compat.cpp - V4L2 compatibility layer
 */

#include "v4l2_compat_manager.h"

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#define LIBCAMERA_PUBLIC __attribute__((visibility("default")))

using namespace libcamera;

#define extract_va_arg(type, arg, last)	\
{					\
	va_list ap;			\
	va_start(ap, last);		\
	arg = va_arg(ap, type);		\
	va_end(ap);			\
}

extern "C" {

LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)
{
	mode_t mode = 0;
	if (oflag & O_CREAT || oflag & O_TMPFILE)
		extract_va_arg(mode_t, mode, oflag);

	return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
						     oflag, mode);
}

/* _FORTIFY_SOURCE redirects open to __open_2 */
LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag)
{
	return open(path, oflag);
}

#ifndef open64
LIBCAMERA_PUBLIC int open64(const char *path, int oflag, ...)
{
	mode_t mode = 0;
	if (oflag & O_CREAT || oflag & O_TMPFILE)
		extract_va_arg(mode_t, mode, oflag);

	return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
						     oflag | O_LARGEFILE, mode);
}

LIBCAMERA_PUBLIC int __open64_2(const char *path, int oflag)
{
	return open(path, oflag);
}
#endif

LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)
{
	mode_t mode = 0;
	if (oflag & O_CREAT || oflag & O_TMPFILE)
		extract_va_arg(mode_t, mode, oflag);

	return V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);
}

LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag)
{
	return openat(dirfd, path, oflag);
}

#ifndef openat64
LIBCAMERA_PUBLIC int openat64(int dirfd, const char *path, int oflag, ...)
{
	mode_t mode = 0;
	if (oflag & O_CREAT || oflag & O_TMPFILE)
		extract_va_arg(mode_t, mode, oflag);

	return V4L2CompatManager::instance()->openat(dirfd, path,
						     oflag | O_LARGEFILE, mode);
}

LIBCAMERA_PUBLIC int __openat64_2(int dirfd, const char *path, int oflag)
{
	return openat(dirfd, path, oflag);
}
#endif

LIBCAMERA_PUBLIC int dup(int oldfd)
{
	return V4L2CompatManager::instance()->dup(oldfd);
}

LIBCAMERA_PUBLIC int close(int fd)
{
	return V4L2CompatManager::instance()->close(fd);
}

LIBCAMERA_PUBLIC void *mmap(void *addr, size_t length, int prot, int flags,
			    int fd, off_t offset)
{
	return V4L2CompatManager::instance()->mmap(addr, length, prot, flags,
						   fd, offset);
}

#ifndef mmap64
LIBCAMERA_PUBLIC void *mmap64(void *addr, size_t length, int prot, int flags,
			      int fd, off64_t offset)
{
	return V4L2CompatManager::instance()->mmap(addr, length, prot, flags,
						   fd, offset);
}
#endif

LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)
{
	return V4L2CompatManager::instance()->munmap(addr, length);
}

LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)
{
	void *arg;
	extract_va_arg(void *, arg, request);

	return V4L2CompatManager::instance()->ioctl(fd, request, arg);
}

}
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([[maybe_unused]] 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 */