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 {