Bidirectional test mode signaling over GPIO

* Implements a bidirectional synchronous handshake protocol over
  dut_flaga/b GPIOs. This protocol is designed to return true (test
  mode) only if the DUT is connected to a servo board which implements
  the remote end.

* Includes unit tests for the test mode signaling routine, complete with
  mock/fake implementation of the remote end.

Note that we still do not deploy GpioHandler in actual update
processing, which will be done later.

BUG=chromium-os:25397,chromium-os:27109,chromium-os:27672
TEST=Builds and passes unit tests (including new ones)

Change-Id: I265407ed735c3e1354e10782ac30566b16caeb20
Reviewed-on: https://gerrit.chromium.org/gerrit/23330
Reviewed-by: Gaurav Shah <gauravsh@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
Commit-Ready: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Gilad Arnold <garnold@chromium.org>
diff --git a/SConstruct b/SConstruct
index 62c6470..fd3fa18 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1,4 +1,4 @@
-# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+# Copyright (c) 2012 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.
 
@@ -306,6 +306,7 @@
                             filesystem_iterator_unittest.cc
                             full_update_generator_unittest.cc
                             gpio_handler_unittest.cc
+                            gpio_mock_file_descriptor.cc
                             gpio_mock_udev_interface.cc
                             graph_utils_unittest.cc
                             http_fetcher_unittest.cc
diff --git a/file_descriptor.cc b/file_descriptor.cc
index 275aa70..a064925 100644
--- a/file_descriptor.cc
+++ b/file_descriptor.cc
@@ -48,10 +48,28 @@
 
 bool EintrSafeFileDescriptor::Close() {
   CHECK_GE(fd_, 0);
-  int ret = HANDLE_EINTR(close(fd_));
-  if (!ret)
-    fd_ = -1;
-  return !ret;
+  if (HANDLE_EINTR(close(fd_)))
+    return false;
+  Reset();
+  return true;
+}
+
+void EintrSafeFileDescriptor::Reset() {
+  fd_ = -1;
+}
+
+
+ScopedFileDescriptorCloser::~ScopedFileDescriptorCloser() {
+  if (descriptor_ && descriptor_->IsOpen() && !descriptor_->Close()) {
+    const char* err_str = "file closing failed";
+    if (descriptor_->IsSettingErrno()) {
+      PLOG(ERROR) << err_str;
+    } else {
+      LOG(ERROR) << err_str;
+    }
+    // Abandon the current descriptor, forcing it back to a closed state.
+    descriptor_->Reset();
+  }
 }
 
 }  // namespace chromeos_update_engine
diff --git a/file_descriptor.h b/file_descriptor.h
index 74749c6..67d7b14 100644
--- a/file_descriptor.h
+++ b/file_descriptor.h
@@ -28,6 +28,12 @@
 // * Write() returns the number of bytes written: this appears to be more useful
 //   for clients, who may wish to retry or otherwise do something useful with
 //   the remaining data that was not written.
+//
+// * Provides a Reset() method, which will force to abandon a currently open
+//   file descriptor and allow opening another file, without necessarily
+//   properly closing the old one. This may be useful in cases where a "closer"
+//   class does not care whether Close() was successful, but may need to reuse
+//   the same file descriptor again.
 
 namespace chromeos_update_engine {
 
@@ -53,14 +59,21 @@
   // no bytes were written. Specific implementations may set errno accordingly.
   virtual ssize_t Write(const void* buf, size_t count) = 0;
 
-  // Wrapper around close. The descriptor must be open prior to this call.
+  // Closes a file descriptor. The descriptor must be open prior to this call.
   // Returns true on success, false otherwise. Specific implementations may set
   // errno accordingly.
   virtual bool Close() = 0;
 
+  // Resets the file descriptor, abandoning a currently open file and returning
+  // the descriptor to the closed state.
+  virtual void Reset() = 0;
+
   // Indicates whether or not an implementation sets meaningful errno.
   virtual bool IsSettingErrno() = 0;
 
+  // Indicates whether the descriptor is currently open.
+  virtual bool IsOpen() = 0;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(FileDescriptor);
 };
@@ -77,26 +90,27 @@
   virtual ssize_t Read(void* buf, size_t count);
   virtual ssize_t Write(const void* buf, size_t count);
   virtual bool Close();
+  virtual void Reset();
   virtual bool IsSettingErrno() {
     return true;
   }
+  virtual bool IsOpen() {
+    return (fd_ >= 0);
+  }
 
  private:
   int fd_;
 };
 
-// A scoped closer for a FileDescriptor object.
+// A scoped closer for a FileDescriptor object. The destructor of this class
+// invokes the Close() method of the given file descriptor, if it's not in the
+// closed state already. Note, however, that if Close() fails, this class will
+// force a Reset() invocation, which will abandon the current file descriptor.
 class ScopedFileDescriptorCloser {
  public:
   explicit ScopedFileDescriptorCloser(FileDescriptor* descriptor)
       : descriptor_(descriptor) {}
-  ~ScopedFileDescriptorCloser() {
-    if (descriptor_ && !descriptor_->Close())
-      LOG(ERROR) << "FileDescriptor::Close failed: "
-                 << (descriptor_->IsSettingErrno() ?
-                     utils::ErrnoNumberAsString(errno) :
-                     "(no error code)");
-  }
+  ~ScopedFileDescriptorCloser();
  private:
   FileDescriptor* descriptor_;
 
diff --git a/gpio_handler.cc b/gpio_handler.cc
index efd5e3d..7145bbb 100644
--- a/gpio_handler.cc
+++ b/gpio_handler.cc
@@ -30,23 +30,30 @@
 
 const StandardGpioHandler::GpioDef
 StandardGpioHandler::gpio_defs_[kGpioIdMax] = {
-  { "dutflaga", "ID_GPIO_DUTFLAGA" },  // kGpioDutflaga
-  { "dutflagb", "ID_GPIO_DUTFLAGB" },  // kGpioDutflagb
+  { "dutflaga", "ID_GPIO_DUTFLAGA" },  // kGpioIdDutflaga
+  { "dutflagb", "ID_GPIO_DUTFLAGB" },  // kGpioIdDutflagb
 };
 
 unsigned StandardGpioHandler::num_instances_ = 0;
 
 
 StandardGpioHandler::StandardGpioHandler(UdevInterface* udev_iface,
-                                         bool is_defer_discovery)
+                                         FileDescriptor* fd,
+                                         bool is_defer_discovery,
+                                         bool is_cache_test_mode)
     : udev_iface_(udev_iface),
+      fd_(fd),
+      is_cache_test_mode_(is_cache_test_mode),
       is_discovery_attempted_(false) {
-  CHECK(udev_iface);
+  CHECK(udev_iface && fd);
 
   // Ensure there's only one instance of this class.
   CHECK_EQ(num_instances_, static_cast<unsigned>(0));
   num_instances_++;
 
+  // Reset test signal flags.
+  ResetTestModeSignalingFlags();
+
   // If GPIO discovery not deferred, do it.
   if (!(is_defer_discovery || DiscoverGpios())) {
     LOG(WARNING) << "GPIO discovery failed";
@@ -57,17 +64,29 @@
   num_instances_--;
 }
 
-// TODO(garnold) currently, this function always returns false and avoids the
-// GPIO signaling protocol altogether; to be extended later.
 bool StandardGpioHandler::IsTestModeSignaled() {
   // Attempt GPIO discovery.
   if (!DiscoverGpios()) {
     LOG(WARNING) << "GPIO discovery failed";
   }
 
-  return false;
+  // Force a check if so requested.
+  if (!is_cache_test_mode_)
+    ResetTestModeSignalingFlags();
+
+  bool is_returning_cached = !is_first_check_;  // for logging purposes
+  if (is_first_check_) {
+    is_first_check_ = false;
+    DoTestModeSignalingProtocol();
+  }
+
+  LOG(INFO) << "result: " << (is_test_mode_ ? "test" : "normal") << " mode"
+            << (is_returning_cached ? " (cached)" : "")
+            << (is_handshake_completed_ ? "" : " (default)");
+  return is_test_mode_;
 }
 
+
 bool StandardGpioHandler::GpioChipUdevEnumHelper::SetupEnumFilters(
     udev_enumerate* udev_enum) {
   CHECK(udev_enum);
@@ -155,6 +174,23 @@
   return true;
 }
 
+StandardGpioHandler::GpioDirResetter::GpioDirResetter(
+    StandardGpioHandler* handler, GpioId id, GpioDir dir) :
+    do_reset_(false), handler_(handler), id_(id), dir_(dir) {
+  CHECK(handler);
+  CHECK_GE(id, 0);
+  CHECK_LT(id, kGpioIdMax);
+  CHECK_GE(dir, 0);
+  CHECK_LT(dir, kGpioDirMax);
+}
+
+StandardGpioHandler::GpioDirResetter::~GpioDirResetter() {
+  if (do_reset_ && !handler_->SetGpioDirection(id_, dir_)) {
+    LOG(WARNING) << "failed to reset direction of " << gpio_defs_[id_].name
+                 << " to " << gpio_dirs_[dir_];
+  }
+}
+
 
 bool StandardGpioHandler::InitUdevEnum(struct udev* udev,
                                        UdevEnumHelper* enum_helper) {
@@ -209,6 +245,12 @@
   return enum_helper->Finalize();
 }
 
+void StandardGpioHandler::ResetTestModeSignalingFlags() {
+  is_first_check_ = true;
+  is_handshake_completed_ = false;
+  is_test_mode_ = false;
+}
+
 bool StandardGpioHandler::DiscoverGpios() {
   if (is_discovery_attempted_)
     return true;
@@ -251,4 +293,289 @@
   return true;
 }
 
+bool StandardGpioHandler::OpenGpioFd(StandardGpioHandler::GpioId id,
+                                     const char* dev_name,
+                                     bool is_write) {
+  CHECK(id >= 0 && id < kGpioIdMax && dev_name);
+  string file_name = StringPrintf("%s/%s", gpios_[id].dev_path.c_str(),
+                                  dev_name);
+  if (!fd_->Open(file_name.c_str(), (is_write ? O_WRONLY : O_RDONLY))) {
+    const string err_str = StringPrintf("failed to open %s (%s) for %s",
+                                        file_name.c_str(), gpio_defs_[id].name,
+                                        (is_write ? "writing" : "reading"));
+    if (fd_->IsSettingErrno()) {
+      PLOG(ERROR) << err_str;
+    } else {
+      LOG(ERROR) << err_str;
+    }
+    return false;
+  }
+  return true;
+}
+
+bool StandardGpioHandler::SetGpio(StandardGpioHandler::GpioId id,
+                                  const char* dev_name, const char* entries[],
+                                  const int num_entries, int index) {
+  CHECK_GE(id, 0);
+  CHECK_LT(id, kGpioIdMax);
+  CHECK(dev_name);
+  CHECK(entries);
+  CHECK_GT(num_entries, 0);
+  CHECK_GE(index, 0);
+  CHECK_LT(index, num_entries);
+
+  // Open device for writing.
+  if (!OpenGpioFd(id, dev_name, true))
+    return false;
+  ScopedFileDescriptorCloser dev_fd_closer(fd_);
+
+  // Write a string corresponding to the requested output index to the GPIO
+  // device, appending a newline.
+  string output_str = entries[index];
+  output_str += '\n';
+  ssize_t write_len = fd_->Write(output_str.c_str(), output_str.length());
+  if (write_len != static_cast<ssize_t>(output_str.length())) {
+    if (write_len < 0) {
+      const string err_str = "failed to write to GPIO";
+      if (fd_->IsSettingErrno()) {
+        PLOG(ERROR) << err_str;
+      } else {
+        LOG(ERROR) << err_str;
+      }
+    } else {
+      LOG(ERROR) << "wrong number of bytes written (" << write_len
+                 << " instead of " << output_str.length() << ")";
+    }
+    return false;
+  }
+
+  // Close the device explicitly, returning the close result.
+  return fd_->Close();
+}
+
+bool StandardGpioHandler::GetGpio(StandardGpioHandler::GpioId id,
+                                  const char* dev_name, const char* entries[],
+                                  const int num_entries, int* index_p) {
+  CHECK_GE(id, 0);
+  CHECK_LT(id, kGpioIdMax);
+  CHECK(dev_name);
+  CHECK(entries);
+  CHECK_GT(num_entries, 0);
+  CHECK(index_p);
+
+  // Open device for reading.
+  if (!OpenGpioFd(id, dev_name, false))
+    return false;
+  ScopedFileDescriptorCloser dev_fd_closer(fd_);
+
+  // Read the GPIO device. We attempt to read more than the max number of
+  // characters expected followed by a newline, to ensure that we've indeed read
+  // all the data available on the device.
+  size_t max_entry_len = 0;
+  for (int i = 0; i < num_entries; i++) {
+    size_t entry_len = strlen(entries[i]);
+    if (entry_len > max_entry_len)
+      max_entry_len = entry_len;
+  }
+  max_entry_len++;  // account for trailing newline
+  size_t buf_len = max_entry_len + 1;  // room for excess char / null terminator
+  char buf[buf_len];
+  memset(buf, 0, buf_len);
+  ssize_t read_len = fd_->Read(buf, buf_len);
+  if (read_len < 0 || read_len > static_cast<ssize_t>(max_entry_len)) {
+    if (read_len < 0) {
+      const string err_str = "failed to read GPIO";
+      if (fd_->IsSettingErrno()) {
+        PLOG(ERROR) << err_str;
+      } else {
+        LOG(ERROR) << err_str;
+      }
+    } else {
+      LOG(ERROR) << "read too many bytes (" << read_len << ")";
+    }
+    return false;
+  }
+
+  // Remove trailing newline.
+  read_len--;
+  if (buf[read_len] != '\n') {
+    LOG(ERROR) << "read value missing trailing newline";
+    return false;
+  }
+  buf[read_len] = '\0';
+
+  // Identify and write GPIO status.
+  for (int i = 0; i < num_entries; i++)
+    if (!strcmp(entries[i], buf)) {
+      *index_p = i;
+      // Close the device explicitly, returning the close result.
+      return fd_->Close();
+    }
+
+  // Oops, unidentified reading...
+  LOG(ERROR) << "read unexpected value from GPIO (`" << buf << "')";
+  return false;
+}
+
+bool StandardGpioHandler::SetGpioDirection(StandardGpioHandler::GpioId id,
+                                           StandardGpioHandler::GpioDir dir) {
+  return SetGpio(id, "direction", gpio_dirs_, kGpioDirMax, dir);
+}
+
+bool StandardGpioHandler::GetGpioDirection(
+    StandardGpioHandler::GpioId id,
+    StandardGpioHandler::GpioDir* direction_p) {
+  return GetGpio(id, "direction", gpio_dirs_, kGpioDirMax,
+                 reinterpret_cast<int*>(direction_p));
+}
+
+bool StandardGpioHandler::SetGpioValue(StandardGpioHandler::GpioId id,
+                                       StandardGpioHandler::GpioVal value,
+                                       bool is_check_direction) {
+  // If so instructed, ensure that the GPIO is indeed in the output direction
+  // before attempting to write to it.
+  if (is_check_direction) {
+    GpioDir dir;
+    if (!(GetGpioDirection(id, &dir) && dir == kGpioDirOut)) {
+      LOG(ERROR) << "couldn't verify that GPIO is in the output direction "
+                    "prior to reading from it";
+      return false;
+    }
+  }
+
+  return SetGpio(id, "value", gpio_vals_, kGpioValMax, value);
+}
+
+bool StandardGpioHandler::GetGpioValue(StandardGpioHandler::GpioId id,
+                                       StandardGpioHandler::GpioVal* value_p,
+                                       bool is_check_direction) {
+  // If so instructed, ensure that the GPIO is indeed in the input direction
+  // before attempting to read from it.
+  if (is_check_direction) {
+    GpioDir dir;
+    if (!(GetGpioDirection(id, &dir) && dir == kGpioDirIn)) {
+      LOG(ERROR) << "couldn't verify that GPIO is in the input direction "
+                    "prior to reading from it";
+      return false;
+    }
+  }
+
+  return GetGpio(id, "value", gpio_vals_, kGpioValMax,
+                 reinterpret_cast<int*>(value_p));
+}
+
+bool StandardGpioHandler::DoTestModeSignalingProtocol() {
+  // The test mode signaling protocol is designed to provide a robust indication
+  // that a Chrome OS device is physically connected to a servo board in a lab
+  // setting. It is making very few assumptions about the soundness of the
+  // hardware, firmware and kernel driver implementation of the GPIO mechanism.
+  // In general, it is performing a three-way handshake between servo and the
+  // Chrome OS client, based on changes in the GPIO value readings. The
+  // client-side implementation does the following:
+  //
+  //  1. Check for an initial signal (0) on the input GPIO (dut_flaga).
+  //
+  //  2. Flip the signal (1 -> 0) on the output GPIO (dut_flagb).
+  //
+  //  3. Check for a flipped signal (1) on the input GPIO.
+  //
+  // TODO(garnold) the current implementation is based on sysfs access to GPIOs.
+  // We will likely change this to using a specialized in-kernel driver
+  // implementation, which would give us better performance and security
+  // guarantees.
+
+  LOG(INFO) << "attempting GPIO handshake";
+
+  const char* dutflaga_name = gpio_defs_[kGpioIdDutflaga].name;
+  const char* dutflagb_name = gpio_defs_[kGpioIdDutflagb].name;
+
+  // Flip GPIO direction, set it to "in".
+  // TODO(garnold) changing the GPIO direction back and forth is necessary for
+  // overcoming a firmware/kernel issue which causes the device to be in the
+  // "out" state whereas the kernel thinks it is in the "in" state.  This should
+  // be abandoned once the firmware and/or kernel driver have been fixed.
+  // Details here: http://code.google.com/p/chromium-os/issues/detail?id=27680
+  if (!(SetGpioDirection(kGpioIdDutflaga, kGpioDirOut) &&
+        SetGpioDirection(kGpioIdDutflaga, kGpioDirIn))) {
+    LOG(ERROR) << "failed to flip direction of input GPIO " << dutflaga_name;
+    return false;
+  }
+
+  // Peek input GPIO state.
+  GpioVal dutflaga_gpio_value;
+  if (!GetGpioValue(kGpioIdDutflaga, &dutflaga_gpio_value, true)) {
+    LOG(ERROR) << "failed to read input GPIO " << dutflaga_name;
+    return false;
+  }
+
+  // If initial handshake signal not received, abort.
+  if (dutflaga_gpio_value != kGpioValDown) {
+    LOG(INFO) << "input GPIO " << dutflaga_name
+              << " unset, terminating handshake";
+    is_handshake_completed_ = true;
+    return true;
+  }
+
+  // Initialize output GPIO to a default state.
+  // TODO(garnold) a similar workaround for possible driver/firmware glitches,
+  // we insist on flipping the direction of the GPIO prior to assuming it is in
+  // the "out" direction.
+  GpioDirResetter dutflagb_dir_resetter(this, kGpioIdDutflagb, kGpioDirIn);
+  if (!(SetGpioDirection(kGpioIdDutflagb, kGpioDirIn) &&
+        dutflagb_dir_resetter.set_do_reset(
+            SetGpioDirection(kGpioIdDutflagb, kGpioDirOut)) &&
+        SetGpioValue(kGpioIdDutflagb, kGpioValUp, false))) {
+    LOG(ERROR) << "failed to initialize output GPIO " << dutflagb_name;
+    return false;
+  }
+
+  // Wait, giving the receiving end enough time to sense the fall.
+  sleep(kServoOutputResponseWaitInSecs);
+
+  // Flip the output signal.
+  if (!SetGpioValue(kGpioIdDutflagb, kGpioValDown, false)) {
+    LOG(ERROR) << "failed to flip output GPIO " << dutflagb_name;
+    return false;
+  }
+
+  // Look for flipped input GPIO value, up to a preset timeout.
+  Time expires =
+      Time::Now() + TimeDelta::FromSeconds(kServoInputResponseTimeoutInSecs);
+  TimeDelta delay =
+      TimeDelta::FromMicroseconds(1000000 / kServoInputNumChecksPerSec);
+  bool is_first_response_check = true;
+  bool is_error = false;
+  while (Time::Now() < expires) {
+    if (is_first_response_check)
+      is_first_response_check = false;
+    else
+      usleep(delay.InMicroseconds());
+
+    // Read input GPIO.
+    if (!GetGpioValue(kGpioIdDutflaga, &dutflaga_gpio_value, true)) {
+      LOG(ERROR) << "failed to read input GPIO " << dutflaga_name;
+      is_error = true;
+      break;
+    }
+
+    // If dutflaga is now up (flipped), we got our signal!
+    if (dutflaga_gpio_value == kGpioValUp) {
+      is_test_mode_ = true;
+      break;
+    }
+  }
+
+  if (!is_error) {
+    if (is_test_mode_) {
+      is_handshake_completed_ = true;
+      LOG(INFO) << "GPIO handshake completed, test mode signaled";
+    } else {
+      LOG(INFO) << "timed out waiting for input GPIO " << dutflaga_name
+                << " to flip, terminating handshake";
+    }
+  }
+
+  return is_handshake_completed_;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/gpio_handler.h b/gpio_handler.h
index b269266..0868cb5 100644
--- a/gpio_handler.h
+++ b/gpio_handler.h
@@ -44,10 +44,14 @@
 // object will actually cause a runtime error.
 class StandardGpioHandler : public GpioHandler {
  public:
-  // This constructor accepts a udev interface |udev_iface|. The value of
-  // |is_defer_discovery| determines whether GPIO discovery should be attempted
-  // right away (false) or done lazily, when necessitated by other calls (true).
-  StandardGpioHandler(UdevInterface* udev_iface, bool is_defer_discovery);
+  // This constructor accepts a udev interface |udev_iface| and a reusable file
+  // descriptor |fd|. The value of |is_defer_discovery| determines whether GPIO
+  // discovery should be attempted right away (false) or done lazily, when
+  // necessitated by other calls (true). If |is_cache_test_mode| is true,
+  // checking for test mode signal is done only once and further queries return
+  // the cached result.
+  StandardGpioHandler(UdevInterface* udev_iface, FileDescriptor* fd,
+                      bool is_defer_discovery, bool is_cache_test_mode);
 
   // Free all resources, allow to reinstantiate.
   virtual ~StandardGpioHandler();
@@ -93,11 +97,21 @@
     std::string dev_path;    // sysfs device name
   };
 
-  // Various constants.
-  static const int kServoInputResponseTimeoutInSecs = 3;
-  static const int kServoInputNumChecksPerSec = 5;
+  // The number of seconds we wait before flipping the output signal (aka,
+  // producing the "challenge" signal). Assuming a 1 second sampling frequency
+  // on the servo side, a two second wait should be enough.
   static const int kServoOutputResponseWaitInSecs = 2;
 
+  // The total number of seconds we wait for a servo response from the point we
+  // flip the output signal. Assuming a 1 second sampling frequency on the servo
+  // side, a two second wait should suffice. We add one more second for grace
+  // (servod / hardware processing delays, etc).
+  static const int kServoInputResponseTimeoutInSecs = 3;
+
+  // The number of times per second we check for a servo response. Five seems
+  // like a reasonable value.
+  static const int kServoInputNumChecksPerSec = 5;
+
   // GPIO value/direction conversion tables.
   static const char* gpio_dirs_[kGpioDirMax];
   static const char* gpio_vals_[kGpioValMax];
@@ -168,19 +182,99 @@
     DISALLOW_COPY_AND_ASSIGN(GpioUdevEnumHelper);
   };
 
-  // Attempt GPIO discovery, at most once. Returns true if discovery process was
-  // successfully completed or already attempted, false otherwise.
-  bool DiscoverGpios();
+  // Helper class for resetting a GPIO direction.
+  class GpioDirResetter {
+   public:
+    GpioDirResetter(StandardGpioHandler* handler, GpioId id, GpioDir dir);
+    ~GpioDirResetter();
+
+    bool do_reset() const {
+      return do_reset_;
+    }
+    bool set_do_reset(bool do_reset) {
+      return (do_reset_ = do_reset);
+    }
+
+   private:
+    // Determines whether or not the GPIO direction should be reset to the
+    // initial value.
+    bool do_reset_;
+
+    // The GPIO handler to use for changing the GPIO direction.
+    StandardGpioHandler* handler_;
+
+    // The GPIO identifier and initial direction.
+    GpioId id_;
+    GpioDir dir_;
+  };
 
   // An initialization helper performing udev enumeration. |enum_helper|
   // implements an enumeration initialization and processing methods. Returns
   // true upon success, false otherwise.
   bool InitUdevEnum(struct udev* udev, UdevEnumHelper* enum_helper);
 
+  // Resets the object's flags which determine the status of test mode
+  // signaling.
+  void ResetTestModeSignalingFlags();
+
+  // Attempt GPIO discovery, at most once. Returns true if discovery process was
+  // successfully completed or already attempted, false otherwise.
+  bool DiscoverGpios();
+
   // Assigns a copy of the device name of GPIO |id| to |dev_path_p|. Assumes
   // initialization. Returns true upon success, false otherwise.
   bool GetGpioDevName(GpioId id, std::string* dev_path_p);
 
+  // Open a sysfs file device |dev_name| of GPIO |id|, for either reading or
+  // writing depending on |is_write|.  Uses the internal file descriptor for
+  // this purpose, which can be reused as long as it is closed between
+  // successive opens. Returns true upon success, false otherwise (optionally,
+  // with errno set accordingly).
+  bool OpenGpioFd(GpioId id, const char* dev_name, bool is_write);
+
+  // Writes a value to device |dev_name| of GPIO |id|. The index |output| is
+  // used to index the corresponding string to be written from the list
+  // |entries| of length |num_entries|.  Returns true upon success, false
+  // otherwise.
+  bool SetGpio(GpioId id, const char* dev_name, const char* entries[],
+               const int num_entries, int output);
+
+  // Reads a value from device |dev_name| of GPIO |id|. The list |entries| of
+  // length |num_entries| is used to convert the read string into an index,
+  // which is written to |input_p|. The call will fail if the value being read
+  // is not listed in |entries|. Returns true upon success, false otherwise.
+  bool GetGpio(GpioId id, const char* dev_name, const char* entries[],
+               const int num_entries, int* input_p);
+
+  // Sets GPIO |id| to to operate in a given |direction|. Assumes
+  // initialization. Returns true on success, false otherwise.
+  bool SetGpioDirection(GpioId id, GpioDir direction);
+
+  // Assigns the current direction of GPIO |id| into |direction_p|. Assumes
+  // initialization. Returns true on success, false otherwise.
+  bool GetGpioDirection(GpioId id, GpioDir* direction_p);
+
+  // Sets the value of GPIO |id| to |value|. Assumues initialization. The GPIO
+  // direction should be set to 'out' prior to this call. If
+  // |is_check_direction| is true, it'll ensure that the direction is indeed
+  // 'out' prior to attempting the write. Returns true on success, false
+  // otherwise.
+  bool SetGpioValue(GpioId id, GpioVal value, bool is_check_direction);
+
+  // Reads the value of a GPIO |id| and stores it in |value_p|. Assumes
+  // initialization.  The GPIO direction should be set to 'in' prior to this
+  // call. If |is_check_direction| is true, it'll ensure that the direction is
+  // indeed 'in' prior to attempting the read. Returns true upon success, false
+  // otherwise.
+  bool GetGpioValue(GpioId id, GpioVal *value_p, bool is_check_direction);
+
+  // Invokes the actual GPIO handshake protocol to determine whether test mode
+  // was signaled. Returns true iff the handshake has terminated gracefully
+  // without encountering any errors; note that a true value does *not* mean
+  // that a test mode signal has been detected.  The spec for this protocol:
+  // https://docs.google.com/a/google.com/document/d/1DB-35ptck1wT1TYrgS5AC5Y3ALfHok-iPA7kLBw2XCI/edit
+  bool DoTestModeSignalingProtocol();
+
   // Dynamic counter for the number of instances this class has. Used to enforce
   // that no more than one instance created. Thread-unsafe.
   static unsigned num_instances_;
@@ -191,9 +285,21 @@
   // Udev interface.
   UdevInterface* const udev_iface_;
 
+  // A file abstraction for handling GPIO devices.
+  FileDescriptor* const fd_;
+
+  // Determines whether test mode signal should be checked at most once and
+  // cached, or reestablished on each query.
+  const bool is_cache_test_mode_;
+
   // Indicates whether GPIO discovery was performed.
   bool is_discovery_attempted_;
 
+  // Persistent state of the test mode check.
+  bool is_first_check_;
+  bool is_handshake_completed_;
+  bool is_test_mode_;
+
   DISALLOW_COPY_AND_ASSIGN(StandardGpioHandler);
 };
 
diff --git a/gpio_handler_unittest.cc b/gpio_handler_unittest.cc
index a133ac7..53ffb61 100644
--- a/gpio_handler_unittest.cc
+++ b/gpio_handler_unittest.cc
@@ -5,15 +5,9 @@
 #include <gtest/gtest.h>
 
 #include "update_engine/gpio_handler.h"
+#include "update_engine/gpio_mock_file_descriptor.h"
 #include "update_engine/gpio_mock_udev_interface.h"
 
-// Some common strings used by the different cooperating mocks for this module.
-// We use preprocessor constants to allow concatenation at compile-time.
-#define MOCK_GPIO_CHIP_ID     "100"
-#define MOCK_DUTFLAGA_GPIO_ID "101"
-#define MOCK_DUTFLAGB_GPIO_ID "102"
-#define MOCK_SYSFS_PREFIX     "/mock/sys/class/gpio"
-
 namespace chromeos_update_engine {
 
 class StandardGpioHandlerTest : public ::testing::Test {};
@@ -23,7 +17,10 @@
   // all udev resources are deallocated afterwards.  The mock file descriptor is
   // not to be used.
   StandardGpioMockUdevInterface mock_udev;
-  StandardGpioHandler gpio_hander(&mock_udev, false);
+  TestModeGpioMockFileDescriptor
+      mock_file_descriptor(base::TimeDelta::FromSeconds(1));
+  StandardGpioHandler gpio_hander(&mock_udev, &mock_file_descriptor,
+                                  false, false);
   mock_udev.ExpectAllResourcesDeallocated();
   mock_udev.ExpectDiscoverySuccess();
 }
@@ -32,27 +29,230 @@
   // Attempt GPIO discovery with a udev mock that returns two GPIO chip devices.
   // It should fail, of course.  The mock file descriptor is not to be used.
   MultiChipGpioMockUdevInterface mock_udev;
-  StandardGpioHandler gpio_handler(&mock_udev, false);
+  TestModeGpioMockFileDescriptor
+      mock_file_descriptor(base::TimeDelta::FromSeconds(1));
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
   mock_udev.ExpectAllResourcesDeallocated();
   mock_udev.ExpectDiscoveryFail();
 }
 
+TEST(StandardGpioHandlerTest, TestModeGpioSignalingTest) {
+  // Initialize the GPIO module and test for successful completion of the test
+  // signaling protocol.
+  StandardGpioMockUdevInterface mock_udev;
+  TestModeGpioMockFileDescriptor
+      mock_file_descriptor(base::TimeDelta::FromSeconds(1));
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_TRUE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, DeferredInitTestModeGpioSignalingTest) {
+  // Initialize the GPIO module with deferred initialization, test for
+  // successful completion of the test signaling protocol.
+  StandardGpioMockUdevInterface mock_udev;
+  TestModeGpioMockFileDescriptor
+      mock_file_descriptor(base::TimeDelta::FromSeconds(1));
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   true, false);
+  EXPECT_TRUE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, TestModeGpioSignalingTwiceTest) {
+  // Initialize the GPIO module and query for test signal twice (uncached); the
+  // first query should succeed whereas the second should fail.
+  StandardGpioMockUdevInterface mock_udev;
+  TestModeGpioMockFileDescriptor
+      mock_file_descriptor(base::TimeDelta::FromSeconds(1));
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_TRUE(gpio_handler.IsTestModeSignaled());
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, TestModeGpioSignalingTwiceCachedTest) {
+  // Initialize the GPIO module and query for test signal twice (cached); both
+  // queries should succeed.
+  StandardGpioMockUdevInterface mock_udev;
+  TestModeGpioMockFileDescriptor
+      mock_file_descriptor(base::TimeDelta::FromSeconds(1));
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, true);
+  EXPECT_TRUE(gpio_handler.IsTestModeSignaled());
+  EXPECT_TRUE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
 TEST(StandardGpioHandlerTest, NormalModeGpioSignalingTest) {
   // Initialize the GPIO module, run the signaling procedure, ensure that it
   // concluded that this is a normal mode run.
   StandardGpioMockUdevInterface mock_udev;
-  StandardGpioHandler gpio_handler(&mock_udev, false);
+  NormalModeGpioMockFileDescriptor mock_file_descriptor;
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
   EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
   mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, NonPulledUpNormalModeGpioSignalingTest) {
+  // Initialize the GPIO module with a non-pulled up mock (which means the it
+  // returns a different default signal), run the signaling procedure, ensure
+  // that it concluded that this is a normal mode run.
+  StandardGpioMockUdevInterface mock_udev;
+  NonPulledUpNormalModeGpioMockFileDescriptor mock_file_descriptor;
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
 }
 
 TEST(StandardGpioHandlerTest, DeferredInitNormalModeGpioSignalingTest) {
   // Initialize the GPIO module with deferred discovery, run the signaling
   // procedure, ensure that it concluded that this is a normal mode run.
   StandardGpioMockUdevInterface mock_udev;
-  StandardGpioHandler gpio_handler(&mock_udev, true);
+  NormalModeGpioMockFileDescriptor mock_file_descriptor;
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   true, false);
   EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
   mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, FlipInputDirErrorNormalModeGpioSignalingTest) {
+  // Test the GPIO module with a mock that simulates a GPIO sysfs race/hack,
+  // which causes the input GPIO to flip direction. Ensure that it concludes
+  // that this is a normal mode run.
+  StandardGpioMockUdevInterface mock_udev;
+  ErrorNormalModeGpioMockFileDescriptor
+      mock_file_descriptor(
+          base::TimeDelta::FromSeconds(1),
+          ErrorNormalModeGpioMockFileDescriptor::kGpioErrorFlipInputDir);
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, ReadInvalidValErrorNormalModeGpioSignalingTest) {
+  // Test the GPIO module with a mock that simulates an invalid value reading
+  // from a GPIO device. Ensure that it concludes that this is a normal mode
+  // run.
+  StandardGpioMockUdevInterface mock_udev;
+  ErrorNormalModeGpioMockFileDescriptor
+      mock_file_descriptor(
+          base::TimeDelta::FromSeconds(1),
+          ErrorNormalModeGpioMockFileDescriptor::kGpioErrorReadInvalidVal);
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, ReadInvalidDirErrorNormalModeGpioSignalingTest) {
+  // Test the GPIO module with a mock that simulates an invalid value reading
+  // from a GPIO device. Ensure that it concludes that this is a normal mode
+  // run.
+  StandardGpioMockUdevInterface mock_udev;
+  ErrorNormalModeGpioMockFileDescriptor
+      mock_file_descriptor(
+          base::TimeDelta::FromSeconds(1),
+          ErrorNormalModeGpioMockFileDescriptor::kGpioErrorReadInvalidDir);
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, FailFileOpenErrorNormalModeGpioSignalingTest) {
+  // Test the GPIO module with a mock that simulates an invalid value reading
+  // from a GPIO device. Ensure that it concludes that this is a normal mode
+  // run.
+  StandardGpioMockUdevInterface mock_udev;
+  ErrorNormalModeGpioMockFileDescriptor
+      mock_file_descriptor(
+          base::TimeDelta::FromSeconds(1),
+          ErrorNormalModeGpioMockFileDescriptor::kGpioErrorFailFileOpen);
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, FailFileReadErrorNormalModeGpioSignalingTest) {
+  // Test the GPIO module with a mock that simulates an invalid value reading
+  // from a GPIO device. Ensure that it concludes that this is a normal mode
+  // run.
+  StandardGpioMockUdevInterface mock_udev;
+  ErrorNormalModeGpioMockFileDescriptor
+      mock_file_descriptor(
+          base::TimeDelta::FromSeconds(1),
+          ErrorNormalModeGpioMockFileDescriptor::kGpioErrorFailFileRead);
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, FailFileWriteErrorNormalModeGpioSignalingTest) {
+  // Test the GPIO module with a mock that simulates an invalid value reading
+  // from a GPIO device. Ensure that it concludes that this is a normal mode
+  // run.
+  StandardGpioMockUdevInterface mock_udev;
+  ErrorNormalModeGpioMockFileDescriptor
+      mock_file_descriptor(
+          base::TimeDelta::FromSeconds(1),
+          ErrorNormalModeGpioMockFileDescriptor::kGpioErrorFailFileWrite);
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllGpiosRestoredToDefault();
+}
+
+TEST(StandardGpioHandlerTest, FailFileCloseErrorNormalModeGpioSignalingTest) {
+  // Test the GPIO module with a mock that simulates an invalid value reading
+  // from a GPIO device. Ensure that it concludes that this is a normal mode
+  // run.
+  StandardGpioMockUdevInterface mock_udev;
+  ErrorNormalModeGpioMockFileDescriptor
+      mock_file_descriptor(
+          base::TimeDelta::FromSeconds(1),
+          ErrorNormalModeGpioMockFileDescriptor::kGpioErrorFailFileClose);
+  StandardGpioHandler gpio_handler(&mock_udev, &mock_file_descriptor,
+                                   false, false);
+  EXPECT_FALSE(gpio_handler.IsTestModeSignaled());
+  mock_udev.ExpectAllResourcesDeallocated();
+  mock_file_descriptor.ExpectAllResourcesDeallocated();
+  // Don't test GPIO status restored; since closing of sysfs files fails, all
+  // bets are off.
 }
 
 }  // namespace chromeos_update_engine
diff --git a/gpio_handler_unittest.h b/gpio_handler_unittest.h
index 5780eaa..c98e248 100644
--- a/gpio_handler_unittest.h
+++ b/gpio_handler_unittest.h
@@ -26,6 +26,20 @@
   kMockGpioIdMax  // marker, do not remove!
 };
 
+// Mock GPIO directions, which are analogous to actual GPIO directions.
+enum MockGpioDir {
+  kMockGpioDirIn = 0,
+  kMockGpioDirOut,
+  kMockGpioDirMax  // marker, do not remove!
+};
+
+// Mock GPIO values, ditto.
+enum MockGpioVal {
+  kMockGpioValUp = 0,
+  kMockGpioValDown,
+  kMockGpioValMax  // marker, do not remove!
+};
+
 }  // chromeos_update_engine
 
 #endif /* CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_HANDLER_UNITTEST_H__ */
diff --git a/gpio_mock_file_descriptor.cc b/gpio_mock_file_descriptor.cc
new file mode 100644
index 0000000..16f9473
--- /dev/null
+++ b/gpio_mock_file_descriptor.cc
@@ -0,0 +1,460 @@
+// Copyright (c) 2012 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 "update_engine/gpio_mock_file_descriptor.h"
+
+#include <base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Typesets a time object into a string; omits the date.
+string TimeToString(Time time) {
+  Time::Exploded exploded_time;
+  time.LocalExplode(&exploded_time);
+  return StringPrintf("%d:%02d:%02d.%03d",
+                      exploded_time.hour,
+                      exploded_time.minute,
+                      exploded_time.second,
+                      exploded_time.millisecond);
+}
+}  // namespace
+
+//
+// GpioMockFileDescriptor
+//
+const char* GpioMockFileDescriptor::gpio_devname_prefixes_[kMockGpioIdMax] = {
+  MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGA_GPIO_ID,
+  MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGB_GPIO_ID,
+};
+
+const char* GpioMockFileDescriptor::gpio_val_strings_[kMockGpioValMax] = {
+  "1",  // kMockGpioValUp
+  "0",  // kMockGpioValDown
+};
+
+const char* GpioMockFileDescriptor::gpio_dir_strings_[kMockGpioDirMax] = {
+  "in",   // kMockGpioDirIn
+  "out",  // kMockGpioDirOut
+};
+
+
+GpioMockFileDescriptor::GpioMockFileDescriptor()
+    : gpio_id_(kMockGpioIdMax),
+      gpio_subdev_(kMockGpioSubdevMax) {
+  // All GPIOs are initially in the input direction, their read value is "up",
+  // and they assume an initial write value of "up" with current (init) time.
+  Time init_time = Time::Now();
+  for (size_t i = 0; i < kMockGpioIdMax; i++) {
+    gpio_dirs_[i] = kMockGpioDirIn;
+    gpio_read_vals_[i] = kMockGpioValUp;
+    SetGpioLastWrite(static_cast<MockGpioId>(i), kMockGpioValUp, init_time);
+  }
+
+  // Nullify the instance-specific override strings.
+  for (size_t i = 0; i < kMockGpioValMax; i++)
+    override_read_gpio_val_strings_[i] = NULL;
+  for (size_t i = 0; i < kMockGpioDirMax; i++)
+    override_read_gpio_dir_strings_[i] = NULL;
+}
+
+bool GpioMockFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  EXPECT_EQ(gpio_id_, kMockGpioIdMax);
+  if (gpio_id_ != kMockGpioIdMax)
+    return false;
+
+  // Determine identifier of opened GPIO device.
+  size_t devname_prefix_len = 0;
+  int id;
+  for (id = 0; id < kMockGpioIdMax; id++) {
+    devname_prefix_len = strlen(gpio_devname_prefixes_[id]);
+    if (!strncmp(path, gpio_devname_prefixes_[id], devname_prefix_len))
+      break;
+  }
+  EXPECT_LT(id, kMockGpioIdMax);
+  if (id == kMockGpioIdMax)
+    return false;
+
+  // Determine specific sub-device.
+  path += devname_prefix_len;
+  EXPECT_EQ(path[0], '/');
+  if (path[0] != '/')
+    return false;
+  path++;
+  if (!strcmp(path, "value"))
+    gpio_subdev_ = kMockGpioSubdevValue;
+  else if (!strcmp(path, "direction"))
+    gpio_subdev_ = kMockGpioSubdevDirection;
+  else {
+    ADD_FAILURE();
+    return false;
+  }
+
+  gpio_id_ = static_cast<MockGpioId>(id);
+  LOG(INFO) << "opened mock gpio "
+            << (id == kMockGpioIdDutflaga ? "dut_flaga" :
+                id == kMockGpioIdDutflagb ? "dut_flagb" :
+                "<unknown>")
+            << "/"
+            << (gpio_subdev_ == kMockGpioSubdevValue ? "value" :
+                gpio_subdev_ == kMockGpioSubdevDirection ? "direction" :
+                "<unknown>");
+  return true;
+}
+
+bool GpioMockFileDescriptor::Open(const char* path, int flags) {
+  return Open(path, flags, 0);
+}
+
+ssize_t GpioMockFileDescriptor::Read(void* buf, size_t count) {
+  EXPECT_TRUE(IsOpen());
+  if (!IsOpen())
+    return -1;
+
+  LOG(INFO) << "reading from gpio";
+
+  // Attempt a state update prior to responding to the read.
+  UpdateState();
+
+  switch (gpio_subdev_) {
+    case kMockGpioSubdevValue: {  // reading the GPIO value
+      // Read values vary depending on the GPIO's direction: an input GPIO will
+      // return the value that was written by the remote end; an output GPIO,
+      // however, will return the value last written to its output register...
+      MockGpioVal gpio_read_val = kMockGpioValMax;
+      switch (gpio_dirs_[gpio_id_]) {
+        case kMockGpioDirIn:
+          gpio_read_val = gpio_read_vals_[gpio_id_];
+          break;
+        case kMockGpioDirOut:
+          gpio_read_val = gpio_last_writes_[gpio_id_].val;
+          break;
+        default:
+          CHECK(false);  // shouldn't get here
+      }
+
+      // Write the value to the client's buffer.
+      return snprintf(reinterpret_cast<char*>(buf), count, "%s\n",
+                      (override_read_gpio_val_strings_[gpio_read_val] ?
+                       override_read_gpio_val_strings_[gpio_read_val] :
+                       gpio_val_strings_[gpio_read_val]));
+    }
+
+    case kMockGpioSubdevDirection: {  // reading the GPIO direction
+      // Write the current GPIO direction to the client's buffer.
+      MockGpioDir gpio_dir = gpio_dirs_[gpio_id_];
+      return snprintf(reinterpret_cast<char*>(buf), count, "%s\n",
+                      (override_read_gpio_dir_strings_[gpio_dir] ?
+                       override_read_gpio_dir_strings_[gpio_dir] :
+                       gpio_dir_strings_[gpio_dir]));
+    }
+
+    default:
+      ADD_FAILURE();  // shouldn't get here
+      return -1;
+  }
+}
+
+ssize_t GpioMockFileDescriptor::Write(const void* buf, size_t count) {
+  EXPECT_TRUE(IsOpen());
+  EXPECT_TRUE(buf);
+  if (!(IsOpen() && buf))
+    return -1;
+
+  string str = StringPrintf("%-*s", static_cast<int>(count),
+                            reinterpret_cast<const char*>(buf));
+  size_t pos = 0;
+  while ((pos = str.find('\n', pos)) != string::npos) {
+    str.replace(pos, 1, "\\n");
+    pos += 2;
+  }
+  LOG(INFO) << "writing to gpio: \"" << str << "\"";
+
+  // Attempt a state update prior to performing the write operation.
+  UpdateState();
+
+  switch (gpio_subdev_) {
+    case kMockGpioSubdevValue: {  // setting the GPIO value
+      // Ensure the GPIO is in the "out" direction
+      EXPECT_EQ(gpio_dirs_[gpio_id_], kMockGpioDirOut);
+      if (gpio_dirs_[gpio_id_] != kMockGpioDirOut)
+        return -1;
+
+      // Decode the written value.
+      MockGpioVal write_val = DecodeGpioVal(reinterpret_cast<const char*>(buf),
+                                            count);
+      EXPECT_LT(write_val, kMockGpioValMax);
+      if (write_val == kMockGpioValMax)
+        return -1;
+
+      // Update the last tracked written value.
+      SetGpioLastWrite(gpio_id_, write_val);
+      break;
+    }
+
+    case kMockGpioSubdevDirection: {  // setting GPIO direction
+      // Decipher the direction to be set.
+      MockGpioDir write_dir = DecodeGpioDir(reinterpret_cast<const char*>(buf),
+                                            count);
+      EXPECT_LT(write_dir, kMockGpioDirMax);
+      if (write_dir == kMockGpioDirMax)
+        return -1;
+
+      // Update the last write time for this GPIO if switching from "in" to
+      // "out" and the written value is different from its read value; this is
+      // due to the GPIO's DUT-side override, which may cause the Servo-side
+      // reading to flip when switching it to "out".
+      if (gpio_dirs_[gpio_id_] == kMockGpioDirIn &&
+          write_dir == kMockGpioDirOut &&
+          gpio_read_vals_[gpio_id_] != gpio_last_writes_[gpio_id_].val)
+        gpio_last_writes_[gpio_id_].time = Time::Now();
+
+      // Now update the GPIO direction.
+      gpio_dirs_[gpio_id_] = write_dir;
+      break;
+    }
+
+    default:
+      ADD_FAILURE();  // shouldn't get here
+      return -1;
+  }
+
+  return count;
+}
+
+bool GpioMockFileDescriptor::Close() {
+  EXPECT_TRUE(IsOpen());
+  if (!IsOpen())
+    return false;
+
+  Reset();
+  return true;
+}
+
+void GpioMockFileDescriptor::Reset() {
+  gpio_id_ = kMockGpioIdMax;
+}
+
+bool GpioMockFileDescriptor::IsSettingErrno() {
+  // This mock doesn't test errno handling, so no.
+  return false;
+}
+
+bool GpioMockFileDescriptor::ExpectAllResourcesDeallocated() {
+  EXPECT_EQ(gpio_id_, kMockGpioIdMax);
+  return (gpio_id_ == kMockGpioIdMax);
+}
+
+bool GpioMockFileDescriptor::ExpectAllGpiosRestoredToDefault() {
+  // We just verify that direction is restored to "in" for all GPIOs.
+  bool is_all_gpios_restored_to_default = true;
+  for (size_t i = 0; i < kMockGpioIdMax; i++) {
+    EXPECT_EQ(gpio_dirs_[i], kMockGpioDirIn)
+        << "i=" << i << " gpio_dirs_[i]=" << gpio_dirs_[i];
+    is_all_gpios_restored_to_default =
+        is_all_gpios_restored_to_default && (gpio_dirs_[i] == kMockGpioDirIn);
+  }
+  return is_all_gpios_restored_to_default;
+}
+
+size_t GpioMockFileDescriptor::DecodeGpioString(const char* buf,
+                                                      size_t count,
+                                                      const char** strs,
+                                                      size_t num_strs) const {
+  CHECK(buf && strs && count);
+
+  // Last character must be a newline.
+  count--;
+  if (buf[count] != '\n')
+    return num_strs;
+
+  // Scan for a precise match within the provided string array.
+  size_t i;
+  for (i = 0; i < num_strs; i++)
+    if (count == strlen(strs[i]) &&
+        !strncmp(buf, strs[i], count))
+      break;
+  return i;
+}
+
+//
+// TestModeGpioMockFileDescriptor
+//
+TestModeGpioMockFileDescriptor::TestModeGpioMockFileDescriptor(
+    TimeDelta servo_poll_interval)
+    : last_state_(kServoStateInit),
+      servo_poll_interval_(servo_poll_interval) {}
+
+void TestModeGpioMockFileDescriptor::UpdateState() {
+  // The following simulates the Servo state transition logic. Note that all of
+  // these tests are (should be) conservative estimates of the actual,
+  // asynchronous logic implemented by an actual Servo. Also, they should remain
+  // so regardless of which operation (read, write) triggers the check. We
+  // repeat the update cycle until no further state changes occur (which assumes
+  // that there are no state transition cycles).
+  Time curr_time = Time::Now();
+  ServoState curr_state = last_state_;
+  do {
+    if (last_state_ != curr_state) {
+      last_state_ = curr_state;
+      curr_servo_poll_fuzz_ = RandomServoPollFuzz();  // fix a new poll fuzz
+      LOG(INFO) << "state=" << last_state_ << ", new poll fuzz="
+                << utils::FormatTimeDelta(curr_servo_poll_fuzz_);
+    }
+
+    switch (last_state_) {
+      case kServoStateInit:
+        // Unconditionally establish the trigger signal.
+        LOG(INFO) << "unconditionally sending trigger signal over dut_flaga";
+        gpio_read_vals_[kMockGpioIdDutflaga] = kMockGpioValDown;
+        curr_state = kServoStateTriggerSent;
+        break;
+
+      case kServoStateTriggerSent:
+        // If dut_flagb is in "out" mode, its last written value is "1", and
+        // it's probable that Servo has polled it since, then advance the state.
+        if (gpio_dirs_[kMockGpioIdDutflagb] == kMockGpioDirOut &&
+            gpio_last_writes_[kMockGpioIdDutflagb].val == kMockGpioValUp &&
+            (gpio_last_writes_[kMockGpioIdDutflagb].time +
+             curr_servo_poll_fuzz_) < curr_time) {
+          LOG(INFO) << "an up signal was written to dut_flagb on "
+                    << TimeToString(gpio_last_writes_[kMockGpioIdDutflagb].time)
+                    << " and polled at "
+                    << TimeToString(
+                           gpio_last_writes_[kMockGpioIdDutflagb].time +
+                           curr_servo_poll_fuzz_)
+                    << " (after "
+                    << utils::FormatTimeDelta(curr_servo_poll_fuzz_)
+                    << "); current time is " << TimeToString(curr_time);
+          curr_state = kServoStateChallengeUpReceived;
+        }
+        break;
+
+      case kServoStateChallengeUpReceived:
+        // If dut_flagb is in "out" mode, its last written value is "0", and
+        // it's probable that Servo has polled it since, then advance the state
+        // and flip the value of dut_flaga.
+        if (gpio_dirs_[kMockGpioIdDutflagb] == kMockGpioDirOut &&
+            gpio_last_writes_[kMockGpioIdDutflagb].val == kMockGpioValDown &&
+            (gpio_last_writes_[kMockGpioIdDutflagb].time +
+             curr_servo_poll_fuzz_) < curr_time) {
+          LOG(INFO) << "a down signal was written to dut_flagb on "
+                    << TimeToString(gpio_last_writes_[kMockGpioIdDutflagb].time)
+                    << " and polled at "
+                    << TimeToString(
+                           gpio_last_writes_[kMockGpioIdDutflagb].time +
+                           curr_servo_poll_fuzz_)
+                    << " (after "
+                    << utils::FormatTimeDelta(curr_servo_poll_fuzz_)
+                    << "); current time is " << TimeToString(curr_time);
+          gpio_read_vals_[kMockGpioIdDutflaga] = kMockGpioValUp;
+          curr_state = kServoStateChallengeDownReceived;
+        }
+        break;
+
+      case kServoStateChallengeDownReceived:
+        break;  // terminal state, nothing to do
+
+      default:
+        CHECK(false);  // shouldn't get here
+    }
+  } while (last_state_ != curr_state);
+}
+
+//
+// ErrorNormalModeGpioMockFileDescriptor
+//
+ErrorNormalModeGpioMockFileDescriptor::ErrorNormalModeGpioMockFileDescriptor(
+    TimeDelta servo_poll_interval,
+    ErrorNormalModeGpioMockFileDescriptor::GpioError error)
+    : TestModeGpioMockFileDescriptor(servo_poll_interval),
+      error_(error),
+      is_dutflaga_dir_flipped_(false) {}
+
+bool ErrorNormalModeGpioMockFileDescriptor::Open(const char* path, int flags,
+                                                 mode_t mode) {
+  if (error_ == kGpioErrorFailFileOpen)
+    return false;
+  return TestModeGpioMockFileDescriptor::Open(path, flags, mode);
+}
+
+ssize_t ErrorNormalModeGpioMockFileDescriptor::Read(void* buf, size_t count) {
+  if (error_ == kGpioErrorFailFileRead)
+    return -1;
+  return TestModeGpioMockFileDescriptor::Read(buf, count);
+}
+
+ssize_t ErrorNormalModeGpioMockFileDescriptor::Write(const void* buf,
+                                                     size_t count) {
+  if (error_ == kGpioErrorFailFileWrite)
+    return -1;
+  return TestModeGpioMockFileDescriptor::Write(buf, count);
+}
+
+bool ErrorNormalModeGpioMockFileDescriptor::Close() {
+  // We actually need to perform the close operation anyway, to avoid
+  // inconsistencies in the file descriptor's state.
+  bool ret = TestModeGpioMockFileDescriptor::Close();
+  return (error_ == kGpioErrorFailFileClose ? false : ret);
+}
+
+void ErrorNormalModeGpioMockFileDescriptor::UpdateState() {
+  // Invoke the base class's update method.
+  TestModeGpioMockFileDescriptor::UpdateState();
+
+  // Sabotage the normal feedback that is to be expected from the GPIOs, in
+  // various ways based on the requested type of error.
+  switch (error_) {
+    case kGpioErrorFlipInputDir:
+      // Intervene by flipping the direction of dut_flaga right after the
+      // challenge signal was sent to servo. Among other things, this could
+      // simulate a benign race condition, or an intentional attempt to fool the
+      // GPIO module to believe that it is talking to an (absent) servo.
+      if (!is_dutflaga_dir_flipped_ &&
+          last_state_ == kServoStateChallengeDownReceived) {
+        is_dutflaga_dir_flipped_ = true;
+        LOG(INFO) << "intervention: setting dut_flaga direction to out";
+        gpio_dirs_[kMockGpioIdDutflaga] = kMockGpioDirOut;
+      }
+      break;
+
+    case kGpioErrorReadInvalidVal:
+      // Cause the GPIO device to return an invalid value reading.
+      override_read_gpio_val_strings_[kMockGpioValUp] = "foo";
+      override_read_gpio_val_strings_[kMockGpioValDown] = "bar";
+      break;
+
+    case kGpioErrorReadInvalidDir:
+      // Cause the GPIO device to return an invlid direction reading.
+      override_read_gpio_dir_strings_[kMockGpioDirIn] = "boo";
+      override_read_gpio_dir_strings_[kMockGpioDirOut] = "far";
+
+    case kGpioErrorFailFileOpen:
+    case kGpioErrorFailFileRead:
+    case kGpioErrorFailFileWrite:
+    case kGpioErrorFailFileClose:
+      break;
+
+    default:
+      CHECK(false);  // shouldn't get here
+  }
+}
+
+bool ErrorNormalModeGpioMockFileDescriptor::ExpectAllGpiosRestoredToDefault() {
+  if (is_dutflaga_dir_flipped_) {
+    LOG(INFO) << "restoring dut_flaga direction back to in";
+    gpio_dirs_[kMockGpioIdDutflaga] = kMockGpioDirIn;
+  }
+
+  return TestModeGpioMockFileDescriptor::ExpectAllGpiosRestoredToDefault();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/gpio_mock_file_descriptor.h b/gpio_mock_file_descriptor.h
new file mode 100644
index 0000000..a4fd4e8
--- /dev/null
+++ b/gpio_mock_file_descriptor.h
@@ -0,0 +1,255 @@
+// Copyright (c) 2012 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 CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_MOCK_FILE_DESCRIPTOR_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_MOCK_FILE_DESCRIPTOR_H__
+
+#include <base/rand_util.h>
+#include <base/time.h>
+
+#include "update_engine/file_descriptor.h"
+#include "update_engine/gpio_handler_unittest.h"
+
+// A set of mock file descriptors used for unit-testing. All classes here
+// inherit the FileDescriptor interface.
+
+namespace chromeos_update_engine {
+
+// An abstract classs implementing a common mock infrastructure for GPIO
+// reading/writing. This includes all the inherited interface methods, and basic
+// logic to manage the opening, reading, writing and closing of GPIO files. It
+// is up to concrete implementations to manage their internal state machine and
+// update the values to be read by clients. In most cases, this amounts to
+// adding internal state and overloading the UpdateState() method to change to
+// various GPIO "registers" accordingly.
+class GpioMockFileDescriptor : public FileDescriptor {
+ public:
+  GpioMockFileDescriptor();
+
+  // Interface methods.
+  virtual bool Open(const char* path, int flags, mode_t mode);
+  virtual bool Open(const char* path, int flags);
+  virtual ssize_t Read(void* buf, size_t count);
+  virtual ssize_t Write(const void* buf, size_t count);
+  virtual bool Close();
+  virtual void Reset();
+  virtual bool IsSettingErrno();
+  virtual bool IsOpen() {
+    return (gpio_id_ < kMockGpioIdMax && gpio_subdev_ < kMockGpioSubdevMax);
+  }
+
+
+  // Returns true iff all file resources were freed; otherwise, will fail the
+  // current test.
+  virtual bool ExpectAllResourcesDeallocated();
+
+  // Returns true iff all GPIOs have been restored to their default state;
+  // otherwise, will fail the current test.
+  virtual bool ExpectAllGpiosRestoredToDefault();
+
+ protected:
+  // A pair of write value and time at which it was written.
+  struct MockGpioWriteEvent {
+    MockGpioVal val;
+    base::Time time;
+  };
+
+  // Sets the last written value and timestamp of GPIO |gpio_id|.
+  inline MockGpioVal SetGpioLastWrite(MockGpioId gpio_id, MockGpioVal val,
+                                      base::Time time) {
+    gpio_last_writes_[gpio_id].time = time;
+    return (gpio_last_writes_[gpio_id].val = val);
+  }
+
+  inline MockGpioVal SetGpioLastWrite(MockGpioId gpio_id, MockGpioVal val) {
+    return SetGpioLastWrite(gpio_id, val, base::Time::Now());
+  }
+
+
+  // The current direction of each GPIO device. These are generally handled by
+  // Write(), but can be modified by concrete implementations of this class to
+  // simulate race conditions on GPIO devices.
+  MockGpioDir gpio_dirs_[kMockGpioIdMax];
+
+  // The current values to be read by the DUT. These can be modified by concrete
+  // implementations of this class, to reflect current GPIO values.
+  MockGpioVal gpio_read_vals_[kMockGpioIdMax];
+
+  // The last values and time they were written by the DUT to each GPIO device.
+  // These are generally handled by Write(), but can be modified by concrete
+  // implementations of this class to simulate race conditions on GPIO devices.
+  MockGpioWriteEvent gpio_last_writes_[kMockGpioIdMax];
+
+  // Override strings for GPIO value / direction readings. Initialized to null
+  // pointers by default, which means the default values will not be overridden.
+  const char* override_read_gpio_val_strings_[kMockGpioValMax];
+  const char* override_read_gpio_dir_strings_[kMockGpioDirMax];
+
+ private:
+  // GPIO subdevice identifiers.
+  enum MockGpioSubdev {
+    kMockGpioSubdevValue,
+    kMockGpioSubdevDirection,
+    kMockGpioSubdevMax  // marker, do not remove!
+  };
+
+  // Device name prefixes of the different GPIOs.
+  static const char* gpio_devname_prefixes_[kMockGpioIdMax];
+
+  // Strings to be written as GPIO values, corresponding to the abstract GPIO
+  // value.
+  static const char* gpio_val_strings_[kMockGpioValMax];
+
+  // Strings to be written as GPIO directions, corresponding to the abstract
+  // GPIO direction.
+  static const char* gpio_dir_strings_[kMockGpioDirMax];
+
+
+  // Compare a string |buf| of length |count| that is written to a GPIO device
+  // with an array of strings |strs| of length |num_strs|. Returns the index of
+  // the string entry that is the same as the written string, or |num_strs| if
+  // none was found. Requires that the the last character in |buf| is a newline.
+  size_t DecodeGpioString(const char* buf, size_t count, const char** strs,
+                          size_t num_strs) const;
+
+  // Decode a written GPIO value.
+  inline MockGpioVal DecodeGpioVal(const char* buf, size_t count) const {
+    return static_cast<MockGpioVal>(
+        DecodeGpioString(buf, count, gpio_val_strings_, kMockGpioValMax));
+  }
+
+  // Decodes a written GPIO direction.
+  inline MockGpioDir DecodeGpioDir(const char* buf, size_t count) const {
+    return static_cast<MockGpioDir>(
+        DecodeGpioString(buf, count, gpio_dir_strings_, kMockGpioDirMax));
+  }
+
+  // Simulate the Servo state transition, based on the last recorded state, the
+  // time it was recorded, and the current GPIO values. This is a pure virtual
+  // function that must be implemented by concrete subclasses.
+  virtual void UpdateState() = 0;
+
+  // The identifier of the currently accessed GPIO device.
+  MockGpioId gpio_id_;
+
+  // The identifier of the currently accessed GPIO sub-device.
+  MockGpioSubdev gpio_subdev_;
+};
+
+
+// A mock file descriptor that implements the GPIO test signaling protocol. In
+// doing so, it simulates the asynchronous behavior of a properly implemented
+// Servo test controller.
+class TestModeGpioMockFileDescriptor : public GpioMockFileDescriptor {
+ public:
+  TestModeGpioMockFileDescriptor(base::TimeDelta servo_poll_interval);
+  virtual ~TestModeGpioMockFileDescriptor() {};
+
+ protected:
+  // The state of the Servo-side GPIO signaling protocol. These do not include
+  // sub-state changes on the DUT side, which can be approximated by tracking
+  // read operations but otherwise cannot be observed by an ordinary Servo.
+  enum ServoState {
+    kServoStateInit,
+    kServoStateTriggerSent,
+    kServoStateChallengeUpReceived,
+    kServoStateChallengeDownReceived,
+    kServoStateMax  // marker, do not remove!
+  };
+
+  // Simulate the Servo state transition, based on the last recorded state, the
+  // time it was recorded, and the current GPIO values.
+  virtual void UpdateState();
+
+  // The last recorded state in the GPIO protocol.
+  ServoState last_state_;
+
+ private:
+  // Return a uniformly distributed random time delta within the Servo poll
+  // interval.
+  inline base::TimeDelta RandomServoPollFuzz() {
+    return base::TimeDelta::FromMicroseconds(
+        base::RandInt(0, servo_poll_interval_.InMicroseconds()));
+  }
+
+  // The Servo poll interval.
+  base::TimeDelta servo_poll_interval_;
+
+  // The current Servo poll fuzz, used for deciding when signals are (simulated
+  // to be) sensed within the poll interval. Must be between zero and
+  // servo_poll_interval_.
+  base::TimeDelta curr_servo_poll_fuzz_;
+};
+
+
+// A mock file descriptor that implements GPIO feedback when not conneced to a
+// Servo, on boards that include a pull-up resistor wiring for GPIOs. This is
+// the typical mode of operations for Chromebooks out in the field, and we need
+// to make sure that the client is not made to believe that it is in test mode.
+class NormalModeGpioMockFileDescriptor : public GpioMockFileDescriptor {
+ private:
+  // This is a no-op, as there's no Servo connected.
+  virtual void UpdateState() {};
+};
+
+// A mock file descriptor that implements GPIOs that are not pulled-up by
+// default, and whose idle reading might be zero. We've seen this problem on
+// Lumpy/Stumpy and need to make sure that the protocol doesn't allow these
+// boards to go into test mode without actually being told so by a Servo.
+class NonPulledUpNormalModeGpioMockFileDescriptor
+    : public GpioMockFileDescriptor {
+ private:
+  // Set the default value of dut_flaga to "down".
+  virtual void UpdateState() {
+    gpio_read_vals_[kMockGpioIdDutflaga] = kMockGpioValDown;
+  }
+};
+
+// A mock file descriptor that implements a bogus GPIO feedback. This includes
+// flipping GPIO directions, invalid value readings, and I/O errors on various
+// file operations. All of these instances must be ruled out by the protocol,
+// resulting in normal mode operation.
+class ErrorNormalModeGpioMockFileDescriptor :
+    public TestModeGpioMockFileDescriptor {
+ public:
+  enum GpioError {
+    kGpioErrorFlipInputDir,
+    kGpioErrorReadInvalidVal,
+    kGpioErrorReadInvalidDir,
+    kGpioErrorFailFileOpen,
+    kGpioErrorFailFileRead,
+    kGpioErrorFailFileWrite,
+    kGpioErrorFailFileClose,
+  };
+
+  ErrorNormalModeGpioMockFileDescriptor(base::TimeDelta servo_poll_interval,
+                                        GpioError error);
+  virtual ~ErrorNormalModeGpioMockFileDescriptor() {};
+
+  // Wrapper methods for the respectively inherited ones, which can fail the
+  // call as part of a test.
+  virtual bool Open(const char* path, int flags, mode_t mode);
+  virtual ssize_t Read(void* buf, size_t count);
+  virtual ssize_t Write(const void* buf, size_t count);
+  virtual bool Close();
+
+  // Wrapper which restores all state we might have tampered with.
+  virtual bool ExpectAllGpiosRestoredToDefault();
+
+ private:
+  // Wraps the ordinary test mode servo simulation with an error injecting
+  // behavior, which corresponds to the requested type of error.
+  virtual void UpdateState();
+
+  // The GPIO error to be injected into the protocol.
+  GpioError error_;
+
+  // A flag denoting whether the direction of dut_flaga was already maliciously
+  // flipped.
+  bool is_dutflaga_dir_flipped_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_GPIO_MOCK_FILE_DESCRIPTOR_H__