[fallback-rtc] Add integration tests for the fallback-rtc driver.

ZX-3816 #comment

The test is using the IsolatedDevmgr so that it can be run even on
boards that don't use this driver.
To achieve proper isolation (and have
the driver not call syscalls) a metadata is passed from the test to
indicated that the driver should operate in a sandbox mode. This is not
ideal and eventually we need to find an approach where the syscalled are
not called directly (e.g. either abstract through a lib or pass a
"testing VDSO" to the driver process).

To run the test first build a configuration with zircon tests enabled:
--with-base='garnet/packages/tests:zircon'

Then pave a device and from the serial console run "runtests -t fallback-rtc"

TEST: runtests -t fallback-rtc

Change-Id: I5f63097612b30213f106a560059b969b70454c81
diff --git a/sdk/lib/ddk/ddk.api b/sdk/lib/ddk/ddk.api
index 26f2d0a..ab19cca 100644
--- a/sdk/lib/ddk/ddk.api
+++ b/sdk/lib/ddk/ddk.api
@@ -14,7 +14,7 @@
   "pkg/ddk/include/ddk/mmio-buffer.h": "d755d3a84678907984a921d807271e14",
   "pkg/ddk/include/ddk/phys-iter.h": "0b7f17747fad48f94644169a20bd8d73",
   "pkg/ddk/include/ddk/physiter.h": "2226a43dfaeb7229463ace48538c30e7",
-  "pkg/ddk/include/ddk/platform-defs.h": "fbd6538e07aedbf1691248e3dab238da",
+  "pkg/ddk/include/ddk/platform-defs.h": "1caf83c8a894ce0ad36d26b5b2c15761",
   "pkg/ddk/include/ddk/protocol/auxdata.h": "1e39aa361e071383c0cba04ca9baf680",
   "pkg/ddk/include/ddk/protodefs.h": "c35fd878a945ba4b05b4af2cd832ea57",
   "pkg/ddk/include/ddk/trace/event.h": "29fbf2c43995de14c020f0dda372093a",
diff --git a/zircon/system/dev/rtc/fallback/fallback-rtc.c b/zircon/system/dev/rtc/fallback/fallback-rtc.c
index 0839819..268ec63 100644
--- a/zircon/system/dev/rtc/fallback/fallback-rtc.c
+++ b/zircon/system/dev/rtc/fallback/fallback-rtc.c
@@ -14,9 +14,13 @@
 };
 
 // clang-format off
-ZIRCON_DRIVER_BEGIN(fallback_rtc, fallback_rtc_ops, "fallback_rtc", "0.1", 3)
+ZIRCON_DRIVER_BEGIN(fallback_rtc, fallback_rtc_ops, "fallback_rtc", "0.1", 7)
+    BI_GOTO_IF(EQ, BIND_PLATFORM_DEV_VID, PDEV_VID_TEST, 0),
     BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GENERIC),
     BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
     BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_RTC_FALLBACK),
+    BI_ABORT(),
+    BI_LABEL(0),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_FALLBACK_RTC_TEST),
 ZIRCON_DRIVER_END(fallback_rtc)
     // clang-format on
diff --git a/zircon/system/dev/rtc/fallback/rtc-impl.cc b/zircon/system/dev/rtc/fallback/rtc-impl.cc
index 2768659..92bf9ca 100644
--- a/zircon/system/dev/rtc/fallback/rtc-impl.cc
+++ b/zircon/system/dev/rtc/fallback/rtc-impl.cc
@@ -4,6 +4,8 @@
 #include <string.h>
 
 #include <ddk/debug.h>
+#include <ddk/metadata.h>
+#include <ddk/platform-defs.h>
 
 #include <ddktl/device.h>
 #include <ddktl/protocol/empty-protocol.h>
@@ -18,7 +20,6 @@
 
 zx_status_t set_utc_offset(const fuchsia_hardware_rtc_Time* rtc) {
   uint64_t rtc_nanoseconds = seconds_since_epoch(rtc) * 1000000000;
-  ;
   int64_t offset = rtc_nanoseconds - zx_clock_get_monotonic();
   // Please do not use get_root_resource() in new code. See ZX-1467.
   return zx_clock_adjust(get_root_resource(), ZX_CLOCK_UTC, offset);
@@ -45,7 +46,21 @@
     rtc_last_.day = 1;
   }
 
-  zx_status_t Bind() { return DdkAdd("fallback-rtc"); }
+  zx_status_t Bind() {
+    // Check if inside an IsolatedDevmgr
+    // TODO: Eventually we should figure out how drivers can be better isolated
+    size_t size;
+    zx_status_t status = DdkGetMetadataSize(DEVICE_METADATA_TEST, &size);
+    if (status == ZX_OK && size == 1) {
+      uint8_t metadata;
+      status = DdkGetMetadata(DEVICE_METADATA_TEST, &metadata, 1, &size);
+      if (status == ZX_OK && metadata == PDEV_PID_FALLBACK_RTC_TEST) {
+        is_isolated_for_testing = true;
+      }
+    }
+
+    return DdkAdd("fallback-rtc");
+  }
 
   void DdkRelease() { delete this; }
 
@@ -70,9 +85,11 @@
 
     rtc_last_ = rtc;
 
-    auto status = set_utc_offset(&rtc_last_);
-    if (status != ZX_OK) {
-      zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
+    if (!is_isolated_for_testing) {
+      auto status = set_utc_offset(&rtc_last_);
+      if (status != ZX_OK) {
+        zxlogf(ERROR, "The RTC driver was unable to set the UTC clock!\n");
+      }
     }
 
     return ZX_OK;
@@ -87,6 +104,7 @@
   };
 
   fuchsia_hardware_rtc_Time rtc_last_;
+  bool is_isolated_for_testing = false;
 };
 
 zx_status_t fidl_Get(void* ctx, fidl_txn_t* txn) {
diff --git a/zircon/system/ulib/ddk/include/ddk/platform-defs.h b/zircon/system/ulib/ddk/include/ddk/platform-defs.h
index 08a062c..6683b0a 100644
--- a/zircon/system/ulib/ddk/include/ddk/platform-defs.h
+++ b/zircon/system/ulib/ddk/include/ddk/platform-defs.h
@@ -209,6 +209,7 @@
 #define PDEV_PID_DDKFIDL_TEST       9
 #define PDEV_PID_COMPATIBILITY_TEST 10
 #define PDEV_PID_POWER_TEST         11
+#define PDEV_PID_FALLBACK_RTC_TEST  12
 
 #define PDEV_DID_TEST_PARENT        1
 #define PDEV_DID_TEST_CHILD_1       2
diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn
index 8aae493..9a8dde9 100644
--- a/zircon/system/utest/BUILD.gn
+++ b/zircon/system/utest/BUILD.gn
@@ -192,6 +192,7 @@
       "evil",
       "exception",
       "exit",
+      "fallback-rtc",
       "fdio",
       "ffl",
       "fidl",
diff --git a/zircon/system/utest/fallback-rtc/BUILD.gn b/zircon/system/utest/fallback-rtc/BUILD.gn
new file mode 100644
index 0000000..e2c173e
--- /dev/null
+++ b/zircon/system/utest/fallback-rtc/BUILD.gn
@@ -0,0 +1,21 @@
+# 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.
+
+test("fallback-rtc") {
+  output_name = "fallback-rtc"
+  test_group = "ddk"
+  sources = [
+    "fallback-rtc-test.cc",
+  ]
+  deps = [
+    "$zx/system/fidl/fuchsia-hardware-rtc:c",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/devmgr-integration-test",
+    "$zx/system/ulib/driver-integration-test",
+    "$zx/system/ulib/fdio",
+    "$zx/system/ulib/rtc",
+    "$zx/system/ulib/zx",
+    "$zx/system/ulib/zxtest",
+  ]
+}
diff --git a/zircon/system/utest/fallback-rtc/fallback-rtc-test.cc b/zircon/system/utest/fallback-rtc/fallback-rtc-test.cc
new file mode 100644
index 0000000..a188a1e
--- /dev/null
+++ b/zircon/system/utest/fallback-rtc/fallback-rtc-test.cc
@@ -0,0 +1,126 @@
+// 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 <fuchsia/hardware/rtc/c/fidl.h>
+#include <lib/driver-integration-test/fixture.h>
+#include <lib/fdio/fdio.h>
+#include <lib/zx/channel.h>
+#include <librtc.h>
+
+#include <ddk/platform-defs.h>
+#include <zxtest/zxtest.h>
+
+namespace {
+
+using driver_integration_test::IsolatedDevmgr;
+
+// Sandboxed drivers always land at sys/platforom/..
+// Below "11" is the hex value of PDEV_VID_TEST
+//       "08" is the hex value of our PID - PDEV_PID_FALLBACK_RTC_TEST
+//       "fallback-rtc" is the name of our driver (the way used in Bind())
+constexpr char kLandingPath[] = "sys/platform/11:0c:0/fallback-rtc";
+constexpr uint8_t kMetadata = PDEV_PID_FALLBACK_RTC_TEST;
+const board_test::DeviceEntry kDeviceEntry = []() {
+  board_test::DeviceEntry entry = {};
+  strcpy(entry.name, "fallback_rtc");
+  entry.vid = PDEV_VID_TEST;
+  entry.pid = PDEV_PID_FALLBACK_RTC_TEST;
+
+  entry.metadata = &kMetadata;
+  entry.metadata_size = 1;
+
+  return entry;
+}();
+
+class FallbackRTCTest : public zxtest::Test {
+  void SetUp() override {
+    // Create the isolated dev manager
+    fbl::unique_fd rtc_fd;
+    IsolatedDevmgr::Args args;
+    args.driver_search_paths.push_back("/boot/driver");
+    args.device_list.push_back(kDeviceEntry);
+    ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr_));
+
+    // Wait for fallback-rtc to be created
+    ASSERT_OK(
+        devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), kLandingPath, &rtc_fd));
+
+    // Get a FIDL channel to the rtc driver
+    ASSERT_OK(fdio_get_service_handle(rtc_fd.release(), rtc_fdio_channel_.reset_and_get_address()));
+  }
+
+ protected:
+  IsolatedDevmgr devmgr_;
+  zx::channel rtc_fdio_channel_;
+};
+
+// Checks that the default time is a valid one
+TEST_F(FallbackRTCTest, GetInitialDatetimeCheckValid) {
+  fuchsia_hardware_rtc_Time rtc;
+
+  ASSERT_OK(fuchsia_hardware_rtc_DeviceGet(rtc_fdio_channel_.get(), &rtc));
+  ASSERT_FALSE(rtc_is_invalid(&rtc));
+}
+
+// Sets a specific date time and then verifies that the same can be red back
+TEST_F(FallbackRTCTest, SetSpecificDatetimeReadBackSame) {
+  int op_status;
+
+  // set datetime
+  fuchsia_hardware_rtc_Time rtcSet;
+  rtcSet.year = 2019;
+  rtcSet.month = 5;
+  rtcSet.day = 24;
+  rtcSet.hours = 19;
+  rtcSet.minutes = 42;
+  rtcSet.seconds = 9;
+  ASSERT_OK(fuchsia_hardware_rtc_DeviceSet(rtc_fdio_channel_.get(), &rtcSet, &op_status));
+  ASSERT_OK(op_status);
+
+  // get datetime
+  fuchsia_hardware_rtc_Time rtcGet;
+  ASSERT_OK(fuchsia_hardware_rtc_DeviceGet(rtc_fdio_channel_.get(), &rtcGet));
+  ASSERT_EQ(rtcGet.year, 2019);
+  ASSERT_EQ(rtcGet.month, 5);
+  ASSERT_EQ(rtcGet.day, 24);
+  ASSERT_EQ(rtcGet.hours, 19);
+  ASSERT_EQ(rtcGet.minutes, 42);
+  ASSERT_EQ(rtcGet.seconds, 9);
+}
+
+TEST_F(FallbackRTCTest, SetInvalidDatetimeErrorAndHasNoEffect) {
+  fuchsia_hardware_rtc_Time rtc;
+  int op_status;
+
+  // set datetime
+  rtc.year = 2022;
+  rtc.month = 6;
+  rtc.day = 27;
+  rtc.hours = 11;
+  rtc.minutes = 2;
+  rtc.seconds = 10;
+  ASSERT_OK(fuchsia_hardware_rtc_DeviceSet(rtc_fdio_channel_.get(), &rtc, &op_status));
+  ASSERT_OK(op_status);
+
+  // pass invalid date
+  rtc.year = 2019;
+  rtc.month = 3;
+  rtc.day = 32;
+  rtc.hours = 17;
+  rtc.minutes = 33;
+  rtc.seconds = 4;
+  ASSERT_OK(fuchsia_hardware_rtc_DeviceSet(rtc_fdio_channel_.get(), &rtc, &op_status));
+  ASSERT_STATUS(ZX_ERR_OUT_OF_RANGE, op_status);
+
+  // get datetime and compare with the one that was successfully set above
+  ASSERT_OK(fuchsia_hardware_rtc_DeviceGet(rtc_fdio_channel_.get(), &rtc));
+  ASSERT_EQ(rtc.year, 2022);
+  ASSERT_EQ(rtc.month, 6);
+  ASSERT_EQ(rtc.day, 27);
+  ASSERT_EQ(rtc.hours, 11);
+  ASSERT_EQ(rtc.minutes, 2);
+  ASSERT_EQ(rtc.seconds, 10);
+}
+
+}  // namespace