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_mock_udev_interface.cc b/gpio_mock_udev_interface.cc
new file mode 100644
index 0000000..22c7b7f
--- /dev/null
+++ b/gpio_mock_udev_interface.cc
@@ -0,0 +1,371 @@
+// 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_udev_interface.h"
+
+#include <string>
+
+#include <base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include "gpio_handler_unittest.h"
+
+namespace chromeos_update_engine {
+
+const char* StandardGpioMockUdevInterface::kUdevGpioSubsystem = "gpio";
+const char* StandardGpioMockUdevInterface::kUdevGpioChipSysname = "gpiochip*";
+
+const StandardGpioMockUdevInterface::GpioDescriptor
+ StandardGpioMockUdevInterface::gpio_descriptors_[kMockGpioIdMax] = {
+ { "ID_GPIO_DUTFLAGA", MOCK_DUTFLAGA_GPIO_ID },
+ { "ID_GPIO_DUTFLAGB", MOCK_DUTFLAGB_GPIO_ID },
+};
+
+const char* StandardGpioMockUdevInterface::enum_gpio_chip_dev_list_[] = {
+ "gpiochip" MOCK_GPIO_CHIP_ID,
+ NULL
+};
+const char* StandardGpioMockUdevInterface::enum_dutflaga_gpio_dev_list_[] = {
+ "gpio" MOCK_DUTFLAGA_GPIO_ID,
+ NULL
+};
+const char* StandardGpioMockUdevInterface::enum_dutflagb_gpio_dev_list_[] = {
+ "gpio" MOCK_DUTFLAGB_GPIO_ID,
+ NULL
+};
+
+StandardGpioMockUdevInterface::StandardGpioMockUdevInterface()
+ : udev_id_(0), udev_enum_id_(0), num_discovered_(0) {
+ std::fill_n(used_udev_ids_, MAX_UDEV_OBJECTS, false);
+ std::fill_n(used_udev_enum_ids_, MAX_UDEV_ENUM_OBJECTS, false);
+
+ gpio_chip_dev_ = (MockDevice) {
+ MOCK_SYSFS_PREFIX "/gpiochip" MOCK_GPIO_CHIP_ID,
+ gpio_descriptors_, kMockGpioIdMax, false, false
+ };
+ dutflaga_gpio_dev_ = (MockDevice) {
+ MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGA_GPIO_ID, NULL, 0, true, false
+ };
+ dutflagb_gpio_dev_ = (MockDevice) {
+ MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGB_GPIO_ID, NULL, 0, true, false
+ };
+}
+
+// Device lists are simply null-terminated arrays of strings.
+const char* StandardGpioMockUdevInterface::ListEntryGetName(
+ struct udev_list_entry* list_entry) {
+ const char** mock_list_entry = reinterpret_cast<const char**>(list_entry);
+ EXPECT_TRUE(mock_list_entry && *mock_list_entry);
+ if (!mock_list_entry)
+ return NULL;
+ // The mock list entry is the name string itself...
+ return *mock_list_entry;
+}
+
+struct udev_list_entry* StandardGpioMockUdevInterface::ListEntryGetNext(
+ struct udev_list_entry* list_entry) {
+ char** mock_list_entry = reinterpret_cast<char**>(list_entry);
+ EXPECT_TRUE(mock_list_entry && *mock_list_entry);
+ if (!mock_list_entry)
+ return NULL;
+ // Advance to the next element in the array.
+ mock_list_entry++;
+ return (*mock_list_entry ?
+ reinterpret_cast<struct udev_list_entry*>(mock_list_entry) : NULL);
+}
+
+struct udev_device* StandardGpioMockUdevInterface::DeviceNewFromSyspath(
+ struct udev* udev, const char* syspath) {
+ const size_t udev_id = UdevToId(udev);
+ if (udev_id >= MAX_UDEV_OBJECTS)
+ return NULL;
+ MockDevice* mock_dev;
+
+ // Generate the desired mock device based on the provided syspath.
+ if (!strcmp(syspath, enum_gpio_chip_dev_list_[0])) {
+ mock_dev = const_cast<MockDevice*>(&gpio_chip_dev_);
+ } else if (!strcmp(syspath, enum_dutflaga_gpio_dev_list_[0])) {
+ mock_dev = const_cast<MockDevice*>(&dutflaga_gpio_dev_);
+ } else if (!strcmp(syspath, enum_dutflagb_gpio_dev_list_[0])) {
+ mock_dev = const_cast<MockDevice*>(&dutflagb_gpio_dev_);
+ } else {
+ ADD_FAILURE();
+ return NULL;
+ }
+
+ EXPECT_FALSE(mock_dev->is_used);
+ if (mock_dev->is_used)
+ return NULL;
+ mock_dev->is_used = true;
+
+ return reinterpret_cast<struct udev_device*>(mock_dev);
+}
+
+const char* StandardGpioMockUdevInterface::DeviceGetPropertyValue(
+ struct udev_device* udev_device, const char* key) {
+ const MockDevice* mock_dev = UdevDeviceToMock(udev_device);
+ EXPECT_TRUE(mock_dev->properties);
+ if (!mock_dev->properties)
+ return NULL;
+ for (size_t i = 0; i < mock_dev->num_properties; i++)
+ if (!strcmp(key, mock_dev->properties[i].property))
+ return mock_dev->properties[i].value;
+ return NULL;
+}
+
+const char* StandardGpioMockUdevInterface::DeviceGetSyspath(
+ struct udev_device* udev_device) {
+ const MockDevice* mock_dev = UdevDeviceToMock(udev_device);
+ if (mock_dev->is_gpio)
+ num_discovered_++;
+ return mock_dev->syspath;
+}
+
+void StandardGpioMockUdevInterface::DeviceUnref(
+ struct udev_device* udev_device) {
+ MockDevice* mock_dev = UdevDeviceToMock(udev_device);
+ mock_dev->is_used = false;
+}
+
+struct udev_enumerate* StandardGpioMockUdevInterface::EnumerateNew(
+ struct udev* udev) {
+ const size_t udev_id = UdevToId(udev);
+ if (udev_id >= MAX_UDEV_OBJECTS)
+ return NULL;
+ // A new enumeration "pointer" is cast for an integer identifier.
+ EXPECT_LT(udev_enum_id_, MAX_UDEV_ENUM_OBJECTS);
+ if (udev_enum_id_ >= MAX_UDEV_ENUM_OBJECTS)
+ return NULL;
+ used_udev_enum_ids_[udev_enum_id_] = true;
+ return reinterpret_cast<struct udev_enumerate*>(++udev_enum_id_);
+}
+
+int StandardGpioMockUdevInterface::EnumerateAddMatchSubsystem(
+ struct udev_enumerate* udev_enum, const char* subsystem) {
+ const size_t udev_enum_id = UdevEnumToId(udev_enum);
+ if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+ return -1;
+
+ // Ensure client is correctly requesting the GPIO subsystem.
+ EXPECT_STREQ(subsystem, kUdevGpioSubsystem);
+ if (strcmp(subsystem, kUdevGpioSubsystem))
+ return -1;
+
+ return 0;
+}
+
+int StandardGpioMockUdevInterface::EnumerateAddMatchSysname(
+ struct udev_enumerate* udev_enum, const char* sysname) {
+ const size_t udev_enum_id = UdevEnumToId(udev_enum);
+ if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+ return -1;
+
+ // Ensure client is requesting the correct sysnamem, depending on the correct
+ // phase in the detection process.
+ switch (udev_enum_id) {
+ case 0: // looking for a GPIO chip
+ EXPECT_STREQ(sysname, kUdevGpioChipSysname);
+ if (strcmp(sysname, kUdevGpioChipSysname))
+ return -1;
+ break;
+ case 1: // looking for dut_flaga/b
+ case 2: {
+ const int gpio_id = udev_enum_id - 1;
+ const std::string gpio_pattern =
+ StringPrintf("*%s", gpio_descriptors_[gpio_id].value);
+ EXPECT_STREQ(sysname, gpio_pattern.c_str());
+ if (strcmp(sysname, gpio_pattern.c_str()))
+ return -1;
+ break;
+ }
+ default:
+ ADD_FAILURE(); // can't get here
+ return -1;
+ }
+
+ return 0;
+}
+
+int StandardGpioMockUdevInterface::EnumerateScanDevices(
+ struct udev_enumerate* udev_enum) {
+ const size_t udev_enum_id = UdevEnumToId(udev_enum);
+ if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+ return -1;
+ return 0; // nothing to do, really
+}
+
+struct udev_list_entry* StandardGpioMockUdevInterface::EnumerateGetListEntry(
+ struct udev_enumerate* udev_enum) {
+ const size_t udev_enum_id = UdevEnumToId(udev_enum);
+ if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+ return NULL;
+
+ // Return a list of devices corresponding to the provided enumeration.
+ switch (udev_enum_id) {
+ case 0:
+ return reinterpret_cast<struct udev_list_entry*>(
+ enum_gpio_chip_dev_list_);
+ case 1:
+ return reinterpret_cast<struct udev_list_entry*>(
+ enum_dutflaga_gpio_dev_list_);
+ case 2:
+ return reinterpret_cast<struct udev_list_entry*>(
+ enum_dutflagb_gpio_dev_list_);
+ }
+
+ ADD_FAILURE(); // can't get here
+ return NULL;
+}
+
+void StandardGpioMockUdevInterface::EnumerateUnref(
+ struct udev_enumerate* udev_enum) {
+ const size_t udev_enum_id = UdevEnumToId(udev_enum);
+ if (udev_enum_id >= MAX_UDEV_ENUM_OBJECTS)
+ return;
+ used_udev_enum_ids_[udev_enum_id] = false; // make sure it's freed just once
+}
+
+struct udev* StandardGpioMockUdevInterface::New() {
+ // The returned "pointer" is cast from an integer identifier.
+ EXPECT_LT(udev_id_, MAX_UDEV_OBJECTS);
+ if (udev_id_ >= MAX_UDEV_OBJECTS)
+ return NULL;
+ used_udev_ids_[udev_id_] = true;
+ return reinterpret_cast<struct udev*>(++udev_id_);
+}
+
+void StandardGpioMockUdevInterface::Unref(struct udev* udev) {
+ // Convert to object identifier, ensuring the object has been "allocated".
+ const size_t udev_id = UdevToId(udev);
+ if (udev_id >= MAX_UDEV_OBJECTS)
+ return;
+ used_udev_ids_[udev_id] = false; // make sure it's freed just once
+}
+
+void StandardGpioMockUdevInterface::ExpectAllResourcesDeallocated() const {
+ // Make sure that all handles were released.
+ for (size_t i = 0; i < MAX_UDEV_OBJECTS; i++)
+ EXPECT_FALSE(used_udev_ids_[i]);
+ for (size_t i = 0; i < MAX_UDEV_ENUM_OBJECTS; i++)
+ EXPECT_FALSE(used_udev_enum_ids_[i]);
+ EXPECT_FALSE(gpio_chip_dev_.is_used);
+ EXPECT_FALSE(dutflaga_gpio_dev_.is_used);
+ EXPECT_FALSE(dutflagb_gpio_dev_.is_used);
+}
+
+void StandardGpioMockUdevInterface::ExpectDiscoverySuccess() const {
+ EXPECT_EQ(num_discovered_, kMockGpioIdMax);
+}
+
+void StandardGpioMockUdevInterface::ExpectDiscoveryFail() const {
+ EXPECT_LT(num_discovered_, kMockGpioIdMax);
+}
+
+StandardGpioMockUdevInterface::MockDevice*
+StandardGpioMockUdevInterface::UdevDeviceToMock(
+ struct udev_device* udev_dev) const {
+ EXPECT_TRUE(udev_dev);
+ if (!udev_dev)
+ return NULL;
+ MockDevice* mock_dev = reinterpret_cast<MockDevice*>(udev_dev);
+ EXPECT_TRUE(mock_dev->is_used);
+ if (!mock_dev->is_used)
+ return NULL;
+ return mock_dev;
+}
+
+size_t StandardGpioMockUdevInterface::UdevEnumToId(
+ struct udev_enumerate* udev_enum) const {
+ EXPECT_TRUE(udev_enum);
+ size_t udev_enum_id = reinterpret_cast<size_t>(udev_enum) - 1;
+ EXPECT_LT(udev_enum_id, MAX_UDEV_ENUM_OBJECTS);
+ EXPECT_TRUE(used_udev_enum_ids_[udev_enum_id]);
+ if (!(udev_enum && udev_enum_id < MAX_UDEV_ENUM_OBJECTS &&
+ used_udev_enum_ids_[udev_enum_id]))
+ return MAX_UDEV_ENUM_OBJECTS;
+ return udev_enum_id;
+}
+
+size_t StandardGpioMockUdevInterface::UdevToId(struct udev* udev) const {
+ EXPECT_TRUE(udev);
+ size_t udev_id = reinterpret_cast<size_t>(udev) - 1;
+ EXPECT_LT(udev_id, MAX_UDEV_OBJECTS);
+ EXPECT_TRUE(used_udev_ids_[udev_id]);
+ if (!(udev && udev_id < MAX_UDEV_OBJECTS && used_udev_ids_[udev_id]))
+ return MAX_UDEV_OBJECTS;
+ return udev_id;
+}
+
+#define MOCK_GPIO_CHIP1_ID "200"
+#define MOCK_GPIO_CHIP2_ID "201"
+
+const char* MultiChipGpioMockUdevInterface::enum_gpio_chip_dev_list_[] = {
+ "gpiochip" MOCK_GPIO_CHIP1_ID,
+ "gpiochip" MOCK_GPIO_CHIP2_ID,
+ NULL
+};
+
+MultiChipGpioMockUdevInterface::MultiChipGpioMockUdevInterface() {
+ gpio_chip1_dev_ = (MockDevice) {
+ MOCK_SYSFS_PREFIX "/gpiochip" MOCK_GPIO_CHIP1_ID,
+ gpio_descriptors_, kMockGpioIdMax, false, false
+ };
+ gpio_chip2_dev_ = (MockDevice) {
+ MOCK_SYSFS_PREFIX "/gpiochip" MOCK_GPIO_CHIP2_ID,
+ gpio_descriptors_, kMockGpioIdMax, false, false
+ };
+}
+
+struct udev_device* MultiChipGpioMockUdevInterface::DeviceNewFromSyspath(
+ struct udev* udev, const char* syspath) {
+ const size_t udev_id = UdevToId(udev);
+ if (udev_id >= MAX_UDEV_OBJECTS)
+ return NULL;
+ MockDevice* mock_dev;
+
+ // Generate the desired mock device based on the provided syspath.
+ if (!strcmp(syspath, enum_gpio_chip_dev_list_[0])) {
+ mock_dev = const_cast<MockDevice*>(&gpio_chip1_dev_);
+ } else if (!strcmp(syspath, enum_gpio_chip_dev_list_[1])) {
+ mock_dev = const_cast<MockDevice*>(&gpio_chip2_dev_);
+ } else if (!strcmp(syspath, enum_dutflaga_gpio_dev_list_[0])) {
+ mock_dev = const_cast<MockDevice*>(&dutflaga_gpio_dev_);
+ } else if (!strcmp(syspath, enum_dutflagb_gpio_dev_list_[0])) {
+ mock_dev = const_cast<MockDevice*>(&dutflagb_gpio_dev_);
+ } else {
+ ADD_FAILURE();
+ return NULL;
+ }
+
+ EXPECT_FALSE(mock_dev->is_used);
+ if (mock_dev->is_used)
+ return NULL;
+ mock_dev->is_used = true;
+
+ return reinterpret_cast<struct udev_device*>(mock_dev);
+}
+
+struct udev_list_entry* MultiChipGpioMockUdevInterface::EnumerateGetListEntry(
+ struct udev_enumerate* udev_enum) {
+ const size_t udev_enum_id = UdevEnumToId(udev_enum);
+
+ // Return a list of devices corresponding to the provided enumeration.
+ switch (udev_enum_id) {
+ case 0:
+ return reinterpret_cast<struct udev_list_entry*>(
+ enum_gpio_chip_dev_list_);
+ case 1:
+ return reinterpret_cast<struct udev_list_entry*>(
+ enum_dutflaga_gpio_dev_list_);
+ case 2:
+ return reinterpret_cast<struct udev_list_entry*>(
+ enum_dutflagb_gpio_dev_list_);
+ }
+
+ ADD_FAILURE(); // can't get here
+ return NULL;
+}
+
+
+} // namespace chromeos_update_engine