[vim3][paver] Add vim3 specific device partitioner

Bug: 177613693
Change-Id: I2c3c087e2f2b0453042f772367dcb7f555b6507e
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/475805
Commit-Queue: Yecheng Zhao <zyecheng@google.com>
Reviewed-by: David Pursell <dpursell@google.com>
Reviewed-by: Suraj Malhotra <surajmalhotra@google.com>
diff --git a/src/devices/board/drivers/vim3/vim3-emmc.cc b/src/devices/board/drivers/vim3/vim3-emmc.cc
index cf44f1f..ecceaab 100644
--- a/src/devices/board/drivers/vim3/vim3-emmc.cc
+++ b/src/devices/board/drivers/vim3/vim3-emmc.cc
@@ -47,23 +47,13 @@
     .version_3 = true,
     .prefs = SDMMC_HOST_PREFS_DISABLE_HS400,
 };
-static const guid_map_t guid_map[] = {
-    {"zircon-a", GUID_ZIRCON_A_VALUE},
-    {"zircon-b", GUID_ZIRCON_B_VALUE},
-    {"zircon-r", GUID_ZIRCON_R_VALUE},
-    {"fvm", GUID_FVM_VALUE},
-};
+
 static const pbus_metadata_t emmc_metadata[] = {
     {
         .type = DEVICE_METADATA_PRIVATE,
         .data_buffer = &config,
         .data_size = sizeof(config),
     },
-    {
-        .type = DEVICE_METADATA_GUID_MAP,
-        .data_buffer = guid_map,
-        .data_size = sizeof(guid_map),
-    },
 };
 
 static const pbus_boot_metadata_t emmc_boot_metadata[] = {
diff --git a/src/storage/lib/paver/BUILD.gn b/src/storage/lib/paver/BUILD.gn
index 47bed58..ad47b1c 100644
--- a/src/storage/lib/paver/BUILD.gn
+++ b/src/storage/lib/paver/BUILD.gn
@@ -167,6 +167,18 @@
   ]
 }
 
+source_set("vim3") {
+  sources = [ "vim3.cc" ]
+  deps = [
+    ":gpt",
+    ":paver-core",
+    ":sysconfig",
+    "//src/devices/lib/amlogic",
+    "//src/lib/uuid",
+    "//zircon/system/ulib/gpt",
+  ]
+}
+
 source_set("x64") {
   sources = [ "x64.cc" ]
   deps = [
@@ -190,6 +202,7 @@
     ":nelson",
     ":paver-core",
     ":sherlock",
+    ":vim3",
     ":x64",
     "//sdk/fidl/fuchsia.paver:fuchsia.paver_llcpp",
     "//zircon/system/ulib/fidl-async:fidl-async-cpp",
diff --git a/src/storage/lib/paver/provider.cc b/src/storage/lib/paver/provider.cc
index 94d2278..3065781 100644
--- a/src/storage/lib/paver/provider.cc
+++ b/src/storage/lib/paver/provider.cc
@@ -17,6 +17,7 @@
 #include "src/storage/lib/paver/nelson.h"
 #include "src/storage/lib/paver/paver.h"
 #include "src/storage/lib/paver/sherlock.h"
+#include "src/storage/lib/paver/vim3.h"
 #include "src/storage/lib/paver/x64.h"
 
 namespace {
@@ -41,6 +42,7 @@
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::NelsonPartitionerFactory>());
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::SherlockPartitionerFactory>());
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::LuisPartitionerFactory>());
+  paver::DevicePartitionerFactory::Register(std::make_unique<paver::Vim3PartitionerFactory>());
   paver::DevicePartitionerFactory::Register(
       std::make_unique<paver::ChromebookX64PartitionerFactory>());
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::X64PartitionerFactory>());
@@ -49,6 +51,7 @@
   abr::ClientFactory::Register(std::make_unique<paver::NelsonAbrClientFactory>());
   abr::ClientFactory::Register(std::make_unique<paver::SherlockAbrClientFactory>());
   abr::ClientFactory::Register(std::make_unique<paver::LuisAbrClientFactory>());
+  abr::ClientFactory::Register(std::make_unique<paver::Vim3AbrClientFactory>());
   abr::ClientFactory::Register(std::make_unique<paver::X64AbrClientFactory>());
   return ZX_OK;
 }
diff --git a/src/storage/lib/paver/test/device-partitioner-test.cc b/src/storage/lib/paver/test/device-partitioner-test.cc
index 72ac5f6..e2c3206 100644
--- a/src/storage/lib/paver/test/device-partitioner-test.cc
+++ b/src/storage/lib/paver/test/device-partitioner-test.cc
@@ -44,6 +44,7 @@
 #include "src/storage/lib/paver/sherlock.h"
 #include "src/storage/lib/paver/test/test-utils.h"
 #include "src/storage/lib/paver/utils.h"
+#include "src/storage/lib/paver/vim3.h"
 #include "src/storage/lib/paver/x64.h"
 
 namespace paver {
@@ -1419,8 +1420,6 @@
   EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kFuchsiaVolumeManager)));
 }
 
-
-
 TEST_F(SherlockPartitionerTests, FindBootloader) {
   std::unique_ptr<BlockDevice> gpt_dev;
   ASSERT_NO_FATAL_FAILURES(CreateDisk(&gpt_dev));
@@ -1990,6 +1989,144 @@
   ASSERT_NO_FATAL_FAILURES(ValidateBootloaderRead(read_buf.data(), kBL2ImageValue, kTplImageValue));
 }
 
+class Vim3PartitionerTests : public GptDevicePartitionerTests {
+ protected:
+  static constexpr size_t kVim3BlockSize = 512;
+  Vim3PartitionerTests() : GptDevicePartitionerTests("vim3", kVim3BlockSize) {}
+
+  // Create a DevicePartition for a device.
+  zx::status<std::unique_ptr<paver::DevicePartitioner>> CreatePartitioner(
+      const fbl::unique_fd& device) {
+    zx::channel svc_root = GetSvcRoot();
+    return paver::Vim3Partitioner::Initialize(devmgr_.devfs_root().duplicate(), std::move(svc_root),
+                                              device);
+  }
+};
+
+TEST_F(Vim3PartitionerTests, InitializeWithoutGptFails) {
+  std::unique_ptr<BlockDevice> gpt_dev;
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(&gpt_dev));
+
+  ASSERT_NOT_OK(CreatePartitioner(kDummyDevice));
+}
+
+TEST_F(Vim3PartitionerTests, InitializeWithoutFvmSucceeds) {
+  std::unique_ptr<BlockDevice> gpt_dev;
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(32 * kGibibyte, &gpt_dev));
+
+  {
+    // Pause the block watcher while we write partitions to the disk.
+    // This is to avoid the block watcher seeing an intermediate state of the partition table
+    // and incorrectly treating it as an MBR.
+    // The watcher is automatically resumed when this goes out of scope.
+    auto pauser = paver::BlockWatcherPauser::Create(GetSvcRoot());
+    ASSERT_OK(pauser);
+
+    // Set up a valid GPT.
+    std::unique_ptr<gpt::GptDevice> gpt;
+    ASSERT_NO_FATAL_FAILURES(CreateGptDevice(gpt_dev.get(), &gpt));
+
+    ASSERT_OK(CreatePartitioner(kDummyDevice));
+  }
+}
+
+TEST_F(Vim3PartitionerTests, AddPartitionNotSupported) {
+  std::unique_ptr<BlockDevice> gpt_dev;
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(64 * kMebibyte, &gpt_dev));
+  fbl::unique_fd gpt_fd(dup(gpt_dev->fd()));
+
+  auto status = CreatePartitioner(std::move(gpt_fd));
+  ASSERT_OK(status);
+
+  ASSERT_STATUS(status->AddPartition(PartitionSpec(paver::Partition::kZirconB)),
+                ZX_ERR_NOT_SUPPORTED);
+}
+
+TEST_F(Vim3PartitionerTests, FindPartition) {
+  std::unique_ptr<BlockDevice> gpt_dev;
+  constexpr uint64_t kBlockCount = 0x800000;
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(kBlockCount * block_size_, &gpt_dev));
+
+  // The initial gpt partitions are randomly chosen and does not necessarily reflect the
+  // actual gpt partition layout in product.
+  std::vector<PartitionDescription> vim3_partitions = {
+      {GPT_DURABLE_BOOT_NAME, kDummyType, 0, 0x10000}, {GPT_VBMETA_A_NAME, kDummyType, 0, 0x10000},
+      {GPT_VBMETA_B_NAME, kDummyType, 0, 0x10000},     {GPT_VBMETA_R_NAME, kDummyType, 0, 0x10000},
+      {GPT_ZIRCON_A_NAME, kDummyType, 0, 0x10000},     {GPT_ZIRCON_B_NAME, kDummyType, 0, 0x10000},
+      {GPT_ZIRCON_R_NAME, kDummyType, 0, 0x10000},     {GPT_DURABLE_NAME, kDummyType, 0, 0x10000},
+      {GPT_FVM_NAME, kDummyType, 0, 0x10000},
+  };
+  vim3_partitions[0].start = 0x10400;
+  for (size_t i = 1; i < vim3_partitions.size(); i++) {
+    vim3_partitions[i].start = vim3_partitions[i - 1].start + vim3_partitions[i - 1].length;
+  }
+  ASSERT_NO_FATAL_FAILURES(InitializeStartingGPTPartitions(gpt_dev.get(), vim3_partitions));
+
+  fbl::unique_fd gpt_fd(dup(gpt_dev->fd()));
+  auto status = CreatePartitioner(std::move(gpt_fd));
+  ASSERT_OK(status);
+  std::unique_ptr<paver::DevicePartitioner>& partitioner = status.value();
+
+  EXPECT_NOT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kBootloaderA)));
+
+  std::unique_ptr<BlockDevice> boot0_dev, boot1_dev;
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(kBlockCount * kBlockSize, kBoot0Type, &boot0_dev));
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(kBlockCount * kBlockSize, kBoot1Type, &boot1_dev));
+
+  // Make sure we can find the important partitions.
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kBootloaderA)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kZirconA)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kZirconB)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kZirconR)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kVbMetaA)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kVbMetaB)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kVbMetaR)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kAbrMeta)));
+  EXPECT_OK(partitioner->FindPartition(PartitionSpec(paver::Partition::kFuchsiaVolumeManager)));
+}
+
+TEST_F(Vim3PartitionerTests, CreateAbrClient) {
+  std::unique_ptr<BlockDevice> gpt_dev;
+  constexpr uint64_t kBlockCount = 0x748034;
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(kBlockCount * block_size_, &gpt_dev));
+
+  const std::vector<PartitionDescription> kStartingPartitions = {
+      {GPT_DURABLE_BOOT_NAME, kDummyType, 0x10400, 0x10000},
+  };
+  ASSERT_NO_FATAL_FAILURES(InitializeStartingGPTPartitions(gpt_dev.get(), kStartingPartitions));
+  zx::channel svc_root = GetSvcRoot();
+  std::shared_ptr<paver::Context> context;
+  EXPECT_OK(paver::Vim3AbrClientFactory().New(devmgr_.devfs_root().duplicate(), std::move(svc_root),
+                                              context));
+}
+
+TEST_F(Vim3PartitionerTests, SupportsPartition) {
+  std::unique_ptr<BlockDevice> gpt_dev;
+  ASSERT_NO_FATAL_FAILURES(CreateDisk(64 * kMebibyte, &gpt_dev));
+  fbl::unique_fd gpt_fd(dup(gpt_dev->fd()));
+
+  auto status = CreatePartitioner(std::move(gpt_fd));
+  ASSERT_OK(status);
+  std::unique_ptr<paver::DevicePartitioner>& partitioner = status.value();
+
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kBootloaderA)));
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kZirconA)));
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kZirconB)));
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kZirconR)));
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kVbMetaA)));
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kVbMetaB)));
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kVbMetaR)));
+  EXPECT_TRUE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kAbrMeta)));
+  EXPECT_TRUE(
+      partitioner->SupportsPartition(PartitionSpec(paver::Partition::kFuchsiaVolumeManager)));
+  // Unsupported partition type.
+  EXPECT_FALSE(partitioner->SupportsPartition(PartitionSpec(paver::Partition::kUnknown)));
+
+  // Unsupported content type.
+  EXPECT_FALSE(
+      partitioner->SupportsPartition(PartitionSpec(paver::Partition::kAbrMeta, "foo_type")));
+}
+
 TEST(AstroPartitionerTests, IsFvmWithinFtl) {
   std::unique_ptr<SkipBlockDevice> device;
   ASSERT_NO_FATAL_FAILURES(SkipBlockDevice::Create(kNandInfo, &device));
diff --git a/src/storage/lib/paver/test/main.cc b/src/storage/lib/paver/test/main.cc
index cb78e09..c0b9201 100644
--- a/src/storage/lib/paver/test/main.cc
+++ b/src/storage/lib/paver/test/main.cc
@@ -12,6 +12,7 @@
 #include "src/storage/lib/paver/luis.h"
 #include "src/storage/lib/paver/nelson.h"
 #include "src/storage/lib/paver/sherlock.h"
+#include "src/storage/lib/paver/vim3.h"
 #include "src/storage/lib/paver/x64.h"
 
 int main(int argc, char** argv) {
@@ -20,6 +21,7 @@
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::NelsonPartitionerFactory>());
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::SherlockPartitionerFactory>());
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::LuisPartitionerFactory>());
+  paver::DevicePartitionerFactory::Register(std::make_unique<paver::Vim3PartitionerFactory>());
   paver::DevicePartitionerFactory::Register(
       std::make_unique<paver::ChromebookX64PartitionerFactory>());
   paver::DevicePartitionerFactory::Register(std::make_unique<paver::X64PartitionerFactory>());
@@ -28,6 +30,7 @@
   abr::ClientFactory::Register(std::make_unique<paver::NelsonAbrClientFactory>());
   abr::ClientFactory::Register(std::make_unique<paver::SherlockAbrClientFactory>());
   abr::ClientFactory::Register(std::make_unique<paver::LuisAbrClientFactory>());
+  abr::ClientFactory::Register(std::make_unique<paver::Vim3AbrClientFactory>());
   abr::ClientFactory::Register(std::make_unique<paver::X64AbrClientFactory>());
   return RUN_ALL_TESTS(argc, argv);
 }
diff --git a/src/storage/lib/paver/vim3.cc b/src/storage/lib/paver/vim3.cc
new file mode 100644
index 0000000..d7bea7a
--- /dev/null
+++ b/src/storage/lib/paver/vim3.cc
@@ -0,0 +1,193 @@
+// Copyright 2021 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 "src/storage/lib/paver/vim3.h"
+
+#include <lib/fzl/owned-vmo-mapper.h>
+
+#include <fbl/span.h>
+#include <gpt/gpt.h>
+#include <soc/aml-common/aml-guid.h>
+
+#include "src/lib/uuid/uuid.h"
+#include "src/storage/lib/paver/pave-logging.h"
+#include "src/storage/lib/paver/utils.h"
+
+namespace paver {
+namespace {
+
+using uuid::Uuid;
+
+}  // namespace
+
+zx::status<std::unique_ptr<DevicePartitioner>> Vim3Partitioner::Initialize(
+    fbl::unique_fd devfs_root, const zx::channel& svc_root, const fbl::unique_fd& block_device) {
+  auto status = IsBoard(devfs_root, "vim3");
+  if (status.is_error()) {
+    return status.take_error();
+  }
+
+  auto status_or_gpt =
+      GptDevicePartitioner::InitializeGpt(std::move(devfs_root), svc_root, block_device);
+  if (status_or_gpt.is_error()) {
+    return status_or_gpt.take_error();
+  }
+
+  auto partitioner = WrapUnique(new Vim3Partitioner(std::move(status_or_gpt->gpt)));
+
+  LOG("Successfully initialized Vim3Partitioner Device Partitioner\n");
+  return zx::ok(std::move(partitioner));
+}
+
+bool Vim3Partitioner::SupportsPartition(const PartitionSpec& spec) const {
+  const PartitionSpec supported_specs[] = {PartitionSpec(paver::Partition::kBootloaderA),
+                                           PartitionSpec(paver::Partition::kZirconA),
+                                           PartitionSpec(paver::Partition::kZirconB),
+                                           PartitionSpec(paver::Partition::kZirconR),
+                                           PartitionSpec(paver::Partition::kVbMetaA),
+                                           PartitionSpec(paver::Partition::kVbMetaB),
+                                           PartitionSpec(paver::Partition::kVbMetaR),
+                                           PartitionSpec(paver::Partition::kAbrMeta),
+                                           PartitionSpec(paver::Partition::kFuchsiaVolumeManager)};
+
+  for (const auto& supported : supported_specs) {
+    if (SpecMatches(spec, supported)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+zx::status<std::unique_ptr<PartitionClient>> Vim3Partitioner::AddPartition(
+    const PartitionSpec& spec) const {
+  ERROR("Cannot add partitions to a vim3 device\n");
+  return zx::error(ZX_ERR_NOT_SUPPORTED);
+}
+
+zx::status<std::unique_ptr<PartitionClient>> Vim3Partitioner::GetEmmcBootPartitionClient() const {
+  auto boot0_part =
+      OpenBlockPartition(gpt_->devfs_root(), std::nullopt, Uuid(GUID_EMMC_BOOT1_VALUE), ZX_SEC(5));
+  if (boot0_part.is_error()) {
+    return boot0_part.take_error();
+  }
+  auto boot0 =
+      std::make_unique<FixedOffsetBlockPartitionClient>(std::move(boot0_part.value()), 1, 0);
+
+  auto boot1_part =
+      OpenBlockPartition(gpt_->devfs_root(), std::nullopt, Uuid(GUID_EMMC_BOOT2_VALUE), ZX_SEC(5));
+  if (boot1_part.is_error()) {
+    return boot1_part.take_error();
+  }
+  auto boot1 =
+      std::make_unique<FixedOffsetBlockPartitionClient>(std::move(boot1_part.value()), 1, 0);
+
+  std::vector<std::unique_ptr<PartitionClient>> partitions;
+  partitions.push_back(std::move(boot0));
+  partitions.push_back(std::move(boot1));
+
+  return zx::ok(std::make_unique<PartitionCopyClient>(std::move(partitions)));
+}
+
+zx::status<std::unique_ptr<PartitionClient>> Vim3Partitioner::FindPartition(
+    const PartitionSpec& spec) const {
+  if (!SupportsPartition(spec)) {
+    ERROR("Unsupported partition %s\n", spec.ToString().c_str());
+    return zx::error(ZX_ERR_NOT_SUPPORTED);
+  }
+
+  std::string_view part_name;
+
+  switch (spec.partition) {
+    case Partition::kBootloaderA:
+      return GetEmmcBootPartitionClient();
+    case Partition::kZirconA:
+      part_name = GPT_ZIRCON_A_NAME;
+      break;
+    case Partition::kZirconB:
+      part_name = GPT_ZIRCON_B_NAME;
+      break;
+    case Partition::kZirconR:
+      part_name = GPT_ZIRCON_R_NAME;
+      break;
+    case Partition::kVbMetaA:
+      part_name = GPT_VBMETA_A_NAME;
+      break;
+    case Partition::kVbMetaB:
+      part_name = GPT_VBMETA_B_NAME;
+      break;
+    case Partition::kVbMetaR:
+      part_name = GPT_VBMETA_R_NAME;
+      break;
+    case Partition::kAbrMeta:
+      part_name = GPT_DURABLE_BOOT_NAME;
+      break;
+    case Partition::kFuchsiaVolumeManager:
+      part_name = GPT_FVM_NAME;
+      break;
+    default:
+      ERROR("Partition type is invalid\n");
+      return zx::error(ZX_ERR_INVALID_ARGS);
+  }
+
+  const auto filter_by_name = [part_name](const gpt_partition_t& part) {
+    char cstring_name[GPT_NAME_LEN] = {};
+    utf16_to_cstring(cstring_name, part.name, GPT_NAME_LEN);
+    return part_name == std::string_view(cstring_name);
+  };
+
+  auto status = gpt_->FindPartition(std::move(filter_by_name));
+  if (status.is_error()) {
+    return status.take_error();
+  }
+  return zx::ok(std::move(status->partition));
+}
+
+zx::status<> Vim3Partitioner::WipeFvm() const { return gpt_->WipeFvm(); }
+
+zx::status<> Vim3Partitioner::InitPartitionTables() const {
+  ERROR("Initializing gpt partitions from paver is not supported on vim3\n");
+  return zx::error(ZX_ERR_NOT_SUPPORTED);
+}
+
+zx::status<> Vim3Partitioner::WipePartitionTables() const {
+  return zx::error(ZX_ERR_NOT_SUPPORTED);
+}
+
+zx::status<> Vim3Partitioner::ValidatePayload(const PartitionSpec& spec,
+                                              fbl::Span<const uint8_t> data) const {
+  if (!SupportsPartition(spec)) {
+    ERROR("Unsupported partition %s\n", spec.ToString().c_str());
+    return zx::error(ZX_ERR_NOT_SUPPORTED);
+  }
+  return zx::ok();
+}
+
+zx::status<std::unique_ptr<DevicePartitioner>> Vim3PartitionerFactory::New(
+    fbl::unique_fd devfs_root, const zx::channel& svc_root, Arch arch,
+    std::shared_ptr<Context> context, const fbl::unique_fd& block_device) {
+  return Vim3Partitioner::Initialize(std::move(devfs_root), svc_root, block_device);
+}
+
+zx::status<std::unique_ptr<abr::Client>> Vim3AbrClientFactory::New(
+    fbl::unique_fd devfs_root, const zx::channel& svc_root,
+    std::shared_ptr<paver::Context> context) {
+  fbl::unique_fd none;
+  auto partitioner = Vim3Partitioner::Initialize(std::move(devfs_root), std::move(svc_root), none);
+
+  if (partitioner.is_error()) {
+    return partitioner.take_error();
+  }
+
+  // ABR metadata has no need of a content type since it's always local rather
+  // than provided in an update package, so just use the default content type.
+  auto partition = partitioner->FindPartition(paver::PartitionSpec(paver::Partition::kAbrMeta));
+  if (partition.is_error()) {
+    return partition.take_error();
+  }
+
+  return abr::AbrPartitionClient::Create(std::move(partition.value()));
+}
+
+}  // namespace paver
diff --git a/src/storage/lib/paver/vim3.h b/src/storage/lib/paver/vim3.h
new file mode 100644
index 0000000..bcc1a2a
--- /dev/null
+++ b/src/storage/lib/paver/vim3.h
@@ -0,0 +1,67 @@
+// Copyright 2021 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 SRC_STORAGE_LIB_PAVER_VIM3_H_
+#define SRC_STORAGE_LIB_PAVER_VIM3_H_
+
+#include "src/storage/lib/paver/abr-client.h"
+#include "src/storage/lib/paver/device-partitioner.h"
+#include "src/storage/lib/paver/gpt.h"
+#include "src/storage/lib/paver/partition-client.h"
+
+namespace paver {
+
+class Vim3Partitioner : public DevicePartitioner {
+ public:
+  static zx::status<std::unique_ptr<DevicePartitioner>> Initialize(
+      fbl::unique_fd devfs_root, const zx::channel& svc_root, const fbl::unique_fd& block_device);
+
+  bool IsFvmWithinFtl() const override { return false; }
+
+  bool SupportsPartition(const PartitionSpec& spec) const override;
+
+  zx::status<std::unique_ptr<PartitionClient>> AddPartition(
+      const PartitionSpec& spec) const override;
+
+  zx::status<std::unique_ptr<PartitionClient>> FindPartition(
+      const PartitionSpec& spec) const override;
+
+  zx::status<> FinalizePartition(const PartitionSpec& spec) const override { return zx::ok(); }
+
+  zx::status<> WipeFvm() const override;
+
+  zx::status<> InitPartitionTables() const override;
+
+  zx::status<> WipePartitionTables() const override;
+
+  zx::status<> ValidatePayload(const PartitionSpec& spec,
+                               fbl::Span<const uint8_t> data) const override;
+
+  zx::status<> Flush() const override { return zx::ok(); }
+
+ private:
+  explicit Vim3Partitioner(std::unique_ptr<GptDevicePartitioner> gpt) : gpt_(std::move(gpt)) {}
+
+  zx::status<std::unique_ptr<PartitionClient>> GetEmmcBootPartitionClient() const;
+
+  std::unique_ptr<GptDevicePartitioner> gpt_;
+};
+
+class Vim3PartitionerFactory : public DevicePartitionerFactory {
+ public:
+  zx::status<std::unique_ptr<DevicePartitioner>> New(fbl::unique_fd devfs_root,
+                                                     const zx::channel& svc_root, Arch arch,
+                                                     std::shared_ptr<Context> context,
+                                                     const fbl::unique_fd& block_device) final;
+};
+
+class Vim3AbrClientFactory : public abr::ClientFactory {
+ public:
+  zx::status<std::unique_ptr<abr::Client>> New(fbl::unique_fd devfs_root,
+                                               const zx::channel& svc_root,
+                                               std::shared_ptr<paver::Context> context) final;
+};
+
+}  // namespace paver
+
+#endif  // SRC_STORAGE_LIB_PAVER_VIM3_H_