iioservice: Add iioservice with SamplesHandler
This commit adds the first part of iioservice, which is the daemon that
provides sensor data to applications, such as Camera, Power Manager,
Chromium and ARC++/ARC_VM.
SamplesHandler is a handler which analyzes data from kernel and dispatch
samples to applications without conflicts.
BUG=chromium:1006141
TEST=builds and unit tests.
Cq-Depend: chromium:2210281
Change-Id: Ib6b1d165b5d9b9ed14055c5faffdbd054e28fa4d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2215941
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
Tested-by: Cheng-Hao Yang <chenghaoyang@chromium.org>
Auto-Submit: Cheng-Hao Yang <chenghaoyang@chromium.org>
Commit-Queue: Cheng-Hao Yang <chenghaoyang@chromium.org>
diff --git a/iioservice/README.md b/iioservice/README.md
index b71b499..cdc8399 100644
--- a/iioservice/README.md
+++ b/iioservice/README.md
@@ -8,3 +8,12 @@
This library provides mojo interfaces to connect to sensor_dispatcher and
iioservice, and C++ utility functions and classes to establish mojo channels.
+
+## Daemon iioservice
+
+- `/usr/sbin/iioservice`
+
+This daemon provides mojo channels that let processes connect to it. iioservice
+will dispatch devices' event data to the processes that need them. Each process
+can set the desired frequencies and IIO channels of devices without conflicts,
+as it owns all IIO devices.
diff --git a/iioservice/daemon/BUILD.gn b/iioservice/daemon/BUILD.gn
new file mode 100644
index 0000000..9da328e
--- /dev/null
+++ b/iioservice/daemon/BUILD.gn
@@ -0,0 +1,71 @@
+# 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.
+
+import("//common-mk/pkg_config.gni")
+
+group("all") {
+ deps = [ ":iioservice" ]
+
+ if (use.test) {
+ deps += [ ":iioservice_testrunner" ]
+ }
+}
+
+pkg_config("target_defaults_pkg_deps") {
+ pkg_deps = [
+ "libbrillo",
+ "libchrome-${libbase_ver}",
+ "libmojo-${libbase_ver}",
+ ]
+}
+
+static_library("libiioservice") {
+ configs += [ ":target_defaults_pkg_deps" ]
+
+ sources = [ "samples_handler.cc" ]
+ public_pkg_deps = [ "libmems" ]
+
+ deps = [
+ "//iioservice/libiioservice_ipc:libiioservice_ipc",
+ "//iioservice/libiioservice_ipc:libiioservice_ipc_mojom",
+ ]
+}
+
+executable("iioservice") {
+ configs += [ ":target_defaults_pkg_deps" ]
+
+ sources = [ "main.cc" ]
+ deps = [ ":libiioservice" ]
+}
+
+if (use.test) {
+ pkg_config("iioservice_testrunner_pkg_deps") {
+ pkg_deps = [
+ "libbrillo-test",
+ "libchrome-test-${libbase_ver}",
+ "libiio",
+ ]
+ }
+
+ executable("iioservice_testrunner") {
+ configs += [
+ "//common-mk:test",
+ ":iioservice_testrunner_pkg_deps",
+ ":target_defaults_pkg_deps",
+ ]
+
+ deps = [
+ ":libiioservice",
+ "//common-mk/testrunner",
+ ]
+
+ pkg_deps = [ "libmems_test_support" ]
+
+ sources = [
+ "samples_handler_test.cc",
+ "test_fakes.cc",
+ "testrunner.cc",
+ ]
+ }
+}
diff --git a/iioservice/daemon/common_types.h b/iioservice/daemon/common_types.h
new file mode 100644
index 0000000..0868542
--- /dev/null
+++ b/iioservice/daemon/common_types.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef IIOSERVICE_DAEMON_COMMON_TYPES_H_
+#define IIOSERVICE_DAEMON_COMMON_TYPES_H_
+
+#include <set>
+#include <string>
+
+#include <mojo/public/cpp/bindings/receiver_set.h>
+
+#include <libmems/iio_device.h>
+
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+constexpr char kSamplingFrequencyAvailable[] = "sampling_frequency_available";
+
+struct ClientData {
+ mojo::ReceiverId id;
+ libmems::IioDevice* iio_device;
+ std::set<int32_t> enabled_chn_indices;
+ double frequency = -1; // Hz
+ uint32_t timeout = 5000; // millisecond
+ mojo::Remote<cros::mojom::SensorDeviceSamplesObserver> observer;
+};
+
+} // namespace iioservice
+
+#endif // IIOSERVICE_DAEMON_COMMON_TYPES_H_
diff --git a/iioservice/daemon/main.cc b/iioservice/daemon/main.cc
new file mode 100644
index 0000000..d6eb38f
--- /dev/null
+++ b/iioservice/daemon/main.cc
@@ -0,0 +1,14 @@
+// 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 <base/logging.h>
+#include <brillo/daemons/daemon.h>
+
+int main() {
+ brillo::Daemon daemon;
+ daemon.Run();
+ LOG(INFO) << "Daemon stopped";
+
+ return 0;
+}
diff --git a/iioservice/daemon/samples_handler.cc b/iioservice/daemon/samples_handler.cc
new file mode 100644
index 0000000..025e4bc
--- /dev/null
+++ b/iioservice/daemon/samples_handler.cc
@@ -0,0 +1,702 @@
+// 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 "iioservice/daemon/samples_handler.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/time/time.h>
+
+#include <libmems/test_fakes.h>
+#include <libmems/common_types.h>
+#include <libmems/iio_channel.h>
+#include <libmems/iio_context.h>
+#include <libmems/iio_device.h>
+
+#include "iioservice/include/common.h"
+#include "iioservice/include/constants.h"
+
+namespace iioservice {
+
+// static
+void SamplesHandler::SamplesHandlerDeleter(SamplesHandler* handler) {
+ if (handler == nullptr)
+ return;
+
+ if (!handler->sample_task_runner_->BelongsToCurrentThread()) {
+ handler->sample_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&SamplesHandler::SamplesHandlerDeleter, handler));
+ return;
+ }
+
+ delete handler;
+}
+
+// static
+bool SamplesHandler::GetDevMinMaxFrequency(libmems::IioDevice* iio_device,
+ double* min_freq,
+ double* max_freq) {
+ auto available_opt =
+ iio_device->ReadStringAttribute(kSamplingFrequencyAvailable);
+ if (!available_opt.has_value()) {
+ LOG(ERROR) << "Failed to read attribute: " << kSamplingFrequencyAvailable;
+ return false;
+ }
+
+ std::string sampling_frequency_available = available_opt.value();
+ // Remove trailing '\0' for parsing
+ auto pos = available_opt->find_first_of('\0');
+ if (pos != std::string::npos)
+ sampling_frequency_available = available_opt->substr(0, pos);
+
+ std::vector<std::string> sampling_frequencies =
+ base::SplitString(sampling_frequency_available, " ",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+ switch (sampling_frequencies.size()) {
+ case 0:
+ LOG(ERROR) << "Invalid format of " << kSamplingFrequencyAvailable << ": "
+ << sampling_frequency_available;
+ return false;
+
+ case 1:
+ if (!base::StringToDouble(sampling_frequencies.front(), min_freq) ||
+ *min_freq < 0.0 || *min_freq < kFrequencyEpsilon) {
+ LOG(ERROR) << "Failed to parse min max sampling_frequency: "
+ << sampling_frequency_available;
+ return false;
+ }
+
+ *max_freq = *min_freq;
+ return true;
+
+ default:
+ if (!base::StringToDouble(sampling_frequencies.back(), max_freq) ||
+ *max_freq < kFrequencyEpsilon) {
+ LOG(ERROR) << "Failed to parse max sampling_frequency: "
+ << sampling_frequency_available;
+ return false;
+ }
+
+ if (!base::StringToDouble(sampling_frequencies.front(), min_freq) ||
+ *min_freq < 0.0) {
+ LOG(ERROR) << "Failed to parse the first sampling_frequency: "
+ << sampling_frequency_available;
+ return false;
+ }
+
+ if (*min_freq == 0.0) {
+ if (!base::StringToDouble(sampling_frequencies[1], min_freq) ||
+ *min_freq < 0.0 || *max_freq < *min_freq) {
+ LOG(ERROR) << "Failed to parse min sampling_frequency: "
+ << sampling_frequency_available;
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
+// static
+SamplesHandler::ScopedSamplesHandler SamplesHandler::CreateWithFifo(
+ scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioDevice* iio_device,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback) {
+ ScopedSamplesHandler handler(nullptr, SamplesHandlerDeleter);
+
+ double min_freq, max_freq;
+ if (!GetDevMinMaxFrequency(iio_device, &min_freq, &max_freq))
+ return handler;
+
+ handler.reset(new SamplesHandler(
+ std::move(ipc_task_runner), std::move(sample_task_runner), iio_device,
+ min_freq, max_freq, std::move(on_sample_updated_callback),
+ std::move(on_error_occurred_callback)));
+ return handler;
+}
+
+// static
+SamplesHandler::ScopedSamplesHandler SamplesHandler::CreateWithoutFifo(
+ scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioContext* iio_context,
+ libmems::IioDevice* iio_device,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback) {
+ ScopedSamplesHandler handler(nullptr, SamplesHandlerDeleter);
+
+ double min_freq, max_freq;
+ if (!GetDevMinMaxFrequency(iio_device, &min_freq, &max_freq))
+ return handler;
+
+ auto trigger_device = iio_device->GetTrigger();
+ if (!trigger_device) {
+ trigger_device = iio_context->GetTriggerById(iio_device->GetId() + 1);
+ if (!trigger_device) {
+ LOGF(ERROR) << "Failed to find trigger with id: " << iio_device->GetId();
+
+ return handler;
+ }
+ }
+
+ handler.reset(new SamplesHandler(
+ std::move(ipc_task_runner), std::move(sample_task_runner), iio_device,
+ trigger_device, min_freq, max_freq, std::move(on_sample_updated_callback),
+ std::move(on_error_occurred_callback)));
+ return handler;
+}
+
+SamplesHandler::~SamplesHandler() {
+ if (requested_frequency_ > 0.0 &&
+ !iio_device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, 0.0))
+ LOGF(ERROR) << "Failed to set frequency";
+}
+
+void SamplesHandler::AddClient(ClientData* client_data) {
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ sample_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&SamplesHandler::AddClientOnThread,
+ weak_factory_.GetWeakPtr(), client_data));
+}
+
+void SamplesHandler::RemoveClient(ClientData* client_data) {
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ sample_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&SamplesHandler::RemoveClientOnThread,
+ weak_factory_.GetWeakPtr(), client_data));
+}
+void SamplesHandler::UpdateFrequency(
+ ClientData* client_data,
+ double frequency,
+ cros::mojom::SensorDevice::SetFrequencyCallback callback) {
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ sample_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&SamplesHandler::UpdateFrequencyOnThread,
+ weak_factory_.GetWeakPtr(), client_data,
+ frequency, std::move(callback)));
+}
+void SamplesHandler::UpdateChannelsEnabled(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ bool en,
+ cros::mojom::SensorDevice::SetChannelsEnabledCallback callback) {
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ sample_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&SamplesHandler::UpdateChannelsEnabledOnThread,
+ weak_factory_.GetWeakPtr(), client_data,
+ std::move(iio_chn_indices), en, std::move(callback)));
+}
+
+void SamplesHandler::GetChannelsEnabled(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ cros::mojom::SensorDevice::GetChannelsEnabledCallback callback) {
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ sample_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&SamplesHandler::GetChannelsEnabledOnThread,
+ weak_factory_.GetWeakPtr(), client_data,
+ std::move(iio_chn_indices), std::move(callback)));
+}
+
+SamplesHandler::SamplesHandler(
+ scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioDevice* iio_device,
+ double min_freq,
+ double max_freq,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback)
+ : ipc_task_runner_(std::move(ipc_task_runner)),
+ sample_task_runner_(std::move(sample_task_runner)),
+ use_fifo_(true),
+ iio_device_(iio_device),
+ trigger_device_(nullptr),
+ dev_min_frequency_(min_freq),
+ dev_max_frequency_(max_freq),
+ on_sample_updated_callback_(std::move(on_sample_updated_callback)),
+ on_error_occurred_callback_(std::move(on_error_occurred_callback)) {
+ DCHECK_GE(dev_max_frequency_, dev_min_frequency_);
+}
+
+SamplesHandler::SamplesHandler(
+ scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioDevice* iio_device,
+ libmems::IioDevice* trigger_device,
+ double min_freq,
+ double max_freq,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback)
+ : ipc_task_runner_(std::move(ipc_task_runner)),
+ sample_task_runner_(std::move(sample_task_runner)),
+ use_fifo_(false),
+ iio_device_(iio_device),
+ trigger_device_(trigger_device),
+ dev_min_frequency_(min_freq),
+ dev_max_frequency_(max_freq),
+ on_sample_updated_callback_(std::move(on_sample_updated_callback)),
+ on_error_occurred_callback_(std::move(on_error_occurred_callback)) {
+ DCHECK_GE(dev_max_frequency_, dev_min_frequency_);
+}
+
+void SamplesHandler::SetSampleWatcherOnThread() {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ auto fd = iio_device_->GetBufferFd();
+ if (!fd.has_value()) {
+ LOGF(ERROR) << "Failed to get fd";
+ for (auto client : clients_map_) {
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client.first->id,
+ cros::mojom::ObserverErrorType::GET_FD_FAILED));
+ }
+
+ return;
+ }
+
+ watcher_ = base::FileDescriptorWatcher::WatchReadable(
+ fd.value(),
+ base::BindRepeating(&SamplesHandler::OnSampleAvailableWithoutBlocking,
+ weak_factory_.GetWeakPtr()));
+}
+
+void SamplesHandler::StopSampleWatcherOnThread() {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ watcher_.reset();
+}
+
+void SamplesHandler::AddActiveClientOnThread(ClientData* client_data) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+ DCHECK_GE(client_data->frequency, kFrequencyEpsilon);
+ DCHECK(!client_data->enabled_chn_indices.empty());
+ DCHECK(inactive_clients_.find(client_data) == inactive_clients_.end());
+ DCHECK(clients_map_.find(client_data) == clients_map_.end());
+
+ clients_map_.emplace(client_data, SampleData{});
+ clients_map_[client_data].sample_index = samples_cnt_;
+
+ if (!watcher_.get())
+ SetSampleWatcherOnThread();
+
+ SetTimeoutTaskOnThread(client_data);
+
+ if (AddFrequencyOnThread(client_data->frequency))
+ return;
+
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client_data->id,
+ cros::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED));
+}
+void SamplesHandler::AddClientOnThread(ClientData* client_data) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ if (inactive_clients_.find(client_data) != inactive_clients_.end() ||
+ clients_map_.find(client_data) != clients_map_.end()) {
+ // Shouldn't happen. Users should check observer exists or not to know
+ // whether the client is already added.
+ LOGF(ERROR) << "Failed to AddClient: Already added";
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client_data->id,
+ cros::mojom::ObserverErrorType::ALREADY_STARTED));
+ return;
+ }
+
+ bool active = true;
+
+ client_data->frequency = FixFrequency(client_data->frequency);
+ if (client_data->frequency == 0.0) {
+ LOGF(ERROR) << "Added an inactive client: Invalid frequency.";
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client_data->id,
+ cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
+ active = false;
+ }
+ if (client_data->enabled_chn_indices.empty()) {
+ LOGF(ERROR) << "Added an inactive client: No enabled channels.";
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client_data->id,
+ cros::mojom::ObserverErrorType::NO_ENABLED_CHANNELS));
+ active = false;
+ }
+
+ if (!active) {
+ inactive_clients_.emplace(client_data);
+ return;
+ }
+
+ AddActiveClientOnThread(client_data);
+}
+
+void SamplesHandler::RemoveActiveClientOnThread(ClientData* client_data,
+ double orig_freq) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+ DCHECK_GE(orig_freq, kFrequencyEpsilon);
+ DCHECK(!client_data->enabled_chn_indices.empty());
+ DCHECK(clients_map_.find(client_data) != clients_map_.end());
+
+ clients_map_.erase(client_data);
+ if (clients_map_.empty())
+ StopSampleWatcherOnThread();
+
+ if (RemoveFrequencyOnThread(orig_freq))
+ return;
+
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client_data->id,
+ cros::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED));
+}
+void SamplesHandler::RemoveClientOnThread(ClientData* client_data) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ auto it = inactive_clients_.find(client_data);
+ if (it != inactive_clients_.end()) {
+ inactive_clients_.erase(it);
+ return;
+ }
+
+ if (clients_map_.find(client_data) == clients_map_.end()) {
+ LOGF(ERROR) << "Failed to RemoveClient: Client not found";
+ return;
+ }
+
+ RemoveActiveClientOnThread(client_data, client_data->frequency);
+}
+
+double SamplesHandler::FixFrequency(double frequency) {
+ if (frequency < kFrequencyEpsilon)
+ return 0.0;
+
+ if (frequency < dev_min_frequency_)
+ return dev_min_frequency_;
+
+ if (frequency > dev_max_frequency_)
+ return dev_max_frequency_;
+
+ return frequency;
+}
+
+void SamplesHandler::UpdateFrequencyOnThread(
+ ClientData* client_data,
+ double frequency,
+ cros::mojom::SensorDevice::SetFrequencyCallback callback) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ frequency = FixFrequency(frequency);
+ double orig_freq = client_data->frequency;
+ client_data->frequency = frequency;
+ ipc_task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(std::move(callback), frequency));
+
+ auto it = inactive_clients_.find(client_data);
+ if (it != inactive_clients_.end()) {
+ if (client_data->frequency > 0.0 &&
+ !client_data->enabled_chn_indices.empty()) {
+ // The client is now active.
+ inactive_clients_.erase(it);
+ AddActiveClientOnThread(client_data);
+ }
+
+ return;
+ }
+
+ if (clients_map_.find(client_data) == clients_map_.end())
+ return;
+
+ if (client_data->frequency == 0.0) {
+ // The client is now inactive
+ RemoveActiveClientOnThread(client_data, orig_freq);
+ inactive_clients_.emplace(client_data);
+
+ return;
+ }
+
+ // The client remains active
+ if (AddFrequencyOnThread(client_data->frequency) &&
+ RemoveFrequencyOnThread(orig_freq))
+ return;
+
+ // Failed to set device frequency
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client_data->id,
+ cros::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED));
+}
+
+bool SamplesHandler::AddFrequencyOnThread(double frequency) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ frequencies_.emplace(frequency);
+ double max_freq = *frequencies_.rbegin();
+ DCHECK_GE(max_freq, requested_frequency_);
+ return UpdateRequestedFrequencyOnThread(max_freq);
+}
+bool SamplesHandler::RemoveFrequencyOnThread(double frequency) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ auto it = frequencies_.find(frequency);
+ DCHECK(it != frequencies_.end());
+ frequencies_.erase(it);
+ auto r_it = frequencies_.rbegin();
+ double max_freq = (r_it == frequencies_.rend()) ? 0.0 : *r_it;
+ DCHECK_LE(max_freq, requested_frequency_);
+ return UpdateRequestedFrequencyOnThread(max_freq);
+}
+
+bool SamplesHandler::UpdateRequestedFrequencyOnThread(double frequency) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ if (frequency == requested_frequency_)
+ return true;
+
+ requested_frequency_ = frequency;
+ if (!iio_device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr,
+ frequency)) {
+ LOGF(ERROR) << "Failed to set frequency";
+ if (use_fifo_) // ignore this error if no fifo
+ return false;
+ }
+
+ auto freq_opt =
+ iio_device_->ReadDoubleAttribute(libmems::kSamplingFrequencyAttr);
+ if (!freq_opt.has_value()) {
+ LOGF(ERROR) << "Failed to get frequency";
+ return false;
+ }
+ dev_frequency_ = freq_opt.value();
+
+ if (use_fifo_) {
+ if (dev_frequency_ < kFrequencyEpsilon)
+ return true;
+
+ if (!iio_device_->WriteDoubleAttribute(libmems::kHWFifoTimeoutAttr,
+ 1.0 / dev_frequency_)) {
+ LOGF(ERROR) << "Failed to set fifo timeout";
+ return false;
+ }
+
+ return true;
+ }
+
+ // no fifo
+ DCHECK(trigger_device_);
+
+ if (!trigger_device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr,
+ frequency)) {
+ LOGF(ERROR) << "Failed to set trigger's frequency";
+ return false;
+ }
+
+ return true;
+}
+
+void SamplesHandler::UpdateChannelsEnabledOnThread(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ bool en,
+ cros::mojom::SensorDevice::SetChannelsEnabledCallback callback) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ std::vector<int32_t> failed_indices;
+
+ if (en) {
+ for (int32_t chn_index : iio_chn_indices) {
+ auto chn = iio_device_->GetChannel(chn_index);
+ if (!chn || !chn->IsEnabled()) {
+ LOG(ERROR) << "Failed to enable chn with index: " << chn_index;
+ failed_indices.push_back(chn_index);
+ continue;
+ }
+
+ client_data->enabled_chn_indices.emplace(chn_index);
+ }
+ } else {
+ for (int32_t chn_index : iio_chn_indices)
+ client_data->enabled_chn_indices.erase(chn_index);
+ }
+
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback), std::move(failed_indices)));
+
+ auto it = inactive_clients_.find(client_data);
+ if (it != inactive_clients_.end()) {
+ if (client_data->frequency > 0.0 &&
+ !client_data->enabled_chn_indices.empty()) {
+ // The client is now active.
+ inactive_clients_.erase(it);
+ AddActiveClientOnThread(client_data);
+ }
+
+ return;
+ }
+
+ if (clients_map_.find(client_data) == clients_map_.end())
+ return;
+
+ if (!client_data->enabled_chn_indices.empty()) {
+ // The client remains active
+ return;
+ }
+
+ RemoveActiveClientOnThread(client_data, client_data->frequency);
+ inactive_clients_.emplace(client_data);
+}
+
+void SamplesHandler::GetChannelsEnabledOnThread(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ cros::mojom::SensorDevice::GetChannelsEnabledCallback callback) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK_EQ(client_data->iio_device, iio_device_);
+
+ std::vector<bool> enabled;
+
+ for (int32_t chn_index : iio_chn_indices) {
+ enabled.push_back(client_data->enabled_chn_indices.find(chn_index) !=
+ client_data->enabled_chn_indices.end());
+ }
+
+ ipc_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::move(enabled)));
+}
+
+void SamplesHandler::SetTimeoutTaskOnThread(ClientData* client_data) {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ if (client_data->timeout == 0)
+ return;
+
+ sample_task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&SamplesHandler::SampleTimeout, weak_factory_.GetWeakPtr(),
+ client_data, clients_map_[client_data].sample_index),
+ base::TimeDelta::FromMilliseconds(client_data->timeout));
+}
+void SamplesHandler::SampleTimeout(ClientData* client_data,
+ uint64_t sample_index) {
+ auto it = clients_map_.find(client_data);
+ if (it == clients_map_.end() || it->second.sample_index != sample_index)
+ return;
+
+ ipc_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(on_error_occurred_callback_, client_data->id,
+ cros::mojom::ObserverErrorType::READ_TIMEOUT));
+}
+
+void SamplesHandler::OnSampleAvailableWithoutBlocking() {
+ DCHECK(sample_task_runner_->BelongsToCurrentThread());
+ DCHECK(num_read_failed_logs_ == 0 || num_read_failed_logs_recovery_ == 0);
+
+ auto sample = iio_device_->ReadSample();
+ if (!sample) {
+ AddReadFailedLog();
+ for (auto client : clients_map_) {
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_error_occurred_callback_, client.first->id,
+ cros::mojom::ObserverErrorType::READ_FAILED));
+ }
+
+ return;
+ }
+
+ if (num_read_failed_logs_ == 0) {
+ if (num_read_failed_logs_recovery_ > 0 &&
+ ++num_read_failed_logs_recovery_ >= kNumReadFailedLogsRecovery) {
+ LOGF(INFO) << "Resuming error logs";
+ num_read_failed_logs_recovery_ = 0;
+ }
+ } else {
+ --num_read_failed_logs_;
+ }
+
+ for (auto& client : clients_map_) {
+ DCHECK(client.first->frequency >= kFrequencyEpsilon);
+ DCHECK(!client.first->enabled_chn_indices.empty());
+
+ int step =
+ std::max(1, static_cast<int>(dev_frequency_ / client.first->frequency));
+
+ if (client.second.sample_index + step - 1 <= samples_cnt_) {
+ // Send a sample to the client
+ int64_t size = samples_cnt_ - client.second.sample_index + 1;
+ DCHECK_GE(size, 1);
+
+ libmems::IioDevice::IioSample client_sample;
+ for (int32_t chn_index : client.first->enabled_chn_indices) {
+ if (sample->find(chn_index) == sample->end()) {
+ LOG(ERROR) << "Missing chn: " << chn_index << " in sample";
+ continue;
+ }
+
+ client_sample[chn_index] = sample.value()[chn_index];
+ }
+
+ client.second.sample_index = samples_cnt_ + 1;
+
+ ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(on_sample_updated_callback_, client.first->id,
+ std::move(client_sample)));
+ SetTimeoutTaskOnThread(client.first);
+ }
+ }
+
+ ++samples_cnt_;
+}
+
+void SamplesHandler::AddReadFailedLog() {
+ if (num_read_failed_logs_recovery_ > 0) {
+ if (++num_read_failed_logs_recovery_ >= kNumReadFailedLogsRecovery) {
+ LOGF(INFO) << "Resuming error logs";
+ num_read_failed_logs_recovery_ = 0;
+ }
+
+ return;
+ }
+
+ if (++num_read_failed_logs_ >= kNumReadFailedLogsBeforeGivingUp) {
+ LOGF(ERROR) << "Too many read failed logs: Skipping logs until "
+ << kNumReadFailedLogsRecovery << " reads are done";
+
+ num_read_failed_logs_ = 0;
+ num_read_failed_logs_recovery_ = 1;
+ return;
+ }
+
+ LOGF(ERROR) << "Failed to read a sample";
+}
+
+} // namespace iioservice
diff --git a/iioservice/daemon/samples_handler.h b/iioservice/daemon/samples_handler.h
new file mode 100644
index 0000000..02b3649
--- /dev/null
+++ b/iioservice/daemon/samples_handler.h
@@ -0,0 +1,197 @@
+// 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.
+
+#ifndef IIOSERVICE_DAEMON_SAMPLES_HANDLER_H_
+#define IIOSERVICE_DAEMON_SAMPLES_HANDLER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_descriptor_watcher_posix.h>
+#include <base/memory/weak_ptr.h>
+#include <base/sequenced_task_runner.h>
+#include <base/single_thread_task_runner.h>
+#include <mojo/public/cpp/bindings/receiver_set.h>
+
+#include <libmems/iio_context.h>
+#include <libmems/iio_device.h>
+
+#include "iioservice/daemon/common_types.h"
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+// A SamplesHandler is a handler of an IioDevice's samples. The user should add,
+// remove, and update clients with frequencies and channels, and this handler
+// will dispatch samples with clients' desired frequencies and channels when
+// samples are received from the kernel.
+// The user can provide the same |sample_task_runner| to all SamplesHandler as
+// there is no blocking function in SamplesHandler and the thread would not be
+// heavily loaded.
+class SamplesHandler {
+ public:
+ static void SamplesHandlerDeleter(SamplesHandler* handler);
+ using ScopedSamplesHandler =
+ std::unique_ptr<SamplesHandler, decltype(&SamplesHandlerDeleter)>;
+
+ using OnSampleUpdatedCallback = base::RepeatingCallback<void(
+ mojo::ReceiverId, libmems::IioDevice::IioSample)>;
+ using OnErrorOccurredCallback = base::RepeatingCallback<void(
+ mojo::ReceiverId, cros::mojom::ObserverErrorType)>;
+
+ static bool GetDevMinMaxFrequency(libmems::IioDevice* iio_device,
+ double* min_freq,
+ double* max_freq);
+
+ // use fifo
+ static ScopedSamplesHandler CreateWithFifo(
+ scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioDevice* iio_device,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback);
+ // no fifo
+ static ScopedSamplesHandler CreateWithoutFifo(
+ scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioContext* iio_context,
+ libmems::IioDevice* iio_device,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback);
+
+ virtual ~SamplesHandler();
+
+ // It's the user's responsibility to maintain |client_data| before being
+ // removed or this class being destructed.
+ // |client_data.iio_device| should be the same as |iio_device_|.
+ void AddClient(ClientData* client_data);
+ void RemoveClient(ClientData* client_data);
+
+ void UpdateFrequency(
+ ClientData* client_data,
+ double frequency,
+ cros::mojom::SensorDevice::SetFrequencyCallback callback);
+ void UpdateChannelsEnabled(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ bool en,
+ cros::mojom::SensorDevice::SetChannelsEnabledCallback callback);
+
+ void GetChannelsEnabled(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ cros::mojom::SensorDevice::GetChannelsEnabledCallback callback);
+
+ protected:
+ struct SampleData {
+ // The starting index of the next sample.
+ uint64_t sample_index = 0;
+ };
+
+ static const uint32_t kNumReadFailedLogsBeforeGivingUp = 100;
+ static const uint32_t kNumReadFailedLogsRecovery = 10000;
+
+ // use fifo
+ SamplesHandler(scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioDevice* iio_device,
+ double min_freq,
+ double max_freq,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback);
+ // no fifo
+ SamplesHandler(scoped_refptr<base::SequencedTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner,
+ libmems::IioDevice* iio_device,
+ libmems::IioDevice* trigger_device,
+ double in_freq,
+ double ax_freq,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback);
+
+ void SetSampleWatcherOnThread();
+ void StopSampleWatcherOnThread();
+
+ double FixFrequency(double frequency);
+
+ void AddActiveClientOnThread(ClientData* client_data);
+ void AddClientOnThread(ClientData* client_data);
+
+ void RemoveActiveClientOnThread(ClientData* client_data, double orig_freq);
+ void RemoveClientOnThread(ClientData* client_data);
+
+ void UpdateFrequencyOnThread(
+ ClientData* client_data,
+ double frequency,
+ cros::mojom::SensorDevice::SetFrequencyCallback callback);
+
+ bool AddFrequencyOnThread(double frequency);
+ bool RemoveFrequencyOnThread(double frequency);
+
+ bool UpdateRequestedFrequencyOnThread(double frequency);
+
+ void UpdateChannelsEnabledOnThread(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ bool en,
+ cros::mojom::SensorDevice::SetChannelsEnabledCallback callback);
+
+ void GetChannelsEnabledOnThread(
+ ClientData* client_data,
+ const std::vector<int32_t>& iio_chn_indices,
+ cros::mojom::SensorDevice::GetChannelsEnabledCallback callback);
+
+ void SetTimeoutTaskOnThread(ClientData* client_data);
+ void SampleTimeout(ClientData* client_data, uint64_t sample_index);
+
+ void OnSampleAvailableWithoutBlocking();
+ void AddReadFailedLog();
+
+ scoped_refptr<base::SequencedTaskRunner> ipc_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> sample_task_runner_;
+ bool use_fifo_ = true;
+ libmems::IioDevice* iio_device_;
+ libmems::IioDevice* trigger_device_ = nullptr;
+
+ // Clients that either have invalid frequency or no enabled channels.
+ std::set<ClientData*> inactive_clients_;
+ // First is the active client, second is its data.
+ std::map<ClientData*, SampleData> clients_map_;
+
+ // Requested frequencies from clients.
+ std::multiset<double> frequencies_;
+ // Max frequency among |frequencies_|.
+ double requested_frequency_ = 0.0;
+ // The real device frequency. Given the kernel is requesting upsampling,
+ // |dev_frequency_| >= |requested_frequency_|.
+ double dev_frequency_ = 0.0;
+
+ double dev_min_frequency_ = 0.0;
+ double dev_max_frequency_ = 0.0;
+
+ // The next coming sample's id. 0-based.
+ // Shouldn't overflow as timestamp will overflow first.
+ uint64_t samples_cnt_ = 0;
+
+ uint32_t num_read_failed_logs_ = 0;
+ uint32_t num_read_failed_logs_recovery_ = 0;
+
+ base::RepeatingCallback<void(mojo::ReceiverId, libmems::IioDevice::IioSample)>
+ on_sample_updated_callback_;
+ base::RepeatingCallback<void(mojo::ReceiverId,
+ cros::mojom::ObserverErrorType)>
+ on_error_occurred_callback_;
+
+ std::unique_ptr<base::FileDescriptorWatcher::Controller> watcher_;
+
+ private:
+ base::WeakPtrFactory<SamplesHandler> weak_factory_{this};
+};
+
+} // namespace iioservice
+
+#endif // IIOSERVICE_DAEMON_SAMPLES_HANDLER_H_
diff --git a/iioservice/daemon/samples_handler_test.cc b/iioservice/daemon/samples_handler_test.cc
new file mode 100644
index 0000000..58f5834
--- /dev/null
+++ b/iioservice/daemon/samples_handler_test.cc
@@ -0,0 +1,500 @@
+// 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 <gtest/gtest.h>
+
+#include <algorithm>
+#include <limits>
+#include <queue>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <base/rand_util.h>
+#include <base/threading/thread.h>
+#include <mojo/core/embedder/scoped_ipc_support.h>
+#include <mojo/public/cpp/bindings/receiver_set.h>
+
+#include <libmems/common_types.h>
+#include <libmems/test_fakes.h>
+
+#include "iioservice/daemon/samples_handler.h"
+#include "iioservice/daemon/test_fakes.h"
+#include "iioservice/include/constants.h"
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+namespace {
+
+constexpr double kMinFrequency = 1.25;
+constexpr double kMaxFrequency = 40.0;
+
+constexpr int kFakeTimeoutInMilliseconds = 10;
+constexpr double kFooFrequency = 20.0;
+constexpr int kNumFailures = 10;
+
+double FixFrequency(double frequency) {
+ if (frequency < kFrequencyEpsilon)
+ return 0.0;
+
+ if (frequency < kMinFrequency)
+ return kMinFrequency;
+
+ if (frequency > kMaxFrequency)
+ return kMaxFrequency;
+
+ return frequency;
+}
+
+class SamplesHandlerTestOnMinMaxFrequencyWithParam
+ : public ::testing::TestWithParam<
+ std::tuple<std::string, bool, double, double>> {
+ protected:
+ void SetUp() override {
+ device_ = std::make_unique<libmems::fakes::FakeIioDevice>(
+ nullptr, fakes::kAccelDeviceName, fakes::kAccelDeviceId);
+
+ EXPECT_TRUE(device_->WriteStringAttribute(kSamplingFrequencyAvailable,
+ std::get<0>(GetParam())));
+
+ result_ = SamplesHandler::GetDevMinMaxFrequency(device_.get(), &min_freq_,
+ &max_freq_);
+ }
+
+ std::unique_ptr<libmems::fakes::FakeIioDevice> device_;
+ bool result_;
+ double min_freq_ = -1;
+ double max_freq_ = -1;
+};
+
+TEST_P(SamplesHandlerTestOnMinMaxFrequencyWithParam, ParseMinMaxFrequency) {
+ EXPECT_EQ(result_, std::get<1>(GetParam()));
+ if (!result_)
+ return;
+
+ EXPECT_EQ(min_freq_, std::get<2>(GetParam()));
+ EXPECT_EQ(max_freq_, std::get<3>(GetParam()));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ SamplesHandlerTestOnMinMaxFrequencyWithParamRun,
+ SamplesHandlerTestOnMinMaxFrequencyWithParam,
+ ::testing::Values(
+ std::make_tuple(" ", false, 0.0, 0.0),
+ std::make_tuple(" 0abc ", false, 0.0, 0.0),
+ std::make_tuple(" 0.0001 ", false, 0.0, 0.0),
+ std::make_tuple("0.5 ", true, 0.5, 0.5),
+ std::make_tuple(" 1000 ", true, 1000.0, 1000.0),
+ std::make_tuple("1.0 100.0 ", true, 1.0, 100.0),
+ std::make_tuple("1.0 10.0 100.0 ", true, 1.0, 100.0),
+ std::make_tuple("1.0 a b c 100.0 ", true, 1.0, 100.0),
+ std::make_tuple("0.0 a b c 100.0 ", false, 0.0, 0.0),
+ std::make_tuple("0.0 1.0 100.0 ", true, 1.0, 100.0),
+ std::make_tuple("0.0 2.0 a b c 100.0 ", true, 2.0, 100.0)));
+
+class SamplesHandlerTestBase {
+ protected:
+ void OnSampleUpdatedCallback(mojo::ReceiverId id,
+ libmems::IioDevice::IioSample sample) {
+ CHECK(ipc_thread_->task_runner()->BelongsToCurrentThread());
+ CHECK_LT(id, observers_.size());
+
+ observers_[id]->OnSampleUpdated(std::move(sample));
+ }
+ void OnErrorOccurredCallback(mojo::ReceiverId id,
+ cros::mojom::ObserverErrorType type) {
+ CHECK(ipc_thread_->task_runner()->BelongsToCurrentThread());
+ if (id >= observers_.size()) {
+ CHECK_EQ(type, cros::mojom::ObserverErrorType::FREQUENCY_INVALID);
+ return;
+ }
+
+ observers_[id]->OnErrorOccurred(type);
+ }
+
+ void SetUpBase() {
+ device_ = std::make_unique<libmems::fakes::FakeIioDevice>(
+ nullptr, fakes::kAccelDeviceName, fakes::kAccelDeviceId);
+ EXPECT_TRUE(device_->WriteStringAttribute(
+ kSamplingFrequencyAvailable, fakes::kFakeSamplingFrequencyAvailable));
+
+ for (int i = 0; i < base::size(libmems::fakes::kFakeAccelChns); ++i) {
+ device_->AddChannel(std::make_unique<libmems::fakes::FakeIioChannel>(
+ libmems::fakes::kFakeAccelChns[i], true));
+ }
+
+ EXPECT_TRUE(
+ device_->WriteDoubleAttribute(libmems::kSamplingFrequencyAttr, 0.0));
+
+ ipc_thread_ = std::make_unique<base::Thread>("IPCThread");
+ EXPECT_TRUE(ipc_thread_->StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ sample_thread_ = std::make_unique<base::Thread>("SampleThread");
+ EXPECT_TRUE(sample_thread_->StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ handler_ = fakes::FakeSamplesHandler::CreateWithFifo(
+ ipc_thread_->task_runner(), sample_thread_->task_runner(),
+ device_.get(),
+ base::BindRepeating(&SamplesHandlerTestBase::OnSampleUpdatedCallback,
+ base::Unretained(this)),
+ base::BindRepeating(&SamplesHandlerTestBase::OnErrorOccurredCallback,
+ base::Unretained(this)));
+ EXPECT_TRUE(handler_);
+ }
+
+ void TearDownBase() {
+ handler_.reset();
+ sample_thread_->Stop();
+
+ EXPECT_EQ(device_->ReadDoubleAttribute(libmems::kSamplingFrequencyAttr)
+ .value_or(-1),
+ 0.0);
+
+ observers_.clear();
+ ipc_thread_->Stop();
+ }
+
+ std::unique_ptr<libmems::fakes::FakeIioDevice> device_;
+
+ std::unique_ptr<base::Thread> ipc_thread_;
+ std::unique_ptr<base::Thread> sample_thread_;
+
+ fakes::FakeSamplesHandler::ScopedFakeSamplesHandler handler_ = {
+ nullptr, SamplesHandler::SamplesHandlerDeleter};
+ std::vector<ClientData> clients_data_;
+ std::vector<fakes::FakeSamplesObserver::ScopedFakeSamplesObserver> observers_;
+};
+
+class SamplesHandlerTest : public ::testing::Test,
+ public SamplesHandlerTestBase {
+ protected:
+ void SetUp() override { SetUpBase(); }
+
+ void TearDown() override { TearDownBase(); }
+};
+
+// Add clients with only timestamp channel enabled, enable all other channels,
+// and disable all channels except for accel_x. Enabled channels are checked
+// after each modification.
+TEST_F(SamplesHandlerTest, UpdateChannelsEnabled) {
+ // No samples in this test
+ device_->SetPauseCallbackAtKthSamples(0, base::BindOnce([]() {}));
+
+ std::vector<double> freqs = {0.0, 10.0};
+ clients_data_.resize(freqs.size());
+ for (size_t i = 0; i < freqs.size(); ++i) {
+ ClientData& client_data = clients_data_[i];
+
+ client_data.id = i;
+ client_data.iio_device = device_.get();
+ client_data.enabled_chn_indices.emplace(3); // timestamp
+ client_data.timeout = 0;
+ client_data.frequency = freqs[i];
+
+ handler_->AddClient(&client_data);
+
+ handler_->UpdateChannelsEnabled(
+ &client_data, std::vector<int32_t>{1, 0, 2}, true,
+ base::BindOnce([](const std::vector<int32_t>& failed_indices) {
+ EXPECT_TRUE(failed_indices.empty());
+ }));
+
+ handler_->GetChannelsEnabled(
+ &client_data, std::vector<int32_t>{1, 0, 2, 3},
+ base::BindOnce([](const std::vector<bool>& enabled) {
+ EXPECT_EQ(enabled.size(), 4);
+ EXPECT_TRUE(enabled[0] && enabled[1] && enabled[2] && enabled[3]);
+ }));
+
+ handler_->UpdateChannelsEnabled(
+ &client_data, std::vector<int32_t>{2, 1, 3}, false,
+ base::BindOnce(
+ [](ClientData* client_data, int32_t chn_index,
+ const std::vector<int32_t>& failed_indices) {
+ EXPECT_EQ(client_data->enabled_chn_indices.size(), 1);
+ EXPECT_EQ(*client_data->enabled_chn_indices.begin(), chn_index);
+ EXPECT_TRUE(failed_indices.empty());
+ },
+ &client_data, 0));
+ }
+
+ // Remove clients
+ for (auto& client_data : clients_data_)
+ handler_->RemoveClient(&client_data);
+}
+
+TEST_F(SamplesHandlerTest, BadDeviceWithNoSamples) {
+ base::MessageLoopForIO message_loop;
+
+ device_->DisableFd();
+
+ std::vector<double> freqs = {5.0, 0.0, 10.0, 100.0};
+ clients_data_.resize(freqs.size());
+ std::vector<std::unique_ptr<base::RunLoop>> loops;
+
+ std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> rt_failures;
+ rt_failures.insert(
+ std::make_pair(0, cros::mojom::ObserverErrorType::READ_TIMEOUT));
+
+ size_t fd_failed_cnt = 0;
+ for (size_t i = 0; i < freqs.size(); ++i) {
+ if (freqs[i] > 0.0)
+ ++fd_failed_cnt;
+ }
+
+ for (size_t i = 0; i < freqs.size(); ++i) {
+ ClientData& client_data = clients_data_[i];
+ std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+
+ client_data.id = i;
+ client_data.iio_device = device_.get();
+ client_data.enabled_chn_indices.emplace(3); // timestamp
+ client_data.frequency = freqs[i];
+ client_data.timeout = kFakeTimeoutInMilliseconds;
+
+ auto failures = rt_failures;
+ if (freqs[i] == 0.0) {
+ failures.insert(
+ std::make_pair(0, cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
+ } else {
+ for (size_t j = 0; j < fd_failed_cnt; ++j) {
+ failures.insert(
+ std::make_pair(0, cros::mojom::ObserverErrorType::GET_FD_FAILED));
+ }
+ --fd_failed_cnt;
+ }
+
+ // Don't care about frequencies as there would be no samples.
+ auto fake_observer = fakes::FakeSamplesObserver::Create(
+ ipc_thread_->task_runner(), run_loop->QuitClosure(), device_.get(),
+ std::move(failures), kFooFrequency, kFooFrequency, kFooFrequency,
+ kFooFrequency);
+
+ handler_->AddClient(&client_data);
+
+ loops.emplace_back(std::move(run_loop));
+ observers_.emplace_back(std::move(fake_observer));
+ }
+
+ // Wait until all observers receive the READ_TIMEOUT error
+ for (auto& run_loop : loops)
+ run_loop->Run();
+
+ // Remove clients
+ for (auto& client_data : clients_data_)
+ handler_->RemoveClient(&client_data);
+}
+
+class SamplesHandlerTestWithParam
+ : public ::testing::TestWithParam<std::vector<std::pair<double, double>>>,
+ public SamplesHandlerTestBase {
+ protected:
+ void SetUp() override { SetUpBase(); }
+
+ void TearDown() override { TearDownBase(); }
+};
+
+// Add clients with the first frequencies set, update clients with the second
+// frequencies, and remove clients. Clients' frequencies and the sample
+// handler's |max_frequency_| are checked after each modification.
+TEST_P(SamplesHandlerTestWithParam, UpdateFrequency) {
+ // No samples in this test
+ device_->SetPauseCallbackAtKthSamples(0, base::BindOnce([]() {}));
+
+ clients_data_.resize(GetParam().size());
+
+ std::multiset<double> frequencies;
+
+ // Add clients
+ for (size_t i = 0; i < GetParam().size(); ++i) {
+ ClientData& client_data = clients_data_[i];
+
+ client_data.id = i;
+ client_data.iio_device = device_.get();
+ // At least one channel enabled
+ client_data.enabled_chn_indices.emplace(3); // timestamp
+ client_data.timeout = 0;
+ client_data.frequency = GetParam()[i].first;
+
+ handler_->AddClient(&client_data);
+
+ frequencies.emplace(FixFrequency(client_data.frequency));
+ handler_->CheckRequestedFrequency(*frequencies.rbegin());
+ }
+
+ base::MessageLoopForIO message_loop;
+
+ // Update clients' frequencies
+ for (size_t i = 0; i < GetParam().size(); ++i) {
+ ClientData& client_data = clients_data_[i];
+
+ double new_freq = GetParam()[i].second;
+ handler_->UpdateFrequency(
+ &client_data, new_freq,
+ base::BindOnce(
+ [](ClientData* client_data, double fixed_new_freq,
+ double result_freq) {
+ EXPECT_EQ(client_data->frequency, fixed_new_freq);
+ EXPECT_EQ(result_freq, fixed_new_freq);
+ },
+ &client_data, FixFrequency(new_freq)));
+
+ auto it = frequencies.find(FixFrequency(GetParam()[i].first));
+ EXPECT_TRUE(it != frequencies.end());
+ frequencies.erase(it);
+ frequencies.emplace(FixFrequency(new_freq));
+
+ handler_->CheckRequestedFrequency(*frequencies.rbegin());
+ }
+
+ // Remove clients
+ for (size_t i = 0; i < GetParam().size(); ++i) {
+ ClientData& client_data = clients_data_[i];
+
+ handler_->RemoveClient(&client_data);
+ auto it = frequencies.find(FixFrequency(GetParam()[i].second));
+ EXPECT_TRUE(it != frequencies.end());
+ frequencies.erase(it);
+
+ handler_->CheckRequestedFrequency(
+ i == GetParam().size() - 1 ? 0.0 : *frequencies.rbegin());
+ }
+}
+
+// Add all clients into the sample handler, read the first |kPauseIndex|
+// samples and pause reading, update clients' frequencies and enable accel_y
+// channel, and read the rest samples. All samples are checked when received by
+// observers.
+TEST_P(SamplesHandlerTestWithParam, ReadSamplesWithFrequency) {
+ base::MessageLoopForIO message_loop;
+
+ base::RunLoop pause_loop;
+ // Set the pause in the beginning to prevent reading samples before all
+ // clients added.
+ device_->SetPauseCallbackAtKthSamples(
+ 0, base::BindOnce([](base::Closure quit_closure) { quit_closure.Run(); },
+ pause_loop.QuitClosure()));
+
+ std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> rf_failures;
+ for (int i = 0; i < kNumFailures; ++i) {
+ int k = base::RandInt(0, base::size(libmems::fakes::kFakeAccelSamples) - 1);
+
+ device_->AddFailedReadAtKthSample(k);
+ rf_failures.insert(
+ std::make_pair(k, cros::mojom::ObserverErrorType::READ_FAILED));
+ }
+
+ clients_data_.resize(GetParam().size());
+ std::vector<std::unique_ptr<base::RunLoop>> loops;
+
+ double max_freq = -1, max_freq2 = -1;
+ for (size_t i = 0; i < GetParam().size(); ++i) {
+ max_freq = std::max(max_freq, GetParam()[i].first);
+ max_freq2 = std::max(max_freq2, GetParam()[i].second);
+ }
+
+ max_freq = FixFrequency(max_freq);
+ max_freq2 = FixFrequency(max_freq2);
+
+ for (size_t i = 0; i < GetParam().size(); ++i) {
+ ClientData& client_data = clients_data_[i];
+ std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+
+ client_data.id = i;
+ client_data.iio_device = device_.get();
+ client_data.enabled_chn_indices.emplace(0); // accel_x
+ client_data.enabled_chn_indices.emplace(2); // accel_y
+ client_data.enabled_chn_indices.emplace(3); // timestamp
+ client_data.frequency = GetParam()[i].first;
+
+ auto failures = rf_failures;
+ if (GetParam()[i].first == 0.0) {
+ failures.insert(
+ std::make_pair(0, cros::mojom::ObserverErrorType::FREQUENCY_INVALID));
+ }
+
+ // The fake observer needs |max_freq| and |max_freq2| to calculate the
+ // correct values of samples
+ auto fake_observer = fakes::FakeSamplesObserver::Create(
+ ipc_thread_->task_runner(), run_loop->QuitClosure(), device_.get(),
+ std::move(failures), FixFrequency(GetParam()[i].first),
+ FixFrequency(GetParam()[i].second), max_freq, max_freq2);
+
+ handler_->AddClient(&client_data);
+
+ loops.emplace_back(std::move(run_loop));
+ observers_.emplace_back(std::move(fake_observer));
+ }
+ pause_loop.Run();
+
+ device_->SetPauseCallbackAtKthSamples(
+ fakes::kPauseIndex,
+ base::BindOnce(
+ [](fakes::FakeSamplesHandler* handler,
+ std::vector<ClientData>* clients_data,
+ libmems::fakes::FakeIioDevice* device) {
+ for (int i = 0; i < clients_data->size(); ++i) {
+ ClientData& client_data = clients_data->at(i);
+
+ // Update to the second frequency
+ handler->UpdateFrequency(
+ &client_data, GetParam()[i].second,
+ base::BindOnce(
+ [](double fixed_new_freq, double result_freq) {
+ EXPECT_EQ(result_freq, fixed_new_freq);
+ },
+ FixFrequency(GetParam()[i].second)));
+
+ // Enable accel_y
+ handler->UpdateChannelsEnabled(
+ &client_data, std::vector<int32_t>{1}, true,
+ base::BindOnce(
+ [](const std::vector<int32_t>& failed_indices) {
+ EXPECT_TRUE(failed_indices.empty());
+ }));
+ }
+
+ handler->ResumeReading(); // Read the rest samples
+ },
+ handler_.get(), &clients_data_, device_.get()));
+
+ handler_->ResumeReading(); // Start reading |kPauseIndex| samples
+
+ // Wait until all observers receive all samples
+ for (auto& run_loop : loops)
+ run_loop->Run();
+
+ // Remove clients
+ for (auto& client_data : clients_data_)
+ handler_->RemoveClient(&client_data);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ SamplesHandlerTestWithParamRun,
+ SamplesHandlerTestWithParam,
+ ::testing::Values(std::vector<std::pair<double, double>>(3, {10.0, 10.0}),
+ std::vector<std::pair<double, double>>{
+ {20.0, 50.0}, {10.0, 10.0}, {2.0, 3.0}},
+ std::vector<std::pair<double, double>>{
+ {10.0, 20.0}, {20.0, 30.0}, {0.0, 0.0}},
+ std::vector<std::pair<double, double>>{
+ {80.0, 50.0}, {10.0, 10.0}, {2.0, 3.0}},
+ std::vector<std::pair<double, double>>{
+ {10.0, 40.0}, {0.0, 20.0}, {2.0, 3.0}, {40.0, 10.0}},
+ std::vector<std::pair<double, double>>{
+ {2.0, 10.0}, {10.0, 30.0}, {80.0, 0.0}},
+ std::vector<std::pair<double, double>>{
+ {0.0, 10.0}, {10.0, 30.0}, {80.0, 60.0}},
+ std::vector<std::pair<double, double>>{
+ {2.0, 10.0}, {50.0, 30.0}, {80.0, 60.0}},
+ std::vector<std::pair<double, double>>{{20.0, 30.0},
+ {10.0, 10.0}}));
+
+} // namespace
+
+} // namespace iioservice
diff --git a/iioservice/daemon/test_fakes.cc b/iioservice/daemon/test_fakes.cc
new file mode 100644
index 0000000..80bcfe8
--- /dev/null
+++ b/iioservice/daemon/test_fakes.cc
@@ -0,0 +1,251 @@
+// 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 "iioservice/daemon/test_fakes.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include <libmems/common_types.h>
+#include <libmems/test_fakes.h>
+
+#include "iioservice/include/constants.h"
+
+namespace iioservice {
+
+namespace fakes {
+
+// static
+FakeSamplesHandler::ScopedFakeSamplesHandler FakeSamplesHandler::CreateWithFifo(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ libmems::fakes::FakeIioDevice* fake_iio_device,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback) {
+ ScopedFakeSamplesHandler handler(nullptr, SamplesHandlerDeleter);
+ double min_freq, max_freq;
+ if (!GetDevMinMaxFrequency(fake_iio_device, &min_freq, &max_freq))
+ return handler;
+
+ handler.reset(new FakeSamplesHandler(
+ std::move(ipc_task_runner), std::move(task_runner), fake_iio_device,
+ min_freq, max_freq, std::move(on_sample_updated_callback),
+ std::move(on_error_occurred_callback)));
+ return handler;
+}
+
+void FakeSamplesHandler::ResumeReading() {
+ sample_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&FakeSamplesHandler::ResumeReadingOnThread,
+ weak_factory_.GetWeakPtr()));
+}
+
+void FakeSamplesHandler::CheckRequestedFrequency(double max_freq) {
+ sample_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&FakeSamplesHandler::CheckRequestedFrequencyOnThread,
+ weak_factory_.GetWeakPtr(), max_freq));
+}
+
+FakeSamplesHandler::FakeSamplesHandler(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ libmems::fakes::FakeIioDevice* fake_iio_device,
+ double min_freq,
+ double max_freq,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback)
+ : SamplesHandler(std::move(ipc_task_runner),
+ std::move(task_runner),
+ fake_iio_device,
+ min_freq,
+ max_freq,
+ std::move(on_sample_updated_callback),
+ std::move(on_error_occurred_callback)),
+ fake_iio_device_(fake_iio_device) {}
+
+void FakeSamplesHandler::ResumeReadingOnThread() {
+ CHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ fake_iio_device_->ResumeReadingSamples();
+}
+
+void FakeSamplesHandler::CheckRequestedFrequencyOnThread(double max_freq) {
+ CHECK(sample_task_runner_->BelongsToCurrentThread());
+
+ CHECK_EQ(max_freq, requested_frequency_);
+}
+
+// static
+void FakeSamplesObserver::ObserverDeleter(FakeSamplesObserver* observer) {
+ if (observer == nullptr)
+ return;
+
+ if (!observer->ipc_task_runner_->BelongsToCurrentThread()) {
+ observer->ipc_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&FakeSamplesObserver::ObserverDeleter, observer));
+ return;
+ }
+
+ delete observer;
+}
+
+// static
+FakeSamplesObserver::ScopedFakeSamplesObserver FakeSamplesObserver::Create(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ base::Closure quit_closure,
+ libmems::IioDevice* device,
+ std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+ double frequency,
+ double frequency2,
+ double dev_frequency,
+ double dev_frequency2,
+ int pause_index) {
+ ScopedFakeSamplesObserver handler(
+ new FakeSamplesObserver(std::move(ipc_task_runner),
+ std::move(quit_closure), device,
+ std::move(failures), frequency, frequency2,
+ dev_frequency, dev_frequency2, pause_index),
+ ObserverDeleter);
+
+ return handler;
+}
+
+void FakeSamplesObserver::OnSampleUpdated(
+ const base::flat_map<int32_t, int64_t>& sample) {
+ CHECK(ipc_task_runner_->BelongsToCurrentThread());
+ CHECK(failures_.empty() || failures_.begin()->first > sample_index_);
+
+ int step = GetStep();
+ CHECK_GE(step, 1);
+
+ CHECK_GT(base::size(libmems::fakes::kFakeAccelSamples),
+ sample_index_ + step - 1);
+
+ if (device_->GetId() == kAccelDeviceId) {
+ for (int chnIndex = 0;
+ chnIndex < base::size(libmems::fakes::kFakeAccelChns); ++chnIndex) {
+ auto it = sample.find(chnIndex);
+
+ // channel: accel_y isn't enabled before |pause_index_|
+ if (sample_index_ + step - 1 < pause_index_ && chnIndex == 1) {
+ CHECK(it == sample.end());
+ continue;
+ }
+
+ CHECK(it != sample.end());
+
+ CHECK_EQ(it->second,
+ libmems::fakes::kFakeAccelSamples[sample_index_ + step - 1]
+ [chnIndex]);
+ }
+ } else {
+ auto channels = device_->GetAllChannels();
+ for (size_t i = 0; i < channels.size(); ++i) {
+ auto raw_value = channels[i]->ReadNumberAttribute(libmems::kRawAttr);
+ if (!raw_value.has_value())
+ continue;
+
+ auto it = sample.find(i);
+ CHECK(it != sample.end());
+ CHECK_EQ(raw_value.value(), it->second);
+ }
+ }
+
+ sample_index_ += step;
+
+ if ((frequency2_ == 0.0 && sample_index_ + step - 1 >= pause_index_) ||
+ sample_index_ + step - 1 >= base::size(libmems::fakes::kFakeAccelSamples))
+ quit_closure_.Run();
+}
+
+void FakeSamplesObserver::OnErrorOccurred(cros::mojom::ObserverErrorType type) {
+ CHECK(ipc_task_runner_->BelongsToCurrentThread());
+
+ CHECK(!failures_.empty());
+ CHECK_LE(failures_.begin()->first, sample_index_ + GetStep());
+ CHECK_EQ(failures_.begin()->second, type);
+
+ failures_.erase(failures_.begin());
+
+ if (type == cros::mojom::ObserverErrorType::FREQUENCY_INVALID) {
+ CHECK_EQ(sample_index_, 0);
+ if (frequency2_ == 0.0) {
+ sample_index_ = base::size(libmems::fakes::kFakeAccelSamples);
+ quit_closure_.Run();
+
+ return;
+ }
+
+ while (!failures_.empty() && failures_.begin()->first < pause_index_) {
+ if (failures_.begin()->second ==
+ cros::mojom::ObserverErrorType::READ_TIMEOUT)
+ quit_closure_.Run();
+
+ failures_.erase(failures_.begin());
+ }
+
+ sample_index_ = pause_index_;
+
+ return;
+ }
+
+ if (type == cros::mojom::ObserverErrorType::READ_TIMEOUT)
+ quit_closure_.Run();
+}
+
+FakeSamplesObserver::FakeSamplesObserver(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ base::Closure quit_closure,
+ libmems::IioDevice* device,
+ std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+ double frequency,
+ double frequency2,
+ double dev_frequency,
+ double dev_frequency2,
+ int pause_index)
+ : ipc_task_runner_(ipc_task_runner),
+ quit_closure_(quit_closure),
+ device_(device),
+ failures_(std::move(failures)),
+ frequency_(frequency),
+ frequency2_(frequency2),
+ dev_frequency_(dev_frequency),
+ dev_frequency2_(dev_frequency2),
+ pause_index_(pause_index),
+ receiver_(this) {
+ CHECK_GE(frequency_, 0.0);
+ CHECK_GE(frequency2_, 0.0);
+ CHECK_GE(dev_frequency_, kFrequencyEpsilon);
+ CHECK_GE(dev_frequency2_, kFrequencyEpsilon);
+}
+
+int FakeSamplesObserver::GetStep() {
+ CHECK_GE(dev_frequency_, kFrequencyEpsilon);
+
+ int step = base::size(libmems::fakes::kFakeAccelSamples);
+ if (frequency_ >= kFrequencyEpsilon)
+ step = dev_frequency_ / frequency_;
+
+ if (sample_index_ + step - 1 < pause_index_)
+ return step;
+
+ if (frequency2_ < kFrequencyEpsilon)
+ return base::size(libmems::fakes::kFakeAccelSamples);
+
+ int step2 = dev_frequency2_ / frequency2_;
+
+ return std::max(pause_index_ - sample_index_ + 1, step2);
+}
+
+} // namespace fakes
+
+} // namespace iioservice
diff --git a/iioservice/daemon/test_fakes.h b/iioservice/daemon/test_fakes.h
new file mode 100644
index 0000000..6e580e6
--- /dev/null
+++ b/iioservice/daemon/test_fakes.h
@@ -0,0 +1,129 @@
+// 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.
+
+#ifndef IIOSERVICE_DAEMON_TEST_FAKES_H_
+#define IIOSERVICE_DAEMON_TEST_FAKES_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/callback.h>
+#include <base/single_thread_task_runner.h>
+#include <base/threading/thread.h>
+#include <mojo/public/cpp/bindings/receiver.h>
+
+#include <libmems/iio_device.h>
+#include <libmems/test_fakes.h>
+
+#include "iioservice/daemon/samples_handler.h"
+#include "mojo/sensor.mojom.h"
+
+namespace iioservice {
+
+namespace fakes {
+
+constexpr char kAccelDeviceName[] = "FakeAccelDevice";
+constexpr int kAccelDeviceId = 1;
+
+constexpr char kFakeSamplingFrequencyAvailable[] =
+ "0.000000 1.250000 40.000000";
+
+constexpr int kPauseIndex = 50;
+
+class FakeSamplesHandler : public SamplesHandler {
+ public:
+ using ScopedFakeSamplesHandler =
+ std::unique_ptr<FakeSamplesHandler, decltype(&SamplesHandlerDeleter)>;
+
+ static ScopedFakeSamplesHandler CreateWithFifo(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ libmems::fakes::FakeIioDevice* fake_iio_device,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback);
+
+ void ResumeReading();
+ void CheckRequestedFrequency(double max_freq);
+
+ private:
+ FakeSamplesHandler(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+ libmems::fakes::FakeIioDevice* fake_iio_device,
+ double min_freq,
+ double max_freq,
+ OnSampleUpdatedCallback on_sample_updated_callback,
+ OnErrorOccurredCallback on_error_occurred_callback);
+
+ void ResumeReadingOnThread();
+ void CheckRequestedFrequencyOnThread(double max_freq);
+
+ libmems::fakes::FakeIioDevice* fake_iio_device_;
+
+ base::WeakPtrFactory<FakeSamplesHandler> weak_factory_{this};
+};
+
+class FakeSamplesObserver : public cros::mojom::SensorDeviceSamplesObserver {
+ public:
+ static void ObserverDeleter(FakeSamplesObserver* observer);
+ using ScopedFakeSamplesObserver =
+ std::unique_ptr<FakeSamplesObserver, decltype(&ObserverDeleter)>;
+
+ static ScopedFakeSamplesObserver Create(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ base::Closure quit_closure,
+ libmems::IioDevice* device,
+ std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+ double frequency,
+ double frequency2,
+ double dev_frequency,
+ double dev_frequency2,
+ int pause_index = kPauseIndex);
+
+ void OnSampleUpdated(const base::flat_map<int32_t, int64_t>& sample) override;
+
+ void OnErrorOccurred(cros::mojom::ObserverErrorType type) override;
+
+ private:
+ FakeSamplesObserver(
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
+ base::Closure quit_closure,
+ libmems::IioDevice* device,
+ std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures,
+ double frequency,
+ double frequency2,
+ double dev_frequency,
+ double dev_frequency2,
+ int pause_index = kPauseIndex);
+
+ int GetStep();
+
+ scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
+
+ base::Closure quit_closure_;
+
+ libmems::IioDevice* device_;
+
+ std::multiset<std::pair<int, cros::mojom::ObserverErrorType>> failures_;
+
+ double frequency_;
+ double frequency2_;
+ double dev_frequency_;
+ double dev_frequency2_;
+ int pause_index_;
+
+ int sample_index_ = 0;
+
+ mojo::Receiver<cros::mojom::SensorDeviceSamplesObserver> receiver_;
+
+ base::WeakPtrFactory<FakeSamplesObserver> weak_factory_{this};
+};
+
+} // namespace fakes
+
+} // namespace iioservice
+
+#endif // IIOSERVICE_DAEMON_TEST_FAKES_H_
diff --git a/iioservice/daemon/testrunner.cc b/iioservice/daemon/testrunner.cc
new file mode 100644
index 0000000..4caee59
--- /dev/null
+++ b/iioservice/daemon/testrunner.cc
@@ -0,0 +1,17 @@
+// 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 <gtest/gtest.h>
+
+#include <base/at_exit.h>
+#include <mojo/core/embedder/embedder.h>
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ base::AtExitManager at_exit;
+
+ mojo::core::Init();
+
+ return RUN_ALL_TESTS();
+}