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__