Revised GPIO module interface + GPIO discovery logic

* The GpioHandler class is no longer a static singleton, rather an
  ordinary object with a dynamic guard against multiple instances. This
  makes testing/mocking a lot easier and simplifies implementation.

* It uses a basic, mockable udev interface; the module comes with
  complete unit testing of the discovery mechanism.

* Corresponding changes to user classes, including UpdateAttempter and
  UpdateCheckScheduler.

Note that the implementation of the test mode signaling protocol is
currently a no-op, always returning false, and hence has no effect on
the update process yet. This mechanism will be implemented in a later
CL.

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

Change-Id: I2f6254db6799ff5ef8616314890833f6e3269ff6
Reviewed-on: https://gerrit.chromium.org/gerrit/22869
Reviewed-by: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
Commit-Ready: Gilad Arnold <garnold@chromium.org>
diff --git a/gpio_handler.cc b/gpio_handler.cc
index d1825bd..efd5e3d 100644
--- a/gpio_handler.cc
+++ b/gpio_handler.cc
@@ -4,333 +4,250 @@
 
 #include "gpio_handler.h"
 
-#include <fcntl.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <base/eintr_wrapper.h>
+#include <base/memory/scoped_ptr.h>
 #include <base/string_util.h>
-#include <glib.h>
+#include <base/stringprintf.h>
 
-#include "update_engine/utils.h"
+#include "update_engine/file_descriptor.h"
 
+using base::Time;
+using base::TimeDelta;
 using std::string;
 
+using namespace chromeos_update_engine;
+
 namespace chromeos_update_engine {
 
-const char* GpioHandler::dutflaga_dev_name_ = NULL;
-const char* GpioHandler::dutflagb_dev_name_ = NULL;
-
-namespace {
-// Names of udev properties that are linked to the GPIO chip device and identify
-// the two dutflag GPIOs on different boards.
-const char kIdGpioDutflaga[] = "ID_GPIO_DUTFLAGA";
-const char kIdGpioDutflagb[] = "ID_GPIO_DUTFLAGB";
-
-// Scoped closer for udev and udev_enumerate objects.
-// TODO(garnold) chromium-os:26934: it would be nice to generalize the different
-// ScopedFooCloser implementations in update engine using a single template.
-class ScopedUdevCloser {
- public:
-  explicit ScopedUdevCloser(udev** udev_p) : udev_p_(udev_p) {}
-  ~ScopedUdevCloser() {
-    if (udev_p_ && *udev_p_) {
-      udev_unref(*udev_p_);
-      *udev_p_ = NULL;
-    }
-  }
- private:
-  struct udev **udev_p_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedUdevCloser);
+const char* StandardGpioHandler::gpio_dirs_[kGpioDirMax] = {
+  "in",   // kGpioDirIn
+  "out",  // kGpioDirOut
 };
 
-class ScopedUdevEnumerateCloser {
- public:
-  explicit ScopedUdevEnumerateCloser(udev_enumerate **udev_enum_p) :
-    udev_enum_p_(udev_enum_p) {}
-  ~ScopedUdevEnumerateCloser() {
-    if (udev_enum_p_ && *udev_enum_p_) {
-      udev_enumerate_unref(*udev_enum_p_);
-      *udev_enum_p_ = NULL;
-    }
-  }
- private:
-  struct udev_enumerate** udev_enum_p_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedUdevEnumerateCloser);
+const char* StandardGpioHandler::gpio_vals_[kGpioValMax] = {
+  "1",  // kGpioValUp
+  "0",  // kGpioValDown
 };
-}  // namespace {}
 
-bool GpioHandler::IsGpioSignalingTest() {
-  // Peek dut_flaga GPIO state.
-  bool dutflaga_gpio_state;
-  if (!GetDutflagGpioStatus(kDutflagaGpio, &dutflaga_gpio_state)) {
-    LOG(WARNING) << "dutflaga GPIO reading failed, defaulting to non-test mode";
+const StandardGpioHandler::GpioDef
+StandardGpioHandler::gpio_defs_[kGpioIdMax] = {
+  { "dutflaga", "ID_GPIO_DUTFLAGA" },  // kGpioDutflaga
+  { "dutflagb", "ID_GPIO_DUTFLAGB" },  // kGpioDutflagb
+};
+
+unsigned StandardGpioHandler::num_instances_ = 0;
+
+
+StandardGpioHandler::StandardGpioHandler(UdevInterface* udev_iface,
+                                         bool is_defer_discovery)
+    : udev_iface_(udev_iface),
+      is_discovery_attempted_(false) {
+  CHECK(udev_iface);
+
+  // Ensure there's only one instance of this class.
+  CHECK_EQ(num_instances_, static_cast<unsigned>(0));
+  num_instances_++;
+
+  // If GPIO discovery not deferred, do it.
+  if (!(is_defer_discovery || DiscoverGpios())) {
+    LOG(WARNING) << "GPIO discovery failed";
+  }
+}
+
+StandardGpioHandler::~StandardGpioHandler() {
+  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;
+}
+
+bool StandardGpioHandler::GpioChipUdevEnumHelper::SetupEnumFilters(
+    udev_enumerate* udev_enum) {
+  CHECK(udev_enum);
+
+  return !(gpio_handler_->udev_iface_->EnumerateAddMatchSubsystem(
+               udev_enum, "gpio") ||
+           gpio_handler_->udev_iface_->EnumerateAddMatchSysname(
+               udev_enum, "gpiochip*"));
+}
+
+bool StandardGpioHandler::GpioChipUdevEnumHelper::ProcessDev(udev_device* dev) {
+  CHECK(dev);
+
+  // Ensure we did not encounter more than one chip.
+  if (num_gpio_chips_++) {
+    LOG(ERROR) << "enumerated multiple GPIO chips";
     return false;
   }
 
-  LOG(INFO) << "dutflaga GPIO reading: "
-            << (dutflaga_gpio_state ? "on (non-test mode)" : "off (test mode)");
-  return !dutflaga_gpio_state;
+  // Obtain GPIO descriptors.
+  for (int id = 0; id < kGpioIdMax; id++) {
+    const GpioDef* gpio_def = &gpio_defs_[id];
+    const char* descriptor =
+        gpio_handler_->udev_iface_->DeviceGetPropertyValue(
+            dev, gpio_def->udev_property);
+    if (!descriptor) {
+      LOG(ERROR) << "could not obtain " << gpio_def->name
+                 << " descriptor using property " << gpio_def->udev_property;
+      return false;
+    }
+    gpio_handler_->gpios_[id].descriptor = descriptor;
+  }
+
+  return true;
 }
 
-bool GpioHandler::GetDutflagGpioDevName(struct udev* udev,
-                                        const string& gpio_dutflag_str,
-                                        const char** dutflag_dev_name_p) {
-  CHECK(udev && dutflag_dev_name_p);
+bool StandardGpioHandler::GpioChipUdevEnumHelper::Finalize() {
+  if (num_gpio_chips_ != 1) {
+    LOG(ERROR) << "could not enumerate a GPIO chip";
+    return false;
+  }
+  return true;
+}
 
-  struct udev_enumerate* udev_enum = NULL;
-  int num_gpio_dutflags = 0;
-  const string gpio_dutflag_pattern = "*" + gpio_dutflag_str;
-  int ret;
+bool StandardGpioHandler::GpioUdevEnumHelper::SetupEnumFilters(
+    udev_enumerate* udev_enum) {
+  CHECK(udev_enum);
+  const string gpio_pattern =
+      string("*").append(gpio_handler_->gpios_[id_].descriptor);
+  return !(
+      gpio_handler_->udev_iface_->EnumerateAddMatchSubsystem(
+          udev_enum, "gpio") ||
+      gpio_handler_->udev_iface_->EnumerateAddMatchSysname(
+          udev_enum, gpio_pattern.c_str()));
+}
 
-  // Initialize udev enumerate context and closer.
-  if (!(udev_enum = udev_enumerate_new(udev))) {
+bool StandardGpioHandler::GpioUdevEnumHelper::ProcessDev(udev_device* dev) {
+  CHECK(dev);
+
+  // Ensure we did not encounter more than one GPIO device.
+  if (num_gpios_++) {
+    LOG(ERROR) << "enumerated multiple GPIO devices for a given descriptor";
+    return false;
+  }
+
+  // Obtain GPIO device sysfs path.
+  const char* dev_path = gpio_handler_->udev_iface_->DeviceGetSyspath(dev);
+  if (!dev_path) {
+    LOG(ERROR) << "failed to obtain device syspath for GPIO "
+               << gpio_defs_[id_].name;
+    return false;
+  }
+  gpio_handler_->gpios_[id_].dev_path = dev_path;
+
+  LOG(INFO) << "obtained device syspath: " << gpio_defs_[id_].name << " -> "
+            << gpio_handler_->gpios_[id_].dev_path;
+  return true;
+}
+
+bool StandardGpioHandler::GpioUdevEnumHelper::Finalize() {
+  if (num_gpios_ != 1) {
+    LOG(ERROR) << "could not enumerate GPIO device " << gpio_defs_[id_].name;
+    return false;
+  }
+  return true;
+}
+
+
+bool StandardGpioHandler::InitUdevEnum(struct udev* udev,
+                                       UdevEnumHelper* enum_helper) {
+  // Obtain a udev enumerate object.
+  struct udev_enumerate* udev_enum;
+  if (!(udev_enum = udev_iface_->EnumerateNew(udev))) {
     LOG(ERROR) << "failed to obtain udev enumerate context";
     return false;
   }
-  ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
 
-  // Populate filters for find an initialized GPIO chip.
-  if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
-      (ret = udev_enumerate_add_match_sysname(udev_enum,
-                                              gpio_dutflag_pattern.c_str()))) {
-    LOG(ERROR) << "failed to initialize udev enumerate context (" << ret << ")";
+  // Assign enumerate object to closer.
+  scoped_ptr<UdevInterface::UdevEnumerateCloser>
+      udev_enum_closer(udev_iface_->NewUdevEnumerateCloser(&udev_enum));
+
+  // Setup enumeration filters.
+  if (!enum_helper->SetupEnumFilters(udev_enum)) {
+    LOG(ERROR) << "failed to setup udev enumerate filters";
     return false;
   }
 
-  // Obtain list of matching devices.
-  if ((ret = udev_enumerate_scan_devices(udev_enum))) {
-    LOG(ERROR) << "udev enumerate context scan failed (error code "
-               << ret << ")";
+  // Scan for matching devices.
+  if (udev_iface_->EnumerateScanDevices(udev_enum)) {
+    LOG(ERROR) << "udev enumerate scan failed";
     return false;
   }
 
-  // Iterate over matching devices, obtain GPIO dut_flaga identifier.
+  // Iterate over matching devices.
   struct udev_list_entry* list_entry;
-  udev_list_entry_foreach(list_entry,
-                          udev_enumerate_get_list_entry(udev_enum)) {
-    // Make sure we're not enumerating more than one device.
-    num_gpio_dutflags++;
-    if (num_gpio_dutflags > 1) {
-      LOG(WARNING) <<
-        "enumerated multiple dutflag GPIOs, ignoring this one";
-      continue;
-    }
-
+  for (list_entry = udev_iface_->EnumerateGetListEntry(udev_enum);
+       list_entry; list_entry = udev_iface_->ListEntryGetNext(list_entry)) {
     // Obtain device name.
-    const char* dev_name = udev_list_entry_get_name(list_entry);
-    if (!dev_name) {
-      LOG(WARNING) << "enumerated device has a null name string, skipping";
-      continue;
+    const char* dev_path = udev_iface_->ListEntryGetName(list_entry);
+    if (!dev_path) {
+      LOG(ERROR) << "enumerated device has a null name string";
+      return false;
     }
 
     // Obtain device object.
-    struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
+    struct udev_device* dev = udev_iface_->DeviceNewFromSyspath(udev, dev_path);
     if (!dev) {
-      LOG(WARNING) <<
-        "obtained a null device object for enumerated device, skipping";
-      continue;
-    }
-
-    // Obtain device syspath.
-    const char* dev_syspath = udev_device_get_syspath(dev);
-    if (dev_syspath) {
-      LOG(INFO) << "obtained device syspath: " << dev_syspath;
-      *dutflag_dev_name_p = strdup(dev_syspath);
-    } else {
-      LOG(WARNING) << "could not obtain device syspath";
-    }
-
-    udev_device_unref(dev);
-  }
-
-  return true;
-}
-
-bool GpioHandler::GetDutflagGpioDevNames(string* dutflaga_dev_name_p,
-                                         string* dutflagb_dev_name_p) {
-  if (!(dutflaga_dev_name_p || dutflagb_dev_name_p))
-    return true;  // No output pointers, nothing to do.
-
-  string gpio_dutflaga_str, gpio_dutflagb_str;
-
-  if (!(dutflaga_dev_name_ && dutflagb_dev_name_)) {
-    struct udev* udev = NULL;
-    struct udev_enumerate* udev_enum = NULL;
-    int num_gpio_chips = 0;
-    const char* id_gpio_dutflaga = NULL;
-    const char* id_gpio_dutflagb = NULL;
-    int ret;
-
-    LOG(INFO) << "begin discovery of dut_flaga/b devices";
-
-    // Obtain libudev instance and closer.
-    if (!(udev = udev_new())) {
-      LOG(ERROR) << "failed to obtain libudev instance";
+      LOG(ERROR) << "obtained a null device object for enumerated device";
       return false;
     }
-    ScopedUdevCloser udev_closer(&udev);
+    scoped_ptr<UdevInterface::UdevDeviceCloser>
+        dev_closer(udev_iface_->NewUdevDeviceCloser(&dev));
 
-    // Initialize a udev enumerate object and closer with a bounded lifespan.
-    {
-      if (!(udev_enum = udev_enumerate_new(udev))) {
-        LOG(ERROR) << "failed to obtain udev enumerate context";
-        return false;
-      }
-      ScopedUdevEnumerateCloser udev_enum_closer(&udev_enum);
-
-      // Populate filters for find an initialized GPIO chip.
-      if ((ret = udev_enumerate_add_match_subsystem(udev_enum, "gpio")) ||
-          (ret = udev_enumerate_add_match_sysname(udev_enum, "gpiochip*")) ||
-          (ret = udev_enumerate_add_match_property(udev_enum,
-                                                   kIdGpioDutflaga, "*")) ||
-          (ret = udev_enumerate_add_match_property(udev_enum,
-                                                   kIdGpioDutflagb, "*"))) {
-        LOG(ERROR) << "failed to initialize udev enumerate context ("
-                   << ret << ")";
-        return false;
-      }
-
-      // Obtain list of matching devices.
-      if ((ret = udev_enumerate_scan_devices(udev_enum))) {
-        LOG(ERROR) << "udev enumerate context scan failed (" << ret << ")";
-        return false;
-      }
-
-      // Iterate over matching devices, obtain GPIO dut_flaga identifier.
-      struct udev_list_entry* list_entry;
-      udev_list_entry_foreach(list_entry,
-                              udev_enumerate_get_list_entry(udev_enum)) {
-        // Make sure we're not enumerating more than one device.
-        num_gpio_chips++;
-        if (num_gpio_chips > 1) {
-          LOG(WARNING) << "enumerated multiple GPIO chips, ignoring this one";
-          continue;
-        }
-
-        // Obtain device name.
-        const char* dev_name = udev_list_entry_get_name(list_entry);
-        if (!dev_name) {
-          LOG(WARNING) << "enumerated device has a null name string, skipping";
-          continue;
-        }
-
-        // Obtain device object.
-        struct udev_device* dev = udev_device_new_from_syspath(udev, dev_name);
-        if (!dev) {
-          LOG(WARNING) <<
-            "obtained a null device object for enumerated device, skipping";
-          continue;
-        }
-
-        // Obtain dut_flaga/b identifiers.
-        id_gpio_dutflaga =
-            udev_device_get_property_value(dev, kIdGpioDutflaga);
-        id_gpio_dutflagb =
-            udev_device_get_property_value(dev, kIdGpioDutflagb);
-        if (id_gpio_dutflaga && id_gpio_dutflagb) {
-          LOG(INFO) << "found dut_flaga/b identifiers: a=" << id_gpio_dutflaga
-                    << " b=" << id_gpio_dutflagb;
-
-          gpio_dutflaga_str = id_gpio_dutflaga;
-          gpio_dutflagb_str = id_gpio_dutflagb;
-        } else {
-          LOG(ERROR) << "GPIO chip missing dut_flaga/b properties";
-        }
-
-        udev_device_unref(dev);
-      }
-    }
-
-    // Obtain dut_flaga, reusing the same udev instance.
-    if (!dutflaga_dev_name_ && !gpio_dutflaga_str.empty()) {
-      LOG(INFO) << "discovering device for GPIO dut_flaga ";
-      if (!GetDutflagGpioDevName(udev, gpio_dutflaga_str,
-                                 &dutflaga_dev_name_)) {
-        LOG(ERROR) << "discovery of dut_flaga GPIO device failed";
-        return false;
-      }
-    }
-
-    // Now obtain dut_flagb.
-    if (!dutflagb_dev_name_ && !gpio_dutflagb_str.empty()) {
-      LOG(INFO) << "discovering device for GPIO dut_flagb";
-      if (!GetDutflagGpioDevName(udev, gpio_dutflagb_str,
-                                 &dutflagb_dev_name_)) {
-        LOG(ERROR) << "discovery of dut_flagb GPIO device failed";
-        return false;
-      }
-    }
-
-    LOG(INFO) << "end discovery of dut_flaga/b devices";
+    if (!enum_helper->ProcessDev(dev))
+      return false;
   }
 
-  // Write cached GPIO dutflag(s) to output strings.
-  if (dutflaga_dev_name_p && dutflaga_dev_name_)
-    *dutflaga_dev_name_p = dutflaga_dev_name_;
-  if (dutflagb_dev_name_p && dutflagb_dev_name_)
-    *dutflagb_dev_name_p = dutflagb_dev_name_;
+  // Make sure postconditions were met.
+  return enum_helper->Finalize();
+}
+
+bool StandardGpioHandler::DiscoverGpios() {
+  if (is_discovery_attempted_)
+    return true;
+
+  is_discovery_attempted_ = true;
+
+  // Obtain libudev instance and attach to a dedicated closer.
+  struct udev* udev;
+  if (!(udev = udev_iface_->New())) {
+    LOG(ERROR) << "failed to obtain libudev instance";
+    return false;
+  }
+  scoped_ptr<UdevInterface::UdevCloser>
+      udev_closer(udev_iface_->NewUdevCloser(&udev));
+
+  // Enumerate GPIO chips, scanning for GPIO descriptors.
+  GpioChipUdevEnumHelper chip_enum_helper(this);
+  if (!InitUdevEnum(udev, &chip_enum_helper)) {
+    LOG(ERROR) << "enumeration error, aborting GPIO discovery";
+    return false;
+  }
+
+  // Obtain device names for all discovered GPIOs, reusing the udev instance.
+  for (int id = 0; id < kGpioIdMax; id++) {
+    GpioUdevEnumHelper gpio_enum_helper(this, static_cast<GpioId>(id));
+    if (!InitUdevEnum(udev, &gpio_enum_helper)) {
+      LOG(ERROR) << "enumeration error, aborting GPIO discovery";
+      return false;
+    }
+  }
 
   return true;
 }
 
-bool GpioHandler::GetDutflagGpioStatus(GpioHandler::DutflagGpioId id,
-                                       bool* status_p) {
-  CHECK(status_p);
+bool StandardGpioHandler::GetGpioDevName(StandardGpioHandler::GpioId id,
+                                         string* dev_path_p) {
+  CHECK(id >= 0 && id < kGpioIdMax && dev_path_p);
 
-  // Obtain GPIO device file name.
-  string dutflag_dev_name;
-  switch (id) {
-    case kDutflagaGpio:
-      GetDutflagGpioDevNames(&dutflag_dev_name, NULL);
-      break;
-    case kDutflagbGpio:
-      GetDutflagGpioDevNames(NULL, &dutflag_dev_name);
-      break;
-    default:
-      LOG(FATAL) << "invalid dutflag GPIO id: " << id;
-  }
-
-  if (dutflag_dev_name.empty()) {
-    LOG(WARNING) << "could not find dutflag GPIO device";
-    return false;
-  }
-
-  // Open device for reading.
-  string dutflaga_value_dev_name = dutflag_dev_name + "/value";
-  int dutflaga_fd = HANDLE_EINTR(open(dutflaga_value_dev_name.c_str(), 0));
-  if (dutflaga_fd < 0) {
-    PLOG(ERROR) << "opening dutflaga GPIO device file failed";
-    return false;
-  }
-  ScopedEintrSafeFdCloser dutflaga_fd_closer(&dutflaga_fd);
-
-  // Read the dut_flaga GPIO signal. We attempt to read more than---but expect
-  // to receive exactly---two characters: a '0' or '1', and a newline. This is
-  // to ensure that the GPIO device returns a legible result.
-  char buf[3];
-  int ret = HANDLE_EINTR(read(dutflaga_fd, buf, 3));
-  if (ret != 2) {
-    if (ret < 0)
-      PLOG(ERROR) << "reading dutflaga GPIO status failed";
-    else
-      LOG(ERROR) << "read more than one byte (" << ret << ")";
-    return false;
-  }
-
-  // Identify and write GPIO status.
-  char c = buf[0];
-  if ((c == '0' || c == '1') && buf[1] == '\n') {
-    *status_p = (c == '1');
-  } else {
-    buf[2] = '\0';
-    LOG(ERROR) << "read unexpected value from dutflaga GPIO: " << buf;
-    return false;
-  }
-
+  *dev_path_p = gpios_[id].dev_path;
   return true;
 }