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/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