| /* |
| * Copyright 2020 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "hal/usb/camera_privacy_switch_monitor.h" |
| |
| #include <fcntl.h> |
| #include <linux/videodev2.h> |
| #include <poll.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| #include <utility> |
| |
| #include <base/files/file_util.h> |
| |
| #include "cros-camera/common.h" |
| |
| namespace cros { |
| |
| namespace { |
| |
| int GetControlValue(int device_fd, int32_t* value) { |
| v4l2_control current = {.id = static_cast<__u32>(V4L2_CID_PRIVACY)}; |
| if (HANDLE_EINTR(ioctl(device_fd, VIDIOC_G_CTRL, ¤t)) < 0) { |
| PLOGF(ERROR) << "Failed to get privacy control value"; |
| return -errno; |
| } |
| *value = current.value; |
| return 0; |
| } |
| |
| bool IsControlAvailable(int device_fd) { |
| v4l2_queryctrl query_ctrl = {.id = static_cast<__u32>(V4L2_CID_PRIVACY)}; |
| if (HANDLE_EINTR(ioctl(device_fd, VIDIOC_QUERYCTRL, &query_ctrl)) < 0) { |
| VLOGF(1) << "Privacy control unsupported"; |
| return false; |
| } |
| if (query_ctrl.flags & V4L2_CTRL_FLAG_DISABLED) { |
| VLOGF(1) << "Privacy control is disabled"; |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| CameraPrivacySwitchMonitor::CameraPrivacySwitchMonitor() |
| : state_(PrivacySwitchState::kUnknown), event_thread_("V4L2Event") { |
| VLOGF_ENTER(); |
| } |
| |
| CameraPrivacySwitchMonitor::~CameraPrivacySwitchMonitor() { |
| VLOGF_ENTER(); |
| |
| UnsubscribeEvents(); |
| } |
| |
| void CameraPrivacySwitchMonitor::RegisterCallback( |
| PrivacySwitchStateChangeCallback callback) { |
| callback_ = std::move(callback); |
| |
| base::AutoLock l(camera_id_lock_); |
| for (const auto& it : subscribed_camera_id_to_fd_) { |
| int camera_id = it.first; |
| |
| int32_t cur_value; |
| if (GetControlValue(it.second.get(), &cur_value) != 0) { |
| LOGF(ERROR) |
| << "Failed to get current value of privacy control for camera: " |
| << camera_id; |
| continue; |
| } |
| OnStatusChanged(camera_id, cur_value != 0 ? PrivacySwitchState::kOn |
| : PrivacySwitchState::kOff); |
| } |
| } |
| |
| void CameraPrivacySwitchMonitor::TrySubscribe(int camera_id, |
| const std::string& device_path) { |
| { |
| base::AutoLock l(camera_id_lock_); |
| if (subscribed_camera_id_to_fd_.find(camera_id) != |
| subscribed_camera_id_to_fd_.end()) { |
| // The camera id is already subscribed. |
| return; |
| } |
| } |
| |
| base::ScopedFD device_fd( |
| TEMP_FAILURE_RETRY(open(device_path.c_str(), O_RDWR))); |
| if (!device_fd.is_valid()) { |
| LOGF(ERROR) << "Failed to open " << device_path; |
| return; |
| } |
| |
| if (!IsControlAvailable(device_fd.get())) { |
| return; |
| } |
| |
| int32_t init_value; |
| if (GetControlValue(device_fd.get(), &init_value) != 0) { |
| LOGF(ERROR) << "Failed to get initial value of privacy control for camera: " |
| << camera_id; |
| return; |
| } |
| OnStatusChanged(camera_id, init_value != 0 ? PrivacySwitchState::kOn |
| : PrivacySwitchState::kOff); |
| SubscribeEvent(camera_id, std::move(device_fd)); |
| } |
| |
| void CameraPrivacySwitchMonitor::Unsubscribe(int camera_id) { |
| base::AutoLock l(camera_id_lock_); |
| auto it = subscribed_camera_id_to_fd_.find(camera_id); |
| if (it == subscribed_camera_id_to_fd_.end()) { |
| return; |
| } |
| subscribed_camera_id_to_fd_.erase(it); |
| |
| RestartEventLoop(); |
| } |
| |
| void CameraPrivacySwitchMonitor::OnStatusChanged(int camera_id, |
| PrivacySwitchState state) { |
| if (state == state_) { |
| return; |
| } |
| |
| state_ = state; |
| if (!callback_.is_null()) { |
| // TODO(b/167994459): Contains |camera_id| information when notifies. |
| callback_.Run(state); |
| } |
| } |
| |
| void CameraPrivacySwitchMonitor::SubscribeEvent(int camera_id, |
| base::ScopedFD device_fd) { |
| struct v4l2_event_subscription sub = {.type = V4L2_EVENT_CTRL, |
| .id = V4L2_CID_PRIVACY}; |
| if (HANDLE_EINTR(ioctl(device_fd.get(), VIDIOC_SUBSCRIBE_EVENT, &sub)) < 0) { |
| PLOGF(ERROR) << "Failed to subscribe for privacy status change"; |
| return; |
| } |
| base::AutoLock l(camera_id_lock_); |
| subscribed_camera_id_to_fd_.emplace(camera_id, std::move(device_fd)); |
| |
| // If the thread hasn't been started, start the thread to listen for events. |
| // If the thread is already started, triggers the thread to make it restart |
| // so that it can listen for the new fd. |
| if (event_thread_.IsRunning()) { |
| RestartEventLoop(); |
| } else { |
| if (!event_thread_.Start()) { |
| LOGF(ERROR) << "Failed to start V4L2 event thread"; |
| return; |
| } |
| |
| if (!base::CreatePipe(&control_fd_, &control_pipe_, true)) { |
| LOGF(ERROR) << "Failed to create the control pipe"; |
| return; |
| } |
| |
| event_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindRepeating(&CameraPrivacySwitchMonitor::RunDequeueEventsLoop, |
| base::Unretained(this))); |
| } |
| } |
| |
| void CameraPrivacySwitchMonitor::UnsubscribeEvents() { |
| control_pipe_.reset(); |
| if (event_thread_.IsRunning()) { |
| event_thread_.Stop(); |
| } |
| |
| base::AutoLock l(camera_id_lock_); |
| for (auto& it : subscribed_camera_id_to_fd_) { |
| struct v4l2_event_subscription sub = {.type = V4L2_EVENT_CTRL, |
| .id = V4L2_CID_PRIVACY}; |
| if (HANDLE_EINTR(ioctl(it.second.get(), VIDIOC_UNSUBSCRIBE_EVENT, &sub)) < |
| 0) { |
| PLOGF(ERROR) << "Failed to unsubscribe for privacy status change"; |
| } |
| } |
| subscribed_camera_id_to_fd_.clear(); |
| } |
| |
| void CameraPrivacySwitchMonitor::RunDequeueEventsLoop() { |
| while (true) { |
| std::vector<struct pollfd> fds; |
| std::vector<int> camera_ids; |
| { |
| base::AutoLock l(camera_id_lock_); |
| for (const auto& it : subscribed_camera_id_to_fd_) { |
| camera_ids.push_back(it.first); |
| fds.push_back({it.second.get(), POLLPRI, 0}); |
| } |
| fds.push_back({control_fd_.get(), POLLIN | POLLHUP, 0}); |
| } |
| |
| if (HANDLE_EINTR(poll(fds.data(), fds.size(), -1)) <= 0) { |
| LOGF(ERROR) << "Failed to poll to dequeue events"; |
| return; |
| } |
| |
| if (fds.back().revents & POLLHUP) { |
| control_fd_.reset(); |
| return; |
| } |
| |
| for (size_t i = 0; i < camera_ids.size(); i++) { |
| if (fds[i].revents > 0) { |
| struct v4l2_event ev = {}; |
| if (HANDLE_EINTR(ioctl(fds[i].fd, VIDIOC_DQEVENT, &ev)) < 0) { |
| PLOGF(ERROR) << "Failed to dequeue event from device"; |
| continue; |
| } |
| |
| if (ev.type == V4L2_EVENT_CTRL && ev.id == V4L2_CID_PRIVACY) { |
| OnStatusChanged(camera_ids[i], ev.u.ctrl.value != 0 |
| ? PrivacySwitchState::kOn |
| : PrivacySwitchState::kOff); |
| } |
| } |
| } |
| |
| // If there is some data in |control_pipe| which is used to trigger the |
| // restart of the blocking loop, clear the data before restarting. |
| if (fds.back().revents & POLLIN) { |
| uint8_t buf; |
| if (read(fds.back().fd, &buf, sizeof(buf)) < 0) { |
| PLOGF(ERROR) << "Failed to read data from control pipe"; |
| } |
| } |
| } |
| } |
| |
| void CameraPrivacySwitchMonitor::RestartEventLoop() { |
| uint8_t value = 0; |
| if (write(control_pipe_.get(), &value, sizeof(value)) < 0) { |
| PLOGF(ERROR) << "Failed to restart the event loop"; |
| } |
| } |
| |
| } // namespace cros |