[kernel][pmt] Optimize pinned contiguous memory

If a pmt dispatcher is pinning a contiguous vmo, then the array of
physical addresses only needs to contain a single element for the start
address.

ZX-4384 #done

Change-Id: Ia961dcaac2cbd2742ca2a5ecaec44697abe24eec
diff --git a/zircon/kernel/object/pinned_memory_token_dispatcher.cpp b/zircon/kernel/object/pinned_memory_token_dispatcher.cpp
index 7a2f2de..082c24d 100644
--- a/zircon/kernel/object/pinned_memory_token_dispatcher.cpp
+++ b/zircon/kernel/object/pinned_memory_token_dispatcher.cpp
@@ -32,11 +32,17 @@
     LTRACE_ENTRY;
     DEBUG_ASSERT(IS_PAGE_ALIGNED(pinned_vmo.offset()) && IS_PAGE_ALIGNED(pinned_vmo.size()));
 
-    const size_t min_contig = bti->minimum_contiguity();
-    DEBUG_ASSERT(fbl::is_pow2(min_contig));
+    size_t num_addrs;
+    if (!pinned_vmo.vmo()->is_contiguous()) {
+        const size_t min_contig = bti->minimum_contiguity();
+        DEBUG_ASSERT(fbl::is_pow2(min_contig));
+
+        num_addrs = ROUNDUP(pinned_vmo.size(), min_contig) / min_contig;
+    } else {
+        num_addrs = 1;
+    }
 
     fbl::AllocChecker ac;
-    const size_t num_addrs = ROUNDUP(pinned_vmo.size(), min_contig) / min_contig;
     fbl::Array<dev_vaddr_t> addr_array(new (&ac) dev_vaddr_t[num_addrs], num_addrs);
     if (!ac.check()) {
         return ZX_ERR_NO_MEMORY;
@@ -92,10 +98,8 @@
         }
 
         DEBUG_ASSERT(vaddr % min_contig == 0);
+        DEBUG_ASSERT(mapped_addrs_.size() == 1);
         mapped_addrs_[0] = vaddr;
-        for (size_t i = 1; i < mapped_addrs_.size(); ++i) {
-            mapped_addrs_[i] = mapped_addrs_[i - 1] + min_contig;
-        }
         return ZX_OK;
     }
 
@@ -250,7 +254,8 @@
         if (num_pages != mapped_addrs_count) {
             return ZX_ERR_INVALID_ARGS;
         }
-        const size_t min_contig = bti_->minimum_contiguity();
+        const size_t min_contig = pinned_vmo_.vmo()->is_contiguous()
+                ? pinned_vmo_.size() : bti_->minimum_contiguity();
         size_t next_idx = 0;
         for (size_t i = 0; i < found_addrs; ++i) {
             dev_vaddr_t extent_base = pmo_addrs[i];
diff --git a/zircon/system/utest/core/BUILD.gn b/zircon/system/utest/core/BUILD.gn
index 4e4d2d0..1b73397 100644
--- a/zircon/system/utest/core/BUILD.gn
+++ b/zircon/system/utest/core/BUILD.gn
@@ -42,6 +42,7 @@
 # These tests need get_root_resource(), which is only available in the
 # unified core-tests binary.
 unified_only = [
+  "bti",
   "interrupt",
   "profile",
   "resource",
diff --git a/zircon/system/utest/core/bti/BUILD.gn b/zircon/system/utest/core/bti/BUILD.gn
new file mode 100644
index 0000000..6f94137
--- /dev/null
+++ b/zircon/system/utest/core/bti/BUILD.gn
@@ -0,0 +1,16 @@
+# 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.
+
+source_set("bti") {
+  testonly = true
+  sources = [
+    "bti.cpp",
+  ]
+  deps = [
+    "$zx/system/ulib/fdio",
+    "$zx/system/ulib/unittest",
+    "$zx/system/ulib/zircon",
+    "$zx/system/ulib/zx",
+  ]
+}
diff --git a/zircon/system/utest/core/bti/bti.cpp b/zircon/system/utest/core/bti/bti.cpp
new file mode 100644
index 0000000..a4cd006
--- /dev/null
+++ b/zircon/system/utest/core/bti/bti.cpp
@@ -0,0 +1,112 @@
+// 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 <lib/zx/bti.h>
+#include <lib/zx/iommu.h>
+#include <unittest/unittest.h>
+#include <zircon/syscalls/iommu.h>
+#include <zircon/types.h>
+
+extern "C" zx_handle_t get_root_resource(void);
+
+namespace {
+
+bool bti_create_test() {
+    BEGIN_TEST;
+
+    zx::iommu iommu;
+    zx::bti bti;
+    zx::pmt pmt;
+    // Please do not use get_root_resource() in new code. See ZX-1467.
+    zx::unowned_resource root_res(get_root_resource());
+    zx_iommu_desc_dummy_t desc;
+    // Please do not use get_root_resource() in new code. See ZX-1467.
+    ASSERT_EQ(zx_iommu_create(get_root_resource(), ZX_IOMMU_TYPE_DUMMY,
+                              &desc, sizeof(desc), iommu.reset_and_get_address()), ZX_OK);
+    ASSERT_EQ(zx::bti::create(iommu, 0, 0xdeadbeef, &bti), ZX_OK);
+
+    END_TEST;
+}
+
+bool bti_pin_test_helper(bool contiguous_vmo) {
+    BEGIN_TEST;
+
+    zx::iommu iommu;
+    zx::bti bti;
+    // Please do not use get_root_resource() in new code. See ZX-1467.
+    zx::unowned_resource root_res(get_root_resource());
+    zx_iommu_desc_dummy_t desc;
+    // Please do not use get_root_resource() in new code. See ZX-1467.
+    ASSERT_EQ(zx_iommu_create(get_root_resource(), ZX_IOMMU_TYPE_DUMMY,
+                              &desc, sizeof(desc), iommu.reset_and_get_address()), ZX_OK);
+    ASSERT_EQ(zx::bti::create(iommu, 0, 0xdeadbeef, &bti), ZX_OK);
+
+    static constexpr uint64_t kPageCount = 256;
+    static constexpr uint64_t kVmoSize = ZX_PAGE_SIZE * kPageCount;
+    zx::vmo vmo;
+    if (contiguous_vmo) {
+        ASSERT_EQ(zx::vmo::create_contiguous(bti, kVmoSize, 0, &vmo), ZX_OK);
+    } else {
+        ASSERT_EQ(zx::vmo::create(kVmoSize, 0, &vmo), ZX_OK);
+    }
+
+    zx_paddr_t paddrs[kPageCount];
+    zx::pmt pmt;
+    ASSERT_EQ(bti.pin(ZX_BTI_PERM_READ, vmo, 0, kVmoSize, paddrs, kPageCount, &pmt), ZX_OK);
+
+    ASSERT_EQ(pmt.unpin(), ZX_OK);
+
+    if (contiguous_vmo) {
+        for (unsigned i = 1; i < kPageCount; i++) {
+            ASSERT_EQ(paddrs[i], paddrs[0] + i * ZX_PAGE_SIZE);
+        }
+    }
+
+    END_TEST;
+}
+
+bool bti_pin_test() {
+    return bti_pin_test_helper(false);
+}
+
+bool bti_pin_contiguous_test() {
+    return bti_pin_test_helper(true);
+}
+
+bool bti_pin_contig_flag_test() {
+    BEGIN_TEST;
+
+    zx::iommu iommu;
+    zx::bti bti;
+    // Please do not use get_root_resource() in new code. See ZX-1467.
+    zx::unowned_resource root_res(get_root_resource());
+    zx_iommu_desc_dummy_t desc;
+    // Please do not use get_root_resource() in new code. See ZX-1467.
+    ASSERT_EQ(zx_iommu_create(get_root_resource(), ZX_IOMMU_TYPE_DUMMY,
+                              &desc, sizeof(desc), iommu.reset_and_get_address()), ZX_OK);
+    ASSERT_EQ(zx::bti::create(iommu, 0, 0xdeadbeef, &bti), ZX_OK);
+
+    static constexpr uint64_t kPageCount = 256;
+    static constexpr uint64_t kVmoSize = ZX_PAGE_SIZE * kPageCount;
+    zx::vmo vmo;
+    ASSERT_EQ(zx::vmo::create_contiguous(bti, kVmoSize, 0, &vmo), ZX_OK);
+
+    zx_paddr_t paddr;
+    zx::pmt pmt;
+    ASSERT_EQ(bti.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, vmo, 0, kVmoSize, &paddr, 1, &pmt),
+              ZX_OK);
+
+    ASSERT_EQ(pmt.unpin(), ZX_OK);
+
+    END_TEST;
+}
+
+} // namespace
+
+BEGIN_TEST_CASE(bti_tests)
+RUN_TEST(bti_create_test);
+RUN_TEST(bti_pin_test);
+RUN_TEST(bti_pin_contiguous_test);
+RUN_TEST(bti_pin_contig_flag_test);
+END_TEST_CASE(bti_tests)