Added GPIO support for update engine automated test.
New functionality in update engine for discovering and reading the
dut_flaga/b GPIOs; we use the dut_flaga value as trigger for using
a different update server URL.
Note: in the future, we will migrate all GPIO functionality outside of
update engine, into its own dedicated package.
CQ-DEPEND=I82cdd28a87f5227e63586810534b92922d43ae52
BUG=chromium-os:25397
TEST=GPIOs were discovered and read on x86-alex, w/ and w/o servo.
Change-Id: Ice3a7ee9669c0916956b492c9524e4b5808d6fb3
Reviewed-on: https://gerrit.chromium.org/gerrit/16554
Commit-Ready: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
diff --git a/update_attempter.cc b/update_attempter.cc
index e79f692..210e78a 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -14,7 +14,9 @@
#include <tr1/memory>
#include <vector>
+#include <base/eintr_wrapper.h>
#include <base/rand_util.h>
+#include <base/string_util.h>
#include <glib.h>
#include <metrics/metrics_library.h>
#include <policy/libpolicy.h>
@@ -46,11 +48,54 @@
const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+// TODO(garnold) this is currently an arbitrary address and will change based on
+// discussion about the actual test lab configuration.
+const char* const UpdateAttempter::kTestUpdateUrl("https://10.0.0.1/update");
+
const char* kUpdateCompletedMarker =
"/var/run/update_engine_autoupdate_completed";
namespace {
const int kMaxConsecutiveObeyProxyRequests = 20;
+
+// 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);
+};
+
+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);
+};
} // namespace {}
const char* UpdateStatusToString(UpdateStatus status) {
@@ -131,8 +176,8 @@
CleanupPriorityManagement();
}
-void UpdateAttempter::Update(const std::string& app_version,
- const std::string& omaha_url,
+void UpdateAttempter::Update(const string& app_version,
+ const string& omaha_url,
bool obey_proxies,
bool interactive) {
chrome_proxy_resolver_.Init();
@@ -163,7 +208,27 @@
if (policy_provider_->device_policy_is_loaded())
policy_provider_->GetDevicePolicy().GetReleaseChannel(&release_track);
- if (!omaha_request_params_.Init(app_version, omaha_url, release_track)) {
+ // Force alternate default address for automated test case, based on GPIO
+ // signal. We replicate the URL string so as not to overwrite the argument.
+ string omaha_url_to_use = omaha_url;
+ if (omaha_url_to_use.empty()) {
+ bool dutflaga_gpio_status;
+ if (GetDutflagaGpio(&dutflaga_gpio_status)) {
+ LOG(INFO) << "dutflaga GPIO status: "
+ << (dutflaga_gpio_status ? "on" : "off");
+
+ // The dut_flaga GPIO is actually signaled when in the 'off' position.
+ if (!dutflaga_gpio_status) {
+ LOG(INFO) << "using alternative server address: " << kTestUpdateUrl;
+ omaha_url_to_use = kTestUpdateUrl;
+ }
+ } else {
+ LOG(ERROR) << "reading dutflaga GPIO status failed";
+ }
+ }
+
+ if (!omaha_request_params_.Init(app_version, omaha_url_to_use,
+ release_track)) {
LOG(ERROR) << "Unable to initialize Omaha request device params.";
return;
}
@@ -291,8 +356,8 @@
UpdateBootFlags();
}
-void UpdateAttempter::CheckForUpdate(const std::string& app_version,
- const std::string& omaha_url) {
+void UpdateAttempter::CheckForUpdate(const string& app_version,
+ const string& omaha_url) {
if (status_ != UPDATE_STATUS_IDLE) {
LOG(INFO) << "Check for update requested, but status is "
<< UpdateStatusToString(status_) << ", so not checking.";
@@ -464,8 +529,8 @@
bool UpdateAttempter::GetStatus(int64_t* last_checked_time,
double* progress,
- std::string* current_operation,
- std::string* new_version,
+ string* current_operation,
+ string* new_version,
int64_t* new_size) {
*last_checked_time = last_checked_time_;
*progress = download_progress_;
@@ -475,6 +540,261 @@
return true;
}
+// Discovers the dut_flag GPIO identified by |gpio_dutflag_str| and stores the
+// full device name in |dutflag_dev_name_p|. The function uses an open libudev
+// instance |udev|. Returns zero on success, -1 otherwise.
+bool UpdateAttempter::GetDutflagGpioDevName(struct udev* udev,
+ const string& gpio_dutflag_str,
+ string* dutflag_dev_name_p) {
+ CHECK(udev && dutflag_dev_name_p);
+
+ struct udev_enumerate* udev_enum = NULL;
+ int num_gpio_dutflags = 0;
+ const string gpio_dutflag_pattern = "*" + gpio_dutflag_str;
+ int ret;
+
+ // Initialize udev enumerate context and closer.
+ 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,
+ gpio_dutflag_pattern.c_str()))) {
+ 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 (error code "
+ << 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_dutflags++;
+ if (num_gpio_dutflags > 1) {
+ LOG(WARNING) <<
+ "enumerated multiple dutflag GPIOs, 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 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 = dev_syspath;
+ } else {
+ LOG(WARNING) << "could not obtain device syspath";
+ }
+
+ udev_device_unref(dev);
+ }
+
+ return true;
+}
+
+// Discovers and stores the device names of the two dut_flag GPIOs. Returns zero
+// upon success, -1 otherwise.
+bool UpdateAttempter::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_.empty() || dutflagb_dev_name_.empty()) {
+ 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";
+ return false;
+ }
+ ScopedUdevCloser udev_closer(&udev);
+
+ // 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_.empty() && !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_.empty() && !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";
+ }
+
+ // Write cached GPIO dutflag(s) to output strings.
+ if (dutflaga_dev_name_p)
+ *dutflaga_dev_name_p = dutflaga_dev_name_;
+ if (dutflagb_dev_name_p)
+ *dutflagb_dev_name_p = dutflagb_dev_name_;
+
+ return true;
+}
+
+// Reads the value of the dut_flaga GPIO and stores it in |status_p|. Returns
+// true upon success, false otherwise (which also means that the GPIO value was
+// not stored and should not be used).
+bool UpdateAttempter::GetDutflagaGpio(bool* status_p) {
+ // Obtain GPIO device file name.
+ string dutflaga_dev_name;
+ GetDutflagGpioDevNames(&dutflaga_dev_name, NULL);
+ if (dutflaga_dev_name.empty()) {
+ LOG(WARNING) << "could not find dutflaga GPIO device";
+ return false;
+ }
+
+ // Open device for reading.
+ string dutflaga_value_dev_name = dutflaga_dev_name + "/value";
+ int dutflaga_fd;
+ HANDLE_EINTR((dutflaga_fd = open(dutflaga_value_dev_name.c_str(), 0)));
+ if (dutflaga_fd < 0) {
+ PLOG(ERROR) << "opening dutflaga GPIO device file failed";
+ return false;
+ }
+ ScopedFdCloser 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((ret = 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;
+ }
+
+ return true;
+}
+
void UpdateAttempter::UpdateBootFlags() {
if (update_boot_flags_running_) {
LOG(INFO) << "Update boot flags running, nothing to do.";
@@ -508,7 +828,7 @@
void UpdateAttempter::StaticCompleteUpdateBootFlags(
int return_code,
- const std::string& output,
+ const string& output,
void* p) {
reinterpret_cast<UpdateAttempter*>(p)->CompleteUpdateBootFlags(return_code);
}