[hid][buttons] Add unit tests
Also add DeviceGetMetadataSize to the fake_ddk and refactor/simplify.
ZX-3814 #done
Test: hid-buttons-test unit tests and trace buttons pressing on Astro
via the hid uapp as in:
$ hid read /dev/sys/platform/00:00:13/hid-buttons/hid-device-000 3
hid read /dev/sys/platform/00:00:13/hid-buttons/hid-device-000 3
hid: input thread started for
/dev/sys/platform/00:00:13/hid-buttons/hid-device-000
hid: /dev/sys/platform/00:00:13/hid-buttons/hid-device-000 proto=0
hid: /dev/sys/platform/00:00:13/hid-buttons/hid-device-000 num reports:
1
hid: /dev/sys/platform/00:00:13/hid-buttons/hid-device-000 report ids...
ID 0x01 : TYPE Input : SIZE 3 bytes
hid: /dev/sys/platform/00:00:13/hid-buttons/hid-device-000 maxreport=3
read returned 3
hid: input from /dev/sys/platform/00:00:13/hid-buttons/hid-device-000
01 00 00
read returned 3
hid: input from /dev/sys/platform/00:00:13/hid-buttons/hid-device-000
01 00 01
read returned 3
hid: input from /dev/sys/platform/00:00:13/hid-buttons/hid-device-000
01 01 01
hid: closing /dev/sys/platform/00:00:13/hid-buttons/hid-device-000
$
Change-Id: I5ca9cc8decd222f2fe872ce291bc6b8e7ba0833e
diff --git a/zircon/system/dev/input/hid-buttons/BUILD.gn b/zircon/system/dev/input/hid-buttons/BUILD.gn
index b99d892..6759b38 100644
--- a/zircon/system/dev/input/hid-buttons/BUILD.gn
+++ b/zircon/system/dev/input/hid-buttons/BUILD.gn
@@ -21,3 +21,27 @@
"$zx/system/ulib/zx",
]
}
+
+test("hid-buttons-test") {
+ output_name = "hid-buttons-test"
+ sources = [
+ "hid-buttons-test.cpp",
+ "hid-buttons.cpp",
+ ]
+ deps = [
+ "$zx/system/banjo/ddk.protocol.hidbus",
+ "$zx/system/banjo/ddk.protocol.platform.bus",
+ "$zx/system/banjo/ddk.protocol.platform.device",
+ "$zx/system/dev/lib/fake_ddk",
+ "$zx/system/dev/lib/mock-gpio",
+ "$zx/system/ulib/ddk",
+ "$zx/system/ulib/ddktl",
+ "$zx/system/ulib/fbl",
+ "$zx/system/ulib/hid",
+ "$zx/system/ulib/mock-function",
+ "$zx/system/ulib/sync",
+ "$zx/system/ulib/zircon",
+ "$zx/system/ulib/zx",
+ "$zx/system/ulib/zxtest",
+ ]
+}
diff --git a/zircon/system/dev/input/hid-buttons/hid-buttons-test.cpp b/zircon/system/dev/input/hid-buttons/hid-buttons-test.cpp
new file mode 100644
index 0000000..9cfbf6b
--- /dev/null
+++ b/zircon/system/dev/input/hid-buttons/hid-buttons-test.cpp
@@ -0,0 +1,432 @@
+// Copyright 2019 The Fuchsia 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 "hid-buttons.h"
+
+#include <ddk/metadata.h>
+#include <ddk/metadata/buttons.h>
+#include <ddk/protocol/platform/bus.h>
+#include <ddktl/protocol/gpio.h>
+#include <lib/fake_ddk/fake_ddk.h>
+#include <lib/mock-gpio/mock-gpio.h>
+#include <zxtest/zxtest.h>
+
+namespace {
+static const buttons_button_config_t buttons_direct[] = {
+ {BUTTONS_TYPE_DIRECT, BUTTONS_ID_VOLUME_UP, 0, 0, 0},
+};
+
+static const buttons_gpio_config_t gpios_direct[] = {
+ {BUTTONS_GPIO_TYPE_INTERRUPT, 0, {GPIO_NO_PULL}}};
+
+static const buttons_button_config_t buttons_matrix[] = {
+ {BUTTONS_TYPE_MATRIX, BUTTONS_ID_VOLUME_UP, 0, 2, 0},
+ {BUTTONS_TYPE_MATRIX, BUTTONS_ID_KEY_A, 1, 2, 0},
+ {BUTTONS_TYPE_MATRIX, BUTTONS_ID_KEY_M, 0, 3, 0},
+ {BUTTONS_TYPE_MATRIX, BUTTONS_ID_PLAY_PAUSE, 1, 3, 0},
+};
+
+static const buttons_gpio_config_t gpios_matrix[] = {
+ {BUTTONS_GPIO_TYPE_INTERRUPT, 0, {GPIO_PULL_UP}},
+ {BUTTONS_GPIO_TYPE_INTERRUPT, 0, {GPIO_PULL_UP}},
+ {BUTTONS_GPIO_TYPE_MATRIX_OUTPUT, 0, {0}},
+ {BUTTONS_GPIO_TYPE_MATRIX_OUTPUT, 0, {0}},
+};
+} // namespace
+
+namespace buttons {
+
+enum class TestType {
+ kTestDirect,
+ kTestMatrix,
+};
+
+class FakeDdkButtons : public fake_ddk::Bind {
+public:
+ FakeDdkButtons(TestType type)
+ : Bind(), type_(type) {}
+
+ zx_status_t DeviceGetMetadata(zx_device_t* dev, uint32_t type, void* buf, size_t buflen,
+ size_t* actual) {
+ switch (type) {
+ case DEVICE_METADATA_BUTTONS_BUTTONS:
+ switch (type_) {
+ case TestType::kTestDirect:
+ *actual = sizeof(buttons_direct);
+ if (buflen != sizeof(buttons_direct)) {
+ return ZX_ERR_INTERNAL;
+ }
+ memcpy(buf, &buttons_direct[0], *actual);
+ break;
+ case TestType::kTestMatrix:
+ *actual = sizeof(buttons_matrix);
+ if (buflen != sizeof(buttons_matrix)) {
+ return ZX_ERR_INTERNAL;
+ }
+ memcpy(buf, &buttons_matrix[0], *actual);
+ break;
+ }
+ break;
+ case DEVICE_METADATA_BUTTONS_GPIOS:
+ switch (type_) {
+ case TestType::kTestDirect:
+ *actual = sizeof(gpios_direct);
+ if (buflen != sizeof(gpios_direct)) {
+ return ZX_ERR_INTERNAL;
+ }
+ memcpy(buf, &gpios_direct[0], *actual);
+ break;
+ case TestType::kTestMatrix:
+ *actual = sizeof(gpios_matrix);
+ if (buflen != sizeof(gpios_matrix)) {
+ return ZX_ERR_INTERNAL;
+ }
+ memcpy(buf, &gpios_matrix[0], *actual);
+ break;
+ }
+ break;
+ default:
+ return ZX_ERR_INTERNAL;
+ }
+ return ZX_OK;
+ }
+
+ zx_status_t DeviceGetMetadataSize(zx_device_t* dev, uint32_t type, size_t* out_size) {
+ switch (type) {
+ case DEVICE_METADATA_BUTTONS_BUTTONS:
+ switch (type_) {
+ case TestType::kTestDirect:
+ *out_size = sizeof(buttons_direct);
+ break;
+ case TestType::kTestMatrix:
+ *out_size = sizeof(buttons_matrix);
+ break;
+ }
+ break;
+ case DEVICE_METADATA_BUTTONS_GPIOS:
+ switch (type_) {
+ case TestType::kTestDirect:
+ *out_size = sizeof(gpios_direct);
+ break;
+ case TestType::kTestMatrix:
+ *out_size = sizeof(gpios_matrix);
+ break;
+ }
+ break;
+ default:
+ return ZX_ERR_INTERNAL;
+ }
+ return ZX_OK;
+ }
+
+private:
+ TestType type_;
+};
+
+class HidButtonsDeviceTest : public HidButtonsDevice {
+public:
+ HidButtonsDeviceTest(mock_gpio::MockGpio* gpios, size_t gpios_count, TestType type)
+ : HidButtonsDevice(fake_ddk::kFakeParent), type_(type) {
+ fbl::AllocChecker ac;
+ using MockPointer = mock_gpio::MockGpio*;
+ gpio_mocks_.reset(new (&ac) MockPointer[gpios_count], gpios_count);
+ for (size_t i = 0; i < gpios_count; ++i) {
+ gpio_mocks_[i] = &gpios[i];
+ }
+ }
+ void ShutDownTest() {
+ HidButtonsDevice::ShutDown();
+ }
+
+ void SetupGpio(const zx::interrupt& irq, size_t gpio_index) {
+ auto* mock = gpio_mocks_[gpio_index];
+ mock->ExpectSetAltFunction(ZX_OK, 0);
+ switch (type_) {
+ case TestType::kTestDirect:
+ mock->ExpectConfigIn(ZX_OK, GPIO_NO_PULL)
+ .ExpectRead(ZX_OK, 0) // Not pushed, low.
+ .ExpectReleaseInterrupt(ZX_OK)
+ .ExpectGetInterrupt(ZX_OK, ZX_INTERRUPT_MODE_EDGE_HIGH, irq);
+
+ // Make sure polarity is correct in case it changed during configuration.
+ mock->ExpectRead(ZX_OK, 0) // Not pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_HIGH) // Set correct polarity.
+ .ExpectRead(ZX_OK, 0); // Still not pushed.
+ break;
+ case TestType::kTestMatrix:
+ if (gpios_matrix[gpio_index].type == BUTTONS_GPIO_TYPE_INTERRUPT) {
+ mock->ExpectConfigIn(ZX_OK, gpios_matrix[gpio_index].internal_pull)
+ .ExpectRead(ZX_OK, 0) // Not pushed, low.
+ .ExpectReleaseInterrupt(ZX_OK)
+ .ExpectGetInterrupt(ZX_OK, ZX_INTERRUPT_MODE_EDGE_HIGH, irq);
+
+ // Make sure polarity is correct in case it changed during configuration.
+ mock->ExpectRead(ZX_OK, 0) // Not pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_HIGH) // Set correct polarity.
+ .ExpectRead(ZX_OK, 0); // Still not pushed.
+ } else {
+ mock->ExpectConfigOut(ZX_OK, gpios_matrix[gpio_index].output_value);
+ }
+ break;
+ }
+ }
+
+ zx_status_t BindTest() {
+ FakeDdkButtons ddk(type_); // In these tests this is only used during binding.
+ fbl::Array<fake_ddk::ProtocolEntry> protocols(new fake_ddk::ProtocolEntry[1], 1);
+ protocols[0] = {ZX_PROTOCOL_PDEV, {nullptr, nullptr}};
+ ddk.SetProtocols(std::move(protocols));
+ return HidButtonsDevice::Bind();
+ }
+
+ zx_status_t PdevGetGpioProtocol(const pdev_protocol_t* proto, uint32_t index,
+ void* out_protocol_buffer, size_t out_protocol_size,
+ size_t* out_protocol_actual) override {
+ if (index > gpio_mocks_.size()) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+ if (out_protocol_size != sizeof(gpio_protocol_t)) {
+ return ZX_ERR_INTERNAL;
+ }
+ gpio_protocol_t* p = reinterpret_cast<gpio_protocol_t*>(out_protocol_buffer);
+ *p = *gpio_mocks_[index]->GetProto();
+ *out_protocol_actual = sizeof(gpio_protocol_t);
+ return ZX_OK;
+ }
+
+ void FakeInterrupt() {
+ // Issue the first interrupt.
+ zx_port_packet packet = {kPortKeyInterruptStart + 0, ZX_PKT_TYPE_USER, ZX_OK, {}};
+ zx_status_t status = port_.queue(&packet);
+ ZX_ASSERT(status == ZX_OK);
+ }
+
+private:
+ TestType type_;
+ fbl::Array<mock_gpio::MockGpio*> gpio_mocks_;
+};
+
+TEST(HidButtonsTest, DirectButtonBind) {
+ mock_gpio::MockGpio mock_gpios[1];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestDirect);
+ zx::interrupt irq;
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq);
+ device.SetupGpio(irq, 0);
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+ device.ShutDownTest();
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[0].VerifyAndClear());
+}
+
+TEST(HidButtonsTest, DirectButtonPush) {
+ mock_gpio::MockGpio mock_gpios[1];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestDirect);
+ zx::interrupt irq;
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq);
+ device.SetupGpio(irq, 0);
+
+ // Reconfigure Polarity due to interrupt.
+ mock_gpios[0].ExpectRead(ZX_OK, 1) // Pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1); // Still pushed, ok to continue.
+ mock_gpios[0].ExpectRead(ZX_OK, 1); // Read value to prepare report.
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+ device.FakeInterrupt();
+ device.ShutDownTest();
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[0].VerifyAndClear());
+}
+
+TEST(HidButtonsTest, DirectButtonUnpushedReport) {
+ mock_gpio::MockGpio mock_gpios[1];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestDirect);
+ zx::interrupt irq;
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq);
+ device.SetupGpio(irq, 0);
+
+ // Reconfigure Polarity due to interrupt.
+ mock_gpios[0].ExpectRead(ZX_OK, 0) // Not Pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_HIGH) // Keep the correct polarity.
+ .ExpectRead(ZX_OK, 0); // Still not pushed, ok to continue.
+ mock_gpios[0].ExpectRead(ZX_OK, 0); // Read value to prepare report.
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+ hidbus_ifc_protocol_ops_t ops = {};
+ ops.io_queue = [](void* ctx, const void* buffer, size_t size) {
+ buttons_input_rpt_t report_volume_up = {};
+ report_volume_up.rpt_id = 1;
+ report_volume_up.volume_up = 0; // Unpushed.
+ ASSERT_BYTES_EQ(buffer, &report_volume_up, size);
+ EXPECT_EQ(size, sizeof(report_volume_up));
+ };
+ hidbus_ifc_protocol_t protocol = {};
+ protocol.ops = &ops;
+ device.HidbusStart(&protocol);
+ device.FakeInterrupt();
+ device.ShutDownTest();
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[0].VerifyAndClear());
+}
+
+TEST(HidButtonsTest, DirectButtonPushedReport) {
+ mock_gpio::MockGpio mock_gpios[1];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestDirect);
+ zx::interrupt irq;
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq);
+ device.SetupGpio(irq, 0);
+
+ // Reconfigure Polarity due to interrupt.
+ mock_gpios[0].ExpectRead(ZX_OK, 1) // Pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1); // Still pushed, ok to continue.
+ mock_gpios[0].ExpectRead(ZX_OK, 1); // Read value to prepare report.
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+ hidbus_ifc_protocol_ops_t ops = {};
+ ops.io_queue = [](void* ctx, const void* buffer, size_t size) {
+ buttons_input_rpt_t report_volume_up = {};
+ report_volume_up.rpt_id = 1;
+ report_volume_up.volume_up = 1; // Pushed
+ ASSERT_BYTES_EQ(buffer, &report_volume_up, size);
+ EXPECT_EQ(size, sizeof(report_volume_up));
+ };
+ hidbus_ifc_protocol_t protocol = {};
+ protocol.ops = &ops;
+ device.HidbusStart(&protocol);
+ device.FakeInterrupt();
+ device.ShutDownTest();
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[0].VerifyAndClear());
+}
+
+TEST(HidButtonsTest, DirectButtonPushUnpushPush) {
+ mock_gpio::MockGpio mock_gpios[1];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestDirect);
+ zx::interrupt irq;
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq);
+ device.SetupGpio(irq, 0);
+
+ // Reconfigure Polarity due to interrupt.
+ mock_gpios[0].ExpectRead(ZX_OK, 1) // Pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1); // Still pushed, ok to continue.
+ mock_gpios[0].ExpectRead(ZX_OK, 1); // Read value to prepare report.
+
+ // Reconfigure Polarity due to interrupt.
+ mock_gpios[0].ExpectRead(ZX_OK, 0) // Not pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_HIGH) // Turn the polarity.
+ .ExpectRead(ZX_OK, 0); // Still not pushed, ok to continue.
+ mock_gpios[0].ExpectRead(ZX_OK, 0); // Read value to prepare report.
+
+ // Reconfigure Polarity due to interrupt.
+ mock_gpios[0].ExpectRead(ZX_OK, 1) // Pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1); // Still pushed, ok to continue.
+ mock_gpios[0].ExpectRead(ZX_OK, 1); // Read value to prepare report.
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+ device.FakeInterrupt();
+ device.FakeInterrupt();
+ device.FakeInterrupt();
+ device.ShutDownTest();
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[0].VerifyAndClear());
+}
+
+TEST(HidButtonsTest, DirectButtonFlaky) {
+ mock_gpio::MockGpio mock_gpios[1];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestDirect);
+ zx::interrupt irq;
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq);
+ device.SetupGpio(irq, 0);
+
+ // Reconfigure Polarity due to interrupt and keep checking until correct.
+ mock_gpios[0].ExpectRead(ZX_OK, 1) // Pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 0) // Oops now not pushed! not ok, retry.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_HIGH) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1) // Oops pushed! not ok, retry.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 0) // Oops now not pushed! not ok, retry.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_HIGH) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1) // Oops pushed again! not ok, retry.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1); // Now pushed and polarity set low, ok.
+ // Read value to generate report.
+ mock_gpios[0].ExpectRead(ZX_OK, 1); // Pushed.
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+ device.FakeInterrupt();
+ device.ShutDownTest();
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[0].VerifyAndClear());
+}
+
+TEST(HidButtonsTest, MatrixButtonBind) {
+ mock_gpio::MockGpio mock_gpios[countof(gpios_matrix)];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestMatrix);
+ zx::interrupt irqs[countof(gpios_matrix)];
+ for (size_t i = 0; i < countof(gpios_matrix); ++i) {
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irqs[i]);
+ device.SetupGpio(irqs[i], i);
+ }
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+ device.ShutDownTest();
+ for (size_t i = 0; i < countof(gpios_matrix); ++i) {
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[i].VerifyAndClear());
+ }
+}
+
+TEST(HidButtonsTest, MatrixButtonPush) {
+ mock_gpio::MockGpio mock_gpios[countof(gpios_matrix)];
+ HidButtonsDeviceTest device(mock_gpios, countof(mock_gpios), TestType::kTestMatrix);
+ zx::interrupt irqs[countof(gpios_matrix)];
+ for (size_t i = 0; i < countof(gpios_matrix); ++i) {
+ zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irqs[i]);
+ device.SetupGpio(irqs[i], i);
+ }
+
+ EXPECT_EQ(ZX_OK, device.BindTest());
+
+ // Reconfigure Polarity due to interrupt.
+ mock_gpios[0].ExpectRead(ZX_OK, 1) // Pushed.
+ .ExpectSetPolarity(ZX_OK, GPIO_POLARITY_LOW) // Turn the polarity.
+ .ExpectRead(ZX_OK, 1); // Still pushed, ok to continue.
+
+ // Matrix Scan for 0.
+ mock_gpios[2].ExpectConfigIn(ZX_OK, GPIO_NO_PULL); // Float column.
+ mock_gpios[0].ExpectRead(ZX_OK, 1); // Read row.
+ mock_gpios[2].ExpectConfigOut(ZX_OK, gpios_matrix[2].output_value); // Restore column.
+
+ // Matrix Scan for 1.
+ mock_gpios[2].ExpectConfigIn(ZX_OK, GPIO_NO_PULL); // Float column.
+ mock_gpios[1].ExpectRead(ZX_OK, 0); // Read row.
+ mock_gpios[2].ExpectConfigOut(ZX_OK, gpios_matrix[2].output_value); // Restore column.
+
+ // Matrix Scan for 2.
+ mock_gpios[3].ExpectConfigIn(ZX_OK, GPIO_NO_PULL); // Float column.
+ mock_gpios[0].ExpectRead(ZX_OK, 0); // Read row.
+ mock_gpios[3].ExpectConfigOut(ZX_OK, gpios_matrix[3].output_value); // Restore column.
+
+ // Matrix Scan for 3.
+ mock_gpios[3].ExpectConfigIn(ZX_OK, GPIO_NO_PULL); // Float column.
+ mock_gpios[1].ExpectRead(ZX_OK, 0); // Read row.
+ mock_gpios[3].ExpectConfigOut(ZX_OK, gpios_matrix[3].output_value); // Restore colument.
+
+ hidbus_ifc_protocol_ops_t ops = {};
+ ops.io_queue = [](void* ctx, const void* buffer, size_t size) {
+ buttons_input_rpt_t report_volume_up = {};
+ report_volume_up.rpt_id = 1;
+ report_volume_up.volume_up = 1;
+ ASSERT_BYTES_EQ(buffer, &report_volume_up, size);
+ EXPECT_EQ(size, sizeof(report_volume_up));
+ };
+ hidbus_ifc_protocol_t protocol = {};
+ protocol.ops = &ops;
+ device.HidbusStart(&protocol);
+ device.FakeInterrupt();
+ device.ShutDownTest();
+ for (size_t i = 0; i < countof(gpios_matrix); ++i) {
+ ASSERT_NO_FATAL_FAILURES(mock_gpios[i].VerifyAndClear());
+ }
+}
+
+} // namespace buttons
diff --git a/zircon/system/dev/input/hid-buttons/hid-buttons.cpp b/zircon/system/dev/input/hid-buttons/hid-buttons.cpp
index c239946..6fc4849 100644
--- a/zircon/system/dev/input/hid-buttons/hid-buttons.cpp
+++ b/zircon/system/dev/input/hid-buttons/hid-buttons.cpp
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "hid-buttons.h"
+
#include <string.h>
#include <threads.h>
#include <unistd.h>
@@ -11,26 +13,14 @@
#include <ddk/platform-defs.h>
#include <ddk/protocol/platform/bus.h>
#include <ddk/protocol/platform/device.h>
-
-#include <hid/descriptor.h>
-
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/unique_ptr.h>
-
+#include <hid/descriptor.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
-#include "hid-buttons.h"
-
-// clang-format off
-// zx_port_packet::key.
-constexpr uint64_t PORT_KEY_SHUTDOWN = 0x01;
-// Start of up to kNumberOfRequiredGpios port types used for interrupts.
-constexpr uint64_t PORT_KEY_INTERRUPT_START = 0x10;
-// clang-format on
-
namespace buttons {
int HidButtonsDevice::Thread() {
@@ -43,12 +33,12 @@
return thrd_error;
}
- if (packet.key == PORT_KEY_SHUTDOWN) {
+ if (packet.key == kPortKeyShutDown) {
zxlogf(INFO, "%s shutting down\n", __FUNCTION__);
return thrd_success;
- } else if (packet.key >= PORT_KEY_INTERRUPT_START &&
- packet.key < (PORT_KEY_INTERRUPT_START + buttons_.size())) {
- uint32_t type = static_cast<uint32_t>(packet.key - PORT_KEY_INTERRUPT_START);
+ } else if (packet.key >= kPortKeyInterruptStart &&
+ packet.key < (kPortKeyInterruptStart + buttons_.size())) {
+ uint32_t type = static_cast<uint32_t>(packet.key - kPortKeyInterruptStart);
if (gpios_[type].config.type == BUTTONS_GPIO_TYPE_INTERRUPT) {
// We need to reconfigure the GPIO to catch the opposite polarity.
ReconfigurePolarity(type, packet.key);
@@ -150,17 +140,6 @@
buttons_input_rpt_t input_rpt = {};
input_rpt.rpt_id = BUTTONS_RPT_ID_INPUT;
- // Disable interrupts.
- for (uint32_t i = 0; i < gpios_.size(); ++i) {
- if (gpios_[i].config.type == BUTTONS_GPIO_TYPE_INTERRUPT) {
- zx_status_t status = gpio_release_interrupt(&gpios_[i].gpio);
- if (status != ZX_OK) {
- zxlogf(ERROR, "%s gpio_release_interrupt failed %d\n", __FUNCTION__, status);
- return status;
- }
- }
- }
-
for (size_t i = 0; i < buttons_.size(); ++i) {
bool new_value = false; // A value true means a button is pressed.
if (buttons_[i].type == BUTTONS_TYPE_MATRIX) {
@@ -186,15 +165,6 @@
auto out = static_cast<buttons_input_rpt_t*>(data);
*out = input_rpt;
- // Reenable interrupts.
- for (uint32_t i = 0; i < gpios_.size(); ++i) {
- if (gpios_[i].config.type == BUTTONS_GPIO_TYPE_INTERRUPT) {
- zx_status_t status = ConfigureInterrupt(i, PORT_KEY_INTERRUPT_START + i);
- if (status != ZX_OK) {
- return status;
- }
- }
- }
return ZX_OK;
}
@@ -273,62 +243,54 @@
return status;
}
- pdev_device_info_t pdev_info;
- status = pdev_get_device_info(&pdev, &pdev_info);
+ // Get buttons metadata.
+ size_t actual = 0;
+ status = device_get_metadata_size(parent_, DEVICE_METADATA_BUTTONS_BUTTONS, &actual);
if (status != ZX_OK) {
- zxlogf(ERROR, "%s pdev_get_device_info failed %d\n", __FUNCTION__, status);
- return status;
+ zxlogf(ERROR, "%s device_get_metadata_size failed %d\n", __FILE__, status);
+ return ZX_OK;
}
-
- // TODO(andresoportus): Remove BUTTONS_ID_MAX usage below once we add metadata size probe
- // capability to devmgr.
-
+ size_t n_buttons = actual / sizeof(buttons_button_config_t);
fbl::AllocChecker ac;
- // We have up to BUTTONS_ID_MAX available buttons.
- auto buttons = fbl::Array(new (&ac) buttons_button_config_t[BUTTONS_ID_MAX], BUTTONS_ID_MAX);
+ buttons_ = fbl::Array(new (&ac) buttons_button_config_t[n_buttons], n_buttons);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
- size_t actual = 0;
- status = device_get_metadata(parent_, DEVICE_METADATA_BUTTONS_BUTTONS, buttons.get(),
- buttons.size() * sizeof(buttons_button_config_t), &actual);
- if (status != ZX_OK) {
+ actual = 0;
+ status = device_get_metadata(parent_, DEVICE_METADATA_BUTTONS_BUTTONS, buttons_.get(),
+ buttons_.size() * sizeof(buttons_button_config_t), &actual);
+ if (status != ZX_OK || actual != buttons_.size() * sizeof(buttons_button_config_t)) {
zxlogf(ERROR, "%s device_get_metadata failed %d\n", __FILE__, status);
return status;
}
- size_t n_buttons = actual / sizeof(buttons_button_config_t);
- // We have up to BUTTONS_ID_MAX available gpios.
- auto gpios_configs = fbl::Array(
- new (&ac) buttons_gpio_config_t[BUTTONS_ID_MAX], BUTTONS_ID_MAX);
+ // Get gpios metadata.
+ actual = 0;
+ status = device_get_metadata_size(parent_, DEVICE_METADATA_BUTTONS_GPIOS, &actual);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "%s device_get_metadata_size failed %d\n", __FILE__, status);
+ return ZX_OK;
+ }
+ size_t n_gpios = actual / sizeof(buttons_gpio_config_t);
+ auto gpios_configs = fbl::Array(new (&ac) buttons_gpio_config_t[n_gpios], n_gpios);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
actual = 0;
status = device_get_metadata(parent_, DEVICE_METADATA_BUTTONS_GPIOS, gpios_configs.get(),
gpios_configs.size() * sizeof(buttons_gpio_config_t), &actual);
- if (status != ZX_OK) {
+ if (status != ZX_OK || actual != gpios_configs.size() * sizeof(buttons_gpio_config_t)) {
zxlogf(ERROR, "%s device_get_metadata failed %d\n", __FILE__, status);
return status;
}
- size_t n_gpios = actual / sizeof(buttons_gpio_config_t);
- buttons_ = fbl::Array(new (&ac) buttons_button_config_t[n_buttons], n_buttons);
- if (!ac.check()) {
- return ZX_ERR_NO_MEMORY;
- }
- gpios_ = fbl::Array(new (&ac) Gpio[n_gpios], n_gpios);
- if (!ac.check()) {
- return ZX_ERR_NO_MEMORY;
- }
-
+ // Check the metadata.
for (uint32_t i = 0; i < buttons_.size(); ++i) {
- buttons_[i] = buttons[i];
- if (buttons_[i].gpioA_idx >= gpios_.size()) {
+ if (buttons_[i].gpioA_idx >= gpios_configs.size()) {
zxlogf(ERROR, "%s invalid gpioA_idx %u\n", __FUNCTION__, buttons_[i].gpioA_idx);
return ZX_ERR_INTERNAL;
}
- if (buttons_[i].gpioB_idx >= gpios_.size()) {
+ if (buttons_[i].gpioB_idx >= gpios_configs.size()) {
zxlogf(ERROR, "%s invalid gpioB_idx %u\n", __FUNCTION__, buttons_[i].gpioB_idx);
return ZX_ERR_INTERNAL;
}
@@ -349,12 +311,17 @@
}
}
+ // Prepare the GPIOs.
+ gpios_ = fbl::Array(new (&ac) Gpio[n_gpios], n_gpios);
+ if (!ac.check()) {
+ return ZX_ERR_NO_MEMORY;
+ }
for (uint32_t i = 0; i < gpios_.size(); ++i) {
gpios_[i].config = gpios_configs[i];
size_t actual;
- status = pdev_get_protocol(&pdev, ZX_PROTOCOL_GPIO, i, &gpios_[i].gpio,
- sizeof(gpios_[i].gpio), &actual);
- if (status != ZX_OK) {
+ status = PdevGetGpioProtocol(&pdev, i, &gpios_[i].gpio,
+ sizeof(gpios_[i].gpio), &actual);
+ if (status != ZX_OK || actual != sizeof(gpios_[i].gpio)) {
zxlogf(ERROR, "%s pdev_get_protocol failed %d\n", __FUNCTION__, status);
return ZX_ERR_NOT_SUPPORTED;
}
@@ -375,7 +342,7 @@
zxlogf(ERROR, "%s gpio_config_in failed %d\n", __FUNCTION__, status);
return ZX_ERR_NOT_SUPPORTED;
}
- status = ConfigureInterrupt(i, PORT_KEY_INTERRUPT_START + i);
+ status = ConfigureInterrupt(i, kPortKeyInterruptStart + i);
if (status != ZX_OK) {
return status;
}
@@ -399,7 +366,7 @@
}
void HidButtonsDevice::ShutDown() {
- zx_port_packet packet = {PORT_KEY_SHUTDOWN, ZX_PKT_TYPE_USER, ZX_OK, {}};
+ zx_port_packet packet = {kPortKeyShutDown, ZX_PKT_TYPE_USER, ZX_OK, {}};
zx_status_t status = port_.queue(&packet);
ZX_ASSERT(status == ZX_OK);
thrd_join(thread_, NULL);
diff --git a/zircon/system/dev/input/hid-buttons/hid-buttons.h b/zircon/system/dev/input/hid-buttons/hid-buttons.h
index ca3ae4b..ad988a7 100644
--- a/zircon/system/dev/input/hid-buttons/hid-buttons.h
+++ b/zircon/system/dev/input/hid-buttons/hid-buttons.h
@@ -6,24 +6,25 @@
#include <ddk/metadata/buttons.h>
#include <ddk/protocol/gpio.h>
-
+#include <ddk/protocol/platform/device.h>
#include <ddktl/device.h>
#include <ddktl/protocol/hidbus.h>
-
#include <fbl/array.h>
#include <fbl/mutex.h>
-
#include <hid/buttons.h>
-
#include <lib/zx/interrupt.h>
#include <lib/zx/port.h>
-
#include <zircon/thread_annotations.h>
#include <optional>
namespace buttons {
+// zx_port_packet::key.
+constexpr uint64_t kPortKeyShutDown = 0x01;
+// Start of up to kNumberOfRequiredGpios port types used for interrupts.
+constexpr uint64_t kPortKeyInterruptStart = 0x10;
+
class HidButtonsDevice;
using DeviceType = ddk::Device<HidButtonsDevice, ddk::Unbindable>;
@@ -32,6 +33,7 @@
public:
explicit HidButtonsDevice(zx_device_t* device)
: DeviceType(device) {}
+ virtual ~HidButtonsDevice() = default;
zx_status_t Bind();
@@ -51,6 +53,11 @@
void DdkUnbind();
void DdkRelease();
+protected:
+ // Protected for unit testing.
+ zx::port port_;
+ void ShutDown() TA_EXCL(client_lock_);
+
private:
struct Gpio {
gpio_protocol_t gpio;
@@ -59,17 +66,22 @@
};
int Thread();
- void ShutDown() TA_EXCL(client_lock_);
void ReconfigurePolarity(uint32_t idx, uint64_t int_port);
zx_status_t ConfigureInterrupt(uint32_t idx, uint64_t int_port);
bool MatrixScan(uint32_t row, uint32_t col, zx_duration_t delay);
+ // To be overwritten in unit testing.
+ virtual zx_status_t PdevGetGpioProtocol(const pdev_protocol_t* proto, uint32_t index,
+ void* out_protocol_buffer, size_t out_protocol_size,
+ size_t* out_protocol_actual) {
+ return pdev_get_protocol(proto, ZX_PROTOCOL_GPIO, index, out_protocol_buffer,
+ out_protocol_size, out_protocol_actual);
+ }
thrd_t thread_;
- zx::port port_;
fbl::Mutex client_lock_;
ddk::HidbusIfcProtocolClient client_ TA_GUARDED(client_lock_);
fbl::Array<buttons_button_config_t> buttons_;
fbl::Array<Gpio> gpios_;
std::optional<uint8_t> fdr_gpio_;
};
-}
+} // namespace buttons
diff --git a/zircon/system/dev/lib/fake_ddk/fake_ddk.cpp b/zircon/system/dev/lib/fake_ddk/fake_ddk.cpp
index 49fb598..0ed0267 100644
--- a/zircon/system/dev/lib/fake_ddk/fake_ddk.cpp
+++ b/zircon/system/dev/lib/fake_ddk/fake_ddk.cpp
@@ -106,6 +106,14 @@
return ZX_OK;
}
+zx_status_t Bind::DeviceGetMetadataSize(zx_device_t* dev, uint32_t type, size_t* out_size) {
+ if (get_metadata_ == nullptr) {
+ return ZX_ERR_BAD_STATE;
+ }
+ *out_size = get_metadata_length_;
+ return ZX_OK;
+}
+
void Bind::DeviceMakeVisible(zx_device_t* device) {
if (device != kFakeDevice) {
bad_device_ = true;
@@ -205,6 +213,13 @@
return fake_ddk::Bind::Instance()->DeviceGetMetadata(device, type, buf, buflen, actual);
}
+zx_status_t device_get_metadata_size(zx_device_t* device, uint32_t type, size_t* out_size) {
+ if (!fake_ddk::Bind::Instance()) {
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+ return fake_ddk::Bind::Instance()->DeviceGetMetadataSize(device, type, out_size);
+}
+
extern "C" void driver_printf(uint32_t flags, const char* fmt, ...) {}
__WEAK zx_driver_rec __zircon_driver_rec__ = {};
diff --git a/zircon/system/dev/lib/fake_ddk/include/lib/fake_ddk/fake_ddk.h b/zircon/system/dev/lib/fake_ddk/include/lib/fake_ddk/fake_ddk.h
index 29d3cc9..b3d774f 100644
--- a/zircon/system/dev/lib/fake_ddk/include/lib/fake_ddk/fake_ddk.h
+++ b/zircon/system/dev/lib/fake_ddk/include/lib/fake_ddk/fake_ddk.h
@@ -81,6 +81,9 @@
size_t length, size_t* actual);
// Internal fake implementation of ddk functionality.
+ virtual zx_status_t DeviceGetMetadataSize(zx_device_t* dev, uint32_t type, size_t* out_size);
+
+ // Internal fake implementation of ddk functionality.
virtual void DeviceMakeVisible(zx_device_t* dev);
// Internal fake implementation of ddk functionality.
diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn
index 2e41924..3511cae 100644
--- a/zircon/system/utest/BUILD.gn
+++ b/zircon/system/utest/BUILD.gn
@@ -45,6 +45,7 @@
"$zx/system/dev/gpio/qcom-gpio:qcom-gpio-test",
"$zx/system/dev/i2c/mt8167-i2c:mt8167-i2c-test",
"$zx/system/dev/input/bma253:bma253-test",
+ "$zx/system/dev/input/hid-buttons:hid-buttons-test",
"$zx/system/dev/lib/scsi:scsilib-disk-test",
"$zx/system/dev/lib/usb:usb-wrapper-test",
"$zx/system/dev/light-sensor/lite-on:ltr-578als-test",