[virtio_magma] Add scenic token to images.

VirtioImages now provide eventpair handles to the wayland bridge,
so this change requires reverts of the image tiling workarounds.

Bug:71878

Change-Id: I2c404a7b339f5262ae31c53fa64422a4103bd860
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/511825
Commit-Queue: Craig Stout <cstout@google.com>
Reviewed-by: Abdulla Kamar <abdulla@google.com>
Reviewed-by: David Reveman <reveman@google.com>
API-Review: Abdulla Kamar <abdulla@google.com>
diff --git a/sdk/fidl/fuchsia.virtualization.hardware/device.fidl b/sdk/fidl/fuchsia.virtualization.hardware/device.fidl
index 05ab77e5..e36c1f0 100644
--- a/sdk/fidl/fuchsia.virtualization.hardware/device.fidl
+++ b/sdk/fidl/fuchsia.virtualization.hardware/device.fidl
@@ -171,6 +171,7 @@
 /// opaque image info needed by VirtioMagma.
 resource struct VirtioImage {
     zx.handle:VMO vmo;
+    zx.handle:EVENTPAIR? token;
     bytes:VIRTIO_WAYLAND_MAX_IMAGE_INFO_SIZE info;
 };
 
diff --git a/src/virtualization/bin/vmm/device/BUILD.gn b/src/virtualization/bin/vmm/device/BUILD.gn
index 3fd19fb..cb88ee4 100644
--- a/src/virtualization/bin/vmm/device/BUILD.gn
+++ b/src/virtualization/bin/vmm/device/BUILD.gn
@@ -182,6 +182,7 @@
     "magma_image.h",
   ]
   deps = [
+    "//sdk/fidl/fuchsia.scenic.allocation:fuchsia.scenic.allocation_llcpp",
     "//sdk/fidl/fuchsia.sysmem",
     "//src/graphics/lib/magma/include:magma_abi",
     "//src/lib/fsl",
diff --git a/src/virtualization/bin/vmm/device/magma_image.cc b/src/virtualization/bin/vmm/device/magma_image.cc
index f0bd470..70998ac1 100644
--- a/src/virtualization/bin/vmm/device/magma_image.cc
+++ b/src/virtualization/bin/vmm/device/magma_image.cc
@@ -4,6 +4,7 @@
 
 #include "magma_image.h"
 
+#include <fuchsia/scenic/allocation/llcpp/fidl.h>
 #include <fuchsia/sysmem/llcpp/fidl.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/image-format-llcpp/image-format-llcpp.h>
@@ -67,6 +68,7 @@
  public:
   vk::Result InitVulkan(uint32_t physical_device_index);
   zx_status_t InitSysmem();
+  zx_status_t InitScenic();
 
   vk::PhysicalDeviceLimits GetPhysicalDeviceLimits();
 
@@ -76,18 +78,24 @@
                               std::vector<uint64_t>& modifiers);
 
   zx_status_t GetImageInfo(uint32_t width, uint32_t height, zx::vmo* vmo_out,
-                           magma_image_info_t* image_info_out);
+                           zx::eventpair* token_out, magma_image_info_t* image_info_out);
+
+  // Scenic is used if client asks for presentable images.
+  bool use_scenic() { return scenic_allocator_.client_end().is_valid(); }
 
  private:
   vk::DispatchLoaderDynamic loader_;
   vk::UniqueInstance instance_;
   vk::PhysicalDevice physical_device_;
   vk::UniqueDevice device_;
+  fidl::WireSyncClient<fuchsia_scenic_allocation::Allocator> scenic_allocator_;
   fidl::WireSyncClient<fuchsia_sysmem::Allocator> sysmem_allocator_;
   fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> local_token_;
   fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken> vulkan_token_;
+  fidl::ClientEnd<fuchsia_sysmem::BufferCollectionToken> scenic_token_endpoint_;
   std::shared_ptr<AsyncHandler> async_handler_;
   fidl::Client<fuchsia_sysmem::BufferCollection> collection_;
+  fuchsia_scenic_allocation::wire::BufferCollectionImportToken scenic_import_token_;
 };
 
 vk::Result VulkanImageCreator::InitVulkan(uint32_t physical_device_index) {
@@ -175,13 +183,7 @@
 
 zx_status_t VulkanImageCreator::InitSysmem() {
   {
-    auto client_end_directory = service::OpenServiceRoot();
-    if (!client_end_directory.is_ok()) {
-      LOG_VERBOSE("Failed to open service root: %d", client_end_directory.status_value());
-      return client_end_directory.status_value();
-    }
-
-    auto client_end = service::ConnectAt<fuchsia_sysmem::Allocator>(*client_end_directory);
+    auto client_end = service::Connect<fuchsia_sysmem::Allocator>();
     if (!client_end.is_ok()) {
       LOG_VERBOSE("Failed to connect to sysmem allocator: %d", client_end.status_value());
       return client_end.status_value();
@@ -210,6 +212,23 @@
         fidl::WireSyncClient<fuchsia_sysmem::BufferCollectionToken>(std::move(endpoints->client));
   }
 
+  if (use_scenic()) {
+    auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>();
+    if (!endpoints.is_ok()) {
+      LOG_VERBOSE("Failed to create endpoints: %d", endpoints.status_value());
+      return endpoints.status_value();
+    }
+
+    constexpr uint32_t kNoRightsAttentuation = ~0;
+    auto result = local_token_.Duplicate(kNoRightsAttentuation, std::move(endpoints->server));
+    if (!result.ok()) {
+      LOG_VERBOSE("Failed to duplicate token: %d", result.status());
+      return result.status();
+    }
+
+    scenic_token_endpoint_ = std::move(endpoints->client);
+  }
+
   {
     auto endpoints = fidl::CreateEndpoints<fuchsia_sysmem::BufferCollectionToken>();
     if (!endpoints.is_ok()) {
@@ -229,7 +248,8 @@
   }
 
   {
-    auto result = vulkan_token_.Sync();
+    // Sync the local token that was used for Duplicating
+    auto result = local_token_.Sync();
     if (!result.ok()) {
       LOG_VERBOSE("Failed to sync token: %d", result.status());
       return result.status();
@@ -239,6 +259,19 @@
   return ZX_OK;
 }
 
+zx_status_t VulkanImageCreator::InitScenic() {
+  auto client_end = service::Connect<fuchsia_scenic_allocation::Allocator>();
+  if (!client_end.is_ok()) {
+    LOG_VERBOSE("Failed to connect to scenic allocator: %d", client_end.status_value());
+    return client_end.status_value();
+  }
+
+  scenic_allocator_ =
+      fidl::WireSyncClient<fuchsia_scenic_allocation::Allocator>(std::move(*client_end));
+
+  return ZX_OK;
+}
+
 vk::PhysicalDeviceLimits VulkanImageCreator::GetPhysicalDeviceLimits() {
   assert(physical_device_);
 
@@ -252,6 +285,29 @@
                                                 std::vector<uint64_t>& modifiers) {
   assert(device_);
 
+  if (use_scenic()) {
+    fuchsia_scenic_allocation::wire::BufferCollectionExportToken export_token;
+
+    zx_status_t status = zx::eventpair::create(0, &export_token.value, &scenic_import_token_.value);
+    if (status != ZX_OK) {
+      LOG_VERBOSE("zx::eventpair::create failed: %d", status);
+      return vk::Result::eErrorInitializationFailed;
+    }
+
+    auto result = scenic_allocator_.RegisterBufferCollection(std::move(export_token),
+                                                             std::move(scenic_token_endpoint_));
+    if (!result.ok()) {
+      LOG_VERBOSE("RegisterBufferCollection returned %d", result.status());
+      return vk::Result::eErrorInitializationFailed;
+    }
+
+    if (result->result.is_err()) {
+      LOG_VERBOSE("RegisterBufferCollection is_err()");
+      return vk::Result::eErrorInitializationFailed;
+    }
+  }
+
+  // Set vulkan constraints.
   vk::UniqueHandle<vk::BufferCollectionFUCHSIA, vk::DispatchLoaderDynamic> vk_collection;
 
   {
@@ -285,6 +341,7 @@
     }
   }
 
+  // Set local constraints.
   async_handler_ = std::make_shared<AsyncHandler>();
 
   {
@@ -342,6 +399,7 @@
 }
 
 zx_status_t VulkanImageCreator::GetImageInfo(uint32_t width, uint32_t height, zx::vmo* vmo_out,
+                                             zx::eventpair* token_out,
                                              magma_image_info_t* image_info_out) {
   auto result = collection_->WaitForBuffersAllocated_Sync();
 
@@ -354,6 +412,8 @@
     return unbind_info->status;
   }
 
+  collection_->Close();
+
   // Run the loop to ensure local unbind completes and async_handler_ is freed
   collection_.Unbind();
   async_handler_->loop().RunUntilIdle();
@@ -419,6 +479,7 @@
   }
 
   *vmo_out = std::move(collection_info.buffers[0].vmo);
+  *token_out = std::move(scenic_import_token_.value);
 
   return ZX_OK;
 }
@@ -489,7 +550,8 @@
 
 magma_status_t CreateDrmImage(uint32_t physical_device_index,
                               const magma_image_create_info_t* create_info,
-                              magma_image_info_t* image_info_out, zx::vmo* vmo_out) {
+                              magma_image_info_t* image_info_out, zx::vmo* vmo_out,
+                              zx::eventpair* token_out) {
   assert(create_info);
   assert(image_info_out);
   assert(vmo_out);
@@ -557,6 +619,14 @@
     }
   }
 
+  if (create_info->flags & MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE) {
+    zx_status_t status = image_creator.InitScenic();
+    if (status != ZX_OK) {
+      LOG_VERBOSE("Failed to initialize scenic: %d", status);
+      return MAGMA_STATUS_INTERNAL_ERROR;
+    }
+  }
+
   zx_status_t status = image_creator.InitSysmem();
   if (status != ZX_OK) {
     LOG_VERBOSE("Failed to initialize sysmem: %d", status);
@@ -585,8 +655,8 @@
     return MagmaStatus(result);
   }
 
-  status =
-      image_creator.GetImageInfo(create_info->width, create_info->height, vmo_out, image_info_out);
+  status = image_creator.GetImageInfo(create_info->width, create_info->height, vmo_out, token_out,
+                                      image_info_out);
   if (status != ZX_OK) {
     LOG_VERBOSE("GetImageInfo failed: %d", status);
     if (status == ZX_ERR_NOT_SUPPORTED)
diff --git a/src/virtualization/bin/vmm/device/magma_image.h b/src/virtualization/bin/vmm/device/magma_image.h
index 636cb6d..faefafb 100644
--- a/src/virtualization/bin/vmm/device/magma_image.h
+++ b/src/virtualization/bin/vmm/device/magma_image.h
@@ -5,6 +5,7 @@
 #ifndef SRC_VIRTUALIZATION_BIN_VMM_DEVICE_MAGMA_IMAGE_H_
 #define SRC_VIRTUALIZATION_BIN_VMM_DEVICE_MAGMA_IMAGE_H_
 
+#include <lib/zx/eventpair.h>
 #include <lib/zx/vmo.h>
 
 #include <cstdint>
@@ -14,14 +15,12 @@
 namespace magma_image {
 
 // Creates a single buffer buffer collection for the given DRM format, and optional
-// DRM format modifiers; returns the VMO and the image parameters, including the
-// negotiated format modifier.
-// TODO(fxbug.dev/71878) - if create_info flags specifies MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE,
-// the buffer collection should be registered with scenic, and a token returned to
-// the caller.
+// DRM format modifiers; returns the VMO, scenic import token, and the image parameters,
+// including the negotiated format modifier.
 magma_status_t CreateDrmImage(uint32_t physical_device_index,
                               const magma_image_create_info_t* create_info,
-                              magma_image_info_t* image_info_out, zx::vmo* vmo_out);
+                              magma_image_info_t* image_info_out, zx::vmo* vmo_out,
+                              zx::eventpair* token_out);
 
 }  // namespace magma_image
 
diff --git a/src/virtualization/bin/vmm/device/magma_image_test.cc b/src/virtualization/bin/vmm/device/magma_image_test.cc
index 65f052a..0c58513 100644
--- a/src/virtualization/bin/vmm/device/magma_image_test.cc
+++ b/src/virtualization/bin/vmm/device/magma_image_test.cc
@@ -17,6 +17,7 @@
 TEST_F(MagmaImageTesting, SpecifyLinear) {
   uint32_t physical_device_index = 0;
   zx::vmo buffer;
+  zx::eventpair token;
 
   magma_image_create_info_t create_info = {
       .drm_format = kFormat,
@@ -27,17 +28,19 @@
   };
 
   magma_image_info_t image_info = {};
-  ASSERT_EQ(MAGMA_STATUS_OK,
-            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer));
+  ASSERT_EQ(MAGMA_STATUS_OK, magma_image::CreateDrmImage(physical_device_index, &create_info,
+                                                         &image_info, &buffer, &token));
 
   EXPECT_EQ(DRM_FORMAT_MOD_LINEAR, image_info.drm_format_modifier);
   EXPECT_EQ(kWidth * 4u, image_info.plane_strides[0]);
   EXPECT_EQ(0u, image_info.plane_offsets[0]);
+  EXPECT_FALSE(token);
 }
 
 TEST_F(MagmaImageTesting, SpecifyIntelX) {
   uint32_t physical_device_index = 0;
   zx::vmo buffer;
+  zx::eventpair token;
 
   magma_image_create_info_t create_info = {
       .drm_format = kFormat,
@@ -48,17 +51,19 @@
   };
 
   magma_image_info_t image_info = {};
-  ASSERT_EQ(MAGMA_STATUS_OK,
-            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer));
+  ASSERT_EQ(MAGMA_STATUS_OK, magma_image::CreateDrmImage(physical_device_index, &create_info,
+                                                         &image_info, &buffer, &token));
 
   EXPECT_EQ(I915_FORMAT_MOD_X_TILED, image_info.drm_format_modifier);
   EXPECT_EQ(0u, image_info.plane_strides[0]);
   EXPECT_EQ(0u, image_info.plane_offsets[0]);
+  EXPECT_FALSE(token);
 }
 
 TEST_F(MagmaImageTesting, SpecifyIntelY) {
   uint32_t physical_device_index = 0;
   zx::vmo buffer;
+  zx::eventpair token;
 
   magma_image_create_info_t create_info = {
       .drm_format = kFormat,
@@ -69,17 +74,19 @@
   };
 
   magma_image_info_t image_info = {};
-  ASSERT_EQ(MAGMA_STATUS_OK,
-            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer));
+  ASSERT_EQ(MAGMA_STATUS_OK, magma_image::CreateDrmImage(physical_device_index, &create_info,
+                                                         &image_info, &buffer, &token));
 
   EXPECT_EQ(I915_FORMAT_MOD_Y_TILED, image_info.drm_format_modifier);
   EXPECT_EQ(0u, image_info.plane_strides[0]);
   EXPECT_EQ(0u, image_info.plane_offsets[0]);
+  EXPECT_FALSE(token);
 }
 
 TEST_F(MagmaImageTesting, SpecifyIntelYf) {
   uint32_t physical_device_index = 0;
   zx::vmo buffer;
+  zx::eventpair token;
 
   magma_image_create_info_t create_info = {
       .drm_format = kFormat,
@@ -91,12 +98,15 @@
 
   magma_image_info_t image_info = {};
   ASSERT_EQ(MAGMA_STATUS_INVALID_ARGS,
-            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer));
+            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer,
+                                        &token));
+  EXPECT_FALSE(token);
 }
 
 TEST_F(MagmaImageTesting, IntelMany) {
   uint32_t physical_device_index = 0;
   zx::vmo buffer;
+  zx::eventpair token;
 
   magma_image_create_info_t create_info = {
       .drm_format = kFormat,
@@ -109,18 +119,20 @@
   };
 
   magma_image_info_t image_info = {};
-  ASSERT_EQ(MAGMA_STATUS_OK,
-            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer));
+  ASSERT_EQ(MAGMA_STATUS_OK, magma_image::CreateDrmImage(physical_device_index, &create_info,
+                                                         &image_info, &buffer, &token));
 
   EXPECT_EQ(I915_FORMAT_MOD_Y_TILED, image_info.drm_format_modifier);
   EXPECT_EQ(0u, image_info.plane_strides[0]);
   EXPECT_EQ(0u, image_info.plane_offsets[0]);
+  EXPECT_FALSE(token);
 }
 
 TEST_F(MagmaImageTesting, IntelNone) {
   uint32_t physical_device_index = 0;
   magma_image_info_t image_info = {};
   zx::vmo buffer;
+  zx::eventpair token;
 
   magma_image_create_info_t create_info = {
       .drm_format = kFormat,
@@ -130,18 +142,20 @@
       .flags = 0,
   };
 
-  ASSERT_EQ(MAGMA_STATUS_OK,
-            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer));
+  ASSERT_EQ(MAGMA_STATUS_OK, magma_image::CreateDrmImage(physical_device_index, &create_info,
+                                                         &image_info, &buffer, &token));
 
   EXPECT_EQ(I915_FORMAT_MOD_Y_TILED, image_info.drm_format_modifier);
   EXPECT_EQ(0u, image_info.plane_strides[0]);
   EXPECT_EQ(0u, image_info.plane_offsets[0]);
+  EXPECT_FALSE(token);
 }
 
 TEST_F(MagmaImageTesting, IntelNonePresentable) {
   uint32_t physical_device_index = 0;
   magma_image_info_t image_info = {};
   zx::vmo buffer;
+  zx::eventpair token;
 
   magma_image_create_info_t create_info = {
       .drm_format = kFormat,
@@ -151,11 +165,12 @@
       .flags = MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE,
   };
 
-  ASSERT_EQ(MAGMA_STATUS_OK,
-            magma_image::CreateDrmImage(physical_device_index, &create_info, &image_info, &buffer));
+  ASSERT_EQ(MAGMA_STATUS_OK, magma_image::CreateDrmImage(physical_device_index, &create_info,
+                                                         &image_info, &buffer, &token));
 
   if (image_info.drm_format_modifier != I915_FORMAT_MOD_Y_TILED)
     EXPECT_EQ(image_info.drm_format_modifier, I915_FORMAT_MOD_X_TILED);
   EXPECT_EQ(0u, image_info.plane_strides[0]);
   EXPECT_EQ(0u, image_info.plane_offsets[0]);
+  EXPECT_TRUE(token);
 }
diff --git a/src/virtualization/bin/vmm/device/virtio_magma.cc b/src/virtualization/bin/vmm/device/virtio_magma.cc
index b9d74ee..1aca734 100644
--- a/src/virtualization/bin/vmm/device/virtio_magma.cc
+++ b/src/virtualization/bin/vmm/device/virtio_magma.cc
@@ -32,6 +32,31 @@
 #define LOG_VERBOSE(msg, ...)
 #endif
 
+static zx_status_t InitFromImageInfo(zx::vmo vmo, ImageInfoWithToken& info,
+                                     fuchsia::virtualization::hardware::VirtioImage* image) {
+  if (info.token) {
+    zx_status_t status = info.token.duplicate(ZX_RIGHT_SAME_RIGHTS, &image->token);
+    if (status != ZX_OK)
+      return status;
+  }
+
+  image->vmo = std::move(vmo);
+
+  image->info.resize(sizeof(magma_image_info_t));
+  memcpy(image->info.data(), &info.info, sizeof(magma_image_info_t));
+
+  return ZX_OK;
+}
+
+static void InitFromVirtioImage(fuchsia::virtualization::hardware::VirtioImage& image, zx::vmo* vmo,
+                                ImageInfoWithToken* info) {
+  *vmo = std::move(image.vmo);
+  info->token = std::move(image.token);
+
+  assert(image.info.size() >= sizeof(magma_image_info_t));
+  memcpy(&info->info, image.info.data(), sizeof(magma_image_info_t));
+}
+
 VirtioMagma::VirtioMagma(sys::ComponentContext* context) : DeviceBase(context) {}
 
 void VirtioMagma::Start(
@@ -259,22 +284,26 @@
       return ZX_OK;
     }
 
-    magma_image_info_t& info = iter->second;
+    ImageInfoWithToken& info = iter->second;
 
-    image.info.resize(sizeof(magma_image_info_t));
+    // Get the VMO handle for this buffer.
+    zx_status_t status = VirtioMagmaGeneric::Handle_export(request, response);
+    if (status != ZX_OK) {
+      LOG_VERBOSE("VirtioMagmaGeneric::Handle_export failed: %d", status);
+      return status;
+    }
 
-    memcpy(image.info.data(), &info, sizeof(info));
+    // Take ownership of the VMO handle.
+    zx::vmo vmo(static_cast<zx_handle_t>(response->buffer_handle_out));
+    response->buffer_handle_out = 0;
+
+    status = InitFromImageInfo(std::move(vmo), info, &image);
+    if (status != ZX_OK) {
+      LOG_VERBOSE("InitFromImageInfo failed: %d", status);
+      return status;
+    }
   }
 
-  // Get the VMO handle for this buffer.
-  zx_status_t status = VirtioMagmaGeneric::Handle_export(request, response);
-  if (status != ZX_OK)
-    return status;
-
-  // Take ownership of the VMO handle.
-  image.vmo = zx::vmo(static_cast<zx_handle_t>(response->buffer_handle_out));
-  response->buffer_handle_out = 0;
-
   // TODO(fxbug.dev/13261): improvement backlog
   // Perform a blocking import of the image, then return the VFD ID in the response.
   // Note that since the virtio-magma device is fully synchronous anyway, this does
@@ -282,9 +311,11 @@
   // return it only when the Import call returns, processing messages from other
   // instances, or even other connections, in the meantime.
   uint32_t vfd_id;
-  status = wayland_importer_->ImportImage(std::move(image), &vfd_id);
-  if (status != ZX_OK)
+  zx_status_t status = wayland_importer_->ImportImage(std::move(image), &vfd_id);
+  if (status != ZX_OK) {
+    LOG_VERBOSE("ImportImage failed: %d", status);
     return status;
+  }
 
   response->buffer_handle_out = vfd_id;
 
@@ -317,9 +348,14 @@
     return result;
   }
 
+  ImageInfoWithToken info;
+  zx::vmo vmo;
+
+  InitFromVirtioImage(*image.get(), &vmo, &info);
+
   {
     virtio_magma_import_ctrl_t request_copy = *request;
-    request_copy.buffer_handle = image->vmo.release();
+    request_copy.buffer_handle = vmo.release();
 
     status = VirtioMagmaGeneric::Handle_import(&request_copy, response);
     if (status != ZX_OK)
@@ -330,13 +366,8 @@
     auto& image_map =
         connection_image_map_[reinterpret_cast<magma_connection_t>(request->connection)];
 
-    magma_image_info_t info;
-    assert(image->info.size() >= sizeof(info));
-
-    memcpy(&info, image->info.data(), sizeof(info));
-
     magma_buffer_t image = response->buffer_out;
-    image_map[image] = info;
+    image_map[image] = std::move(info);
   }
 
   return ZX_OK;
@@ -369,14 +400,14 @@
   memcpy(&image_create_info, reinterpret_cast<const magma_image_create_info_t*>(request + 1),
          sizeof(image_create_info));
 
-  magma_image_info_t image_info;
+  ImageInfoWithToken image_info;
   zx::vmo vmo;
 
   response->hdr.type = VIRTIO_MAGMA_RESP_VIRT_CREATE_IMAGE;
   // Assuming the current connection is on the one and only physical device.
   uint32_t physical_device_index = 0;
-  response->result_return =
-      magma_image::CreateDrmImage(physical_device_index, &image_create_info, &image_info, &vmo);
+  response->result_return = magma_image::CreateDrmImage(physical_device_index, &image_create_info,
+                                                        &image_info.info, &vmo, &image_info.token);
 
   if (response->result_return != MAGMA_STATUS_OK)
     return ZX_OK;
@@ -396,7 +427,7 @@
 
   auto& map = connection_image_map_[connection];
 
-  map[response->image_out] = image_info;
+  map[response->image_out] = std::move(image_info);
 
   return ZX_OK;
 }
@@ -416,9 +447,11 @@
     return ZX_OK;
   }
 
+  ImageInfoWithToken& image_info = iter->second;
+
   auto image_info_out = reinterpret_cast<magma_image_info_t*>(
       const_cast<virtio_magma_virt_get_image_info_ctrl_t*>(request) + 1);
-  *image_info_out = iter->second;
+  *image_info_out = image_info.info;
 
   response->result_return = MAGMA_STATUS_OK;
   return ZX_OK;
diff --git a/src/virtualization/bin/vmm/device/virtio_magma.h b/src/virtualization/bin/vmm/device/virtio_magma.h
index 245952b..cfa8832 100644
--- a/src/virtualization/bin/vmm/device/virtio_magma.h
+++ b/src/virtualization/bin/vmm/device/virtio_magma.h
@@ -21,6 +21,11 @@
 #include "src/virtualization/bin/vmm/device/virtio_magma_generic.h"
 #include "src/virtualization/bin/vmm/device/virtio_queue.h"
 
+struct ImageInfoWithToken {
+  magma_image_info_t info;
+  zx::eventpair token;
+};
+
 class VirtioMagma : public VirtioMagmaGeneric,
                     public DeviceBase<VirtioMagma>,
                     public fuchsia::virtualization::hardware::VirtioMagma {
@@ -75,7 +80,7 @@
   std::unordered_multimap<zx_koid_t, std::pair<zx_vaddr_t, size_t>> buffer_maps_;
 
   // Each connection maps images to info, populated either when image is created or imported
-  using ImageMap = std::unordered_map<magma_buffer_t, magma_image_info_t>;
+  using ImageMap = std::unordered_map<magma_buffer_t, ImageInfoWithToken>;
   std::unordered_map<magma_connection_t, ImageMap> connection_image_map_;
 
   FXL_DISALLOW_COPY_AND_ASSIGN(VirtioMagma);
diff --git a/src/virtualization/bin/vmm/device/virtio_magma_test.cc b/src/virtualization/bin/vmm/device/virtio_magma_test.cc
index 1705c1e..6304357 100644
--- a/src/virtualization/bin/vmm/device/virtio_magma_test.cc
+++ b/src/virtualization/bin/vmm/device/virtio_magma_test.cc
@@ -5,8 +5,10 @@
 #include "src/graphics/lib/magma/include/virtio/virtio_magma.h"
 
 #include <drm_fourcc.h>
+#include <fuchsia/scenic/allocation/cpp/fidl.h>
 #include <fuchsia/virtualization/cpp/fidl.h>
 #include <fuchsia/virtualization/hardware/cpp/fidl.h>
+#include <lib/sys/cpp/component_context.h>
 #include <lib/zx/socket.h>
 #include <string.h>
 
@@ -57,12 +59,70 @@
   std::unique_ptr<VirtioImage> image_;
 };
 
+class ScenicAllocatorFake : public fuchsia::scenic::allocation::Allocator {
+ public:
+  // Must set constraints on the given buffer collection token to allow the constraints
+  // negotiation to complete.
+  void RegisterBufferCollection(
+      fuchsia::scenic::allocation::BufferCollectionExportToken export_token,
+      fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> scenic_token,
+      RegisterBufferCollectionCallback callback) override {
+    fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator;
+    auto context = sys::ComponentContext::Create();
+    context->svc()->Connect(sysmem_allocator.NewRequest());
+
+    fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
+    zx_status_t status = sysmem_allocator->BindSharedCollection(std::move(scenic_token),
+                                                                buffer_collection.NewRequest());
+    if (status != ZX_OK) {
+      FX_LOGS(ERROR) << "BindSharedCollection failed: " << status;
+      callback(
+          fit::error(fuchsia::scenic::allocation::RegisterBufferCollectionError::BAD_OPERATION));
+      return;
+    }
+
+    fuchsia::sysmem::BufferCollectionConstraints constraints;
+    constraints.min_buffer_count = 1;
+    constraints.usage.cpu =
+        fuchsia::sysmem::cpuUsageReadOften | fuchsia::sysmem::cpuUsageWriteOften;
+    constraints.has_buffer_memory_constraints = true;
+    constraints.image_format_constraints_count = 1;
+    fuchsia::sysmem::ImageFormatConstraints& image_constraints =
+        constraints.image_format_constraints[0];
+    image_constraints = fuchsia::sysmem::ImageFormatConstraints();
+    image_constraints.min_coded_width = 0;
+    image_constraints.min_coded_height = 0;
+    image_constraints.max_coded_width = 0;
+    image_constraints.max_coded_height = 0;
+    image_constraints.min_bytes_per_row = 0;
+    image_constraints.color_spaces_count = 1;
+    image_constraints.color_space[0].type = fuchsia::sysmem::ColorSpaceType::SRGB;
+    image_constraints.pixel_format.type = fuchsia::sysmem::PixelFormatType::BGRA32;
+    image_constraints.pixel_format.has_format_modifier = false;
+
+    status = buffer_collection->SetConstraints(true, constraints);
+    if (status != ZX_OK) {
+      FX_LOGS(ERROR) << "SetConstraints failed: " << status;
+      callback(
+          fit::error(fuchsia::scenic::allocation::RegisterBufferCollectionError::BAD_OPERATION));
+      return;
+    }
+
+    buffer_collection->Close();
+
+    callback(fit::ok());
+  }
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
 class VirtioMagmaTest : public TestWithDevice {
  public:
   VirtioMagmaTest()
       : out_queue_(phys_mem_, kDescriptorSize, kQueueSize),
         wayland_importer_mock_binding_(&wayland_importer_mock_),
-        wayland_importer_mock_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
+        wayland_importer_mock_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
+        scenic_allocator_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
 
   void SetUp() override {
     uintptr_t vmar_addr;
@@ -76,12 +136,18 @@
       std::unique_ptr<sys::testing::EnvironmentServices> env_services = CreateServices();
       env_services->AllowParentService("fuchsia.vulkan.loader.Loader");
       env_services->AllowParentService("fuchsia.sysmem.Allocator");
+
+      ASSERT_EQ(ZX_OK, env_services->AddService(scenic_allocator_binding_set_.GetHandler(
+                           &scenic_allocator_fake_, scenic_allocator_loop_.dispatcher())));
+
       ASSERT_EQ(ZX_OK, LaunchDevice(kVirtioMagmaUrl, out_queue_.end(), &start_info,
                                     std::move(env_services)));
     }
 
     // Start device execution.
     ASSERT_EQ(wayland_importer_mock_loop_.StartThread(), ZX_OK);
+    ASSERT_EQ(scenic_allocator_loop_.StartThread(), ZX_OK);
+
     services_->Connect(magma_.NewRequest());
 
     {
@@ -263,6 +329,8 @@
         .drm_format_modifiers = {DRM_FORMAT_MOD_INVALID},
         .width = 1920,
         .height = 1080,
+        // Presentable causes VirtioMagma to register buffer collection with scenic
+        .flags = MAGMA_IMAGE_CREATE_FLAGS_PRESENTABLE,
     };
 
     std::vector<uint8_t> request_buffer(sizeof(request) + sizeof(create_image));
@@ -299,6 +367,9 @@
   fidl::Binding<fuchsia::virtualization::hardware::VirtioWaylandImporter>
       wayland_importer_mock_binding_;
   async::Loop wayland_importer_mock_loop_;
+  ScenicAllocatorFake scenic_allocator_fake_;
+  async::Loop scenic_allocator_loop_;
+  fidl::BindingSet<fuchsia::scenic::allocation::Allocator> scenic_allocator_binding_set_;
 };
 
 TEST_F(VirtioMagmaTest, HandleQuery) {
diff --git a/src/virtualization/bin/vmm/device/virtio_wl.cc b/src/virtualization/bin/vmm/device/virtio_wl.cc
index 0ad8ae7..b4489b1 100644
--- a/src/virtualization/bin/vmm/device/virtio_wl.cc
+++ b/src/virtualization/bin/vmm/device/virtio_wl.cc
@@ -106,7 +106,8 @@
                                     nullptr);
   }
 
-  // Create a memory instance with a VirtioImage.
+  // Create a memory instance with a VirtioImage.  The image VMO was moved out and passed as
+  // the first parameter here.  The image may or may not have an eventpair token.
   static std::unique_ptr<Memory> CreateWithImage(zx::vmo vmo,
                                                  std::unique_ptr<VirtioWl::VirtioImage> image,
                                                  zx::vmar* vmar, uint32_t map_flags) {
@@ -119,6 +120,13 @@
       FX_LOGS(ERROR) << "Failed to map VMO into guest VMAR: " << status;
       return nullptr;
     }
+    if (image->token) {
+      // If we have an eventpair token, use it as the VFD primary handle.
+      // Don't drop the VMO, move it back into the image.
+      image->vmo = std::move(vmo);
+      return std::make_unique<Memory>(zx::handle(image->token.release()), addr, size, vmar,
+                                      std::move(image));
+    }
     return std::make_unique<Memory>(zx::handle(vmo.release()), addr, size, vmar, std::move(image));
   }
 
@@ -130,7 +138,7 @@
   uintptr_t addr() const { return addr_; }
   uint64_t size() const { return size_; }
 
-  VirtioWl::VirtioImage* GetImage() override { return image_.get(); }
+  std::unique_ptr<VirtioWl::VirtioImage> DuplicateImage() override;
 
  private:
   zx::handle handle_;
@@ -140,6 +148,37 @@
   std::unique_ptr<VirtioWl::VirtioImage> image_;
 };
 
+std::unique_ptr<VirtioWl::VirtioImage> Memory::DuplicateImage() {
+  if (!image_)
+    return nullptr;
+
+  zx::handle handle;
+  zx_status_t status = Duplicate(&handle);
+  if (status != ZX_OK) {
+    FX_LOGS(ERROR) << "VFD duplicate failed: " << status;
+    return nullptr;
+  }
+
+  auto image = std::make_unique<VirtioWl::VirtioImage>();
+
+  // If image VMO is valid, an eventpair token was moved into the primary handle.
+  if (image_->vmo) {
+    image->token = zx::eventpair(handle.release());
+
+    status = image_->vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &image->vmo);
+    if (status != ZX_OK) {
+      FX_LOGS(ERROR) << "VMO duplicate failed: " << status;
+      return nullptr;
+    }
+  } else {
+    // VMO was moved into the primary handle, there is no eventpair token.
+    image->vmo = zx::vmo(handle.release());
+  }
+
+  image->info = image_->info;
+  return image;
+}
+
 // Vfd type that holds a wayland dispatcher connection.
 class Connection : public VirtioWl::Vfd {
  public:
@@ -287,6 +326,7 @@
   TRACE_DURATION("machina", "VirtioWl::ImportImage");
   auto deferred = fit::defer(
       [&]() { callback(fuchsia::virtualization::hardware::VIRTIO_WAYLAND_INVALID_VFD_ID); });
+
   zx_info_handle_basic_t handle_basic_info{};
   zx_status_t status = image.vmo.get_info(ZX_INFO_HANDLE_BASIC, &handle_basic_info,
                                           sizeof(handle_basic_info), nullptr, nullptr);
@@ -294,17 +334,21 @@
     FX_LOGS(ERROR) << "Image VMO failed handle type check";
     return;
   }
+  if (image.token) {
+    zx_info_handle_basic_t token_info{};
+    status = image.token.get_info(ZX_INFO_HANDLE_BASIC, &token_info, sizeof(token_info), nullptr,
+                                  nullptr);
+    if (status != ZX_OK || token_info.type != ZX_OBJ_TYPE_EVENTPAIR) {
+      FX_LOGS(ERROR) << "Image eventpair failed handle type check";
+      return;
+    }
+  }
 
   uint32_t vfd_id = next_vfd_id_++;
   PendingVfd pending_vfd{};
 
-  status =
-      zx_handle_duplicate(image.vmo.get(), ZX_RIGHT_SAME_RIGHTS, &pending_vfd.handle_info.handle);
-  if (status != ZX_OK) {
-    FX_LOGS(ERROR) << "Failed to duplicate image VMO";
-    return;
-  }
-
+  // Move the VMO into the primary "pending VFD" handle.
+  pending_vfd.handle_info.handle = image.vmo.release();
   pending_vfd.handle_info.type = handle_basic_info.type;
   pending_vfd.handle_info.rights = handle_basic_info.rights;
   pending_vfd.vfd_id = vfd_id;
@@ -328,17 +372,12 @@
 
   std::unique_ptr<Vfd>& vfd = iter->second;
 
-  auto image = std::make_unique<VirtioWl::VirtioImage>();
-
-  zx_status_t status = vfd->GetImage()->vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &image->vmo);
-  if (status != ZX_OK) {
-    FX_LOGS(ERROR) << "Duplicate failed";
+  std::unique_ptr<VirtioWl::VirtioImage> image = vfd->DuplicateImage();
+  if (!image) {
     callback(ZX_ERR_INTERNAL, {});
     return;
   }
 
-  image->info = vfd->GetImage()->info;
-
   callback(ZX_OK, std::move(image));
 }
 
diff --git a/src/virtualization/bin/vmm/device/virtio_wl.h b/src/virtualization/bin/vmm/device/virtio_wl.h
index 4989db4..fd39cbb 100644
--- a/src/virtualization/bin/vmm/device/virtio_wl.h
+++ b/src/virtualization/bin/vmm/device/virtio_wl.h
@@ -80,8 +80,8 @@
     // Returns ZX_ERR_NOT_SUPPORTED if duplication is not supported.
     virtual zx_status_t Duplicate(zx::handle* handle) { return ZX_ERR_NOT_SUPPORTED; }
 
-    // Returns the VirtioImage associated with this VFD.
-    virtual VirtioWl::VirtioImage* GetImage() { return nullptr; }
+    // Creates a duplicate VirtioImage.
+    virtual std::unique_ptr<VirtioWl::VirtioImage> DuplicateImage() { return nullptr; }
   };
 
   explicit VirtioWl(sys::ComponentContext* context);
diff --git a/src/virtualization/bin/vmm/device/virtio_wl_unittest.cc b/src/virtualization/bin/vmm/device/virtio_wl_unittest.cc
index 8a1504c..4f5a5d0d 100644
--- a/src/virtualization/bin/vmm/device/virtio_wl_unittest.cc
+++ b/src/virtualization/bin/vmm/device/virtio_wl_unittest.cc
@@ -735,4 +735,48 @@
   }
 }
 
+TEST_F(VirtioWlTest, ImportExportImageWithToken) {
+  ASSERT_EQ(CreateConnection(1u), ZX_OK);
+  RunLoopUntilIdle();
+  ASSERT_EQ(channels_.size(), 1u);
+  fuchsia::virtualization::hardware::VirtioWaylandImporterSyncPtr importer;
+  ASSERT_EQ(wl_->GetImporter(importer.NewRequest()), ZX_OK);
+
+  virtio_wl_ctrl_vfd_new_t* new_vfd_cmd;
+  uint16_t descriptor_id;
+  ASSERT_EQ(DescriptorChainBuilder(in_queue_)
+                .AppendWritableDescriptor(&new_vfd_cmd, sizeof(virtio_wl_ctrl_vfd_new_t))
+                .Build(&descriptor_id),
+            ZX_OK);
+
+  uint32_t vfd_id = 0;
+  uint64_t koid = 0;
+  {
+    fuchsia::virtualization::hardware::VirtioImage image;
+    ASSERT_EQ(zx::vmo::create(kImportVmoSize, 0u, &image.vmo), ZX_OK);
+    zx_info_handle_basic_t info;
+    ASSERT_EQ(image.vmo.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr),
+              ZX_OK);
+    koid = info.koid;
+    zx::eventpair ep;
+    ASSERT_EQ(zx::eventpair::create(0, &ep, &image.token), ZX_OK);
+    ASSERT_EQ(importer->ImportImage(std::move(image), &vfd_id), ZX_OK);
+    ASSERT_NE(vfd_id, 0u);
+  }
+  {
+    std::unique_ptr<fuchsia::virtualization::hardware::VirtioImage> image;
+    zx_status_t result;
+    ASSERT_EQ(importer->ExportImage(vfd_id, &result, &image), ZX_OK);
+    ASSERT_TRUE(image);
+    EXPECT_EQ(result, ZX_OK);
+    zx_info_handle_basic_t info;
+    ASSERT_EQ(image->vmo.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr),
+              ZX_OK);
+    EXPECT_EQ(koid, info.koid);
+    ASSERT_EQ(image->token.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr),
+              ZX_OK);
+    EXPECT_EQ(info.type, ZX_OBJ_TYPE_EVENTPAIR);
+  }
+}
+
 }  // namespace
diff --git a/src/virtualization/bin/vmm/meta/device_tests.cmx b/src/virtualization/bin/vmm/meta/device_tests.cmx
index e42c305..ad8a62e 100644
--- a/src/virtualization/bin/vmm/meta/device_tests.cmx
+++ b/src/virtualization/bin/vmm/meta/device_tests.cmx
@@ -1,4 +1,11 @@
 {
+    "facets": {
+        "fuchsia.test": {
+            "system-services": [
+                "fuchsia.scenic.allocation.Allocator"
+            ]
+        }
+    },
     "include": [
         "sdk/lib/diagnostics/syslog/client.shard.cmx",
         "src/lib/vulkan/test-application.shard.cmx"
@@ -11,6 +18,7 @@
             "isolated-temp"
         ],
         "services": [
+            "fuchsia.scenic.allocation.Allocator",
             "fuchsia.sys.Environment",
             "fuchsia.sys.Loader"
         ]
diff --git a/src/virtualization/bin/vmm/meta/virtio_magma.cmx b/src/virtualization/bin/vmm/meta/virtio_magma.cmx
index 8749f72..5d5d8f9 100644
--- a/src/virtualization/bin/vmm/meta/virtio_magma.cmx
+++ b/src/virtualization/bin/vmm/meta/virtio_magma.cmx
@@ -11,6 +11,7 @@
             "vulkan"
         ],
         "services": [
+            "fuchsia.scenic.allocation.Allocator",
             "fuchsia.tracing.provider.Registry"
         ]
     }