libmems: Add iio event support
Adds iio event support in libmems with fake classes.
BUG=b:176431550
TEST=none
Change-Id: I72d3a0e44b6d719996d8ea680b4739318481e9ac
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/3322433
Tested-by: Cheng-Hao Yang <chenghaoyang@chromium.org>
Auto-Submit: Cheng-Hao Yang <chenghaoyang@chromium.org>
Reviewed-by: Gwendal Grignou <gwendal@chromium.org>
Commit-Queue: Cheng-Hao Yang <chenghaoyang@chromium.org>
NOKEYCHECK=True
GitOrigin-RevId: 8f2b3ecab9bacc26ac3d0b0a19f07ddd8ae67337
diff --git a/BUILD.gn b/BUILD.gn
index 8377645..7b7d2a5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -26,11 +26,14 @@
shared_library("libmems") {
sources = [
+ "common_types.cc",
"iio_channel_impl.cc",
"iio_context_impl.cc",
"iio_device.cc",
"iio_device_impl.cc",
"iio_device_trigger_impl.cc",
+ "iio_event.cc",
+ "iio_event_impl.cc",
]
configs += [ ":target_defaults_pkg_deps" ]
install_path = "lib"
@@ -77,7 +80,10 @@
}
executable("libmems_testrunner") {
- sources = [ "iio_device_test.cc" ]
+ sources = [
+ "iio_device_test.cc",
+ "iio_event_test.cc",
+ ]
configs += [
"//common-mk:test",
":libmems_testrunner_pkg_deps",
diff --git a/common_types.cc b/common_types.cc
new file mode 100644
index 0000000..28ee350
--- /dev/null
+++ b/common_types.cc
@@ -0,0 +1,19 @@
+// Copyright 2022 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 "libmems/common_types.h"
+
+namespace libmems {
+
+uint64_t IioEventCode(iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction dir,
+ int channel) {
+ return (uint64_t)chan_type << 32 | (uint64_t)dir << 48 |
+ (uint64_t)event_type << 56 | (uint64_t)channel;
+ // TODO(chenghaoyang): use the existing IIO_EVENT_CODE instead.
+ // return IIO_EVENT_CODE(chan_type_, 0, 0, dir, event_type_, channel_, 0, 0);
+}
+
+} // namespace libmems
diff --git a/common_types.h b/common_types.h
index 36b9e5b..b03d8f0 100644
--- a/common_types.h
+++ b/common_types.h
@@ -5,10 +5,18 @@
#ifndef LIBMEMS_COMMON_TYPES_H_
#define LIBMEMS_COMMON_TYPES_H_
+#include <linux/iio/types.h>
#include <vector>
+#include "libmems/export.h"
+
namespace libmems {
+LIBMEMS_EXPORT uint64_t IioEventCode(iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction dir,
+ int channel);
+
constexpr int kErrorBufferSize = 256;
constexpr int kReadAttrBufferSize = 256;
@@ -33,6 +41,7 @@
constexpr char kTimestampAttr[] = "timestamp";
constexpr char kSysDevString[] = "/sys/bus/iio/devices";
+constexpr char kDevString[] = "/dev";
constexpr char kAccelName[] = "accel";
constexpr char kGyroName[] = "anglvel";
diff --git a/export.h b/export.h
index 13e36c6..3d6edfc 100644
--- a/export.h
+++ b/export.h
@@ -5,6 +5,6 @@
#ifndef LIBMEMS_EXPORT_H_
#define LIBMEMS_EXPORT_H_
-#define LIBMEMS_EXPORT __attribute__((__visibility__("default")))
+#define LIBMEMS_EXPORT __attribute__((visibility("default")))
#endif // LIBMEMS_EXPORT_H_
diff --git a/iio_device.cc b/iio_device.cc
index b883991..08c72b7 100644
--- a/iio_device.cc
+++ b/iio_device.cc
@@ -12,6 +12,7 @@
#include "libmems/common_types.h"
#include "libmems/iio_channel.h"
+#include "libmems/iio_event.h"
namespace libmems {
@@ -69,6 +70,28 @@
return nullptr;
}
+std::vector<IioEvent*> IioDevice::GetAllEvents() {
+ std::vector<IioEvent*> events;
+ for (const auto& event : events_)
+ events.push_back(event.get());
+
+ return events;
+}
+
+void IioDevice::EnableAllEvents() {
+ for (const auto& event : events_) {
+ if (!event->SetEnabledAndCheck(true))
+ LOG(ERROR) << "Failed to enable event: " << event->GetChannelNumber();
+ }
+}
+
+IioEvent* IioDevice::GetEvent(int32_t index) {
+ if (index < 0 || index >= events_.size())
+ return nullptr;
+
+ return events_[index].get();
+}
+
bool IioDevice::GetMinMaxFrequency(double* min_freq, double* max_freq) {
auto available_opt = ReadStringAttribute(kSamplingFrequencyAvailable);
if (!available_opt.has_value()) {
diff --git a/iio_device.h b/iio_device.h
index 3a0b997..67e5e8d 100644
--- a/iio_device.h
+++ b/iio_device.h
@@ -7,6 +7,7 @@
#include <iio.h>
+#include <linux/iio/events.h>
#include <memory>
#include <string>
#include <vector>
@@ -18,6 +19,7 @@
#include <base/time/time.h>
#include "libmems/export.h"
+#include "libmems/iio_event.h"
namespace libmems {
@@ -121,6 +123,16 @@
// It will return nullptr if no such channel can be found.
IioChannel* GetChannel(const std::string& name);
+ // Returns all events belonging to this device.
+ std::vector<IioEvent*> GetAllEvents();
+
+ // Enables all events belonging to this device.
+ void EnableAllEvents();
+
+ // Finds the IIO event |index| as the index in this device and returns it.
+ // It will return nullptr if no such channel can be found.
+ IioEvent* GetEvent(int32_t index);
+
// Returns the sample size in this device.
// Returns base::nullopt on failure.
virtual base::Optional<size_t> GetSampleSize() const = 0;
@@ -167,6 +179,14 @@
// used along with EnableBuffer.
virtual void FreeBuffer() = 0;
+ // Gets the file descriptor to poll for events.
+ // Returns base::nullopt on failure.
+ virtual base::Optional<int32_t> GetEventFd() = 0;
+
+ // Reads & returns one event.
+ // Returns base::nullopt on failure.
+ virtual base::Optional<iio_event_data> ReadEvent() = 0;
+
bool GetMinMaxFrequency(double* min_freq, double* max_freq);
protected:
@@ -183,6 +203,7 @@
IioDevice& operator=(const IioDevice&) = delete;
std::vector<ChannelData> channels_;
+ std::vector<std::unique_ptr<IioEvent>> events_;
};
} // namespace libmems
diff --git a/iio_device_impl.cc b/iio_device_impl.cc
index 3501b08..3bd07bc 100644
--- a/iio_device_impl.cc
+++ b/iio_device_impl.cc
@@ -2,11 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <fcntl.h>
#include <memory>
#include <string>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <utility>
#include <vector>
#include <base/check.h>
+#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
@@ -17,6 +22,7 @@
#include "libmems/iio_context_impl.h"
#include "libmems/iio_device_impl.h"
#include "libmems/iio_device_trigger_impl.h"
+#include "libmems/iio_event_impl.h"
#define ERROR_BUFFER_SIZE 256
@@ -64,6 +70,20 @@
channel, GetId(), GetName() ? GetName() : "null");
channels_[i].chn_id = channels_[i].chn->GetId();
}
+
+ base::FileEnumerator file_enumerator(GetPath().Append("events"), false,
+ base::FileEnumerator::FILES, "*_en");
+ for (base::FilePath file = file_enumerator.Next(); !file.empty();
+ file = file_enumerator.Next()) {
+ auto iio_event = IioEventImpl::Create(file);
+ if (iio_event)
+ events_.push_back(std::move(iio_event));
+ }
+
+ // To read events and samples at the same time, the event fd must be created
+ // first, to avoid opening /dev/iio:deviceX twice.
+ if (!GetAllEvents().empty() && !GetAllChannels().empty())
+ GetEventFd();
}
IioContext* IioDeviceImpl::GetContext() const {
@@ -342,6 +362,44 @@
buffer_.reset();
}
+base::Optional<int32_t> IioDeviceImpl::GetEventFd() {
+ if (event_fd_.has_value())
+ return event_fd_;
+
+ const std::string file =
+ base::StringPrintf("%s/%s", kDevString, iio_device_get_id(device_));
+ int fd = open(file.c_str(), O_RDONLY);
+ if (fd == -1) {
+ LOG(ERROR) << "Unable to open file " << file;
+ return base::nullopt;
+ }
+
+ int event_fd = -1;
+ int ret = ioctl(fd, IIO_GET_EVENT_FD_IOCTL, &event_fd);
+ close(fd);
+
+ if (ret < 0 || event_fd == -1) {
+ LOG(ERROR) << "Unable to open event descriptor for file " << file;
+ return base::nullopt;
+ }
+
+ event_fd_ = event_fd;
+ return event_fd_;
+}
+
+base::Optional<iio_event_data> IioDeviceImpl::ReadEvent() {
+ if (!event_fd_ && !GetEventFd().has_value())
+ return base::nullopt;
+
+ struct iio_event_data iio_event_buf = {0};
+ if (read(event_fd_.value(), &iio_event_buf, sizeof(iio_event_buf)) == -1) {
+ LOG(ERROR) << "Failed to read from FD " << event_fd_.value();
+ return base::nullopt;
+ }
+
+ return iio_event_buf;
+}
+
// static
void IioDeviceImpl::IioBufferDeleter(iio_buffer* buffer) {
iio_buffer_cancel(buffer);
diff --git a/iio_device_impl.h b/iio_device_impl.h
index 8261d8a..058def1 100644
--- a/iio_device_impl.h
+++ b/iio_device_impl.h
@@ -73,6 +73,9 @@
base::Optional<IioSample> ReadSample() override;
void FreeBuffer() override;
+ base::Optional<int32_t> GetEventFd() override;
+ base::Optional<iio_event_data> ReadEvent() override;
+
private:
static void IioBufferDeleter(iio_buffer* buffer);
@@ -85,6 +88,7 @@
using ScopedBuffer = std::unique_ptr<iio_buffer, decltype(&IioBufferDeleter)>;
ScopedBuffer buffer_;
+ base::Optional<int32_t> event_fd_;
std::string log_prefix_;
};
diff --git a/iio_device_trigger_impl.h b/iio_device_trigger_impl.h
index fb9915e..a2ea92a 100644
--- a/iio_device_trigger_impl.h
+++ b/iio_device_trigger_impl.h
@@ -78,6 +78,9 @@
base::Optional<IioSample> ReadSample() override { return base::nullopt; }
void FreeBuffer() override {}
+ base::Optional<int32_t> GetEventFd() override { return base::nullopt; }
+ base::Optional<iio_event_data> ReadEvent() override { return base::nullopt; }
+
private:
IioContextImpl* context_; // non-owned
iio_device* const trigger_; // non-owned
diff --git a/iio_event.cc b/iio_event.cc
new file mode 100644
index 0000000..fecd289
--- /dev/null
+++ b/iio_event.cc
@@ -0,0 +1,100 @@
+// Copyright 2022 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 "libmems/iio_event.h"
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+
+namespace libmems {
+
+IioEvent::~IioEvent() = default;
+
+bool IioEvent::SetEnabledAndCheck(bool en) {
+ SetEnabled(en);
+ return en == IsEnabled();
+}
+
+bool IioEvent::MatchMask(uint64_t mask) {
+ if (IIO_EVENT_CODE_EXTRACT_CHAN_TYPE(mask) != chan_type_)
+ return false;
+
+ if (IIO_EVENT_CODE_EXTRACT_TYPE(mask) != event_type_)
+ return false;
+
+ if (direction_ != iio_event_direction::IIO_EV_DIR_EITHER &&
+ IIO_EVENT_CODE_EXTRACT_DIR(mask) != direction_) {
+ return false;
+ }
+
+ if (channel_ != -1 && IIO_EVENT_CODE_EXTRACT_CHAN(mask) != channel_)
+ return false;
+
+ return true;
+}
+
+iio_chan_type IioEvent::GetChannelType() const {
+ return chan_type_;
+}
+
+iio_event_type IioEvent::GetEventType() const {
+ return event_type_;
+}
+
+iio_event_direction IioEvent::GetDirection() const {
+ return direction_;
+}
+
+int IioEvent::GetChannelNumber() const {
+ return channel_;
+}
+
+base::Optional<int64_t> IioEvent::ReadNumberAttribute(
+ const std::string& name) const {
+ base::Optional<std::string> value = ReadStringAttribute(name);
+ if (!value.has_value())
+ return base::nullopt;
+
+ int64_t number;
+ if (!base::StringToInt64(value.value(), &number)) {
+ LOG(ERROR) << "Cannot convert string to int64: " << value.value();
+ return base::nullopt;
+ }
+
+ return number;
+}
+
+base::Optional<double> IioEvent::ReadDoubleAttribute(
+ const std::string& name) const {
+ base::Optional<std::string> value = ReadStringAttribute(name);
+ if (!value.has_value())
+ return base::nullopt;
+
+ double number;
+ if (!base::StringToDouble(value.value(), &number)) {
+ LOG(ERROR) << "Cannot convert string to double: " << value.value();
+ return base::nullopt;
+ }
+
+ return number;
+}
+
+bool IioEvent::WriteNumberAttribute(const std::string& name, int64_t value) {
+ return WriteStringAttribute(name, base::NumberToString(value));
+}
+
+bool IioEvent::WriteDoubleAttribute(const std::string& name, double value) {
+ return WriteStringAttribute(name, base::NumberToString(value));
+}
+
+IioEvent::IioEvent(iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction direction,
+ int channel)
+ : chan_type_(chan_type),
+ event_type_(event_type),
+ direction_(direction),
+ channel_(channel) {}
+
+} // namespace libmems
diff --git a/iio_event.h b/iio_event.h
new file mode 100644
index 0000000..86304ff
--- /dev/null
+++ b/iio_event.h
@@ -0,0 +1,92 @@
+// Copyright 2022 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 LIBMEMS_IIO_EVENT_H_
+#define LIBMEMS_IIO_EVENT_H_
+
+#include <iio.h>
+
+#include <linux/iio/events.h>
+#include <linux/iio/types.h>
+#include <string>
+
+#include <base/optional.h>
+
+#include "libmems/export.h"
+
+namespace libmems {
+
+// The IioEvent represents an event on an IIO device, for example the
+// channel: 0, event type: threshold, direction: either in proximity sensor.
+// Events can be enabled and/or disabled via this class.
+class LIBMEMS_EXPORT IioEvent {
+ public:
+ virtual ~IioEvent();
+
+ // Returns true if this event is enabled.
+ virtual bool IsEnabled() const = 0;
+
+ // Sets this event's enabled status to |en|.
+ virtual void SetEnabled(bool en) = 0;
+
+ // Sets the event's enabled status to |en|,
+ // and returns true if the event's enabled status matches
+ // what was set, false otherwise.
+ bool SetEnabledAndCheck(bool en);
+
+ // Checks if the mask (iio_event_data.id) matches this event.
+ bool MatchMask(uint64_t mask);
+
+ iio_chan_type GetChannelType() const;
+ iio_event_type GetEventType() const;
+ iio_event_direction GetDirection() const;
+ // Returns -1 if invalid
+ int GetChannelNumber() const;
+
+ // Reads the |name| attribute of this event and returns the value
+ // as a string. It will return base::nullopt if the attribute cannot
+ // be read.
+ virtual base::Optional<std::string> ReadStringAttribute(
+ const std::string& name) const = 0;
+
+ // Reads the |name| attribute of this event and returns the value
+ // as a signed number. It will return base::nullopt if the attribute
+ // cannot be read or is not a valid number.
+ base::Optional<int64_t> ReadNumberAttribute(const std::string& name) const;
+
+ // Reads the |name| attribute of this device and returns the value
+ // as a double precision floating point. It will return base::nullopt
+ // if the attribute cannot be read or is not a valid number.
+ base::Optional<double> ReadDoubleAttribute(const std::string& name) const;
+
+ // Writes the string |value| to the attribute |name| of this event. Returns
+ // false if an error occurs.
+ virtual bool WriteStringAttribute(const std::string& name,
+ const std::string& value) = 0;
+
+ // Writes the number |value| to the attribute |name| of this event. Returns
+ // false if an error occurs.
+ bool WriteNumberAttribute(const std::string& name, int64_t value);
+
+ // Writes the floating point |value| to the attribute |name| of this event.
+ // Returns false if an error occurs.
+ bool WriteDoubleAttribute(const std::string& name, double value);
+
+ protected:
+ IioEvent(iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction direction,
+ int channel);
+ IioEvent(const IioEvent&) = delete;
+ IioEvent& operator=(const IioEvent&) = delete;
+
+ const iio_chan_type chan_type_;
+ const iio_event_type event_type_;
+ const iio_event_direction direction_;
+ const int channel_; // -1 if invalid
+};
+
+} // namespace libmems
+
+#endif // LIBMEMS_IIO_EVENT_H_
diff --git a/iio_event_impl.cc b/iio_event_impl.cc
new file mode 100644
index 0000000..09855cd
--- /dev/null
+++ b/iio_event_impl.cc
@@ -0,0 +1,208 @@
+// Copyright 2022 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 "libmems/iio_event_impl.h"
+
+#include <map>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+
+namespace libmems {
+
+namespace {
+
+// iio_chan_type strings.
+constexpr char kProximity[] = "proximity";
+
+// iio_event_type strings.
+constexpr char kThresh[] = "thresh";
+constexpr char kMag[] = "mag";
+constexpr char kRoc[] = "roc";
+constexpr char kAdaptive[] = "adaptive";
+constexpr char kChange[] = "change";
+
+// iio_event_direction strings.
+constexpr char kEither[] = "either";
+constexpr char kRising[] = "rising";
+constexpr char kFalling[] = "falling";
+constexpr char kNone[] = "none";
+
+const std::map<std::string, iio_chan_type> kChanTypeMap = {
+ {"proximity", iio_chan_type::IIO_PROXIMITY}};
+
+bool MatchString(std::string chan_str, const char prefix[]) {
+ auto prefix_len = strlen(prefix);
+ if (chan_str.size() < prefix_len)
+ return false;
+
+ return chan_str.compare(0, prefix_len, prefix) == 0;
+}
+
+base::Optional<iio_chan_type> GetChanType(std::string chan_str) {
+ if (MatchString(chan_str, kProximity))
+ return iio_chan_type::IIO_PROXIMITY;
+
+ return base::nullopt;
+}
+
+const char* GetChanTypeStr(iio_chan_type chan_type) {
+ switch (chan_type) {
+ case iio_chan_type::IIO_PROXIMITY:
+ return kProximity;
+
+ default:
+ return nullptr;
+ }
+}
+
+int GetChannel(std::string chan_str, iio_chan_type chan_type) {
+ auto chan_type_str = GetChanTypeStr(chan_type);
+ DCHECK(chan_type_str);
+
+ std::string substr = chan_str.substr(strlen(chan_type_str));
+ int channel;
+ if (!base::StringToInt(substr, &channel)) {
+ LOG(ERROR) << "Cannot convert string to int: " << substr;
+ return -1;
+ }
+
+ return channel;
+}
+
+base::Optional<iio_event_type> GetEventType(std::string event_type_str,
+ std::string prev_str) {
+ if (event_type_str.compare(kThresh) == 0)
+ return iio_event_type::IIO_EV_TYPE_THRESH;
+ if (event_type_str.compare(kMag) == 0)
+ return iio_event_type::IIO_EV_TYPE_MAG;
+ if (event_type_str.compare(kRoc) == 0)
+ return iio_event_type::IIO_EV_TYPE_ROC;
+ if (event_type_str.compare(kAdaptive) == 0) {
+ if (prev_str.compare(kThresh) == 0)
+ return iio_event_type::IIO_EV_TYPE_THRESH_ADAPTIVE;
+ if (prev_str.compare(kMag) == 0)
+ return iio_event_type::IIO_EV_TYPE_MAG_ADAPTIVE;
+ }
+ if (event_type_str.compare(kChange) == 0)
+ return iio_event_type::IIO_EV_TYPE_CHANGE;
+
+ return base::nullopt;
+}
+
+base::Optional<iio_event_direction> GetDirection(std::string direction_str) {
+ if (direction_str.compare(kEither) == 0)
+ return iio_event_direction::IIO_EV_DIR_EITHER;
+ if (direction_str.compare(kRising) == 0)
+ return iio_event_direction::IIO_EV_DIR_RISING;
+ if (direction_str.compare(kFalling) == 0)
+ return iio_event_direction::IIO_EV_DIR_FALLING;
+ if (direction_str.compare(kNone) == 0)
+ return iio_event_direction::IIO_EV_DIR_NONE;
+
+ return base::nullopt;
+}
+
+} // namespace
+
+// static
+std::unique_ptr<IioEventImpl> IioEventImpl::Create(base::FilePath file) {
+ std::string file_name = file.BaseName().value();
+ std::vector<std::string> pieces = base::SplitString(
+ file_name, "_", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+ if (pieces.size() < 5 || pieces.front().compare("in") != 0 ||
+ pieces.back().compare("en") != 0) {
+ return nullptr;
+ }
+
+ base::Optional<iio_chan_type> chan_type = ::libmems::GetChanType(pieces[1]);
+ if (!chan_type.has_value())
+ return nullptr;
+
+ int channel = ::libmems::GetChannel(pieces[1], chan_type.value());
+
+ base::Optional<iio_event_type> event_type = ::libmems::GetEventType(
+ pieces[pieces.size() - 3], pieces[pieces.size() - 4]);
+ if (!event_type.has_value())
+ return nullptr;
+
+ base::Optional<iio_event_direction> direction =
+ ::libmems::GetDirection(pieces[pieces.size() - 2]);
+ if (!direction.has_value())
+ return nullptr;
+
+ std::unique_ptr<IioEventImpl> iio_event_impl(new IioEventImpl(
+ file.DirName(), file_name.substr(0, file_name.size() - 2),
+ chan_type.value(), event_type.value(), direction.value(), channel));
+
+ return iio_event_impl;
+}
+
+IioEventImpl::IioEventImpl(base::FilePath event_dir,
+ std::string event_pattern,
+ iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction direction,
+ int channel)
+ : IioEvent(chan_type, event_type, direction, channel),
+ event_dir_(event_dir),
+ event_pattern_(event_pattern) {}
+
+bool IioEventImpl::IsEnabled() const {
+ base::FilePath file = GetAttributePath("en");
+
+ std::string en;
+ if (!ReadFileToString(file, &en)) {
+ LOG(ERROR) << "Failed to read file: " << file.value();
+ return false;
+ }
+
+ if (!en.empty() && en.front() == '1')
+ return true;
+
+ return false;
+}
+
+void IioEventImpl::SetEnabled(bool en) {
+ base::FilePath file = GetAttributePath("en");
+
+ if (!WriteFile(file, en ? "1\n" : "0\n"))
+ LOG(ERROR) << "Failed to write file: " << file.value();
+}
+
+base::Optional<std::string> IioEventImpl::ReadStringAttribute(
+ const std::string& name) const {
+ base::FilePath file = GetAttributePath(name);
+
+ std::string value;
+ if (!ReadFileToString(file, &value)) {
+ LOG(ERROR) << "Failed to read file: " << file.value();
+ return base::nullopt;
+ }
+
+ return value;
+}
+
+bool IioEventImpl::WriteStringAttribute(const std::string& name,
+ const std::string& value) {
+ base::FilePath file = GetAttributePath(name);
+
+ if (!WriteFile(file, value)) {
+ LOG(ERROR) << "Failed to write file: " << file.value();
+ return false;
+ }
+
+ return true;
+}
+
+base::FilePath IioEventImpl::GetAttributePath(
+ const std::string& attribute) const {
+ return event_dir_.Append(event_pattern_ + attribute);
+}
+
+} // namespace libmems
diff --git a/iio_event_impl.h b/iio_event_impl.h
new file mode 100644
index 0000000..5248dc8
--- /dev/null
+++ b/iio_event_impl.h
@@ -0,0 +1,53 @@
+// Copyright 2022 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 LIBMEMS_IIO_EVENT_IMPL_H_
+#define LIBMEMS_IIO_EVENT_IMPL_H_
+
+#include <memory>
+#include <string>
+
+#include <base/files/file_path.h>
+
+#include "libmems/export.h"
+#include "libmems/iio_event.h"
+
+namespace libmems {
+
+class LIBMEMS_EXPORT IioEventImpl : public IioEvent {
+ public:
+ // in_[chan_type][channel]_[event_type]_[direction]_en.
+ static std::unique_ptr<IioEventImpl> Create(base::FilePath file);
+
+ IioEventImpl(const IioEventImpl&) = delete;
+ IioEventImpl& operator=(const IioEventImpl&) = delete;
+ ~IioEventImpl() override = default;
+
+ // IioEvent overrides.
+ bool IsEnabled() const override;
+ void SetEnabled(bool en) override;
+ base::Optional<std::string> ReadStringAttribute(
+ const std::string& name) const override;
+ bool WriteStringAttribute(const std::string& name,
+ const std::string& value) override;
+
+ private:
+ IioEventImpl(base::FilePath event_dir,
+ std::string event_pattern,
+ iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction direction,
+ int channel);
+
+ base::FilePath GetAttributePath(const std::string& attribute) const;
+
+ // /sys/bus/iio/devices/iio:deviceX/events/.
+ base::FilePath event_dir_;
+ // Ex: "in_proximity0_thresh_either_%s".
+ std::string event_pattern_;
+};
+
+} // namespace libmems
+
+#endif // LIBMEMS_IIO_EVENT_IMPL_H_
diff --git a/iio_event_test.cc b/iio_event_test.cc
new file mode 100644
index 0000000..3023816
--- /dev/null
+++ b/iio_event_test.cc
@@ -0,0 +1,76 @@
+// Copyright 2022 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 "libmems/common_types.h"
+#include "libmems/test_fakes.h"
+
+namespace libmems {
+
+namespace {
+
+class IioEventTestOnMatchMaskWithParam
+ : public ::testing::TestWithParam<std::tuple<iio_chan_type,
+ iio_event_type,
+ iio_event_direction,
+ int,
+ uint64_t,
+ bool>> {
+ protected:
+ void SetUp() override {
+ event_ = std::make_unique<libmems::fakes::FakeIioEvent>(
+ std::get<0>(GetParam()), std::get<1>(GetParam()),
+ std::get<2>(GetParam()), std::get<3>(GetParam()));
+ }
+
+ std::unique_ptr<libmems::fakes::FakeIioEvent> event_;
+};
+
+TEST_P(IioEventTestOnMatchMaskWithParam, MatchMask) {
+ EXPECT_EQ(event_->MatchMask(std::get<4>(GetParam())),
+ std::get<5>(GetParam()));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ IioEventTestOnMatchMaskWithParamRun,
+ IioEventTestOnMatchMaskWithParam,
+ ::testing::Values(
+ std::make_tuple(iio_chan_type::IIO_PROXIMITY,
+ iio_event_type::IIO_EV_TYPE_THRESH,
+ iio_event_direction::IIO_EV_DIR_EITHER,
+ 0,
+ 0,
+ false),
+ std::make_tuple(iio_chan_type::IIO_PROXIMITY,
+ iio_event_type::IIO_EV_TYPE_THRESH,
+ iio_event_direction::IIO_EV_DIR_EITHER,
+ 0,
+ IioEventCode(iio_chan_type::IIO_PROXIMITY,
+ iio_event_type::IIO_EV_TYPE_MAG,
+ iio_event_direction::IIO_EV_DIR_EITHER,
+ 0),
+ false),
+ std::make_tuple(iio_chan_type::IIO_PROXIMITY,
+ iio_event_type::IIO_EV_TYPE_THRESH,
+ iio_event_direction::IIO_EV_DIR_EITHER,
+ 0,
+ IioEventCode(iio_chan_type::IIO_PROXIMITY,
+ iio_event_type::IIO_EV_TYPE_THRESH,
+ iio_event_direction::IIO_EV_DIR_EITHER,
+ 0),
+ true),
+ std::make_tuple(iio_chan_type::IIO_PROXIMITY,
+ iio_event_type::IIO_EV_TYPE_THRESH,
+ iio_event_direction::IIO_EV_DIR_EITHER,
+ 0,
+ IioEventCode(iio_chan_type::IIO_PROXIMITY,
+ iio_event_type::IIO_EV_TYPE_THRESH,
+ iio_event_direction::IIO_EV_DIR_RISING,
+ 0),
+ true)));
+
+} // namespace
+
+} // namespace libmems
diff --git a/test_fakes.cc b/test_fakes.cc
index bfb6c9f..b337cd3 100644
--- a/test_fakes.cc
+++ b/test_fakes.cc
@@ -4,6 +4,7 @@
#include "libmems/test_fakes.h"
+#include <linux/iio/events.h>
#include <sys/eventfd.h>
#include <iterator>
@@ -85,6 +86,44 @@
return base::nullopt;
}
+FakeIioEvent::FakeIioEvent(iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction direction,
+ int channel)
+ : IioEvent(chan_type, event_type, direction, channel) {}
+
+void FakeIioEvent::SetEnabled(bool en) {
+ enabled_ = en;
+}
+
+base::Optional<std::string> FakeIioEvent::ReadStringAttribute(
+ const std::string& name) const {
+ auto k = text_attributes_.find(name);
+ if (k == text_attributes_.end())
+ return base::nullopt;
+ return k->second;
+}
+
+bool FakeIioEvent::WriteStringAttribute(const std::string& name,
+ const std::string& value) {
+ text_attributes_[name] = value;
+ return true;
+}
+
+base::Optional<uint64_t> FakeIioEvent::GetData(int index) {
+ if (index >= kEventNumber)
+ return base::nullopt;
+
+ iio_event_direction dir =
+ (direction_ == iio_event_direction::IIO_EV_DIR_EITHER)
+ ? (dir_turn_ ? iio_event_direction::IIO_EV_DIR_RISING
+ : iio_event_direction::IIO_EV_DIR_FALLING)
+ : direction_;
+
+ dir_turn_ = !dir_turn_;
+ return IioEventCode(chan_type_, event_type_, dir, channel_);
+}
+
FakeIioDevice::FakeIioDevice(FakeIioContext* ctx,
const std::string& name,
int id)
@@ -156,13 +195,13 @@
int fd = eventfd(0, 0);
CHECK_GE(fd, 0);
- sample_fd_.reset(fd);
+ sample_fd_.fd.reset(fd);
- if (sample_index_ >= std::size(kFakeAccelSamples) || is_paused_)
+ if (sample_fd_.index >= base::size(kFakeAccelSamples) || sample_fd_.is_paused)
return true;
- if (!WriteByte()) {
- ClosePipe();
+ if (!sample_fd_.WriteByte()) {
+ sample_fd_.ClosePipe();
return false;
}
@@ -177,18 +216,18 @@
}
base::Optional<IioDevice::IioSample> FakeIioDevice::ReadSample() {
- if (is_paused_ || disabled_fd_ || !sample_fd_.is_valid())
+ if (sample_fd_.is_paused || disabled_fd_ || !sample_fd_.is_valid())
return base::nullopt;
- if (!failed_read_queue_.empty()) {
- CHECK_GE(failed_read_queue_.top(), sample_index_);
- if (failed_read_queue_.top() == sample_index_) {
- failed_read_queue_.pop();
+ if (!sample_fd_.failed_read_queue.empty()) {
+ CHECK_GE(sample_fd_.failed_read_queue.top(), sample_fd_.index);
+ if (sample_fd_.failed_read_queue.top() == sample_fd_.index) {
+ sample_fd_.failed_read_queue.pop();
return base::nullopt;
}
}
- if (!ReadByte())
+ if (!sample_fd_.ReadByte())
return base::nullopt;
base::Optional<double> freq_opt = ReadDoubleAttribute(kSamplingFrequencyAttr);
@@ -206,7 +245,7 @@
auto channels = GetAllChannels();
for (int32_t i = 0; i < channels.size(); ++i) {
FakeIioChannel* chn = dynamic_cast<FakeIioChannel*>(channels[i]);
- auto value = chn->GetData(sample_index_);
+ auto value = chn->GetData(sample_fd_.index);
if (!value.has_value()) {
LOG(ERROR) << "Channel: " << channels_[i].chn_id << " has no sample";
return base::nullopt;
@@ -215,93 +254,190 @@
sample[i] = value.value();
}
- sample_index_ += 1;
+ sample_fd_.index += 1;
- if (sample_index_ < std::size(kFakeAccelSamples)) {
- if (pause_index_.has_value() && sample_index_ == pause_index_.value())
- SetPause();
- else if (!WriteByte())
+ if (sample_fd_.index < base::size(kFakeAccelSamples)) {
+ if (sample_fd_.pause_index.has_value() &&
+ sample_fd_.index == sample_fd_.pause_index.value()) {
+ sample_fd_.SetPause();
+ } else if (!sample_fd_.WriteByte()) {
return base::nullopt;
+ }
}
return sample;
}
void FakeIioDevice::FreeBuffer() {
- ClosePipe();
+ sample_fd_.ClosePipe();
+}
+
+base::Optional<int32_t> FakeIioDevice::GetEventFd() {
+ if (disabled_fd_)
+ return base::nullopt;
+
+ if (!event_fd_.is_valid()) {
+ int fd = eventfd(0, 0);
+ CHECK_GE(fd, 0);
+ event_fd_.fd.reset(fd);
+
+ if (event_fd_.index < kEventNumber && !event_fd_.is_paused &&
+ !event_fd_.readable) {
+ if (!event_fd_.WriteByte()) {
+ event_fd_.ClosePipe();
+ return base::nullopt;
+ }
+ }
+ }
+
+ return event_fd_.get();
+}
+
+base::Optional<iio_event_data> FakeIioDevice::ReadEvent() {
+ if (event_fd_.is_paused || disabled_fd_ || !event_fd_.is_valid())
+ return base::nullopt;
+
+ if (!event_fd_.failed_read_queue.empty()) {
+ CHECK_GE(event_fd_.failed_read_queue.top(), event_fd_.index);
+ if (event_fd_.failed_read_queue.top() == event_fd_.index) {
+ event_fd_.failed_read_queue.pop();
+ return base::nullopt;
+ }
+ }
+
+ if (!event_fd_.ReadByte())
+ return base::nullopt;
+
+ iio_event_data data;
+ data.timestamp = 1000000000LL * (int64_t)event_fd_.index;
+
+ auto iio_events = GetAllEvents();
+ if (!iio_events.empty()) {
+ FakeIioEvent* iio_event = dynamic_cast<FakeIioEvent*>(
+ iio_events[event_fd_.index % iio_events.size()]);
+ auto value = iio_event->GetData(event_fd_.index);
+ if (value.has_value()) {
+ data.id = value.value();
+ } else {
+ LOG(ERROR) << "Event: " << event_fd_.index % iio_events.size()
+ << " has no data";
+ }
+ }
+
+ event_fd_.index += 1;
+
+ if (event_fd_.index < kEventNumber) {
+ if (event_fd_.pause_index.has_value() &&
+ event_fd_.index == event_fd_.pause_index.value()) {
+ event_fd_.SetPause();
+ } else if (!event_fd_.WriteByte()) {
+ return base::nullopt;
+ }
+ }
+
+ return data;
}
void FakeIioDevice::DisableFd() {
disabled_fd_ = true;
- if (readable_fd_)
- CHECK(ReadByte());
+ if (sample_fd_.readable)
+ CHECK(sample_fd_.ReadByte());
}
void FakeIioDevice::AddFailedReadAtKthSample(int k) {
- CHECK_GE(k, sample_index_);
+ CHECK_GE(k, sample_fd_.index);
- failed_read_queue_.push(k);
+ sample_fd_.failed_read_queue.push(k);
}
void FakeIioDevice::SetPauseCallbackAtKthSamples(
int k, base::OnceCallback<void()> callback) {
- CHECK_GE(k, sample_index_);
- CHECK_LE(k, std::size(kFakeAccelSamples));
- CHECK(!pause_index_.has_value()); // pause callback hasn't been set
+ CHECK_GE(k, sample_fd_.index);
+ CHECK_LE(k, base::size(kFakeAccelSamples));
+ CHECK(!sample_fd_.pause_index.has_value()); // pause callback hasn't been set
- pause_index_ = k;
- pause_callback_ = std::move(callback);
+ sample_fd_.pause_index = k;
+ sample_fd_.pause_callback = std::move(callback);
- if (pause_index_.value() != sample_index_)
+ if (sample_fd_.pause_index.value() != sample_fd_.index)
return;
- SetPause();
+ sample_fd_.SetPause();
}
void FakeIioDevice::ResumeReadingSamples() {
- CHECK(is_paused_);
-
- is_paused_ = false;
- if (sample_fd_.is_valid() && !readable_fd_)
- CHECK(WriteByte());
+ sample_fd_.ResumeReading();
}
-bool FakeIioDevice::WriteByte() {
- if (!sample_fd_.is_valid())
+void FakeIioDevice::AddFailedReadAtKthEvent(int k) {
+ CHECK_GE(k, event_fd_.index);
+
+ event_fd_.failed_read_queue.push(k);
+}
+
+void FakeIioDevice::SetPauseCallbackAtKthEvents(
+ int k, base::OnceCallback<void()> callback) {
+ CHECK_GE(k, event_fd_.index);
+ CHECK_LE(k, kEventNumber);
+ CHECK(!event_fd_.pause_index.has_value()); // pause callback hasn't been set
+
+ event_fd_.pause_index = k;
+ event_fd_.pause_callback = std::move(callback);
+
+ if (event_fd_.pause_index.value() != event_fd_.index)
+ return;
+
+ event_fd_.SetPause();
+}
+
+void FakeIioDevice::ResumeReadingEvents() {
+ event_fd_.ResumeReading();
+}
+
+bool FakeIioDevice::FakeFD::WriteByte() {
+ if (!is_valid())
return false;
- CHECK(!readable_fd_);
+ CHECK(!readable);
uint64_t val = 1;
- CHECK_EQ(write(sample_fd_.get(), &val, sizeof(uint64_t)), sizeof(uint64_t));
- readable_fd_ = true;
+ CHECK_EQ(write(get(), &val, sizeof(uint64_t)), sizeof(uint64_t));
+ readable = true;
return true;
}
-bool FakeIioDevice::ReadByte() {
- if (!sample_fd_.is_valid())
+bool FakeIioDevice::FakeFD::ReadByte() {
+ if (!is_valid())
return false;
- CHECK(readable_fd_);
+ CHECK(readable);
int64_t val = 1;
- CHECK_EQ(read(sample_fd_.get(), &val, sizeof(uint64_t)), sizeof(uint64_t));
- readable_fd_ = false;
+ CHECK_EQ(read(get(), &val, sizeof(uint64_t)), sizeof(uint64_t));
+ readable = false;
return true;
}
-void FakeIioDevice::ClosePipe() {
- sample_fd_.reset();
+void FakeIioDevice::FakeFD::ClosePipe() {
+ fd.reset();
}
-void FakeIioDevice::SetPause() {
- is_paused_ = true;
- pause_index_.reset();
- std::move(pause_callback_).Run();
- if (readable_fd_)
+void FakeIioDevice::FakeFD::SetPause() {
+ is_paused = true;
+ pause_index.reset();
+ std::move(pause_callback).Run();
+ if (readable)
CHECK(ReadByte());
}
+void FakeIioDevice::FakeFD::ResumeReading() {
+ CHECK(is_paused);
+
+ is_paused = false;
+ if (is_valid() && !readable)
+ CHECK(WriteByte());
+}
+
void FakeIioContext::AddDevice(std::unique_ptr<FakeIioDevice> device) {
CHECK(device.get());
devices_.emplace(device->GetId(), std::move(device));
diff --git a/test_fakes.h b/test_fakes.h
index 6db821f..71bc08c 100644
--- a/test_fakes.h
+++ b/test_fakes.h
@@ -22,6 +22,7 @@
#include "libmems/iio_channel.h"
#include "libmems/iio_context.h"
#include "libmems/iio_device.h"
+#include "libmems/iio_event.h"
namespace libmems {
@@ -29,6 +30,8 @@
constexpr double kFakeSamplingFrequency = 20.0;
+constexpr int kEventNumber = 100;
+
constexpr char kFakeAccelChns[][10] = {"accel_x", "accel_y", "accel_z",
"timestamp"};
@@ -174,6 +177,33 @@
std::map<std::string, double> double_attributes_;
};
+class LIBMEMS_EXPORT FakeIioEvent : public IioEvent {
+ public:
+ FakeIioEvent(iio_chan_type chan_type,
+ iio_event_type event_type,
+ iio_event_direction direction,
+ int channel);
+
+ // IioEvent overrides.
+ bool IsEnabled() const override { return enabled_; }
+ void SetEnabled(bool en) override;
+ base::Optional<std::string> ReadStringAttribute(
+ const std::string& name) const override;
+ bool WriteStringAttribute(const std::string& name,
+ const std::string& value) override;
+
+ // |index| should be within [0, |kEventNumber|). If direction is either,
+ // returns rising and falling by turn.
+ // Returns base::nullopt if |index| is out of bound.
+ base::Optional<uint64_t> GetData(int index);
+
+ private:
+ bool dir_turn_ = true;
+
+ bool enabled_;
+ std::map<std::string, std::string> text_attributes_;
+};
+
class FakeIioContext;
class LIBMEMS_EXPORT FakeIioDevice : public IioDevice {
@@ -214,6 +244,9 @@
void AddChannel(std::unique_ptr<FakeIioChannel> chn) {
channels_.push_back({chn->GetId(), std::move(chn)});
}
+ void AddEvent(std::unique_ptr<FakeIioEvent> event) {
+ events_.push_back(std::move(event));
+ }
bool EnableBuffer(size_t n) override;
bool DisableBuffer() override;
@@ -228,14 +261,18 @@
base::Optional<IioSample> ReadSample() override;
void FreeBuffer() override;
- // Simulates a bad device: not readable fd and fails all reads.
+ base::Optional<int32_t> GetEventFd() override;
+ base::Optional<iio_event_data> ReadEvent() override;
+
+ // Simulates a bad device: not readable fd and fails all reading samples and
+ // events.
void DisableFd();
+
// Simulates some failures when reading the kth sample. Can be called multiple
// times. The user should make sure the kth sample hasn't been read.
void AddFailedReadAtKthSample(int k);
-
// Pauses at kth sample. |callback| is run when (k-1)th sample is retrieved or
- // when this function is called and |k| == |sample_index_|.
+ // when this function is called and |k| == |sample_fd_.index|.
// The user should make sure that there wasn't a pause set and not occurred
// yet, |k| doesn't exceeds fake data's size, and the kth sample hasn't been
// read.
@@ -244,17 +281,49 @@
// The user should make sure this device is paused.
void ResumeReadingSamples();
+ // Simulates some failures when reading the kth event. Can be called multiple
+ // times. The user should make sure the kth event hasn't been read.
+ void AddFailedReadAtKthEvent(int k);
+ // Pauses at kth event. |callback| is run when (k-1)th event is retrieved or
+ // when this function is called and |k| == |event_fd_.index|.
+ // The user should make sure that there wasn't a pause set and not occurred
+ // yet, |k| doesn't exceeds fake data's size, and the kth event hasn't been
+ // read.
+ void SetPauseCallbackAtKthEvents(int k, base::OnceCallback<void()> callback);
+ // Resumes reading after being paused.
+ // The user should make sure this device is paused.
+ void ResumeReadingEvents();
+
private:
+ struct FakeFD {
+ bool is_valid() { return fd.is_valid(); }
+ int32_t get() { return fd.get(); }
+
+ bool WriteByte();
+ bool ReadByte();
+ void ClosePipe();
+
+ void SetPause();
+ void ResumeReading();
+
+ base::ScopedFD fd;
+ bool readable = false;
+ int index = 0;
+
+ bool is_paused = false;
+ base::Optional<int> pause_index;
+ base::OnceCallback<void()> pause_callback;
+
+ // Pops from the failure with the smallest sample index.
+ std::priority_queue<int, std::vector<int>, std::greater<int>>
+ failed_read_queue;
+ };
+
struct ChannelData {
std::string chn_id;
FakeIioChannel* chn = nullptr;
};
- bool WriteByte();
- bool ReadByte();
- void ClosePipe();
- void SetPause();
-
FakeIioContext* context_ = nullptr;
std::string name_;
int id_;
@@ -269,19 +338,11 @@
size_t buffer_length_ = 0;
bool buffer_enabled_ = false;
- // For |CreateBuffer|, |GetBufferFd|, and |ReadSample|.
- base::ScopedFD sample_fd_;
- bool readable_fd_ = false;
- int sample_index_ = 0;
+ FakeFD sample_fd_;
- // Pops from the failure with the smallest sample index.
- std::priority_queue<int, std::vector<int>, std::greater<int>>
- failed_read_queue_;
+ FakeFD event_fd_;
bool disabled_fd_ = false;
- bool is_paused_ = false;
- base::Optional<int> pause_index_;
- base::OnceCallback<void()> pause_callback_;
};
class LIBMEMS_EXPORT FakeIioContext : public IioContext {