[zircon][dev][goldfish] Add goldfish control driver

DX-939 #comment

This implements a goldfish control device driver. The
purpose of this device is to manage HW side resources
associated sysmem allocated VMOs. It provides a FIDL
interface that can be used by the vulkan ICD to create
HW resources (color buffers) for a VMO and query the
existing resource associated with a VMO.

In order to support this VMO to color buffer mapping,
the concept of a 'heap' is introduced in sysmem. Sysmem
participants can require that memory is allocated on
a specific heap. Memory allocated on a specific heap
can be device local and doesn't need to support CPU
access.

The FIDL interface makes it possible to implement
import/export of memory dedicated to images in the
goldfish vulkan ICD.

The query interface is also exposed as a banjo protocol.
This allows child drivers (e.g. goldfish display driver)
to acquire HW resource associated with VMOs.

This change is part of a series of changes that allow
Fuchsia to run in AEMU, which makes it possible to run a
large set of our existing UI unit/integration tests
without Fuchsia hardware.

Test: /boot/test/sys/sysmem-test
Test: /boot/test/sys/golfish-test
Change-Id: Ib3517bcbf4f5b8561f51f6a7cb19805d984f7923
diff --git a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc
index d3116ab..e119154 100644
--- a/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc
+++ b/garnet/bin/media/codecs/sw/ffmpeg/codec_adapter_ffmpeg_decoder.cc
@@ -317,7 +317,6 @@
   // These are all false because SW decode.
   result.buffer_memory_constraints.physically_contiguous_required = false;
   result.buffer_memory_constraints.secure_required = false;
-  result.buffer_memory_constraints.secure_permitted = false;
 
   if (port == kOutputPort) {
     ZX_ASSERT(decoded_output_info_.has_value());
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc b/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc
index f3acf2d..bbe8be6 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_h264.cc
@@ -600,9 +600,6 @@
   // amlogic requires physically contiguous on both input and output
   result.buffer_memory_constraints.physically_contiguous_required = true;
   result.buffer_memory_constraints.secure_required = false;
-  // This isn't expected to fully work at first, but allow getting as far as we
-  // can.
-  result.buffer_memory_constraints.secure_permitted = true;
 
   if (port == kOutputPort) {
     result.image_format_constraints_count = 1;
diff --git a/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc b/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc
index 2ecbc81..1d08a38 100644
--- a/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc
+++ b/garnet/drivers/video/amlogic-decoder/codec_adapter_vp9.cc
@@ -210,9 +210,6 @@
   // amlogic requires physically contiguous on both input and output
   result.buffer_memory_constraints.physically_contiguous_required = true;
   result.buffer_memory_constraints.secure_required = false;
-  // This isn't expected to fully work at first, but allow getting as far as we
-  // can.
-  result.buffer_memory_constraints.secure_permitted = true;
 
   if (port == kOutputPort) {
     result.image_format_constraints_count = 1;
diff --git a/garnet/lib/magma/src/magma_util/platform/zircon/zircon_platform_sysmem_connection.cc b/garnet/lib/magma/src/magma_util/platform/zircon/zircon_platform_sysmem_connection.cc
index e5085d7b..b7a09de 100644
--- a/garnet/lib/magma/src/magma_util/platform/zircon/zircon_platform_sysmem_connection.cc
+++ b/garnet/lib/magma/src/magma_util/platform/zircon/zircon_platform_sysmem_connection.cc
@@ -40,8 +40,7 @@
         // No buffer constraints, except those passed directly through from the client. These two
         // are for whether this memory should be protected (e.g. usable for DRM content, the precise
         // definition depending on the system).
-        constraints_.buffer_memory_constraints.secure_required = constraints->secure_permitted;
-        constraints_.buffer_memory_constraints.secure_permitted = constraints->secure_required;
+        constraints_.buffer_memory_constraints.secure_required = constraints->secure_required;
         constraints_.buffer_memory_constraints.ram_domain_supported =
             constraints->ram_domain_supported;
         constraints_.buffer_memory_constraints.cpu_domain_supported =
@@ -253,7 +252,6 @@
         constraints.buffer_memory_constraints.min_size_bytes = size;
         if (flags & MAGMA_SYSMEM_FLAG_PROTECTED) {
             constraints.buffer_memory_constraints.secure_required = true;
-            constraints.buffer_memory_constraints.secure_permitted = true;
         }
         constraints.image_format_constraints_count = 0;
 
diff --git a/garnet/lib/media/codec_impl/codec_impl.cc b/garnet/lib/media/codec_impl/codec_impl.cc
index e44e180..e28d845 100644
--- a/garnet/lib/media/codec_impl/codec_impl.cc
+++ b/garnet/lib/media/codec_impl/codec_impl.cc
@@ -2864,7 +2864,7 @@
   ZX_DEBUG_ASSERT(!usage.display);
   if (IsCoreCodecHwBased()) {
     // Let's see if we can deprecate videoUsageHwProtected, since it's redundant
-    // with secure_required and secure_permitted.
+    // with secure_required.
     if (usage.video & fuchsia::sysmem::videoUsageHwProtected) {
       Fail("Core codec set deprecated videoUsageHwProtected - disallow");
       return false;
diff --git a/garnet/lib/media/test/codec_client.cc b/garnet/lib/media/test/codec_client.cc
index a1ab748..58c8ed7 100644
--- a/garnet/lib/media/test/codec_client.cc
+++ b/garnet/lib/media/test/codec_client.cc
@@ -745,9 +745,6 @@
       std::numeric_limits<uint32_t>::max();
   constraints.buffer_memory_constraints.physically_contiguous_required = false;
   constraints.buffer_memory_constraints.secure_required = false;
-  // This test client code has no way to produce or consume output data in
-  // protected memory.
-  constraints.buffer_memory_constraints.secure_permitted = false;
 
   // Despite being a consumer of output uncompressed video frames (when decoding
   // video and is_output), for now we intentionally don't constrain to the
diff --git a/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api b/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api
index 48c76c5..0af834b 100644
--- a/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api
+++ b/sdk/fidl/fuchsia.sysmem/fuchsia.sysmem.api
@@ -2,10 +2,11 @@
   "fidl/fuchsia.sysmem/allocator.fidl": "d75f7dfb7275520d89113d9b72b4a493",
   "fidl/fuchsia.sysmem/collection.fidl": "8f9c46318344c3b589266a97dc2786a4",
   "fidl/fuchsia.sysmem/collections_deprecated.fidl": "4953fa82beb051ff8e12be5df4e3c1a7",
-  "fidl/fuchsia.sysmem/constraints.fidl": "91f6e92108fbe62a4d74f577755b4a27",
+  "fidl/fuchsia.sysmem/constraints.fidl": "74cbdd576b7c8300802c64b0239c7fc5",
   "fidl/fuchsia.sysmem/driver_connector.fidl": "d2818e5d7c2a0eae18fb006c8d802bf4",
   "fidl/fuchsia.sysmem/format_modifier.fidl": "72bd4509f27b9581544a9b3915240aac",
   "fidl/fuchsia.sysmem/formats_deprecated.fidl": "8c47d0b4664069b45b4af650e1089dde",
+  "fidl/fuchsia.sysmem/heap.fidl": "6f983cbe501760262b68f1f1ac1c8f37",
   "fidl/fuchsia.sysmem/image_formats.fidl": "e3ada1fe345acead3768ccac584a9e10",
   "fidl/fuchsia.sysmem/image_formats_deprecated.fidl": "0660023fbaed2b5abca9e5038d809df8",
   "fidl/fuchsia.sysmem/usages.fidl": "64442fca34901fc3c7e45b5b640be209"
diff --git a/src/media/codecs/stress/src/buffer_collection_constraints.rs b/src/media/codecs/stress/src/buffer_collection_constraints.rs
index e5b2f4f..36a98d2 100644
--- a/src/media/codecs/stress/src/buffer_collection_constraints.rs
+++ b/src/media/codecs/stress/src/buffer_collection_constraints.rs
@@ -48,9 +48,11 @@
             max_size_bytes: std::u32::MAX,
             physically_contiguous_required: false,
             secure_required: false,
-            secure_permitted: false,
             ram_domain_supported: false,
             cpu_domain_supported: true,
+            inaccessible_domain_supported: false,
+            heap_permitted_count: 0,
+            heap_permitted: [HeapType::SystemRam; 32],
         },
         image_format_constraints_count: 0,
         image_format_constraints: [IMAGE_FORMAT_CONSTRAINTS_DEFAULT; 32],
diff --git a/zircon/system/banjo/ddk.protocol.acpi/acpi.banjo b/zircon/system/banjo/ddk.protocol.acpi/acpi.banjo
index 4e46138..b4db2d9 100644
--- a/zircon/system/banjo/ddk.protocol.acpi/acpi.banjo
+++ b/zircon/system/banjo/ddk.protocol.acpi/acpi.banjo
@@ -19,4 +19,6 @@
     GetMmio(uint32 index) -> (zx.status s, AcpiMmio mmio);
     MapInterrupt(int64 index) -> (zx.status s, handle<interrupt> @handle);
     GetBti(uint32 bdf, uint32 index) -> (zx.status s, handle<bti> bti);
+    ConnectSysmem(handle<channel> connection) -> (zx.status s);
+    RegisterSysmemHeap(uint64 heap, handle<channel> connection) -> (zx.status s);
 };
diff --git a/zircon/system/banjo/ddk.protocol.goldfish.control/BUILD.gn b/zircon/system/banjo/ddk.protocol.goldfish.control/BUILD.gn
new file mode 100644
index 0000000..7e1abf2
--- /dev/null
+++ b/zircon/system/banjo/ddk.protocol.goldfish.control/BUILD.gn
@@ -0,0 +1,11 @@
+# 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.
+
+import("$zx/public/gn/banjo.gni")
+
+banjo_library("ddk.protocol.goldfish.control") {
+  sources = [
+    "goldfish-control.banjo",
+  ]
+}
diff --git a/zircon/system/banjo/ddk.protocol.goldfish.control/goldfish-control.banjo b/zircon/system/banjo/ddk.protocol.goldfish.control/goldfish-control.banjo
new file mode 100644
index 0000000..1c4a53d
--- /dev/null
+++ b/zircon/system/banjo/ddk.protocol.goldfish.control/goldfish-control.banjo
@@ -0,0 +1,14 @@
+// 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.
+
+library ddk.protocol.goldfish.control;
+
+using zx;
+
+[Layout = "ddk-protocol"]
+protocol GoldfishControl {
+    /// Get color buffer for VMO. Fails if VMO is not associated with a color
+    /// buffer.
+    GetColorBuffer(handle<vmo> vmo) -> (zx.status ret, uint32 cb);
+};
diff --git a/zircon/system/banjo/ddk.protocol.goldfish.pipe/goldfish-pipe.banjo b/zircon/system/banjo/ddk.protocol.goldfish.pipe/goldfish-pipe.banjo
index eaec961..89b67f4 100644
--- a/zircon/system/banjo/ddk.protocol.goldfish.pipe/goldfish-pipe.banjo
+++ b/zircon/system/banjo/ddk.protocol.goldfish.pipe/goldfish-pipe.banjo
@@ -89,4 +89,10 @@
 
     /// Get BTI that can be used create IO buffers for read/write commands.
     GetBti() -> (zx.status s, handle<bti> bti);
+
+    /// Create a sysmem connection - used to implement ddk.protocol.sysmem.
+    ConnectSysmem(handle<channel> connection) -> (zx.status s);
+
+    /// Register a sysmem heap.
+    RegisterSysmemHeap(uint64 heap, handle<channel> connection) -> (zx.status s);
 };
diff --git a/zircon/system/banjo/ddk.protocol.sysmem/sysmem.banjo b/zircon/system/banjo/ddk.protocol.sysmem/sysmem.banjo
index b2f441d..b236003 100644
--- a/zircon/system/banjo/ddk.protocol.sysmem/sysmem.banjo
+++ b/zircon/system/banjo/ddk.protocol.sysmem/sysmem.banjo
@@ -12,4 +12,8 @@
     /// fuchsia.sysmem.Allocator.  If the connection fails, the channel will
     /// close.
     Connect(handle<channel> allocator_request) -> (zx.status s);
+
+    /// Takes the client end of a FIDL connection that'll serve
+    /// fuchsia.sysmem.Heap.
+    RegisterHeap(uint64 heap, handle<channel> heap_connection) -> (zx.status s);
 };
diff --git a/zircon/system/core/devmgr/component/component-proxy.cpp b/zircon/system/core/devmgr/component/component-proxy.cpp
index 5dee5808..e9a5d45d 100644
--- a/zircon/system/core/devmgr/component/component-proxy.cpp
+++ b/zircon/system/core/devmgr/component/component-proxy.cpp
@@ -580,6 +580,17 @@
     return Rpc(&req.header, sizeof(req), &resp, sizeof(resp), &handle, 1, nullptr, 0, nullptr);
 }
 
+zx_status_t ComponentProxy::SysmemRegisterHeap(uint64_t heap, zx::channel heap_connection) {
+    SysmemProxyRequest req = {};
+    ProxyResponse resp = {};
+    req.header.proto_id = ZX_PROTOCOL_SYSMEM;
+    req.op = SysmemOp::REGISTER_HEAP;
+    req.heap = heap;
+    zx_handle_t handle = heap_connection.release();
+
+    return Rpc(&req.header, sizeof(req), &resp, sizeof(resp), &handle, 1, nullptr, 0, nullptr);
+}
+
 zx_status_t ComponentProxy::UsbModeSwitchSetMode(usb_mode_t mode) {
     UsbModeSwitchProxyRequest req = {};
     ProxyResponse resp = {};
diff --git a/zircon/system/core/devmgr/component/component-proxy.h b/zircon/system/core/devmgr/component/component-proxy.h
index bcb4b2a..95412b8 100644
--- a/zircon/system/core/devmgr/component/component-proxy.h
+++ b/zircon/system/core/devmgr/component/component-proxy.h
@@ -94,6 +94,7 @@
     zx_status_t PowerWritePmicCtrlReg(uint32_t reg_addr, uint32_t value);
     zx_status_t PowerReadPmicCtrlReg(uint32_t reg_addr, uint32_t* out_value);
     zx_status_t SysmemConnect(zx::channel allocator2_request);
+    zx_status_t SysmemRegisterHeap(uint64_t heap, zx::channel heap_connection);
     zx_status_t MipiCsiInit(const mipi_info_t* mipi_info,
                             const mipi_adap_info_t* adap_info);
     zx_status_t MipiCsiDeInit();
diff --git a/zircon/system/core/devmgr/component/component.cpp b/zircon/system/core/devmgr/component/component.cpp
index ab19710..b7cde1e 100644
--- a/zircon/system/core/devmgr/component/component.cpp
+++ b/zircon/system/core/devmgr/component/component.cpp
@@ -357,10 +357,10 @@
     *out_resp_size = sizeof(ProxyResponse);
 
     switch (req->op) {
-    case SysmemOp::CONNECT: {
-
+    case SysmemOp::CONNECT:
         return sysmem_.Connect(zx::channel(req_handles[0]));
-    }
+    case SysmemOp::REGISTER_HEAP:
+        return sysmem_.RegisterHeap(req->heap, zx::channel(req_handles[0]));
     default:
         zxlogf(ERROR, "%s: unknown sysmem op %u\n", __func__, static_cast<uint32_t>(req->op));
         return ZX_ERR_INTERNAL;
diff --git a/zircon/system/core/devmgr/component/proxy-protocol.h b/zircon/system/core/devmgr/component/proxy-protocol.h
index d0c65d2..ff6e62a 100644
--- a/zircon/system/core/devmgr/component/proxy-protocol.h
+++ b/zircon/system/core/devmgr/component/proxy-protocol.h
@@ -137,11 +137,13 @@
 // ZX_PROTOCOL_SYSMEM proxy support.
 enum class SysmemOp {
     CONNECT,
+    REGISTER_HEAP,
 };
 
 struct SysmemProxyRequest {
     ProxyRequest header;
     SysmemOp op;
+    uint64_t heap;
 };
 
 // ZX_PROTOCOL_AMLOGIC_CANVAS proxy support.
diff --git a/zircon/system/dev/board/x86/acpi-nswalk.c b/zircon/system/dev/board/x86/acpi-nswalk.c
index 7790ddc..d3bbd10 100644
--- a/zircon/system/dev/board/x86/acpi-nswalk.c
+++ b/zircon/system/dev/board/x86/acpi-nswalk.c
@@ -5,6 +5,7 @@
 #include <ddk/debug.h>
 #include <ddk/protocol/acpi.h>
 #include <ddk/protocol/pciroot.h>
+#include <ddk/protocol/sysmem.h>
 
 #include <inttypes.h>
 #include <limits.h>
@@ -353,11 +354,46 @@
     return zx_bti_create(iommu_handle, 0, bdf, bti);
 }
 
+static zx_status_t acpi_op_connect_sysmem(void* ctx, zx_handle_t handle) {
+    acpi_device_t* dev = (acpi_device_t*)ctx;
+    mtx_lock(&dev->lock);
+
+    sysmem_protocol_t sysmem;
+    zx_status_t st = device_get_protocol(dev->platform_bus, ZX_PROTOCOL_SYSMEM, &sysmem);
+    if (st != ZX_OK) {
+        zx_handle_close(handle);
+        goto unlock;
+    }
+    st = sysmem_connect(&sysmem, handle);
+unlock:
+    mtx_unlock(&dev->lock);
+    return st;
+}
+
+static zx_status_t acpi_op_register_sysmem_heap(void* ctx, uint64_t heap, zx_handle_t handle) {
+    acpi_device_t* dev = (acpi_device_t*)ctx;
+    mtx_lock(&dev->lock);
+
+    sysmem_protocol_t sysmem;
+    zx_status_t st = device_get_protocol(dev->platform_bus, ZX_PROTOCOL_SYSMEM, &sysmem);
+    if (st != ZX_OK) {
+        zx_handle_close(handle);
+        goto unlock;
+    }
+
+    st = sysmem_register_heap(&sysmem, heap, handle);
+unlock:
+    mtx_unlock(&dev->lock);
+    return st;
+}
+
 // TODO marking unused until we publish some devices
 static __attribute__ ((unused)) acpi_protocol_ops_t acpi_proto = {
     .get_mmio = acpi_op_get_mmio,
     .map_interrupt = acpi_op_map_interrupt,
     .get_bti = acpi_op_get_bti,
+    .connect_sysmem = acpi_op_connect_sysmem,
+    .register_sysmem_heap = acpi_op_register_sysmem_heap,
 };
 
 static const char* hid_from_acpi_devinfo(ACPI_DEVICE_INFO* info) {
diff --git a/zircon/system/dev/bus/platform/platform-proxy.cpp b/zircon/system/dev/bus/platform/platform-proxy.cpp
index 17af4c3..1ae66b81 100644
--- a/zircon/system/dev/bus/platform/platform-proxy.cpp
+++ b/zircon/system/dev/bus/platform/platform-proxy.cpp
@@ -134,14 +134,27 @@
 }
 
 zx_status_t ProxySysmem::SysmemConnect(zx::channel allocator_request) {
-    platform_proxy_req_t req = {};
+    rpc_sysmem_req_t req = {};
     platform_proxy_rsp_t resp = {};
-    req.proto_id = ZX_PROTOCOL_SYSMEM;
-    req.op = SYSMEM_CONNECT;
+    req.header.proto_id = ZX_PROTOCOL_SYSMEM;
+    req.header.op = SYSMEM_CONNECT;
     zx_handle_t handle = allocator_request.release();
 
-    return proxy_->Rpc(&req, sizeof(req), &resp, sizeof(resp), &handle, 1, nullptr, 0,
-                       nullptr);
+    return proxy_->Rpc(&req.header, sizeof(req), &resp, sizeof(resp), &handle,
+                       1, nullptr, 0, nullptr);
+}
+
+zx_status_t ProxySysmem::SysmemRegisterHeap(uint64_t heap,
+                                            zx::channel heap_connection) {
+    rpc_sysmem_req_t req = {};
+    platform_proxy_rsp_t resp = {};
+    req.header.proto_id = ZX_PROTOCOL_SYSMEM;
+    req.header.op = SYSMEM_REGISTER_HEAP;
+    req.heap = heap;
+    zx_handle_t handle = heap_connection.release();
+
+    return proxy_->Rpc(&req.header, sizeof(req), &resp, sizeof(resp), &handle,
+                       1, nullptr, 0, nullptr);
 }
 
 zx_status_t ProxyAmlogicCanvas::AmlogicCanvasConfig(zx::vmo vmo, size_t offset,
diff --git a/zircon/system/dev/bus/platform/platform-proxy.h b/zircon/system/dev/bus/platform/platform-proxy.h
index 0478018..732a011 100644
--- a/zircon/system/dev/bus/platform/platform-proxy.h
+++ b/zircon/system/dev/bus/platform/platform-proxy.h
@@ -72,6 +72,7 @@
 
     // Sysmem protocol implementation.
     zx_status_t SysmemConnect(zx::channel allocator2_request);
+    zx_status_t SysmemRegisterHeap(uint64_t heap, zx::channel heap_connection);
 
     void GetProtocol(sysmem_protocol_t* proto) {
         proto->ops = &sysmem_protocol_ops_;
diff --git a/zircon/system/dev/bus/platform/proxy-protocol.h b/zircon/system/dev/bus/platform/proxy-protocol.h
index 71418d1..98341ee 100644
--- a/zircon/system/dev/bus/platform/proxy-protocol.h
+++ b/zircon/system/dev/bus/platform/proxy-protocol.h
@@ -106,6 +106,12 @@
 // ZX_PROTOCOL_SYSMEM proxy support.
 enum {
     SYSMEM_CONNECT,
+    SYSMEM_REGISTER_HEAP,
+};
+
+struct rpc_sysmem_req_t {
+    platform_proxy_req_t header;
+    uint64_t heap;
 };
 
 // ZX_PROTOCOL_AMLOGIC_CANVAS proxy support.
diff --git a/zircon/system/dev/display/astro-display/astro-display.cpp b/zircon/system/dev/display/astro-display/astro-display.cpp
index 3df1589..322c0e2 100644
--- a/zircon/system/dev/display/astro-display/astro-display.cpp
+++ b/zircon/system/dev/display/astro-display/astro-display.cpp
@@ -570,9 +570,11 @@
     buffer_constraints.max_size_bytes = 0xffffffff;
     buffer_constraints.physically_contiguous_required = true;
     buffer_constraints.secure_required = false;
-    buffer_constraints.secure_permitted = true;
     buffer_constraints.ram_domain_supported = true;
     buffer_constraints.cpu_domain_supported = true;
+    buffer_constraints.heap_permitted_count = 2;
+    buffer_constraints.heap_permitted[0] = fuchsia_sysmem_HeapType_SYSTEM_RAM;
+    buffer_constraints.heap_permitted[1] = fuchsia_sysmem_HeapType_AMLOGIC_SECURE;
     constraints.image_format_constraints_count = 1;
     fuchsia_sysmem_ImageFormatConstraints& image_constraints =
         constraints.image_format_constraints[0];
diff --git a/zircon/system/dev/display/display/client.cpp b/zircon/system/dev/display/display/client.cpp
index d681f63..0a1e7f3 100644
--- a/zircon/system/dev/display/display/client.cpp
+++ b/zircon/system/dev/display/display/client.cpp
@@ -504,7 +504,7 @@
         buffer_constraints.max_size_bytes = 0xffffffff;
         buffer_constraints.physically_contiguous_required = true;
         buffer_constraints.secure_required = false;
-        buffer_constraints.secure_permitted = false;
+        buffer_constraints.ram_domain_supported = true;
         constraints.image_format_constraints_count = 1;
         fuchsia_sysmem_ImageFormatConstraints& image_constraints =
             constraints.image_format_constraints[0];
diff --git a/zircon/system/dev/display/dummy/dummy-display.cpp b/zircon/system/dev/display/dummy/dummy-display.cpp
index f4684f1..38039cf 100644
--- a/zircon/system/dev/display/dummy/dummy-display.cpp
+++ b/zircon/system/dev/display/dummy/dummy-display.cpp
@@ -175,7 +175,6 @@
     buffer_constraints.max_size_bytes = 0xffffffff;
     buffer_constraints.physically_contiguous_required = false;
     buffer_constraints.secure_required = false;
-    buffer_constraints.secure_permitted = false;
     buffer_constraints.ram_domain_supported = true;
     buffer_constraints.cpu_domain_supported = true;
     constraints.image_format_constraints_count = 1;
diff --git a/zircon/system/dev/display/hikey-display/ddk-interface.cpp b/zircon/system/dev/display/hikey-display/ddk-interface.cpp
index 43c9888..90d5963 100644
--- a/zircon/system/dev/display/hikey-display/ddk-interface.cpp
+++ b/zircon/system/dev/display/hikey-display/ddk-interface.cpp
@@ -154,9 +154,10 @@
     buffer_constraints.max_size_bytes = 0xffffffff;
     buffer_constraints.physically_contiguous_required = true;
     buffer_constraints.secure_required = false;
-    buffer_constraints.secure_permitted = false;
     buffer_constraints.ram_domain_supported = true;
     buffer_constraints.cpu_domain_supported = true;
+    buffer_constraints.heap_permitted_count = 1;
+    buffer_constraints.heap_permitted[0] = fuchsia_sysmem_HeapType_SYSTEM_RAM;
     constraints.image_format_constraints_count = 1;
     fuchsia_sysmem_ImageFormatConstraints& image_constraints =
         constraints.image_format_constraints[0];
diff --git a/zircon/system/dev/display/intel-i915/intel-i915.cpp b/zircon/system/dev/display/intel-i915/intel-i915.cpp
index 9bf5f19..041ed77 100644
--- a/zircon/system/dev/display/intel-i915/intel-i915.cpp
+++ b/zircon/system/dev/display/intel-i915/intel-i915.cpp
@@ -1772,9 +1772,10 @@
     buffer_constraints.max_size_bytes = 0xffffffff;
     buffer_constraints.physically_contiguous_required = false;
     buffer_constraints.secure_required = false;
-    buffer_constraints.secure_permitted = false;
     buffer_constraints.ram_domain_supported = true;
     buffer_constraints.cpu_domain_supported = true;
+    buffer_constraints.heap_permitted_count = 1;
+    buffer_constraints.heap_permitted[0] = fuchsia_sysmem_HeapType_SYSTEM_RAM;
     constraints.image_format_constraints_count = 1;
     fuchsia_sysmem_ImageFormatConstraints& image_constraints =
         constraints.image_format_constraints[0];
diff --git a/zircon/system/dev/display/mt8167s-display/mt8167s-display.cpp b/zircon/system/dev/display/mt8167s-display/mt8167s-display.cpp
index 0629b52..6c83700 100644
--- a/zircon/system/dev/display/mt8167s-display/mt8167s-display.cpp
+++ b/zircon/system/dev/display/mt8167s-display/mt8167s-display.cpp
@@ -355,9 +355,10 @@
     buffer_constraints.max_size_bytes = 0xffffffff;
     buffer_constraints.physically_contiguous_required = true;
     buffer_constraints.secure_required = false;
-    buffer_constraints.secure_permitted = false;
     buffer_constraints.ram_domain_supported = true;
     buffer_constraints.cpu_domain_supported = true;
+    buffer_constraints.heap_permitted_count = 1;
+    buffer_constraints.heap_permitted[0] = fuchsia_sysmem_HeapType_SYSTEM_RAM;
     constraints.image_format_constraints_count = 1;
     fuchsia_sysmem_ImageFormatConstraints& image_constraints =
         constraints.image_format_constraints[0];
diff --git a/zircon/system/dev/display/vim-display/vim-display.cpp b/zircon/system/dev/display/vim-display/vim-display.cpp
index 66b9eb9..96f45dd 100644
--- a/zircon/system/dev/display/vim-display/vim-display.cpp
+++ b/zircon/system/dev/display/vim-display/vim-display.cpp
@@ -502,9 +502,10 @@
     buffer_constraints.max_size_bytes = 0xffffffff;
     buffer_constraints.physically_contiguous_required = true;
     buffer_constraints.secure_required = false;
-    buffer_constraints.secure_permitted = false;
     buffer_constraints.ram_domain_supported = true;
     buffer_constraints.cpu_domain_supported = true;
+    buffer_constraints.heap_permitted_count = 1;
+    buffer_constraints.heap_permitted[0] = fuchsia_sysmem_HeapType_SYSTEM_RAM;
     constraints.image_format_constraints_count = 1;
     fuchsia_sysmem_ImageFormatConstraints& image_constraints =
         constraints.image_format_constraints[0];
diff --git a/zircon/system/dev/misc/BUILD.gn b/zircon/system/dev/misc/BUILD.gn
index 12acdf0..c170d036 100644
--- a/zircon/system/dev/misc/BUILD.gn
+++ b/zircon/system/dev/misc/BUILD.gn
@@ -9,6 +9,7 @@
     "cpu-trace",
     "goldfish",
     "goldfish-address-space",
+    "goldfish-control",
     "ktrace",
     "pty",
     "sysinfo",
diff --git a/zircon/system/dev/misc/goldfish-control/BUILD.gn b/zircon/system/dev/misc/goldfish-control/BUILD.gn
new file mode 100644
index 0000000..6714e0a
--- /dev/null
+++ b/zircon/system/dev/misc/goldfish-control/BUILD.gn
@@ -0,0 +1,26 @@
+# 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.
+
+driver("goldfish-control") {
+  sources = [
+    "control-device.cpp",
+  ]
+  deps = [
+    "$zx/system/banjo/ddk.protocol.goldfish.control",
+    "$zx/system/banjo/ddk.protocol.goldfish.pipe",
+    "$zx/system/fidl/fuchsia-hardware-goldfish-control:c",
+    "$zx/system/fidl/fuchsia-sysmem:c",
+    "$zx/system/ulib/async-loop:async-loop-cpp",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/fidl",
+    "$zx/system/ulib/fidl-async-2",
+    "$zx/system/ulib/fidl-utils",
+    "$zx/system/ulib/fit",
+    "$zx/system/ulib/trace:headers",
+    "$zx/system/ulib/trace:trace-driver",
+    "$zx/system/ulib/zx",
+  ]
+}
diff --git a/zircon/system/dev/misc/goldfish-control/control-device.cpp b/zircon/system/dev/misc/goldfish-control/control-device.cpp
new file mode 100644
index 0000000..2fb7743
--- /dev/null
+++ b/zircon/system/dev/misc/goldfish-control/control-device.cpp
@@ -0,0 +1,573 @@
+// 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 "control-device.h"
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/trace/event.h>
+#include <fbl/auto_call.h>
+#include <fbl/auto_lock.h>
+#include <fbl/unique_ptr.h>
+#include <fuchsia/sysmem/c/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fidl-async-2/fidl_server.h>
+#include <lib/fidl-async-2/simple_binding.h>
+#include <lib/fidl-utils/bind.h>
+#include <lib/zx/event.h>
+#include <zircon/syscalls.h>
+
+#include <memory>
+
+namespace goldfish {
+namespace {
+
+const char* kTag = "goldfish-control";
+
+const char* kPipeName = "pipe:opengles";
+
+constexpr uint32_t kClientFlags = 0;
+
+constexpr uint32_t VULKAN_ONLY = 1;
+
+struct CreateColorBufferCmd {
+    uint32_t op;
+    uint32_t size;
+    uint32_t width;
+    uint32_t height;
+    uint32_t internalformat;
+};
+constexpr uint32_t kOP_rcCreateColorBuffer = 10012;
+constexpr uint32_t kSize_rcCreateColorBuffer = 20;
+
+struct CloseColorBufferCmd {
+    uint32_t op;
+    uint32_t size;
+    uint32_t id;
+};
+constexpr uint32_t kOP_rcCloseColorBuffer = 10014;
+constexpr uint32_t kSize_rcCloseColorBuffer = 12;
+
+struct SetColorBufferVulkanModeCmd {
+    uint32_t op;
+    uint32_t size;
+    uint32_t id;
+    uint32_t mode;
+};
+constexpr uint32_t kOP_rcSetColorBufferVulkanMode = 10045;
+constexpr uint32_t kSize_rcSetColorBufferVulkanMode = 16;
+
+zx_koid_t GetKoidForVmo(const zx::vmo& vmo) {
+    zx_info_handle_basic_t info;
+    zx_status_t status = zx_object_get_info(
+        vmo.get(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: zx_object_get_info() failed - status: %d\n", kTag,
+               status);
+        return ZX_KOID_INVALID;
+    }
+    return info.koid;
+}
+
+void vLog(bool is_error, const char* prefix1, const char* prefix2,
+          const char* format, va_list args) {
+    va_list args2;
+    va_copy(args2, args);
+
+    size_t buffer_bytes = vsnprintf(nullptr, 0, format, args) + 1;
+
+    std::unique_ptr<char[]> buffer(new char[buffer_bytes]);
+
+    size_t buffer_bytes_2 =
+        vsnprintf(buffer.get(), buffer_bytes, format, args2) + 1;
+    (void)buffer_bytes_2;
+    // sanity check; should match so go ahead and assert that it does.
+    ZX_DEBUG_ASSERT(buffer_bytes == buffer_bytes_2);
+    va_end(args2);
+
+    if (is_error) {
+        zxlogf(ERROR, "[%s %s] %s\n", prefix1, prefix2, buffer.get());
+    } else {
+        zxlogf(TRACE, "[%s %s] %s\n", prefix1, prefix2, buffer.get());
+    }
+}
+
+constexpr uint32_t kConcurrencyCap = 64;
+
+// An instance of this class serves a Heap connection.
+class Heap : public FidlServer<Heap,
+                               SimpleBinding<Heap, fuchsia_sysmem_Heap_ops_t,
+                                             fuchsia_sysmem_Heap_dispatch>,
+                               vLog> {
+public:
+    // Public for std::unique_ptr<Heap>:
+    ~Heap() = default;
+
+private:
+    friend class FidlServer;
+
+    Heap(Control* control)
+        : FidlServer("GoldfishHeap", kConcurrencyCap), control_(control) {}
+
+    zx_status_t AllocateVmo(uint64_t size, fidl_txn* txn) {
+        BindingType::Txn::RecognizeTxn(txn);
+
+        zx::vmo vmo;
+        zx_status_t status = zx::vmo::create(size, 0, &vmo);
+        if (status != ZX_OK) {
+            zxlogf(ERROR,
+                   "%s: zx::vmo::create() failed - size: %lu status: %d\n",
+                   kTag, size, status);
+            return fuchsia_sysmem_HeapAllocateVmo_reply(txn, status,
+                                                        ZX_HANDLE_INVALID);
+        }
+
+        return fuchsia_sysmem_HeapAllocateVmo_reply(txn, ZX_OK, vmo.release());
+    }
+
+    zx_status_t CreateResource(zx_handle_t vmo_handle, fidl_txn* txn) {
+        BindingType::Txn::RecognizeTxn(txn);
+
+        zx::vmo vmo(vmo_handle);
+
+        zx_koid_t id = GetKoidForVmo(vmo);
+        if (id == ZX_KOID_INVALID) {
+            return fuchsia_sysmem_HeapCreateResource_reply(
+                txn, ZX_ERR_INVALID_ARGS, 0);
+        }
+
+        control_->RegisterColorBuffer(id);
+        return fuchsia_sysmem_HeapCreateResource_reply(txn, ZX_OK, id);
+    }
+
+    zx_status_t DestroyResource(uint64_t id, fidl_txn* txn) {
+        BindingType::Txn::RecognizeTxn(txn);
+
+        control_->FreeColorBuffer(id);
+        return fuchsia_sysmem_HeapDestroyResource_reply(txn);
+    }
+
+    static constexpr fuchsia_sysmem_Heap_ops_t kOps = {
+        fidl::Binder<Heap>::BindMember<&Heap::AllocateVmo>,
+        fidl::Binder<Heap>::BindMember<&Heap::CreateResource>,
+        fidl::Binder<Heap>::BindMember<&Heap::DestroyResource>,
+    };
+
+    Control* const control_;
+};
+
+} // namespace
+
+// static
+zx_status_t Control::Create(void* ctx, zx_device_t* device) {
+    auto control = std::make_unique<Control>(device);
+
+    zx_status_t status = control->Bind();
+    if (status == ZX_OK) {
+        // devmgr now owns device.
+        __UNUSED auto* dev = control.release();
+    }
+    return status;
+}
+
+Control::Control(zx_device_t* parent)
+    : ControlType(parent), pipe_(parent),
+      heap_loop_(&kAsyncLoopConfigNoAttachToThread) {
+    goldfish_control_protocol_t self{&goldfish_control_protocol_ops_, this};
+    control_ = ddk::GoldfishControlProtocolClient(&self);
+}
+
+Control::~Control() {
+    heap_loop_.Shutdown();
+    if (id_) {
+        fbl::AutoLock lock(&lock_);
+        if (cmd_buffer_.is_valid()) {
+            for (auto& buffer : color_buffers_) {
+                CloseColorBufferLocked(buffer.second);
+            }
+            auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt());
+            buffer->id = id_;
+            buffer->cmd = PIPE_CMD_CODE_CLOSE;
+            buffer->status = PIPE_ERROR_INVAL;
+
+            pipe_.Exec(id_);
+            ZX_DEBUG_ASSERT(!buffer->status);
+        }
+        pipe_.Destroy(id_);
+    }
+}
+
+zx_status_t Control::Bind() {
+    fbl::AutoLock lock(&lock_);
+
+    if (!pipe_.is_valid()) {
+        zxlogf(ERROR, "%s: no pipe protocol\n", kTag);
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
+    zx_status_t status = pipe_.GetBti(&bti_);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: GetBti failed: %d\n", kTag, status);
+        return status;
+    }
+
+    status =
+        io_buffer_.Init(bti_.get(), PAGE_SIZE, IO_BUFFER_RW | IO_BUFFER_CONTIG);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: io_buffer_init failed: %d\n", kTag, status);
+        return status;
+    }
+
+    zx::vmo vmo;
+    goldfish_pipe_signal_value_t signal_cb = {Control::OnSignal, this};
+    status = pipe_.Create(&signal_cb, &id_, &vmo);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: Create failed: %d\n", kTag, status);
+        return status;
+    }
+
+    status = cmd_buffer_.InitVmo(bti_.get(), vmo.get(), 0, IO_BUFFER_RW);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: io_buffer_init_vmo failed: %d\n", kTag, status);
+        return status;
+    }
+
+    auto release_buffer =
+        fbl::MakeAutoCall([this]() TA_NO_THREAD_SAFETY_ANALYSIS
+            { cmd_buffer_.release(); });
+
+    auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt());
+    buffer->id = id_;
+    buffer->cmd = PIPE_CMD_CODE_OPEN;
+    buffer->status = PIPE_ERROR_INVAL;
+
+    pipe_.Open(id_);
+    if (buffer->status) {
+        zxlogf(ERROR, "%s: Open failed: %d\n", kTag, buffer->status);
+        return ZX_ERR_INTERNAL;
+    }
+
+    // Keep buffer after successful execution of OPEN command. This way
+    // we'll send CLOSE later.
+    release_buffer.cancel();
+
+    size_t length = strlen(kPipeName) + 1;
+    memcpy(io_buffer_.virt(), kPipeName, length);
+    WriteLocked(static_cast<uint32_t>(length));
+
+    memcpy(io_buffer_.virt(), &kClientFlags, sizeof(kClientFlags));
+    WriteLocked(sizeof(kClientFlags));
+
+    // We are now ready to serve goldfish heap allocations. Create a channel
+    // and register client-end with sysmem.
+    zx::channel heap_request, heap_connection;
+    status = zx::channel::create(0, &heap_request, &heap_connection);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: zx::channel:create() failed: %d\n", kTag, status);
+        return status;
+    }
+    status =
+        pipe_.RegisterSysmemHeap(fuchsia_sysmem_HeapType_GOLDFISH_DEVICE_LOCAL,
+                                 std::move(heap_connection));
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: failed to register heap: %d\n", kTag, status);
+        return status;
+    }
+
+    // Start server thread. Heap server must be running on a seperate
+    // thread as sysmem might be making synchronous allocation requests
+    // from the main thread.
+    heap_loop_.StartThread("goldfish_control_heap_thread");
+    async::PostTask(heap_loop_.dispatcher(),
+                    [this, request = std::move(heap_request)]() mutable {
+                        // The Heap is channel-owned / self-owned.
+                        Heap::CreateChannelOwned(std::move(request), this);
+                    });
+
+    return DdkAdd("goldfish-control", 0, nullptr, 0,
+                  ZX_PROTOCOL_GOLDFISH_CONTROL);
+}
+
+void Control::RegisterColorBuffer(zx_koid_t koid) {
+    fbl::AutoLock lock(&lock_);
+    color_buffers_[koid] = 0;
+}
+
+void Control::FreeColorBuffer(zx_koid_t koid) {
+    fbl::AutoLock lock(&lock_);
+
+    auto it = color_buffers_.find(koid);
+    if (it == color_buffers_.end()) {
+        zxlogf(ERROR, "%s: invalid key\n", kTag);
+        return;
+    }
+
+    if (it->second) {
+        CloseColorBufferLocked(it->second);
+    }
+    color_buffers_.erase(it);
+}
+
+zx_status_t Control::FidlCreateColorBuffer(zx_handle_t vmo_handle,
+                                           uint32_t width, uint32_t height,
+                                           uint32_t format, fidl_txn_t* txn) {
+    TRACE_DURATION("gfx", "Control::FidlCreateColorBuffer", "width", width,
+                   "height", height);
+
+    zx::vmo vmo(vmo_handle);
+    zx_koid_t koid = GetKoidForVmo(vmo);
+    if (koid == ZX_KOID_INVALID) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    fbl::AutoLock lock(&lock_);
+
+    auto it = color_buffers_.find(koid);
+    if (it == color_buffers_.end()) {
+        zxlogf(ERROR, "%s: invalid VMO\n", kTag);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    if (it->second) {
+        zxlogf(ERROR, "%s: color buffer already exists\n", kTag);
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    uint32_t id;
+    zx_status_t status = CreateColorBufferLocked(width, height, format, &id);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: failed to create color buffer: %d\n", kTag, status);
+        return status;
+    }
+
+    auto close_color_buffer =
+        fbl::MakeAutoCall([this, id]() TA_NO_THREAD_SAFETY_ANALYSIS {
+            CloseColorBufferLocked(id);
+        });
+
+    uint32_t result = 0;
+    status = SetColorBufferVulkanModeLocked(id, VULKAN_ONLY, &result);
+    if (status != ZX_OK || result) {
+        zxlogf(ERROR, "%s: failed to set vulkan mode: %d %d\n", kTag, status,
+               result);
+        return status;
+    }
+
+    close_color_buffer.cancel();
+    it->second = id;
+    return fuchsia_hardware_goldfish_control_DeviceCreateColorBuffer_reply(
+        txn, ZX_OK);
+}
+
+zx_status_t Control::FidlGetColorBuffer(zx_handle_t vmo_handle,
+                                        fidl_txn_t* txn) {
+    TRACE_DURATION("gfx", "Control::FidlGetColorBuffer");
+
+    zx::vmo vmo(vmo_handle);
+    zx_koid_t koid = GetKoidForVmo(vmo);
+    if (koid == ZX_KOID_INVALID) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    fbl::AutoLock lock(&lock_);
+
+    auto it = color_buffers_.find(koid);
+    if (it == color_buffers_.end()) {
+        return fuchsia_hardware_goldfish_control_DeviceGetColorBuffer_reply(
+            txn, ZX_ERR_INVALID_ARGS, 0);
+    }
+
+    return fuchsia_hardware_goldfish_control_DeviceGetColorBuffer_reply(
+        txn, ZX_OK, it->second);
+}
+
+void Control::DdkUnbind() {
+    DdkRemove();
+}
+
+void Control::DdkRelease() {
+    delete this;
+}
+
+zx_status_t Control::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
+    using Binder = fidl::Binder<Control>;
+
+    static const fuchsia_hardware_goldfish_control_Device_ops_t kOps = {
+        .CreateColorBuffer =
+            Binder::BindMember<&Control::FidlCreateColorBuffer>,
+        .GetColorBuffer = Binder::BindMember<&Control::FidlGetColorBuffer>,
+    };
+
+    return fuchsia_hardware_goldfish_control_Device_dispatch(this, txn, msg,
+                                                             &kOps);
+}
+
+zx_status_t Control::DdkGetProtocol(uint32_t proto_id, void* out_protocol) {
+    fbl::AutoLock lock(&lock_);
+
+    switch (proto_id) {
+    case ZX_PROTOCOL_GOLDFISH_PIPE: {
+        pipe_.GetProto(static_cast<goldfish_pipe_protocol_t*>(out_protocol));
+        return ZX_OK;
+    }
+    case ZX_PROTOCOL_GOLDFISH_CONTROL: {
+        control_.GetProto(
+            static_cast<goldfish_control_protocol_t*>(out_protocol));
+        return ZX_OK;
+    }
+    default:
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+}
+
+zx_status_t Control::GoldfishControlGetColorBuffer(zx::vmo vmo,
+                                                   uint32_t* out_id) {
+    zx_koid_t koid = GetKoidForVmo(vmo);
+    if (koid == ZX_KOID_INVALID) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    fbl::AutoLock lock(&lock_);
+
+    auto it = color_buffers_.find(koid);
+    if (it == color_buffers_.end()) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    *out_id = it->second;
+    return ZX_OK;
+}
+
+void Control::OnSignal(void* ctx, int32_t flags) {
+    TRACE_DURATION("gfx", "Control::OnSignal", "flags", flags);
+
+    if (flags & (PIPE_WAKE_FLAG_READ | PIPE_WAKE_FLAG_CLOSED)) {
+        static_cast<Control*>(ctx)->OnReadable();
+    }
+}
+
+void Control::OnReadable() {
+    TRACE_DURATION("gfx", "Control::OnReadable");
+
+    fbl::AutoLock lock(&lock_);
+    readable_cvar_.Signal();
+}
+
+void Control::WriteLocked(uint32_t cmd_size) {
+    TRACE_DURATION("gfx", "Control::Write", "cmd_size", cmd_size);
+
+    auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt());
+    buffer->id = id_;
+    buffer->cmd = PIPE_CMD_CODE_WRITE;
+    buffer->status = PIPE_ERROR_INVAL;
+    buffer->rw_params.ptrs[0] = io_buffer_.phys();
+    buffer->rw_params.sizes[0] = cmd_size;
+    buffer->rw_params.buffers_count = 1;
+    buffer->rw_params.consumed_size = 0;
+    pipe_.Exec(id_);
+    ZX_DEBUG_ASSERT(buffer->rw_params.consumed_size ==
+                    static_cast<int32_t>(cmd_size));
+}
+
+zx_status_t Control::ReadResultLocked(uint32_t* result) {
+    TRACE_DURATION("gfx", "Control::ReadResult");
+
+    while (true) {
+        auto buffer = static_cast<pipe_cmd_buffer_t*>(cmd_buffer_.virt());
+        buffer->id = id_;
+        buffer->cmd = PIPE_CMD_CODE_READ;
+        buffer->status = PIPE_ERROR_INVAL;
+        buffer->rw_params.ptrs[0] = io_buffer_.phys();
+        buffer->rw_params.sizes[0] = sizeof(*result);
+        buffer->rw_params.buffers_count = 1;
+        buffer->rw_params.consumed_size = 0;
+        pipe_.Exec(id_);
+
+        // Positive consumed size always indicate a successful transfer.
+        if (buffer->rw_params.consumed_size) {
+            ZX_DEBUG_ASSERT(buffer->rw_params.consumed_size == sizeof(*result));
+            *result = *static_cast<uint32_t*>(io_buffer_.virt());
+            return ZX_OK;
+        }
+
+        // Early out if error is not because of back-pressure.
+        if (buffer->status != PIPE_ERROR_AGAIN) {
+            zxlogf(ERROR, "%s: reading result failed: %d\n", kTag,
+                   buffer->status);
+            return ZX_ERR_INTERNAL;
+        }
+
+        buffer->id = id_;
+        buffer->cmd = PIPE_CMD_CODE_WAKE_ON_READ;
+        buffer->status = PIPE_ERROR_INVAL;
+        pipe_.Exec(id_);
+        ZX_DEBUG_ASSERT(!buffer->status);
+
+        // Wait for pipe to become readable.
+        readable_cvar_.Wait(&lock_);
+    }
+}
+
+zx_status_t Control::ExecuteCommandLocked(uint32_t cmd_size, uint32_t* result) {
+    TRACE_DURATION("gfx", "Control::ExecuteCommand", "cnd_size", cmd_size);
+
+    WriteLocked(cmd_size);
+    return ReadResultLocked(result);
+}
+
+zx_status_t Control::CreateColorBufferLocked(uint32_t width, uint32_t height,
+                                             uint32_t format, uint32_t* id) {
+    TRACE_DURATION("gfx", "Control::CreateColorBuffer", "width", width,
+                   "height", height);
+
+    auto cmd = static_cast<CreateColorBufferCmd*>(io_buffer_.virt());
+    cmd->op = kOP_rcCreateColorBuffer;
+    cmd->size = kSize_rcCreateColorBuffer;
+    cmd->width = width;
+    cmd->height = height;
+    cmd->internalformat = format;
+
+    return ExecuteCommandLocked(kSize_rcCreateColorBuffer, id);
+}
+
+void Control::CloseColorBufferLocked(uint32_t id) {
+    TRACE_DURATION("gfx", "Control::CloseColorBuffer", "id", id);
+
+    auto cmd = static_cast<CloseColorBufferCmd*>(io_buffer_.virt());
+    cmd->op = kOP_rcCloseColorBuffer;
+    cmd->size = kSize_rcCloseColorBuffer;
+    cmd->id = id;
+
+    WriteLocked(kSize_rcCloseColorBuffer);
+}
+
+zx_status_t Control::SetColorBufferVulkanModeLocked(uint32_t id, uint32_t mode,
+                                                    uint32_t* result) {
+    TRACE_DURATION("gfx", "Control::SetColorBufferVulkanMode", "id", id, "mode",
+                   mode);
+
+    auto cmd = static_cast<SetColorBufferVulkanModeCmd*>(io_buffer_.virt());
+    cmd->op = kOP_rcSetColorBufferVulkanMode;
+    cmd->size = kSize_rcSetColorBufferVulkanMode;
+    cmd->id = id;
+    cmd->mode = mode;
+
+    return ExecuteCommandLocked(kSize_rcSetColorBufferVulkanMode, result);
+}
+
+} // namespace goldfish
+
+static zx_driver_ops_t goldfish_control_driver_ops = []() -> zx_driver_ops_t {
+    zx_driver_ops_t ops;
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = goldfish::Control::Create;
+    return ops;
+}();
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(goldfish_control, goldfish_control_driver_ops, "zircon",
+                    "0.1", 1)
+    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_GOLDFISH_PIPE),
+ZIRCON_DRIVER_END(goldfish_control)
+// clang-format on
diff --git a/zircon/system/dev/misc/goldfish-control/control-device.h b/zircon/system/dev/misc/goldfish-control/control-device.h
new file mode 100644
index 0000000..883f8f6
--- /dev/null
+++ b/zircon/system/dev/misc/goldfish-control/control-device.h
@@ -0,0 +1,87 @@
+// 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.
+
+#ifndef ZIRCON_SYSTEM_DEV_MISC_GOLDFISH_CONTROL_CONTROL_DEVICE_H_
+#define ZIRCON_SYSTEM_DEV_MISC_GOLDFISH_CONTROL_CONTROL_DEVICE_H_
+
+#include <ddk/device.h>
+#include <ddk/io-buffer.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/goldfish/control.h>
+#include <ddktl/protocol/goldfish/pipe.h>
+#include <fbl/condition_variable.h>
+#include <fbl/mutex.h>
+#include <fuchsia/hardware/goldfish/control/c/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/async/cpp/wait.h>
+#include <zircon/thread_annotations.h>
+#include <zircon/types.h>
+
+#include <map>
+
+namespace goldfish {
+
+class Control;
+using ControlType = ddk::Device<Control, ddk::Unbindable, ddk::Messageable,
+                                ddk::GetProtocolable>;
+
+class Control
+    : public ControlType,
+      public ddk::GoldfishControlProtocol<Control, ddk::base_protocol> {
+public:
+    static zx_status_t Create(void* ctx, zx_device_t* parent);
+
+    explicit Control(zx_device_t* parent);
+    ~Control();
+
+    zx_status_t Bind();
+
+    void RegisterColorBuffer(zx_koid_t koid);
+    void FreeColorBuffer(zx_koid_t koid);
+
+    zx_status_t FidlCreateColorBuffer(zx_handle_t vmo_handle, uint32_t width,
+                                      uint32_t height, uint32_t format,
+                                      fidl_txn_t* txn);
+    zx_status_t FidlGetColorBuffer(zx_handle_t vmo_handle, fidl_txn_t* txn);
+
+    // Device protocol implementation.
+    void DdkUnbind();
+    void DdkRelease();
+    zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn);
+    zx_status_t DdkGetProtocol(uint32_t proto_id, void* out_protocol);
+    zx_status_t GoldfishControlGetColorBuffer(zx::vmo vmo, uint32_t* out_id);
+
+private:
+    static void OnSignal(void* ctx, int32_t flags);
+    void OnReadable();
+
+    void WriteLocked(uint32_t cmd_size) TA_REQ(lock_);
+    zx_status_t ReadResultLocked(uint32_t* result) TA_REQ(lock_);
+    zx_status_t ExecuteCommandLocked(uint32_t cmd_size, uint32_t* result)
+        TA_REQ(lock_);
+    zx_status_t CreateColorBufferLocked(uint32_t width, uint32_t height,
+                                        uint32_t format, uint32_t* id)
+        TA_REQ(lock_);
+    void CloseColorBufferLocked(uint32_t id) TA_REQ(lock_);
+    zx_status_t SetColorBufferVulkanModeLocked(uint32_t id, uint32_t mode,
+                                               uint32_t* result) TA_REQ(lock_);
+
+    fbl::Mutex lock_;
+    fbl::ConditionVariable readable_cvar_;
+    ddk::GoldfishPipeProtocolClient pipe_;
+    ddk::GoldfishControlProtocolClient control_;
+    int32_t id_ = 0;
+    zx::bti bti_ TA_GUARDED(lock_);
+    ddk::IoBuffer cmd_buffer_ TA_GUARDED(lock_);
+    ddk::IoBuffer io_buffer_ TA_GUARDED(lock_);
+    // TODO(TC-383): This should be std::unordered_map.
+    std::map<zx_koid_t, uint32_t> color_buffers_ TA_GUARDED(lock_);
+    async::Loop heap_loop_;
+
+    DISALLOW_COPY_ASSIGN_AND_MOVE(Control);
+};
+
+} // namespace goldfish
+
+#endif // ZIRCON_SYSTEM_DEV_MISC_GOLDFISH_CONTROL_CONTROL_DEVICE_H_
diff --git a/zircon/system/dev/misc/goldfish/pipe-device.cpp b/zircon/system/dev/misc/goldfish/pipe-device.cpp
index 8337a60..0a1298a 100644
--- a/zircon/system/dev/misc/goldfish/pipe-device.cpp
+++ b/zircon/system/dev/misc/goldfish/pipe-device.cpp
@@ -253,6 +253,19 @@
     return bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, out_bti);
 }
 
+zx_status_t PipeDevice::GoldfishPipeConnectSysmem(zx::channel connection) {
+    TRACE_DURATION("gfx", "PipeDevice::GoldfishPipeConnectSysmem");
+
+    return acpi_.ConnectSysmem(std::move(connection));
+}
+
+zx_status_t PipeDevice::GoldfishPipeRegisterSysmemHeap(uint64_t heap,
+                                                       zx::channel connection) {
+    TRACE_DURATION("gfx", "PipeDevice::GoldfishPipeRegisterSysmemHeap");
+
+    return acpi_.RegisterSysmemHeap(heap, std::move(connection));
+}
+
 int PipeDevice::IrqHandler() {
     while (1) {
         zx_status_t status = irq_.wait(nullptr);
diff --git a/zircon/system/dev/misc/goldfish/pipe-device.h b/zircon/system/dev/misc/goldfish/pipe-device.h
index 6fd0d66..ddcc639 100644
--- a/zircon/system/dev/misc/goldfish/pipe-device.h
+++ b/zircon/system/dev/misc/goldfish/pipe-device.h
@@ -49,6 +49,9 @@
     void GoldfishPipeOpen(int32_t id);
     void GoldfishPipeExec(int32_t id);
     zx_status_t GoldfishPipeGetBti(zx::bti* out_bti);
+    zx_status_t GoldfishPipeConnectSysmem(zx::channel connection);
+    zx_status_t GoldfishPipeRegisterSysmemHeap(uint64_t heap,
+                                               zx::channel connection);
 
     int IrqHandler();
 
diff --git a/zircon/system/dev/sysmem/sysmem/BUILD.gn b/zircon/system/dev/sysmem/sysmem/BUILD.gn
index c79ebce..05ef78c 100644
--- a/zircon/system/dev/sysmem/sysmem/BUILD.gn
+++ b/zircon/system/dev/sysmem/sysmem/BUILD.gn
@@ -14,6 +14,7 @@
     "koid_util.cpp",
     "logging.cpp",
     "logical_buffer_collection.cpp",
+    "memory_allocator.cpp",
     "usage_pixel_format_cost.cpp",
   ]
   deps = [
diff --git a/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.cpp b/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.cpp
index 28927d7..610419c 100644
--- a/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.cpp
+++ b/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.cpp
@@ -80,6 +80,10 @@
     return ZX_OK;
 }
 
+bool AmlogicMemoryAllocator::CoherencyDomainIsInaccessible() {
+    return false;
+}
+
 zx_status_t AmlogicMemoryAllocator::GetProtectedMemoryInfo(uint64_t* base, uint64_t* size) {
     *base = start_;
     *size = size_;
diff --git a/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.h b/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.h
index 91b38cc..d673441 100644
--- a/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.h
+++ b/zircon/system/dev/sysmem/sysmem/amlogic_memory_allocator.h
@@ -17,6 +17,7 @@
     zx_status_t Init(uint64_t size);
 
     zx_status_t Allocate(uint64_t size, zx::vmo* vmo) override;
+    bool CoherencyDomainIsInaccessible() override;
     zx_status_t GetProtectedMemoryInfo(uint64_t* base, uint64_t* size) override;
 
 private:
diff --git a/zircon/system/dev/sysmem/sysmem/device.cpp b/zircon/system/dev/sysmem/sysmem/device.cpp
index 3dc4154..336a982 100644
--- a/zircon/system/dev/sysmem/sysmem/device.cpp
+++ b/zircon/system/dev/sysmem/sysmem/device.cpp
@@ -20,6 +20,149 @@
 
 namespace {
 
+class SystemRamMemoryAllocator : public MemoryAllocator {
+public:
+    zx_status_t Allocate(uint64_t size, zx::vmo* vmo) override {
+        return zx::vmo::create(size, 0, vmo);
+    }
+    bool CoherencyDomainIsInaccessible() override { return false; }
+};
+
+class ContiguousSystemRamMemoryAllocator : public MemoryAllocator {
+public:
+    explicit ContiguousSystemRamMemoryAllocator(Device* parent_device)
+        : parent_device_(parent_device) {}
+
+    zx_status_t Allocate(uint64_t size, zx::vmo* vmo) override {
+        // TODO(dustingreen): Optionally (per board) pre-allocate a
+        // physically-contiguous VMO with board-specific size, have a page heap,
+        // dole out portions of that VMO or non-copy-on-write child VMOs of that
+        // VMO.  For now, we just attempt to allocate contiguous when buffers
+        // are allocated, which is unlikely to work after the system has been
+        // running for a while and physical memory is more fragmented than early
+        // during boot.
+        zx_status_t status =
+            zx::vmo::create_contiguous(parent_device_->bti(), size, 0, vmo);
+        if (status != ZX_OK) {
+            DRIVER_ERROR(
+                "zx::vmo::create_contiguous() failed - size_bytes: %lu "
+                "status: %d",
+                size, status);
+            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
+            status = ZX_ERR_NO_MEMORY;
+            return status;
+        }
+        return ZX_OK;
+    }
+    bool CoherencyDomainIsInaccessible() override { return false; }
+
+private:
+    Device* const parent_device_;
+};
+
+class ExternalMemoryAllocator : public MemoryAllocator {
+public:
+    ExternalMemoryAllocator(zx::channel connection,
+                            fbl::unique_ptr<async::Wait> wait_for_close)
+        : connection_(std::move(connection)),
+          wait_for_close_(std::move(wait_for_close)) {}
+
+    zx_status_t Allocate(uint64_t size, zx::vmo* vmo) override {
+        zx::vmo parent_vmo;
+        zx_status_t status2 = ZX_OK;
+        zx_status_t status =
+            fuchsia_sysmem_HeapAllocateVmo(connection_.get(), size, &status2,
+                                           parent_vmo.reset_and_get_address());
+        if (status != ZX_OK || status2 != ZX_OK) {
+            DRIVER_ERROR("HeapAllocate() failed - status: %d status2: %d",
+                         status, status2);
+            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
+            status = ZX_ERR_NO_MEMORY;
+            return status;
+        }
+
+        // Create child VMO. This makes it possible to detect when all
+        // references to the VMO are gone by waiting for VMO_ZERO_CHILDREN
+        // signal on parent VMO.
+        //
+        // Note: Always a 1:1 relationship between parent and child VMOs.
+        //
+        // TODO(reveman): Don't assume that copy-on-write VMO is OK.
+        zx::vmo child_vmo;
+        status =
+            parent_vmo.create_child(ZX_VMO_CHILD_COPY_ON_WRITE, 0, size, &child_vmo);
+        if (status != ZX_OK) {
+            DRIVER_ERROR("zx::vmo::create_child() failed - status: %d\n",
+                         status);
+            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
+            status = ZX_ERR_NO_MEMORY;
+            return status;
+        }
+
+        zx::vmo vmo_copy;
+        status = child_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_copy);
+        if (status != ZX_OK) {
+            DRIVER_ERROR("duplicate() failed - status: %d", status);
+            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
+            status = ZX_ERR_NO_MEMORY;
+            return status;
+        }
+
+        uint64_t id;
+        status = fuchsia_sysmem_HeapCreateResource(
+            connection_.get(), vmo_copy.release(), &status2, &id);
+        if (status != ZX_OK || status2 != ZX_OK) {
+            DRIVER_ERROR("HeapCreateResource() failed - status: %d status2: %d",
+                         status, status2);
+            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
+            status = ZX_ERR_NO_MEMORY;
+            return status;
+        }
+
+        auto vmo_handle = parent_vmo.get();
+
+        // Free resource when parent VMO has zero references.
+        auto wait = std::make_unique<async::Wait>(
+            vmo_handle, ZX_VMO_ZERO_CHILDREN,
+            async::Wait::Handler([this, id, vmo = std::move(parent_vmo)](
+                                     async_dispatcher_t* dispatcher,
+                                     async::Wait* wait, zx_status_t status,
+                                     const zx_packet_signal_t* signal) mutable {
+                auto it = allocations_.find(vmo.get());
+                if (it == allocations_.end()) {
+                    DRIVER_ERROR("Invalid allocation - vmo_handle: %d",
+                                 vmo.get());
+                    return;
+                }
+                status =
+                    fuchsia_sysmem_HeapDestroyResource(connection_.get(), id);
+                if (status != ZX_OK) {
+                    DRIVER_ERROR("HeapDestroyResource() failed - status: %d",
+                                 status);
+                    // fall-through - this can only fail because resource has
+                    // already been destroyed.
+                }
+                allocations_.erase(it);
+            }));
+        // It is safe to call Begin() here before adding entry to the map as
+        // handler will run on current thread.
+        wait->Begin(async_get_default_dispatcher());
+
+        allocations_[vmo_handle] = std::move(wait);
+        *vmo = std::move(child_vmo);
+        return ZX_OK;
+    }
+    bool CoherencyDomainIsInaccessible() override {
+        // TODO(reveman): Add support for CPU/RAM domains to external heaps.
+        return true;
+    }
+
+private:
+    zx::channel connection_;
+    fbl::unique_ptr<async::Wait> wait_for_close_;
+    std::map<zx_handle_t, std::unique_ptr<async::Wait>> allocations_;
+};
+
 fuchsia_sysmem_DriverConnector_ops_t driver_connector_ops = {
     .Connect = fidl::Binder<Device>::BindMember<&Device::Connect>,
     .GetProtectedMemoryInfo = fidl::Binder<Device>::BindMember<&Device::GetProtectedMemoryInfo>,
@@ -44,10 +187,17 @@
     return self->Connect(allocator_request_param);
 }
 
+zx_status_t in_proc_sysmem_RegisterHeap(void* ctx, uint64_t heap,
+                                        zx_handle_t heap_connection_param) {
+    Device* self = static_cast<Device*>(ctx);
+    return self->RegisterHeap(heap, heap_connection_param);
+}
+
 // In-proc sysmem interface.  Essentially an in-proc version of
 // fuchsia.sysmem.DriverConnector.
 sysmem_protocol_ops_t in_proc_sysmem_protocol_ops = {
     .connect = in_proc_sysmem_Connect,
+    .register_heap = in_proc_sysmem_RegisterHeap,
 };
 
 } // namespace
@@ -83,6 +233,11 @@
         protected_memory_size = metadata.protected_memory_size;
     }
 
+    allocators_[fuchsia_sysmem_HeapType_SYSTEM_RAM] =
+        std::make_unique<SystemRamMemoryAllocator>();
+    contiguous_system_ram_allocator_ =
+        std::make_unique<ContiguousSystemRamMemoryAllocator>(this);
+
     status = pdev_get_bti(&pdev_, 0, bti_.reset_and_get_address());
     if (status != ZX_OK) {
         DRIVER_ERROR("Failed pdev_get_bti() - status: %d", status);
@@ -104,7 +259,8 @@
             DRIVER_ERROR("Failed to init allocator for amlogic protected memory: %d", status);
             return status;
         }
-        protected_allocator_ = std::move(amlogic_allocator);
+        protected_allocator_ = amlogic_allocator.get();
+        allocators_[fuchsia_sysmem_HeapType_AMLOGIC_SECURE] = std::move(amlogic_allocator);
     }
 
     pbus_protocol_t pbus;
@@ -168,6 +324,35 @@
     return ZX_OK;
 }
 
+zx_status_t Device::RegisterHeap(uint64_t heap, zx_handle_t heap_connection) {
+    zx::channel local_heap_connection(heap_connection);
+
+    // External heaps should not have bit 63 set but bit 60 must be set.
+    if ((heap & 0x8000000000000000) || !(heap & 0x1000000000000000)) {
+        DRIVER_ERROR("Invalid external heap");
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    // Clean up heap allocator after peer closed channel.
+    auto wait_for_close = std::make_unique<async::Wait>(
+        local_heap_connection.get(), ZX_CHANNEL_PEER_CLOSED,
+        async::Wait::Handler([this, heap](async_dispatcher_t* dispatcher,
+                                          async::Wait* wait, zx_status_t status,
+                                          const zx_packet_signal_t* signal) {
+            allocators_.erase(heap);
+        }));
+    // It is safe to call Begin() here before adding entry to the map as
+    // handler will run on current thread.
+    wait_for_close->Begin(async_get_default_dispatcher());
+
+    // This replaces any previously registered allocator for heap. This
+    // behavior is preferred as it avoids a potential race-condition during
+    // heap restart.
+    allocators_[heap] = std::make_unique<ExternalMemoryAllocator>(
+        std::move(local_heap_connection), std::move(wait_for_close));
+    return ZX_OK;
+}
+
 zx_status_t Device::GetProtectedMemoryInfo(fidl_txn* txn) {
     if (!protected_allocator_) {
         return fuchsia_sysmem_DriverConnectorGetProtectedMemoryInfo_reply(txn, ZX_ERR_NOT_SUPPORTED, 0u, 0u);
@@ -222,3 +407,17 @@
     }
     return iter->second;
 }
+
+MemoryAllocator* Device::GetAllocator(
+    const fuchsia_sysmem_BufferMemorySettings* settings) {
+    if (settings->heap == fuchsia_sysmem_HeapType_SYSTEM_RAM &&
+        settings->is_physically_contiguous) {
+        return contiguous_system_ram_allocator_.get();
+    }
+
+    auto iter = allocators_.find(settings->heap);
+    if (iter == allocators_.end()) {
+        return nullptr;
+    }
+    return iter->second.get();
+}
diff --git a/zircon/system/dev/sysmem/sysmem/device.h b/zircon/system/dev/sysmem/sysmem/device.h
index 13c5b8c..f177c9d 100644
--- a/zircon/system/dev/sysmem/sysmem/device.h
+++ b/zircon/system/dev/sysmem/sysmem/device.h
@@ -14,7 +14,9 @@
 #include <fbl/unique_ptr.h>
 #include <fbl/vector.h>
 #include <fuchsia/sysmem/c/fidl.h>
+#include <lib/async/cpp/wait.h>
 #include <lib/zx/bti.h>
+#include <lib/zx/channel.h>
 #include <region-alloc/region-alloc.h>
 
 #include "protected_memory_allocator.h"
@@ -35,6 +37,7 @@
     //
 
     zx_status_t Connect(zx_handle_t allocator_request);
+    zx_status_t RegisterHeap(uint64_t heap, zx_handle_t heap_connection);
 
     zx_status_t GetProtectedMemoryInfo(fidl_txn* txn);
 
@@ -58,7 +61,12 @@
     BufferCollectionToken* FindTokenByServerChannelKoid(
         zx_koid_t token_server_koid);
 
-    ProtectedMemoryAllocator* protected_allocator() { return protected_allocator_.get(); }
+    // Get allocator for |settings|. Returns NULL if allocator is not
+    // registered for settings.
+    MemoryAllocator* GetAllocator(
+        const fuchsia_sysmem_BufferMemorySettings* settings);
+
+    ProtectedMemoryAllocator* protected_allocator() { return protected_allocator_; }
 
 private:
     zx_device_t* parent_device_ = nullptr;
@@ -81,7 +89,12 @@
     // the server end of a BufferCollectionToken channel.
     std::map<zx_koid_t, BufferCollectionToken*> tokens_by_koid_;
 
-    fbl::unique_ptr<ProtectedMemoryAllocator> protected_allocator_;
+    // This map contains all registered memory allocators.
+    std::map<fuchsia_sysmem_HeapType, fbl::unique_ptr<MemoryAllocator>> allocators_;
+
+    fbl::unique_ptr<MemoryAllocator> contiguous_system_ram_allocator_;
+
+    ProtectedMemoryAllocator* protected_allocator_ = nullptr;
 };
 
 #endif // ZIRCON_SYSTEM_DEV_SYSMEM_SYSMEM_DEVICE_H_
diff --git a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp
index 7613e5d..9178847 100644
--- a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp
+++ b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.cpp
@@ -253,6 +253,11 @@
     // empty.
     ZX_DEBUG_ASSERT(token_views_.empty());
     ZX_DEBUG_ASSERT(collection_views_.empty());
+
+    if (memory_allocator_) {
+        memory_allocator_->RemoveDestroyCallback(
+            reinterpret_cast<intptr_t>(this));
+    }
 }
 
 void LogicalBufferCollection::Fail(const char* format, ...) {
@@ -551,6 +556,11 @@
         return false;
     }
     if (constraints->has_buffer_memory_constraints) {
+        if (IsCpuUsage(constraints->usage) &&
+            constraints->buffer_memory_constraints.inaccessible_domain_supported) {
+            LogError("IsCpuUsage && inaccessible_domain_supported doesn't make sense.");
+            return false;
+        }
         if (!CheckSanitizeBufferMemoryConstraints(
                 &constraints->buffer_memory_constraints)) {
             return false;
@@ -565,19 +575,28 @@
     return true;
 }
 
+static bool
+IsHeapPermitted(const fuchsia_sysmem_BufferMemoryConstraints* constraints,
+                fuchsia_sysmem_HeapType heap) {
+    if (constraints->heap_permitted_count) {
+        auto begin = constraints->heap_permitted;
+        auto end =
+            constraints->heap_permitted + constraints->heap_permitted_count;
+        return std::find(begin, end, heap) != end;
+    }
+    return true;
+}
+
 bool LogicalBufferCollection::CheckSanitizeBufferMemoryConstraints(
     fuchsia_sysmem_BufferMemoryConstraints* constraints) {
-    if (!constraints->ram_domain_supported &&
-        !constraints->cpu_domain_supported) {
-        LogError("Neither RAM nor CPU coherency domains supported");
-        return false;
-    }
     if (constraints->min_size_bytes > constraints->max_size_bytes) {
         LogError("min_size_bytes > max_size_bytes");
         return false;
     }
-    if (constraints->secure_required && !constraints->secure_permitted) {
-        LogError("secure_required && !secure_permitted");
+    bool secure_permitted =
+        IsHeapPermitted(constraints, fuchsia_sysmem_HeapType_AMLOGIC_SECURE);
+    if (constraints->secure_required && !secure_permitted) {
+        LogError("secure memory required but not permitted");
         return false;
     }
     return true;
@@ -834,6 +853,42 @@
     return true;
 }
 
+bool LogicalBufferCollection::AccumulateConstraintHeapPermitted(
+    uint32_t* acc_count, fuchsia_sysmem_HeapType acc[],
+    uint32_t c_count, const fuchsia_sysmem_HeapType c[]) {
+    // Remove any heap in acc that's not in c.  If zero heaps
+    // remain in acc, return false.
+    ZX_DEBUG_ASSERT(*acc_count > 0);
+
+    for (uint32_t ai = 0; ai < *acc_count; ++ai) {
+        uint32_t ci;
+        for (ci = 0; ci < c_count; ++ci) {
+            if (acc[ai] == c[ci]) {
+                // We found heap in c.  Break so we can move on to
+                // the next heap.
+                break;
+            }
+        }
+        if (ci == c_count) {
+            // remove from acc because not found in c
+            --(*acc_count);
+            // copy of formerly last item on top of the item being
+            // removed
+            acc[ai] = acc[*acc_count];
+            // adjust ai to force current index to be processed again as it's
+            // now a different item
+            --ai;
+        }
+    }
+
+    if (!*acc_count) {
+        LogError("Zero heap permitted overlap");
+        return false;
+    }
+
+    return true;
+}
+
 bool LogicalBufferCollection::AccumulateConstraintBufferMemory(
     fuchsia_sysmem_BufferMemoryConstraints* acc,
     const fuchsia_sysmem_BufferMemoryConstraints* c) {
@@ -852,11 +907,26 @@
                                           c->physically_contiguous_required;
 
     acc->secure_required = acc->secure_required || c->secure_required;
-    acc->secure_permitted = acc->secure_permitted && c->secure_permitted;
 
     acc->ram_domain_supported = acc->ram_domain_supported && c->ram_domain_supported;
     acc->cpu_domain_supported = acc->cpu_domain_supported && c->cpu_domain_supported;
+    acc->inaccessible_domain_supported =
+        acc->inaccessible_domain_supported && c->inaccessible_domain_supported;
 
+    if (!acc->heap_permitted_count) {
+        std::copy(c->heap_permitted,
+                  c->heap_permitted + c->heap_permitted_count,
+                  acc->heap_permitted);
+        acc->heap_permitted_count = c->heap_permitted_count;
+    } else {
+        if (c->heap_permitted_count) {
+            if (!AccumulateConstraintHeapPermitted(
+                    &acc->heap_permitted_count, acc->heap_permitted,
+                    c->heap_permitted_count, c->heap_permitted)) {
+                return false;
+            }
+        }
+    }
     return true;
 }
 
@@ -1049,20 +1119,77 @@
     return a.type == b.type;
 }
 
-static fuchsia_sysmem_CoherencyDomain
-GetCoherencyDomain(const fuchsia_sysmem_BufferCollectionConstraints* constraints) {
-    if (!constraints->has_buffer_memory_constraints)
-        return fuchsia_sysmem_CoherencyDomain_Cpu;
+static uint64_t
+GetHeap(const fuchsia_sysmem_BufferMemoryConstraints* constraints) {
+    if (constraints->secure_required) {
+        // checked previously
+        ZX_DEBUG_ASSERT(
+            !(constraints->secure_required &&
+              !IsHeapPermitted(constraints,
+                               fuchsia_sysmem_HeapType_AMLOGIC_SECURE)));
+        return fuchsia_sysmem_HeapType_AMLOGIC_SECURE;
+    }
+    if (IsHeapPermitted(constraints, fuchsia_sysmem_HeapType_SYSTEM_RAM)) {
+        return fuchsia_sysmem_HeapType_SYSTEM_RAM;
+    }
+    ZX_DEBUG_ASSERT(constraints->heap_permitted_count);
+    return constraints->heap_permitted[0];
+}
 
-    if (!constraints->buffer_memory_constraints.ram_domain_supported)
-        return fuchsia_sysmem_CoherencyDomain_Cpu;
-    if (!constraints->buffer_memory_constraints.cpu_domain_supported)
-        return fuchsia_sysmem_CoherencyDomain_Ram;
+static bool GetCoherencyDomain(
+    const fuchsia_sysmem_BufferCollectionConstraints* constraints,
+    MemoryAllocator* memory_allocator,
+    fuchsia_sysmem_CoherencyDomain* domain_out) {
+    if (!constraints->has_buffer_memory_constraints) {
+        // CPU domain by default.
+        *domain_out = fuchsia_sysmem_CoherencyDomain_Cpu;
+        return true;
+    }
 
-    // Display controllers generally aren't cache coherent.
-    // TODO - base on the system in use.
-    return constraints->usage.display != 0 ? fuchsia_sysmem_CoherencyDomain_Ram
-                                           : fuchsia_sysmem_CoherencyDomain_Cpu;
+    // The heap not being accessible from the CPU can force Inaccessible as the only
+    // potential option.
+    if (memory_allocator->CoherencyDomainIsInaccessible()) {
+        if (!constraints->buffer_memory_constraints.inaccessible_domain_supported) {
+            return false;
+        }
+        *domain_out = fuchsia_sysmem_CoherencyDomain_Inaccessible;
+        return true;
+    }
+
+    // Display prefers RAM coherency domain for now.
+    if (constraints->usage.display != 0) {
+        if (constraints->buffer_memory_constraints.ram_domain_supported) {
+            // Display controllers generally aren't cache coherent, so prefer
+            // RAM coherency domain.
+            //
+            // TODO - base on the system in use.
+            *domain_out = fuchsia_sysmem_CoherencyDomain_Ram;
+            return true;
+        }
+    }
+
+    // If none of the above cases apply, then prefer CPU, RAM, Inaccessible
+    // in that order.
+
+    if (constraints->buffer_memory_constraints.cpu_domain_supported) {
+        *domain_out = fuchsia_sysmem_CoherencyDomain_Cpu;
+        return true;
+    }
+
+    if (constraints->buffer_memory_constraints.ram_domain_supported) {
+        *domain_out = fuchsia_sysmem_CoherencyDomain_Ram;
+        return true;
+    }
+
+    if (constraints->buffer_memory_constraints.inaccessible_domain_supported) {
+        // Intentionally permit treating as Inaccessible if we reach here, even
+        // if the heap permits CPU access.  Only domain in common among
+        // participants is Inaccessible.
+        *domain_out = fuchsia_sysmem_CoherencyDomain_Inaccessible;
+        return true;
+    }
+
+    return false;
 }
 
 BufferCollection::BufferCollectionInfo
@@ -1123,17 +1250,35 @@
             buffer_constraints->physically_contiguous_required;
         // checked previously
         ZX_DEBUG_ASSERT(!(buffer_constraints->secure_required &&
-                          !buffer_constraints->secure_permitted));
-        // checked previously
-        ZX_DEBUG_ASSERT(!(buffer_constraints->secure_required &&
                           IsCpuUsage(constraints_->usage)));
         buffer_settings->is_secure = buffer_constraints->secure_required;
+        buffer_settings->heap = GetHeap(buffer_constraints);
         // We can't fill out buffer_settings yet because that also depends on
         // ImageFormatConstraints.  We do need the min and max from here though.
         min_size_bytes = buffer_constraints->min_size_bytes;
         max_size_bytes = buffer_constraints->max_size_bytes;
     }
-    buffer_settings->coherency_domain = GetCoherencyDomain(constraints_.get());
+
+    // Get memory allocator for settings.
+    MemoryAllocator* allocator = parent_device_->GetAllocator(buffer_settings);
+    if (!allocator) {
+        LogError("No memory allocator for buffer settings");
+        *allocation_result = ZX_ERR_NO_MEMORY;
+        return BufferCollection::BufferCollectionInfo(
+            BufferCollection::BufferCollectionInfo::Null);
+    }
+
+    if (!GetCoherencyDomain(constraints_.get(), allocator,
+                            &buffer_settings->coherency_domain)) {
+        LogError("No coherency domain found for buffer constraints");
+        *allocation_result = ZX_ERR_NOT_SUPPORTED;
+        return BufferCollection::BufferCollectionInfo(
+            BufferCollection::BufferCollectionInfo::Null);
+    }
+
+    ZX_DEBUG_ASSERT(
+        constraints_->usage.cpu == 0 || buffer_settings->coherency_domain !=
+        fuchsia_sysmem_CoherencyDomain_Inaccessible);
 
     // It's allowed for zero participants to have any ImageFormatConstraint(s),
     // in which case the combined constraints_ will have zero (and that's fine,
@@ -1280,7 +1425,7 @@
         // Assign directly into result to benefit from FidlStruct<> management
         // of handle lifetime.
         zx::vmo vmo;
-        zx_status_t allocate_result = AllocateVmo(settings, &vmo);
+        zx_status_t allocate_result = AllocateVmo(allocator, settings, &vmo);
         if (allocate_result != ZX_OK) {
             ZX_DEBUG_ASSERT(allocate_result == ZX_ERR_NO_MEMORY);
             LogError("AllocateVmo() failed - status: %d", allocate_result);
@@ -1294,79 +1439,37 @@
         result->buffers[i].vmo = vmo.release();
     }
 
+    // Register failure handler with memory allocator.
+    allocator->AddDestroyCallback(reinterpret_cast<intptr_t>(this), [this](){
+        Fail("LogicalBufferCollection memory allocator gone - now auto-failing self.");
+    });
+    memory_allocator_ = allocator;
+
     ZX_DEBUG_ASSERT(*allocation_result == ZX_OK);
     return result;
 }
 
 zx_status_t LogicalBufferCollection::AllocateVmo(
-    const fuchsia_sysmem_SingleBufferSettings* settings, zx::vmo* vmo) {
-    if (settings->buffer_settings.is_secure) {
-        ProtectedMemoryAllocator* allocator = parent_device_->protected_allocator();
-        if (!allocator) {
-            LogError("No protected memory allocator");
-            return ZX_ERR_NOT_SUPPORTED;
-        }
-
-        zx::vmo raw_vmo;
-        zx_status_t status = allocator->Allocate(
-            settings->buffer_settings.size_bytes, &raw_vmo);
-        if (status != ZX_OK) {
-            LogError("Protected allocate failed - size_bytes: %u "
-                     "status: %d",
-                     settings->buffer_settings.size_bytes, status);
-            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
-            status = ZX_ERR_NO_MEMORY;
-            return status;
-        }
-        status = raw_vmo.duplicate(kSysmemVmoRights, vmo);
-        if (status != ZX_OK) {
-            LogError("zx::object::duplicate() failed - status: %d", status);
-            return status;
-        }
-    } else if (settings->buffer_settings.is_physically_contiguous) {
-        // TODO(dustingreen): Optionally (per board) pre-allocate a
-        // physically-contiguous VMO with board-specific size, have a page heap,
-        // dole out portions of that VMO or non-copy-on-write child VMOs of that
-        // VMO.  For now, we just attempt to allocate contiguous when buffers
-        // are allocated, which is unlikely to work after the system has been
-        // running for a while and physical memory is more fragmented than early
-        // during boot.
-        zx::vmo raw_vmo;
-        zx_status_t status = zx::vmo::create_contiguous(
-            parent_device_->bti(), settings->buffer_settings.size_bytes, 0,
-            &raw_vmo);
-        if (status != ZX_OK) {
-            LogError("zx::vmo::create_contiguous() failed - size_bytes: %u "
-                     "status: %d",
-                     settings->buffer_settings.size_bytes, status);
-            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
-            status = ZX_ERR_NO_MEMORY;
-            return status;
-        }
-        status = raw_vmo.duplicate(kSysmemVmoRights, vmo);
-        if (status != ZX_OK) {
-            LogError("zx::object::duplicate() failed - status: %d", status);
-            return status;
-        }
-        // ~raw_vmo - *vmo is a duplicate with slightly-reduced rights.
-    } else {
-        zx::vmo raw_vmo;
-        zx_status_t status =
-            zx::vmo::create(settings->buffer_settings.size_bytes, 0, &raw_vmo);
-        if (status != ZX_OK) {
-            LogError("zx::vmo::create() failed - size_bytes: %u status: %d",
-                     settings->buffer_settings.size_bytes, status);
-            // sanitize to ZX_ERR_NO_MEMORY regardless of why.
-            status = ZX_ERR_NO_MEMORY;
-            return status;
-        }
-        status = raw_vmo.duplicate(kSysmemVmoRights, vmo);
-        if (status != ZX_OK) {
-            LogError("zx::object::duplicate() failed - status: %d", status);
-            return status;
-        }
-        // ~raw_vmo - *vmo is a duplicate with slightly-reduced rights.
+    MemoryAllocator* allocator,
+    const fuchsia_sysmem_SingleBufferSettings* settings,
+    zx::vmo* vmo) {
+    zx::vmo raw_vmo;
+    zx_status_t status =
+        allocator->Allocate(settings->buffer_settings.size_bytes, &raw_vmo);
+    if (status != ZX_OK) {
+        LogError("Allocate failed - size_bytes: %u "
+                 "status: %d",
+                 settings->buffer_settings.size_bytes, status);
+        // sanitize to ZX_ERR_NO_MEMORY regardless of why.
+        status = ZX_ERR_NO_MEMORY;
+        return status;
     }
+    status = raw_vmo.duplicate(kSysmemVmoRights, vmo);
+    if (status != ZX_OK) {
+        LogError("zx::object::duplicate() failed - status: %d", status);
+        return status;
+    }
+    // ~raw_vmo - *vmo is a duplicate with slightly-reduced rights.
     return ZX_OK;
 }
 
diff --git a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h
index 6636ef1..c3afa89 100644
--- a/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h
+++ b/zircon/system/dev/sysmem/sysmem/logical_buffer_collection.h
@@ -23,6 +23,7 @@
 
 class BufferCollectionToken;
 class BufferCollection;
+class MemoryAllocator;
 class LogicalBufferCollection
     : public fbl::RefCounted<LogicalBufferCollection> {
 public:
@@ -120,6 +121,11 @@
         fuchsia_sysmem_BufferCollectionConstraints* acc,
         const fuchsia_sysmem_BufferCollectionConstraints* c);
 
+    bool AccumulateConstraintHeapPermitted(uint32_t* acc_count,
+                                           fuchsia_sysmem_HeapType acc[],
+                                           uint32_t c_count,
+                                           const fuchsia_sysmem_HeapType c[]);
+
     bool AccumulateConstraintBufferMemory(
         fuchsia_sysmem_BufferMemoryConstraints* acc,
         const fuchsia_sysmem_BufferMemoryConstraints* c);
@@ -142,7 +148,8 @@
 
     BufferCollectionInfo Allocate(zx_status_t* allocation_result);
 
-    zx_status_t AllocateVmo(const fuchsia_sysmem_SingleBufferSettings* settings,
+    zx_status_t AllocateVmo(MemoryAllocator* allocator,
+                            const fuchsia_sysmem_SingleBufferSettings* settings,
                             zx::vmo* vmo);
 
     int32_t CompareImageFormatConstraintsTieBreaker(
@@ -175,6 +182,8 @@
     bool has_allocation_result_ = false;
     zx_status_t allocation_result_status_ = ZX_OK;
     BufferCollectionInfo allocation_result_info_{BufferCollectionInfo::Null};
+
+    MemoryAllocator* memory_allocator_ = nullptr;
 };
 
 #endif // ZIRCON_SYSTEM_DEV_SYSMEM_SYSMEM_LOGICAL_BUFFER_COLLECTION_H_
diff --git a/zircon/system/dev/sysmem/sysmem/memory_allocator.cpp b/zircon/system/dev/sysmem/sysmem/memory_allocator.cpp
new file mode 100644
index 0000000..9cb6737
--- /dev/null
+++ b/zircon/system/dev/sysmem/sysmem/memory_allocator.cpp
@@ -0,0 +1,25 @@
+// 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 "memory_allocator.h"
+
+#include <zircon/assert.h>
+
+MemoryAllocator::~MemoryAllocator() {
+    for (auto& it : destroy_callbacks_) {
+        it.second();
+    }
+}
+
+void MemoryAllocator::AddDestroyCallback(intptr_t key,
+                                         fit::callback<void()> callback) {
+    ZX_DEBUG_ASSERT(destroy_callbacks_.find(key) == destroy_callbacks_.end());
+    destroy_callbacks_[key] = std::move(callback);
+}
+
+void MemoryAllocator::RemoveDestroyCallback(intptr_t key) {
+    // The key isn't required to be in the map in case of failures during
+    // create.  Erase if present.
+    destroy_callbacks_.erase(key);
+}
diff --git a/zircon/system/dev/sysmem/sysmem/memory_allocator.h b/zircon/system/dev/sysmem/sysmem/memory_allocator.h
new file mode 100644
index 0000000..98d41eb
--- /dev/null
+++ b/zircon/system/dev/sysmem/sysmem/memory_allocator.h
@@ -0,0 +1,24 @@
+// 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.
+
+#pragma once
+
+#include <lib/fit/function.h>
+#include <lib/zx/vmo.h>
+
+#include <map>
+
+class MemoryAllocator {
+public:
+    virtual ~MemoryAllocator();
+
+    virtual zx_status_t Allocate(uint64_t size, zx::vmo* vmo) = 0;
+    virtual bool CoherencyDomainIsInaccessible() = 0;
+
+    void AddDestroyCallback(intptr_t key, fit::callback<void()> callback);
+    void RemoveDestroyCallback(intptr_t key);
+
+public:
+    std::map<intptr_t, fit::callback<void()>> destroy_callbacks_;
+};
diff --git a/zircon/system/dev/sysmem/sysmem/protected_memory_allocator.h b/zircon/system/dev/sysmem/sysmem/protected_memory_allocator.h
index e7cded2..4cec46f 100644
--- a/zircon/system/dev/sysmem/sysmem/protected_memory_allocator.h
+++ b/zircon/system/dev/sysmem/sysmem/protected_memory_allocator.h
@@ -6,10 +6,11 @@
 
 #include <lib/zx/vmo.h>
 
-class ProtectedMemoryAllocator {
+#include "memory_allocator.h"
+
+class ProtectedMemoryAllocator : public MemoryAllocator {
 public:
     virtual ~ProtectedMemoryAllocator() {}
 
-    virtual zx_status_t Allocate(uint64_t size, zx::vmo* vmo) = 0;
     virtual zx_status_t GetProtectedMemoryInfo(uint64_t* base, uint64_t* size) = 0;
 };
diff --git a/zircon/system/fidl/BUILD.gn b/zircon/system/fidl/BUILD.gn
index 69da451..0b55ac8 100644
--- a/zircon/system/fidl/BUILD.gn
+++ b/zircon/system/fidl/BUILD.gn
@@ -31,6 +31,7 @@
     "fuchsia-hardware-ethernet",
     "fuchsia-hardware-ethertap",
     "fuchsia-hardware-goldfish-address-space",
+    "fuchsia-hardware-goldfish-control",
     "fuchsia-hardware-goldfish-pipe",
     "fuchsia-hardware-gpu-clock",
     "fuchsia-hardware-hidctl",
diff --git a/zircon/system/fidl/fuchsia-hardware-goldfish-control/BUILD.gn b/zircon/system/fidl/fuchsia-hardware-goldfish-control/BUILD.gn
new file mode 100644
index 0000000..e43c421
--- /dev/null
+++ b/zircon/system/fidl/fuchsia-hardware-goldfish-control/BUILD.gn
@@ -0,0 +1,11 @@
+# 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.
+
+import("$zx/public/gn/fidl.gni")
+
+fidl_library("fuchsia-hardware-goldfish-control") {
+  sources = [
+    "goldfish-control.fidl",
+  ]
+}
diff --git a/zircon/system/fidl/fuchsia-hardware-goldfish-control/goldfish-control.fidl b/zircon/system/fidl/fuchsia-hardware-goldfish-control/goldfish-control.fidl
new file mode 100644
index 0000000..4ff2d09
--- /dev/null
+++ b/zircon/system/fidl/fuchsia-hardware-goldfish-control/goldfish-control.fidl
@@ -0,0 +1,29 @@
+// 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.
+
+library fuchsia.hardware.goldfish.control;
+
+using zx;
+
+/// Color buffer formats.
+enum FormatType : uint32 {
+    RGBA = 0x1908;
+    BGRA = 0x80E1;
+};
+
+[Layout = "Simple"]
+protocol Device {
+    /// Create shared color buffer. Color buffer is automatically freed when
+    /// all references to |vmo| have been closed. Fails if VMO is not
+    /// associated with goldfish heap memory.
+    CreateColorBuffer(handle<vmo> vmo,
+                      uint32 width,
+                      uint32 height,
+                      FormatType format)
+        -> (zx.status res);
+
+    /// Get color buffer for VMO. Fails if VMO is not associated with a color
+    /// buffer.
+    GetColorBuffer(handle<vmo> vmo) -> (zx.status res, uint32 id);
+};
diff --git a/zircon/system/fidl/fuchsia-sysmem/BUILD.gn b/zircon/system/fidl/fuchsia-sysmem/BUILD.gn
index 7cc35ef..0d7a779 100644
--- a/zircon/system/fidl/fuchsia-sysmem/BUILD.gn
+++ b/zircon/system/fidl/fuchsia-sysmem/BUILD.gn
@@ -14,6 +14,7 @@
     "driver_connector.fidl",
     "format_modifier.fidl",
     "formats_deprecated.fidl",
+    "heap.fidl",
     "image_formats.fidl",
     "image_formats_deprecated.fidl",
     "usages.fidl",
diff --git a/zircon/system/fidl/fuchsia-sysmem/constraints.fidl b/zircon/system/fidl/fuchsia-sysmem/constraints.fidl
index 4690595..2289691 100644
--- a/zircon/system/fidl/fuchsia-sysmem/constraints.fidl
+++ b/zircon/system/fidl/fuchsia-sysmem/constraints.fidl
@@ -172,6 +172,19 @@
     ImageFormatConstraints image_format_constraints;
 };
 
+/// Known heap types.
+/// Device specific types should have bit 60 set. Top order bit is reserved
+/// and should not be set.
+enum HeapType : uint64 {
+    SYSTEM_RAM = 0x0000000000000000;
+
+    /// Heap used for amlogic protected memory.
+    AMLOGIC_SECURE = 0x1000000000010000;
+
+    /// Heap used by goldfish vulkan for device-local memory.
+    GOLDFISH_DEVICE_LOCAL = 0x1000000000020000;
+};
+
 struct BufferMemoryConstraints {
     uint32 min_size_bytes = 0;
     uint32 max_size_bytes = 0xFFFFFFFF;
@@ -183,12 +196,6 @@
     /// When aggregating BufferCollectionConstraints, these values boolean-OR.
     bool secure_required = false;
 
-    /// If false, at least one participant can't handle secure memory.  All
-    /// participants that can handle secure memory must specify true here.
-    ///
-    /// When aggregating BufferCollectionConstraints, these values boolean-AND.
-    bool secure_permitted = false;
-
     /// By default, participants must ensure the CPU can read or write data to
     /// the buffer without cache operations. If they support using the RAM
     /// domain, data must be available in RAM (with CPU cache state such that
@@ -198,11 +205,26 @@
     /// zero stale "clean" cache lines)
     bool ram_domain_supported = false;
     bool cpu_domain_supported = true;
+    bool inaccessible_domain_supported = false;
+
+    /// Optional heap constraints. Participants that don't care which heap
+    /// memory is allocated on should leave this field 0.
+    uint32 heap_permitted_count;
+    array<HeapType>:32 heap_permitted;
 };
 
+/// Inaccessible is only for cases where there is no CPU-based access to the
+/// buffers.  A secure_required buffer can still have CoherencyDomain Cpu or
+/// Ram even if the secure_required buffer can only be accessed by the CPU when
+/// the CPU is running in secure mode (or similar).  In contrast, device-local
+/// memory that isn't reachable from the CPU is CoherencyDomain Inaccessible,
+/// even if it's possible to cause a device (physical or virtual) to copy the
+/// data from the Inaccessible buffers to buffers that are visible to the CPU.
+/// TODO: Use upper snake case for these enum members.
 enum CoherencyDomain {
     Cpu = 0;
     Ram = 1;
+    Inaccessible = 2;
 };
 
 struct BufferMemorySettings {
@@ -210,6 +232,9 @@
     bool is_physically_contiguous;
     bool is_secure;
     CoherencyDomain coherency_domain;
+    /// The specific heap from which buffers are allocated.
+    /// See above in this file for heap identifier values.
+    HeapType heap;
 };
 
 /// Describes constraints on layout of image data in buffers.
diff --git a/zircon/system/fidl/fuchsia-sysmem/heap.fidl b/zircon/system/fidl/fuchsia-sysmem/heap.fidl
new file mode 100644
index 0000000..60213e6
--- /dev/null
+++ b/zircon/system/fidl/fuchsia-sysmem/heap.fidl
@@ -0,0 +1,54 @@
+// 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.
+
+library fuchsia.sysmem;
+
+using zx;
+
+/// Manages resources on a specific sysmem heap.
+///
+/// Needs Layout = "Simple" because used with "FIDL Simple C Bindings".
+[Layout = "Simple"]
+protocol Heap {
+    /// Request a new memory allocation of |size| on heap.
+    /// For heaps which don't permit CPU access to the buffer data, this
+    /// will create a VMO with an official size, but which never has any
+    /// physical pages.  For such heaps, the VMO is effectively used as
+    /// an opaque buffer identifier.
+    ///
+    /// Heaps should defer allocation of any associated resources until
+    /// CreateResource(), because the caller of AllocateVmo() may simply
+    /// delete the returned VMO with no further notification to the heap.
+    /// In contrast, after CreateResource(), the caller guarantees that
+    /// DestroyResource() or heap channel closure will occur.
+    ///
+    /// The caller guarantees that CreateResource() will be called prior
+    /// to the returned VMO or any associated child VMO being used.
+    AllocateVmo(uint64 size) -> (zx.status s, handle<vmo>? vmo);
+
+    /// Create resources and associate heap-specific resources with the
+    /// passed-in VMO. Resources can be hardware specific and their
+    /// lifetime don't have to be tied to |vmo|. |vmo| must be a VMO
+    /// (or a direct or indirect child of a VMO) acquired through a call
+    /// to AllocateVmo method above.  If the passed-in vmo is a child VMO,
+    /// it's size must match the size of the parent VMO created by
+    /// AllocateVmo().  For heaps that permit CPU access, the passed-in
+    /// VMO must not have a copy-on-write relationship with the parent
+    /// VMO, but rather a pass-through relationship. Successful return
+    /// status indicate that Heap has established a mapping between
+    /// VMO and hardware specific resources.
+    ///
+    /// The returned id must be passed to DestroyResource() later when
+    /// resources associated with VMO are no longer needed, unless the
+    /// heap channel closes first.
+    ///
+    /// The heap must not own/keep a handle to VMO, or any derived child
+    /// VMO, or any VMAR mapping to VMO, as any of those would keep VMO
+    /// alive beyond all sysmem participant usages of the vmo; instead
+    /// the heap can get the vmo's koid for the heap's mapping.
+    CreateResource(handle<vmo> vmo) -> (zx.status s, uint64 id);
+
+    /// Destroy previously created resources.
+    DestroyResource(uint64 id) -> ();
+};
diff --git a/zircon/system/ulib/ddk/include/ddk/protodefs.h b/zircon/system/ulib/ddk/include/ddk/protodefs.h
index 4588ad6..4e31f6d 100644
--- a/zircon/system/ulib/ddk/include/ddk/protodefs.h
+++ b/zircon/system/ulib/ddk/include/ddk/protodefs.h
@@ -24,6 +24,7 @@
 DDK_PROTOCOL_DEF(ETHMAC,         'pEMA', "ethmac", 0)
 DDK_PROTOCOL_DEF(FRAMEBUFFER,    'pFRB', "framebuffer", 0)
 DDK_PROTOCOL_DEF(GOLDFISH_ADDRESS_SPACE, 'pGFA', "goldfish-address-space", 0)
+DDK_PROTOCOL_DEF(GOLDFISH_CONTROL, 'pGFC', "goldfish-control", 0)
 DDK_PROTOCOL_DEF(GOLDFISH_PIPE,  'pGFP', "goldfish-pipe", 0)
 DDK_PROTOCOL_DEF(GPIO,           'pGPO', "gpio", PF_NOPUB)
 DDK_PROTOCOL_DEF(GPIO_IMPL,      'pGPI', "gpio-impl", PF_NOPUB)
diff --git a/zircon/system/utest/goldfish/BUILD.gn b/zircon/system/utest/goldfish/BUILD.gn
index 2a52ec6..03c7ce6 100644
--- a/zircon/system/utest/goldfish/BUILD.gn
+++ b/zircon/system/utest/goldfish/BUILD.gn
@@ -8,9 +8,12 @@
   ]
   deps = [
     "$zx/system/fidl/fuchsia-hardware-goldfish-address-space:c",
+    "$zx/system/fidl/fuchsia-hardware-goldfish-control:c",
     "$zx/system/fidl/fuchsia-hardware-goldfish-pipe:c",
+    "$zx/system/fidl/fuchsia-sysmem:c",
     "$zx/system/ulib/fdio",
     "$zx/system/ulib/fidl",
+    "$zx/system/ulib/fidl-async-2",
     "$zx/system/ulib/unittest",
     "$zx/system/ulib/zircon",
     "$zx/system/ulib/zx",
diff --git a/zircon/system/utest/goldfish/goldfish-test.cpp b/zircon/system/utest/goldfish/goldfish-test.cpp
index 0b59dbe..4a6746816 100644
--- a/zircon/system/utest/goldfish/goldfish-test.cpp
+++ b/zircon/system/utest/goldfish/goldfish-test.cpp
@@ -4,8 +4,12 @@
 
 #include <fcntl.h>
 #include <fuchsia/hardware/goldfish/address/space/c/fidl.h>
+#include <fuchsia/hardware/goldfish/control/c/fidl.h>
 #include <fuchsia/hardware/goldfish/pipe/c/fidl.h>
+#include <fuchsia/sysmem/c/fidl.h>
+#include <lib/fdio/directory.h>
 #include <lib/fdio/fdio.h>
+#include <lib/fidl-async-2/fidl_struct.h>
 #include <lib/zx/channel.h>
 #include <lib/zx/vmo.h>
 #include <unistd.h>
@@ -96,6 +100,113 @@
 RUN_TEST(GoldfishPipeTest)
 END_TEST_CASE(GoldfishPipeTests)
 
+extern const fidl_type_t fuchsia_sysmem_BufferCollectionConstraintsTable;
+using BufferCollectionConstraints =
+    FidlStruct<fuchsia_sysmem_BufferCollectionConstraints,
+               &fuchsia_sysmem_BufferCollectionConstraintsTable>;
+extern const fidl_type_t fuchsia_sysmem_BufferCollectionInfo_2Table;
+using BufferCollectionInfo =
+    FidlStruct<fuchsia_sysmem_BufferCollectionInfo_2,
+               &fuchsia_sysmem_BufferCollectionInfo_2Table>;
+
+static bool GoldfishControlTest() {
+    BEGIN_TEST;
+
+    int fd = open("/dev/class/goldfish-control/000", O_RDWR);
+    EXPECT_GE(fd, 0);
+
+    zx::channel channel;
+    EXPECT_EQ(fdio_get_service_handle(fd, channel.reset_and_get_address()),
+              ZX_OK);
+
+    zx::channel allocator_client;
+    zx::channel allocator_server;
+    EXPECT_EQ(zx::channel::create(0, &allocator_client, &allocator_server),
+              ZX_OK);
+    EXPECT_EQ(fdio_service_connect("/svc/fuchsia.sysmem.Allocator",
+                                   allocator_server.release()),
+              ZX_OK);
+
+    zx::channel token_client;
+    zx::channel token_server;
+    EXPECT_EQ(zx::channel::create(0, &token_client, &token_server), ZX_OK);
+    EXPECT_EQ(fuchsia_sysmem_AllocatorAllocateSharedCollection(
+                  allocator_client.get(), token_server.release()),
+              ZX_OK);
+
+    zx::channel collection_client;
+    zx::channel collection_server;
+    EXPECT_EQ(zx::channel::create(0, &collection_client, &collection_server),
+              ZX_OK);
+    EXPECT_EQ(fuchsia_sysmem_AllocatorBindSharedCollection(
+                  allocator_client.get(), token_client.release(),
+                  collection_server.release()),
+              ZX_OK);
+
+    BufferCollectionConstraints constraints(
+        BufferCollectionConstraints::Default);
+    constraints->usage.vulkan = fuchsia_sysmem_vulkanUsageTransferDst;
+    constraints->min_buffer_count_for_camping = 1;
+    constraints->has_buffer_memory_constraints = true;
+    constraints->buffer_memory_constraints =
+        fuchsia_sysmem_BufferMemoryConstraints{
+            .min_size_bytes = 4 * 1024,
+            .max_size_bytes = 4 * 1024,
+            .physically_contiguous_required = false,
+            .secure_required = false,
+            .ram_domain_supported = false,
+            .cpu_domain_supported = false,
+            .inaccessible_domain_supported = true,
+            .heap_permitted_count = 1,
+            .heap_permitted = {fuchsia_sysmem_HeapType_GOLDFISH_DEVICE_LOCAL}};
+    EXPECT_EQ(fuchsia_sysmem_BufferCollectionSetConstraints(
+                  collection_client.get(), true, constraints.release()),
+              ZX_OK);
+
+    zx_status_t status2 = ZX_OK;
+    BufferCollectionInfo info(BufferCollectionInfo::Default);
+    EXPECT_EQ(fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
+                  collection_client.get(), &status2, info.get()),
+              ZX_OK);
+    EXPECT_EQ(status2, ZX_OK);
+    EXPECT_EQ(info->buffer_count, 1);
+    EXPECT_NE(info->buffers[0].vmo, ZX_HANDLE_INVALID);
+
+    zx::vmo vmo(info->buffers[0].vmo);
+    info->buffers[0].vmo = ZX_HANDLE_INVALID;
+    EXPECT_TRUE(vmo.is_valid());
+
+    EXPECT_EQ(fuchsia_sysmem_BufferCollectionClose(collection_client.get()),
+              ZX_OK);
+
+    zx::vmo vmo_copy;
+    EXPECT_EQ(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_copy), ZX_OK);
+
+    status2 = ZX_OK;
+    EXPECT_EQ(fuchsia_hardware_goldfish_control_DeviceCreateColorBuffer(
+                  channel.get(), vmo_copy.release(), 64, 64,
+                  fuchsia_hardware_goldfish_control_FormatType_BGRA, &status2),
+              ZX_OK);
+    EXPECT_EQ(status2, ZX_OK);
+
+    zx::vmo vmo_copy2;
+    EXPECT_EQ(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_copy2), ZX_OK);
+
+    status2 = ZX_OK;
+    uint32_t id = 0;
+    EXPECT_EQ(fuchsia_hardware_goldfish_control_DeviceGetColorBuffer(
+                  channel.get(), vmo_copy2.release(), &status2, &id),
+              ZX_OK);
+    EXPECT_EQ(status2, ZX_OK);
+    EXPECT_NE(id, 0);
+
+    END_TEST;
+}
+
+BEGIN_TEST_CASE(GoldfishControlTests)
+RUN_TEST(GoldfishControlTest)
+END_TEST_CASE(GoldfishControlTests)
+
 static bool GoldfishAddressSpaceTest() {
     BEGIN_TEST;
 
diff --git a/zircon/system/utest/sysmem/sysmem_tests.cpp b/zircon/system/utest/sysmem/sysmem_tests.cpp
index 4837bb0..6e35055 100644
--- a/zircon/system/utest/sysmem/sysmem_tests.cpp
+++ b/zircon/system/utest/sysmem/sysmem_tests.cpp
@@ -160,9 +160,11 @@
         .max_size_bytes = 128 * 1024,
         .physically_contiguous_required = false,
         .secure_required = false,
-        .secure_permitted = false,
         .ram_domain_supported = false,
         .cpu_domain_supported = true,
+        .inaccessible_domain_supported = false,
+        .heap_permitted_count = 0,
+        .heap_permitted = {},
     };
     ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
     status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
@@ -240,9 +242,11 @@
         .max_size_bytes = 128 * 1024,
         .physically_contiguous_required = false,
         .secure_required = false,
-        .secure_permitted = false,
         .ram_domain_supported = false,
         .cpu_domain_supported = true,
+        .inaccessible_domain_supported = false,
+        .heap_permitted_count = 0,
+        .heap_permitted = {},
     };
     constraints->image_format_constraints_count = 1;
     fuchsia_sysmem_ImageFormatConstraints& image_constraints =
@@ -352,9 +356,11 @@
         .max_size_bytes = 128 * 1024,
         .physically_contiguous_required = false,
         .secure_required = false,
-        .secure_permitted = false,
         .ram_domain_supported = false,
         .cpu_domain_supported = true,
+        .inaccessible_domain_supported = false,
+        .heap_permitted_count = 0,
+        .heap_permitted = {},
     };
     ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
     status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
@@ -403,9 +409,11 @@
         .max_size_bytes = 128 * 1024,
         .physically_contiguous_required = false,
         .secure_required = false,
-        .secure_permitted = false,
         .ram_domain_supported = true,
         .cpu_domain_supported = true,
+        .inaccessible_domain_supported = false,
+        .heap_permitted_count = 0,
+        .heap_permitted = {},
     };
     ZX_DEBUG_ASSERT(constraints->image_format_constraints_count == 0);
     status = fuchsia_sysmem_BufferCollectionSetConstraints(collection_client.get(), true,
@@ -511,9 +519,11 @@
         .max_size_bytes = (512 * 512) * 3 / 2,
         .physically_contiguous_required = false,
         .secure_required = false,
-        .secure_permitted = false,
         .ram_domain_supported = false,
         .cpu_domain_supported = true,
+        .inaccessible_domain_supported = false,
+        .heap_permitted_count = 0,
+        .heap_permitted = {},
     };
     constraints_1->image_format_constraints_count = 1;
     fuchsia_sysmem_ImageFormatConstraints& image_constraints_1 =
@@ -797,9 +807,11 @@
         .max_size_bytes = 64 * 1024,
         .physically_contiguous_required = false,
         .secure_required = false,
-        .secure_permitted = false,
         .ram_domain_supported = false,
         .cpu_domain_supported = true,
+        .inaccessible_domain_supported = false,
+        .heap_permitted_count = 0,
+        .heap_permitted = {},
     };
 
     // constraints_2 is just a copy of constraints_1 - since both participants
@@ -885,6 +897,139 @@
     END_TEST;
 }
 
+extern "C" bool test_sysmem_heap_constraints(void) {
+    BEGIN_TEST;
+
+    zx_status_t status;
+    zx::channel allocator_client;
+    status = connect_to_sysmem_driver(&allocator_client);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    zx::channel token_client;
+    zx::channel token_server;
+    status = zx::channel::create(0, &token_client, &token_server);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    status = fuchsia_sysmem_AllocatorAllocateSharedCollection(
+        allocator_client.get(), token_server.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    zx::channel collection_client;
+    zx::channel collection_server;
+    status = zx::channel::create(0, &collection_client, &collection_server);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
+    status = fuchsia_sysmem_AllocatorBindSharedCollection(
+        allocator_client.get(), token_client.release(),
+        collection_server.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    BufferCollectionConstraints constraints(
+        BufferCollectionConstraints::Default);
+    constraints->usage.vulkan = fuchsia_sysmem_vulkanUsageTransferDst;
+    constraints->min_buffer_count_for_camping = 1;
+    constraints->has_buffer_memory_constraints = true;
+    constraints->buffer_memory_constraints =
+        fuchsia_sysmem_BufferMemoryConstraints{
+            .min_size_bytes = 4 * 1024,
+            .max_size_bytes = 4 * 1024,
+            .physically_contiguous_required = true,
+            .secure_required = false,
+            .ram_domain_supported = false,
+            .cpu_domain_supported = false,
+            .inaccessible_domain_supported = true,
+            .heap_permitted_count = 1,
+            .heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
+
+    status = fuchsia_sysmem_BufferCollectionSetConstraints(
+        collection_client.get(), true, constraints.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    zx_status_t allocation_status;
+    BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
+    status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
+        collection_client.get(), &allocation_status,
+        buffer_collection_info.get());
+    // This is the first round-trip to/from sysmem.  A failure here can be due
+    // to any step above failing async.
+    ASSERT_EQ(status, ZX_OK, "");
+    ASSERT_EQ(allocation_status, ZX_OK, "");
+    ASSERT_EQ(buffer_collection_info->buffer_count, 1, "");
+    ASSERT_EQ(buffer_collection_info->settings.buffer_settings.coherency_domain,
+              fuchsia_sysmem_CoherencyDomain_Inaccessible, "");
+    ASSERT_EQ(buffer_collection_info->settings.buffer_settings.heap,
+              fuchsia_sysmem_HeapType_SYSTEM_RAM, "");
+    ASSERT_EQ(buffer_collection_info->settings.buffer_settings
+                  .is_physically_contiguous,
+               true, "");
+
+    END_TEST;
+}
+
+extern "C" bool test_sysmem_cpu_usage_and_inaccessible_domain_fails(void) {
+    BEGIN_TEST;
+
+    zx_status_t status;
+    zx::channel allocator_client;
+    status = connect_to_sysmem_driver(&allocator_client);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    zx::channel token_client;
+    zx::channel token_server;
+    status = zx::channel::create(0, &token_client, &token_server);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    status = fuchsia_sysmem_AllocatorAllocateSharedCollection(
+        allocator_client.get(), token_server.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    zx::channel collection_client;
+    zx::channel collection_server;
+    status = zx::channel::create(0, &collection_client, &collection_server);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    ASSERT_NE(token_client.get(), ZX_HANDLE_INVALID, "");
+    status = fuchsia_sysmem_AllocatorBindSharedCollection(
+        allocator_client.get(), token_client.release(),
+        collection_server.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    BufferCollectionConstraints constraints(
+        BufferCollectionConstraints::Default);
+    constraints->usage.cpu = fuchsia_sysmem_cpuUsageReadOften | fuchsia_sysmem_cpuUsageWriteOften;
+    constraints->min_buffer_count_for_camping = 1;
+    constraints->has_buffer_memory_constraints = true;
+    constraints->buffer_memory_constraints =
+        fuchsia_sysmem_BufferMemoryConstraints{
+            .min_size_bytes = 4 * 1024,
+            .max_size_bytes = 4 * 1024,
+            .physically_contiguous_required = true,
+            .secure_required = false,
+            .ram_domain_supported = false,
+            .cpu_domain_supported = false,
+            .inaccessible_domain_supported = true,
+            .heap_permitted_count = 1,
+            .heap_permitted = {fuchsia_sysmem_HeapType_SYSTEM_RAM}};
+
+    status = fuchsia_sysmem_BufferCollectionSetConstraints(
+        collection_client.get(), true, constraints.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    zx_status_t allocation_status;
+    BufferCollectionInfo buffer_collection_info(BufferCollectionInfo::Default);
+    status = fuchsia_sysmem_BufferCollectionWaitForBuffersAllocated(
+        collection_client.get(), &allocation_status,
+        buffer_collection_info.get());
+    // usage.cpu != 0 && inaccessible_domain_supported is expected to result in failure to
+    // allocate.
+    ASSERT_NE(status, ZX_OK, "");
+
+    END_TEST;
+}
+
+// TODO(dustingreen): Add tests to cover more failure cases.
+
 // clang-format off
 BEGIN_TEST_CASE(sysmem_tests)
     RUN_TEST(test_sysmem_driver_connection)
@@ -895,5 +1040,7 @@
     RUN_TEST(test_sysmem_no_token)
     RUN_TEST(test_sysmem_multiple_participants)
     RUN_TEST(test_sysmem_constraints_retained_beyond_clean_close)
+    RUN_TEST(test_sysmem_heap_constraints)
+    RUN_TEST(test_sysmem_cpu_usage_and_inaccessible_domain_fails)
 END_TEST_CASE(sysmem_tests)
 // clang-format on