[release] Snap to 82089872a3
Change-Id: I40b95c5d1e87654ba539a30934e923e268f971e4
diff --git a/src/developer/ffx/src/main.rs b/src/developer/ffx/src/main.rs
index 40a4b50..63235c3 100644
--- a/src/developer/ffx/src/main.rs
+++ b/src/developer/ffx/src/main.rs
@@ -13,7 +13,7 @@
     fidl::endpoints::create_proxy,
     fidl_fuchsia_developer_bridge::{DaemonError, DaemonProxy, FastbootMarker, FastbootProxy},
     fidl_fuchsia_developer_remotecontrol::{RemoteControlMarker, RemoteControlProxy},
-    futures::try_join,
+    fuchsia_async::TimeoutExt,
     lazy_static::lazy_static,
     std::sync::{Arc, Mutex},
     std::time::Duration,
@@ -115,7 +115,7 @@
     false
 }
 
-async fn run() -> Result<((), ())> {
+async fn run() -> Result<()> {
     let app: Ffx = argh::from_env();
 
     // Configuration initialization must happen before ANY calls to the config (or the cache won't
@@ -133,24 +133,34 @@
 
     let notice_writer = Box::new(std::io::stderr());
     show_analytics_notice(notice_writer);
-    let args: Vec<String> = std::env::args().collect();
 
-    // drop arg[0]: executable with hard path
-    // TODO do we want to break out subcommands for analytics?
-    let args_str = &args[1..].join(" ");
-    let launch_args = format!("{}", &args_str);
-    let build_info = build_info();
-    let build_version = build_info.build_version.as_deref();
-    try_join!(
-        add_launch_event(APP_NAME, build_version, Some(&launch_args)),
-        ffx_lib_suite::ffx_plugin_impl(
-            get_daemon_proxy,
-            get_remote_proxy,
-            get_fastboot_proxy,
-            is_experiment_subcommand_on,
-            app,
-        )
+    let analytics_task = fuchsia_async::Task::spawn(async {
+        let args: Vec<String> = std::env::args().collect();
+        // drop arg[0]: executable with hard path
+        // TODO do we want to break out subcommands for analytics?
+        let args_str = &args[1..].join(" ");
+        let launch_args = format!("{}", &args_str);
+        let build_info = build_info();
+        let build_version = build_info.build_version;
+        add_launch_event(APP_NAME, build_version.as_deref(), Some(launch_args).as_deref()).await
+    });
+    let res = ffx_lib_suite::ffx_plugin_impl(
+        get_daemon_proxy,
+        get_remote_proxy,
+        get_fastboot_proxy,
+        is_experiment_subcommand_on,
+        app,
     )
+    .await;
+    let _ = analytics_task
+        // TODO(66918): make configurable, and evaluate chosen time value.
+        .on_timeout(Duration::from_secs(2), || {
+            log::error!("analytics submission timed out");
+            // Analytics timeouts should not impact user flows
+            Ok(())
+        })
+        .await;
+    res
 }
 
 #[fuchsia_async::run_singlethreaded]
@@ -167,8 +177,16 @@
                 eprintln!("BUG: An internal command error occurred.\n{:?}", err);
             }
             let err_msg = format!("{}", err);
-            add_crash_event(&err_msg).await.unwrap(); // unwrap because return is always empty and
-                                                      // users can't act on analytics failures
+            // TODO(66918): make configurable, and evaluate chosen time value.
+            if let Err(e) = add_crash_event(&err_msg)
+                .on_timeout(Duration::from_secs(2), || {
+                    log::error!("analytics timed out reporting crash event");
+                    Ok(())
+                })
+                .await
+            {
+                log::error!("analytics failed to submit crash event: {}", e);
+            }
             std::process::exit(1);
         }
     }
diff --git a/src/lib/analytics/rust/src/lib.rs b/src/lib/analytics/rust/src/lib.rs
index aec6e66..47cb93d 100644
--- a/src/lib/analytics/rust/src/lib.rs
+++ b/src/lib/analytics/rust/src/lib.rs
@@ -41,7 +41,7 @@
 
     let body = make_body_with_hash(&app_name, app_version, args);
     let client = new_https_client();
-    let req = Request::builder().method(Method::POST).uri(GA_URL).body(Body::from(body)).unwrap();
+    let req = Request::builder().method(Method::POST).uri(GA_URL).body(Body::from(body))?;
     let mut res = client.request(req).await;
     match res {
         Ok(res) => log::info!("Analytics response: {}", res.status()),
diff --git a/src/lib/fidl/llcpp/tests/protocol_test.cc b/src/lib/fidl/llcpp/tests/protocol_test.cc
index 62d64c1..8991408 100644
--- a/src/lib/fidl/llcpp/tests/protocol_test.cc
+++ b/src/lib/fidl/llcpp/tests/protocol_test.cc
@@ -385,3 +385,29 @@
   EmptyImpl server;
   fidl::BindServer(loop.dispatcher(), std::move(server_end), &server);
 }
+
+// Test creating a typed channel endpoint pair.
+TEST(Endpoints, CreateFromProtocol) {
+  // `std::move` pattern
+  {
+    auto endpoints = fidl::CreateEndpoints<test::Empty>();
+    ASSERT_TRUE(endpoints.is_ok());
+    ASSERT_EQ(ZX_OK, endpoints.status_value()) << endpoints.status_string();
+    fidl::ClientEnd<test::Empty> client_end = std::move(endpoints->client);
+    fidl::ServerEnd<test::Empty> server_end = std::move(endpoints->server);
+
+    ASSERT_TRUE(client_end.is_valid());
+    ASSERT_TRUE(server_end.is_valid());
+  }
+
+  // Destructuring pattern
+  {
+    auto endpoints = fidl::CreateEndpoints<test::Empty>();
+    ASSERT_TRUE(endpoints.is_ok());
+    ASSERT_EQ(ZX_OK, endpoints.status_value()) << endpoints.status_string();
+    auto [client_end, server_end] = std::move(endpoints.value());
+
+    ASSERT_TRUE(client_end.is_valid());
+    ASSERT_TRUE(server_end.is_valid());
+  }
+}
diff --git a/src/sys/lib/library_loader/BUILD.gn b/src/sys/lib/library_loader/BUILD.gn
index b4ce7f9..cdef3b2 100644
--- a/src/sys/lib/library_loader/BUILD.gn
+++ b/src/sys/lib/library_loader/BUILD.gn
@@ -2,10 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/package.gni")
+import("//build/dist/resource.gni")
 import("//build/rust/rustc_library.gni")
-import("//build/test/test_package.gni")
 import("//build/testing/environments.gni")
+import("//src/sys/build/components.gni")
 
 rustc_library("library_loader") {
   version = "0.1.0"
@@ -34,30 +34,26 @@
 # files using OPEN_RIGHT_EXECUTABLE/VMO_FLAG_EXEC, and the easiest way to provide a test directory
 # that supports that is a real pkgfs directory.
 # TODO(fxbug.dev/37534): Remove this once Rust vfs supports OPEN_RIGHT_EXECUTABLE
-generate_manifest("config_test_files") {
-  args = [
-    "--entry=lib/config_test/foo=" +
-        rebase_path("config_test_files/foo", root_build_dir),
-    "--entry=lib/config_test/bar/baz=" +
-        rebase_path("config_test_files/bar/baz", root_build_dir),
-  ]
+resource("config_test_foo") {
+  sources = [ "config_test_files/foo" ]
+  outputs = [ "lib/config_test/foo" ]
 }
 
-test_package("library_loader_tests") {
+resource("config_test_bar_baz") {
+  sources = [ "config_test_files/bar/baz" ]
+  outputs = [ "lib/config_test/bar/baz" ]
+}
+
+fuchsia_unittest_package("library_loader_tests") {
   deps = [
-    ":config_test_files",
+    ":config_test_bar_baz",
+    ":config_test_foo",
     ":library_loader_test",
   ]
-
-  extra = get_target_outputs(":config_test_files")
-
-  tests = [
-    {
-      name = "library_loader_lib_test"
-      dest = "library_loader_tests"
-      environments = basic_envs
-    },
-  ]
+  manifest = "meta/library_loader_tests.cmx"
+  test_specs = {
+    environments = basic_envs
+  }
 }
 
 group("tests") {
diff --git a/src/sys/lib/library_loader/meta/library_loader_tests.cmx b/src/sys/lib/library_loader/meta/library_loader_tests.cmx
index e41c6945..e5c8b70 100644
--- a/src/sys/lib/library_loader/meta/library_loader_tests.cmx
+++ b/src/sys/lib/library_loader/meta/library_loader_tests.cmx
@@ -3,6 +3,6 @@
         "sdk/lib/diagnostics/syslog/client.shard.cmx"
     ],
     "program": {
-        "binary": "test/library_loader_tests"
+        "binary": "bin/library_loader_lib_test"
     }
 }
diff --git a/src/sys/pkg/tests/system-tests/README.md b/src/sys/pkg/tests/system-tests/README.md
index 0fa387a..b10ec5c 100644
--- a/src/sys/pkg/tests/system-tests/README.md
+++ b/src/sys/pkg/tests/system-tests/README.md
@@ -154,7 +154,7 @@
 ```
 % fx set ... \
   --with //src/sys/pkg:tests \
-  --args 'kernel_cmdline_args=["kernel.serial=legacy"]'
+  --args 'dev_bootfs_labels+=["//src/zircon/kernel/cmdline:serial-legacy"]'
 % fx build
 ```
 
diff --git a/src/tests/benchmarks/fidl/reference/BUILD.gn b/src/tests/benchmarks/fidl/reference/BUILD.gn
index a0e6918..b3d0da4 100644
--- a/src/tests/benchmarks/fidl/reference/BUILD.gn
+++ b/src/tests/benchmarks/fidl/reference/BUILD.gn
@@ -40,7 +40,6 @@
     "//zircon/public/lib/fidl",
     "//zircon/public/lib/sync",
     "//zircon/system/ulib/perftest",
-    benchmark_suite_fidl_target,
   ]
 }
 
diff --git a/src/zircon/kernel/cmdline/BUILD.gn b/src/zircon/kernel/cmdline/BUILD.gn
new file mode 100644
index 0000000..ca6f2b2
--- /dev/null
+++ b/src/zircon/kernel/cmdline/BUILD.gn
@@ -0,0 +1,9 @@
+# Copyright 2020 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("//build/zbi/kernel_cmdline.gni")
+
+kernel_cmdline("serial-legacy") {
+  args = [ "kernel.serial=legacy" ]
+}
diff --git a/src/zircon/kernel/cmdline/README.md b/src/zircon/kernel/cmdline/README.md
new file mode 100644
index 0000000..d7182b8
--- /dev/null
+++ b/src/zircon/kernel/cmdline/README.md
@@ -0,0 +1,9 @@
+# Kernel command-line arguments
+
+The build file in this directory contains targets for various options for kernel
+command-line arguments.
+
+These arguments should be passed via the relevant GN arguments:
+- `board_bootfs_labels` (from [`//build/board.gni`](build/board.gni)) in a board file;
+- `product_bootfs_labels` (from [`//build/product.gni`](build/product.gni)) in a product file;
+- `dev_bootfs_labels` (from [`//build/dev.gni`](build/dev.gni)) in an `fx set` and `gn gen` invocation.
diff --git a/tools/fidl/gidl/gidl.gni b/tools/fidl/gidl/gidl.gni
index 569af43..7fa47a6 100644
--- a/tools/fidl/gidl/gidl.gni
+++ b/tools/fidl/gidl/gidl.gni
@@ -103,10 +103,8 @@
 
   source_set(target_name) {
     sources = [ output_filename ]
-    deps = [
-      ":${gidl_target}",
-      fidl_bindings,
-    ]
+    deps = [ ":${gidl_target}" ]
+    public_deps = [ fidl_bindings ]
     if (defined(invoker.deps)) {
       deps += invoker.deps
     }
diff --git a/zircon/kernel/arch/x86/mp.cc b/zircon/kernel/arch/x86/mp.cc
index c61ad08..a2c831a 100644
--- a/zircon/kernel/arch/x86/mp.cc
+++ b/zircon/kernel/arch/x86/mp.cc
@@ -113,10 +113,10 @@
     }
     memset(ap_percpus, 0, len);
 
-    use_monitor = arch::BootCpuid<arch::CpuidFeatureFlagsC>().monitor() &&
-                  (arch::BootCpuid<arch::CpuidMaximumLeaf>().reg_value() >=
-                   arch::CpuidMonitorMwaitB::kLeaf) &&
-                  !x86_get_microarch_config()->idle_prefer_hlt;
+    use_monitor =
+        arch::BootCpuid<arch::CpuidFeatureFlagsC>().monitor() &&
+        (arch::BootCpuid<arch::CpuidMaximumLeaf>().leaf() >= arch::CpuidMonitorMwaitB::kLeaf) &&
+        !x86_get_microarch_config()->idle_prefer_hlt;
     if (use_monitor) {
       uint16_t monitor_size =
           arch::BootCpuid<arch::CpuidMonitorMwaitB>().largest_monitor_line_size();
diff --git a/zircon/kernel/arch/x86/perf_mon.cc b/zircon/kernel/arch/x86/perf_mon.cc
index 9c0c3ef..70993e3 100644
--- a/zircon/kernel/arch/x86/perf_mon.cc
+++ b/zircon/kernel/arch/x86/perf_mon.cc
@@ -420,8 +420,7 @@
 }
 
 static void x86_perfmon_init_once(uint level) {
-  if (arch::BootCpuid<arch::CpuidMaximumLeaf>().reg_value() <
-      arch::CpuidPerformanceMonitoringA::kLeaf) {
+  if (arch::BootCpuid<arch::CpuidMaximumLeaf>().leaf() < arch::CpuidPerformanceMonitoringA::kLeaf) {
     return;
   }
 
diff --git a/zircon/kernel/arch/x86/proc_trace.cc b/zircon/kernel/arch/x86/proc_trace.cc
index b382847..dd691c5 100644
--- a/zircon/kernel/arch/x86/proc_trace.cc
+++ b/zircon/kernel/arch/x86/proc_trace.cc
@@ -111,7 +111,8 @@
 static uint32_t ipt_num_traces TA_GUARDED(IptLock::Get());
 
 void x86_processor_trace_init(void) {
-  if (!arch::BootCpuid<arch::CpuidExtendedFeatureFlagsB>().intel_pt()) {
+  if ((arch::BootCpuid<arch::CpuidMaximumLeaf>().leaf() < arch::CpuidProcessorTraceMainB::kLeaf) ||
+      !arch::BootCpuid<arch::CpuidExtendedFeatureFlagsB>().intel_pt()) {
     return;
   }
 
diff --git a/zircon/kernel/lib/arch/include/lib/arch/x86/cpuid.h b/zircon/kernel/lib/arch/include/lib/arch/x86/cpuid.h
index 10c90b7..8e64315f 100644
--- a/zircon/kernel/lib/arch/include/lib/arch/x86/cpuid.h
+++ b/zircon/kernel/lib/arch/include/lib/arch/x86/cpuid.h
@@ -47,6 +47,43 @@
   uint32_t values_[4];
 };
 
+// Define a "CPUID value type" as any type with the following:
+// * static constexpr uint32_t members, `kLeaf` and `kSubleaf` giving the
+//   associated leaf and subleaf;
+// * a static `Get()` method returning a `hwreg::RegisterAddr` object holding
+//   an "address" of one of the `CpuidIo::Register` values.
+//
+// CPUID "I/O" providers will deal in such types. See arch::BootCpuidIo and
+// arch::testing::FakeCpuidIo for instances of such contracts.
+//
+// Note: there is no inherent relationship between the CPUID value type and the
+// return type of `Get()`. In practice, a CPUID value type might be precisely
+// the hwreg register type expressing the bit layout or a sort of getter for
+// such a type (the utility of which lies in the fact that hwreg register
+// types cannot be templated, for various reasons).
+//
+// We use Intel's terms of "leaf" and "subleaf" over AMD's "function" and
+// "subfunction" as the latter pair is more overloaded and ambiguous.
+
+// CpuidIoValue is a convenience type for defining a CPUID value type.
+template <typename ValueType, uint32_t Leaf, uint32_t Subleaf, CpuidIo::Register OutputRegister>
+struct CpuidIoValue {
+  static constexpr uint32_t kLeaf = Leaf;
+  static constexpr uint32_t kSubleaf = Subleaf;
+
+  static auto Get() { return hwreg::RegisterAddr<ValueType>(OutputRegister); }
+};
+
+// CpuidIoValueBase is a convenience type for defining both a CPUID value type
+// as well as the associated register type.
+//
+// Assembly macro generation requires the use of `hwreg::EnablePrinter`; to
+// make such use-cases more conveniently accessible - and since the code-gen
+// cost here is rather minimal - we generally use the feature  below.
+template <typename ValueType, uint32_t Leaf, uint32_t Subleaf, CpuidIo::Register OutputRegister>
+struct CpuidIoValueBase : public hwreg::RegisterBase<ValueType, uint32_t, hwreg::EnablePrinter>,
+                          public CpuidIoValue<ValueType, Leaf, Subleaf, OutputRegister> {};
+
 enum class Vendor {
   kUnknown,
   kIntel,
@@ -92,28 +129,6 @@
 std::string_view ToString(Vendor vendor);
 std::string_view ToString(Microarchitecture microarch);
 
-// A convenient and self-documenting wrapper for defining CPUID value bitsets
-// as hwreg register objects, along with their associated leaf and subleaf
-// values (which are always expected to be defined as static constexpr
-// members).
-//
-// We use Intel's terms of "leaf", "subleaf" over AMD's "function", "subfuntion"
-// as the latter pair is more overloaded and ambiguous.
-//
-// Assembly macro generation requires the use of `hwreg::EnablePrinter`.
-//
-// TODO(joshuaseaton|mcgrathr): When global templated CpuidIo objects are
-// defined, also define a templated getter that takes a CpuidValue type,
-// consults kLeaf and kSubleaf, and returns the appropriate object (e.g.,
-// `GetCpuidFor<ValueType>()`).
-template <typename ValueType, uint32_t Leaf, uint32_t Subleaf, CpuidIo::Register OutputRegister>
-struct CpuidValueBase : public hwreg::RegisterBase<ValueType, uint32_t, hwreg::EnablePrinter> {
-  static constexpr uint32_t kLeaf = Leaf;
-  static constexpr uint32_t kSubleaf = Subleaf;
-
-  static auto Get() { return hwreg::RegisterAddr<ValueType>(OutputRegister); }
-};
-
 //---------------------------------------------------------------------------//
 // Leaf/Function 0x0.
 //
@@ -122,21 +137,29 @@
 //---------------------------------------------------------------------------//
 
 // [amd/vol3]: E.3.1, CPUID Fn0000_0000_EAX Largest Standard Function Number.
-struct CpuidMaximumLeaf : public CpuidValueBase<CpuidMaximumLeaf, 0x0, 0x0, CpuidIo::kEax> {};
+struct CpuidMaximumLeaf : public CpuidIoValueBase<CpuidMaximumLeaf, 0x0, 0x0, CpuidIo::kEax> {
+  DEF_FIELD(31, 0, leaf);
+};
 
 // [amd/vol3]: E.3.1, CPUID Fn0000_0000_E[D,C,B]X Processor Vendor.
-struct CpuidVendorB : public CpuidValueBase<CpuidVendorB, 0x0, 0x0, CpuidIo::kEbx> {};
-struct CpuidVendorC : public CpuidValueBase<CpuidVendorC, 0x0, 0x0, CpuidIo::kEcx> {};
-struct CpuidVendorD : public CpuidValueBase<CpuidVendorD, 0x0, 0x0, CpuidIo::kEdx> {};
+struct CpuidVendorB : public CpuidIoValueBase<CpuidVendorB, 0x0, 0x0, CpuidIo::kEbx> {
+  DEF_FIELD(31, 0, value);
+};
+struct CpuidVendorC : public CpuidIoValueBase<CpuidVendorC, 0x0, 0x0, CpuidIo::kEcx> {
+  DEF_FIELD(31, 0, value);
+};
+struct CpuidVendorD : public CpuidIoValueBase<CpuidVendorD, 0x0, 0x0, CpuidIo::kEdx> {
+  DEF_FIELD(31, 0, value);
+};
 
 template <typename CpuidIoProvider>
 Vendor GetVendor(CpuidIoProvider&& io) {
   using namespace std::string_view_literals;
 
   const uint32_t ids[] = {
-      io.template Read<CpuidVendorB>().reg_value(),
-      io.template Read<CpuidVendorD>().reg_value(),
-      io.template Read<CpuidVendorC>().reg_value(),
+      io.template Read<CpuidVendorB>().value(),
+      io.template Read<CpuidVendorD>().value(),
+      io.template Read<CpuidVendorC>().value(),
   };
   std::string_view name{reinterpret_cast<const char*>(ids), sizeof(ids)};
   if (name == "GenuineIntel"sv) {
@@ -156,7 +179,7 @@
 
 // [intel/vol2]: Figure 3-6.  Version Information Returned by CPUID in EAX.
 // [amd/vol3]: E.3.2, CPUID Fn0000_0001_EAX  Family, Model, Stepping Identifiers.
-struct CpuidVersionInfo : public CpuidValueBase<CpuidVersionInfo, 0x1, 0x0, CpuidIo::kEax> {
+struct CpuidVersionInfo : public CpuidIoValueBase<CpuidVersionInfo, 0x1, 0x0, CpuidIo::kEax> {
   // [intel/vol2]: Table 3-9.  Processor Type Field.
   enum class IntelProcessorType : uint8_t {
     kOriginalOem = 0b00,
@@ -191,7 +214,7 @@
 
 // [intel/vol2]: Table 3-10.  Feature Information Returned in the ECX Register.
 // [amd/vol3]: E.3.2, CPUID Fn0000_0001_ECX Feature Identifiers.
-struct CpuidFeatureFlagsC : public CpuidValueBase<CpuidFeatureFlagsC, 0x1, 0x0, CpuidIo::kEcx> {
+struct CpuidFeatureFlagsC : public CpuidIoValueBase<CpuidFeatureFlagsC, 0x1, 0x0, CpuidIo::kEcx> {
   // AMD documented "RAZ. Reserved for use by hypervisor to indicate guest
   // status."; Intel documents "Not Used. Always returns 0.".
   DEF_BIT(31, hypervisor);
@@ -230,7 +253,7 @@
 
 // [intel/vol2]: Table 3-11.  More on Feature Information Returned in the EDX Register.
 // [amd/vol3]: E.3.6  Function 7h—Structured Extended Feature Identifiers.
-struct CpuidFeatureFlagsD : public CpuidValueBase<CpuidFeatureFlagsD, 0x1, 0x0, CpuidIo::kEdx> {
+struct CpuidFeatureFlagsD : public CpuidIoValueBase<CpuidFeatureFlagsD, 0x1, 0x0, CpuidIo::kEdx> {
   DEF_BIT(31, pbe);
   // Bit 30 is reserved.
   DEF_BIT(29, tm);
@@ -272,23 +295,23 @@
 // [amd/vol3]: E.3.4  Function 5h—Monitor and MWait Features.
 //---------------------------------------------------------------------------//
 
-struct CpuidMonitorMwaitA : public CpuidValueBase<CpuidMonitorMwaitA, 0x5, 0x0, CpuidIo::kEax> {
+struct CpuidMonitorMwaitA : public CpuidIoValueBase<CpuidMonitorMwaitA, 0x5, 0x0, CpuidIo::kEax> {
   DEF_RSVDZ_FIELD(31, 16);
   DEF_FIELD(15, 0, smallest_monitor_line_size);
 };
 
-struct CpuidMonitorMwaitB : public CpuidValueBase<CpuidMonitorMwaitB, 0x5, 0x0, CpuidIo::kEbx> {
+struct CpuidMonitorMwaitB : public CpuidIoValueBase<CpuidMonitorMwaitB, 0x5, 0x0, CpuidIo::kEbx> {
   DEF_RSVDZ_FIELD(31, 16);
   DEF_FIELD(15, 0, largest_monitor_line_size);
 };
 
-struct CpuidMonitorMwaitC : public CpuidValueBase<CpuidMonitorMwaitC, 0x5, 0x0, CpuidIo::kEcx> {
+struct CpuidMonitorMwaitC : public CpuidIoValueBase<CpuidMonitorMwaitC, 0x5, 0x0, CpuidIo::kEcx> {
   // Bits [31: 2] are reserved.
   DEF_BIT(1, ibe);
   DEF_BIT(0, emx);
 };
 
-struct CpuidMonitorMwaitD : public CpuidValueBase<CpuidMonitorMwaitD, 0x5, 0x0, CpuidIo::kEdx> {
+struct CpuidMonitorMwaitD : public CpuidIoValueBase<CpuidMonitorMwaitD, 0x5, 0x0, CpuidIo::kEdx> {
   DEF_FIELD(31, 28, c7_sub_c_states);
   DEF_FIELD(27, 24, c6_sub_c_states);
   DEF_FIELD(23, 20, c5_sub_c_states);
@@ -308,7 +331,7 @@
 
 // [amd/vol3]: E.3.6, CPUID Fn0000_0007_EBX_x0 Structured Extended Feature Identifiers (ECX=0).
 struct CpuidExtendedFeatureFlagsB
-    : public CpuidValueBase<CpuidExtendedFeatureFlagsB, 0x7, 0x0, CpuidIo::kEbx> {
+    : public CpuidIoValueBase<CpuidExtendedFeatureFlagsB, 0x7, 0x0, CpuidIo::kEbx> {
   DEF_BIT(31, avx512vl);
   DEF_BIT(30, avx512bw);
   DEF_BIT(29, sha);
@@ -350,7 +373,7 @@
 //---------------------------------------------------------------------------//
 
 struct CpuidPerformanceMonitoringA
-    : public CpuidValueBase<CpuidPerformanceMonitoringA, 0xa, 0x0, CpuidIo::kEax> {
+    : public CpuidIoValueBase<CpuidPerformanceMonitoringA, 0xa, 0x0, CpuidIo::kEax> {
   DEF_FIELD(31, 24, ebx_vector_length);
   DEF_FIELD(23, 16, general_counter_width);
   DEF_FIELD(15, 8, num_general_counters);
@@ -358,7 +381,7 @@
 };
 
 struct CpuidPerformanceMonitoringB
-    : public CpuidValueBase<CpuidPerformanceMonitoringB, 0xa, 0x0, CpuidIo::kEbx> {
+    : public CpuidIoValueBase<CpuidPerformanceMonitoringB, 0xa, 0x0, CpuidIo::kEbx> {
   DEF_RSVDZ_FIELD(31, 7);
   DEF_BIT(6, branch_mispredict_retired_event_unavailable);
   DEF_BIT(5, branch_instruction_retired_event_unavailable);
@@ -370,7 +393,7 @@
 };
 
 struct CpuidPerformanceMonitoringD
-    : public CpuidValueBase<CpuidPerformanceMonitoringD, 0xa, 0x0, CpuidIo::kEdx> {
+    : public CpuidIoValueBase<CpuidPerformanceMonitoringD, 0xa, 0x0, CpuidIo::kEdx> {
   DEF_RSVDZ_FIELD(31, 16);
   DEF_BIT(15, anythread_deprecation);
   DEF_RSVDZ_FIELD(14, 13);
@@ -385,7 +408,7 @@
 //---------------------------------------------------------------------------//
 
 struct CpuidProcessorTraceMainB
-    : public CpuidValueBase<CpuidProcessorTraceMainB, 0x14, 0x0, CpuidIo::kEbx> {
+    : public CpuidIoValueBase<CpuidProcessorTraceMainB, 0x14, 0x0, CpuidIo::kEbx> {
   DEF_RSVDZ_FIELD(31, 6);
   DEF_BIT(5, power_event_trace);
   DEF_BIT(4, ptwrite);
@@ -396,7 +419,7 @@
 };
 
 struct CpuidProcessorTraceMainC
-    : public CpuidValueBase<CpuidProcessorTraceMainC, 0x14, 0x0, CpuidIo::kEcx> {
+    : public CpuidIoValueBase<CpuidProcessorTraceMainC, 0x14, 0x0, CpuidIo::kEcx> {
   DEF_BIT(31, lip);
   DEF_RSVDZ_FIELD(30, 4);
   DEF_BIT(3, trace_transport);
@@ -419,14 +442,22 @@
 //---------------------------------------------------------------------------//
 
 struct CpuidMaximumHypervisorLeaf
-    : public CpuidValueBase<CpuidMaximumHypervisorLeaf, 0x4000'0000, 0x0, CpuidIo::kEax> {};
+    : public CpuidIoValueBase<CpuidMaximumHypervisorLeaf, 0x4000'0000, 0x0, CpuidIo::kEax> {
+  DEF_FIELD(31, 0, leaf);
+};
 
 struct CpuidHypervisorNameB
-    : public CpuidValueBase<CpuidHypervisorNameB, 0x4000'0000, 0x0, CpuidIo::kEbx> {};
+    : public CpuidIoValueBase<CpuidHypervisorNameB, 0x4000'0000, 0x0, CpuidIo::kEbx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidHypervisorNameC
-    : public CpuidValueBase<CpuidHypervisorNameC, 0x4000'0000, 0x0, CpuidIo::kEcx> {};
+    : public CpuidIoValueBase<CpuidHypervisorNameC, 0x4000'0000, 0x0, CpuidIo::kEcx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidHypervisorNameD
-    : public CpuidValueBase<CpuidHypervisorNameD, 0x4000'0000, 0x0, CpuidIo::kEdx> {};
+    : public CpuidIoValueBase<CpuidHypervisorNameD, 0x4000'0000, 0x0, CpuidIo::kEdx> {
+  DEF_FIELD(31, 0, value);
+};
 
 // HypervisorName is a simple class that serves to hold the content of a
 // hypervisor's name (or "vendor string").
@@ -437,9 +468,9 @@
     // Check if we are actually within a hypervisor.
     if (io.template Read<CpuidFeatureFlagsC>().hypervisor()) {
       const uint32_t values[] = {
-          io.template Read<CpuidHypervisorNameB>().reg_value(),
-          io.template Read<CpuidHypervisorNameC>().reg_value(),
-          io.template Read<CpuidHypervisorNameD>().reg_value(),
+          io.template Read<CpuidHypervisorNameB>().value(),
+          io.template Read<CpuidHypervisorNameC>().value(),
+          io.template Read<CpuidHypervisorNameD>().value(),
       };
       static_assert(kSize == sizeof(values));
       memcpy(str_.data(), values, kSize);
@@ -469,7 +500,9 @@
 
 // [amd/vol3]: CPUID Fn8000_0000_EAX Largest Extended Function Number
 struct CpuidMaximumExtendedLeaf
-    : public CpuidValueBase<CpuidMaximumExtendedLeaf, 0x8000'0000, 0x0, CpuidIo::kEax> {};
+    : public CpuidIoValueBase<CpuidMaximumExtendedLeaf, 0x8000'0000, 0x0, CpuidIo::kEax> {
+  DEF_FIELD(31, 0, leaf);
+};
 
 //---------------------------------------------------------------------------//
 // Leaves/Functions 0x8000'0002 - 0x8000'0004
@@ -482,31 +515,55 @@
 // express (zero-based) index into how the combine to form the processor name
 // string.
 struct CpuidProcessorName2A
-    : public CpuidValueBase<CpuidProcessorName2A, 0x8000'0002, 0x0, CpuidIo::kEax> {};
+    : public CpuidIoValueBase<CpuidProcessorName2A, 0x8000'0002, 0x0, CpuidIo::kEax> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName2B
-    : public CpuidValueBase<CpuidProcessorName2B, 0x8000'0002, 0x0, CpuidIo::kEbx> {};
+    : public CpuidIoValueBase<CpuidProcessorName2B, 0x8000'0002, 0x0, CpuidIo::kEbx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName2C
-    : public CpuidValueBase<CpuidProcessorName2C, 0x8000'0002, 0x0, CpuidIo::kEcx> {};
+    : public CpuidIoValueBase<CpuidProcessorName2C, 0x8000'0002, 0x0, CpuidIo::kEcx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName2D
-    : public CpuidValueBase<CpuidProcessorName2D, 0x8000'0002, 0x0, CpuidIo::kEdx> {};
+    : public CpuidIoValueBase<CpuidProcessorName2D, 0x8000'0002, 0x0, CpuidIo::kEdx> {
+  DEF_FIELD(31, 0, value);
+};
 
 struct CpuidProcessorName3A
-    : public CpuidValueBase<CpuidProcessorName3A, 0x8000'0003, 0x0, CpuidIo::kEax> {};
+    : public CpuidIoValueBase<CpuidProcessorName3A, 0x8000'0003, 0x0, CpuidIo::kEax> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName3B
-    : public CpuidValueBase<CpuidProcessorName3B, 0x8000'0003, 0x0, CpuidIo::kEbx> {};
+    : public CpuidIoValueBase<CpuidProcessorName3B, 0x8000'0003, 0x0, CpuidIo::kEbx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName3C
-    : public CpuidValueBase<CpuidProcessorName3C, 0x8000'0003, 0x0, CpuidIo::kEcx> {};
+    : public CpuidIoValueBase<CpuidProcessorName3C, 0x8000'0003, 0x0, CpuidIo::kEcx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName3D
-    : public CpuidValueBase<CpuidProcessorName3D, 0x8000'0003, 0x0, CpuidIo::kEdx> {};
+    : public CpuidIoValueBase<CpuidProcessorName3D, 0x8000'0003, 0x0, CpuidIo::kEdx> {
+  DEF_FIELD(31, 0, value);
+};
 
 struct CpuidProcessorName4A
-    : public CpuidValueBase<CpuidProcessorName4A, 0x8000'0004, 0x0, CpuidIo::kEax> {};
+    : public CpuidIoValueBase<CpuidProcessorName4A, 0x8000'0004, 0x0, CpuidIo::kEax> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName4B
-    : public CpuidValueBase<CpuidProcessorName4B, 0x8000'0004, 0x0, CpuidIo::kEbx> {};
+    : public CpuidIoValueBase<CpuidProcessorName4B, 0x8000'0004, 0x0, CpuidIo::kEbx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName4C
-    : public CpuidValueBase<CpuidProcessorName4C, 0x8000'0004, 0x0, CpuidIo::kEcx> {};
+    : public CpuidIoValueBase<CpuidProcessorName4C, 0x8000'0004, 0x0, CpuidIo::kEcx> {
+  DEF_FIELD(31, 0, value);
+};
 struct CpuidProcessorName4D
-    : public CpuidValueBase<CpuidProcessorName4D, 0x8000'0004, 0x0, CpuidIo::kEdx> {};
+    : public CpuidIoValueBase<CpuidProcessorName4D, 0x8000'0004, 0x0, CpuidIo::kEdx> {
+  DEF_FIELD(31, 0, value);
+};
 
 // ProcessorName is a simple class that serves to hold the content of a
 // processor name (or "brand string" in Intel-speak), a general identifier.
@@ -515,20 +572,20 @@
   template <typename CpuidIoProvider>
   explicit ProcessorName(CpuidIoProvider&& io) {
     // The name string needs leaves 0x8000'0002-0x8000'0004.
-    if (io.template Read<CpuidMaximumExtendedLeaf>().reg_value() >= CpuidProcessorName4D::kLeaf) {
+    if (io.template Read<CpuidMaximumExtendedLeaf>().leaf() >= CpuidProcessorName4D::kLeaf) {
       const uint32_t values[] = {
-          io.template Read<CpuidProcessorName2A>().reg_value(),
-          io.template Read<CpuidProcessorName2B>().reg_value(),
-          io.template Read<CpuidProcessorName2C>().reg_value(),
-          io.template Read<CpuidProcessorName2D>().reg_value(),
-          io.template Read<CpuidProcessorName3A>().reg_value(),
-          io.template Read<CpuidProcessorName3B>().reg_value(),
-          io.template Read<CpuidProcessorName3C>().reg_value(),
-          io.template Read<CpuidProcessorName3D>().reg_value(),
-          io.template Read<CpuidProcessorName4A>().reg_value(),
-          io.template Read<CpuidProcessorName4B>().reg_value(),
-          io.template Read<CpuidProcessorName4C>().reg_value(),
-          io.template Read<CpuidProcessorName4D>().reg_value(),
+          io.template Read<CpuidProcessorName2A>().value(),
+          io.template Read<CpuidProcessorName2B>().value(),
+          io.template Read<CpuidProcessorName2C>().value(),
+          io.template Read<CpuidProcessorName2D>().value(),
+          io.template Read<CpuidProcessorName3A>().value(),
+          io.template Read<CpuidProcessorName3B>().value(),
+          io.template Read<CpuidProcessorName3C>().value(),
+          io.template Read<CpuidProcessorName3D>().value(),
+          io.template Read<CpuidProcessorName4A>().value(),
+          io.template Read<CpuidProcessorName4B>().value(),
+          io.template Read<CpuidProcessorName4C>().value(),
+          io.template Read<CpuidProcessorName4D>().value(),
       };
       static_assert(kSize == sizeof(values));
       memcpy(str_.data(), values, kSize);
diff --git a/zircon/kernel/lib/arch/test/cpuid-corpus-tests.cc b/zircon/kernel/lib/arch/test/cpuid-corpus-tests.cc
index 10ee96f..6f4d90b 100644
--- a/zircon/kernel/lib/arch/test/cpuid-corpus-tests.cc
+++ b/zircon/kernel/lib/arch/test/cpuid-corpus-tests.cc
@@ -546,7 +546,7 @@
   auto hypervisor = arch::HypervisorName(cpuid);
   EXPECT_TRUE(hypervisor.name() == "VBoxVBoxVBox"sv);
 
-  EXPECT_EQ(0x4000'0006, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().reg_value());
+  EXPECT_EQ(0x4000'0006, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().leaf());
 
   {
     auto features = cpuid.Read<arch::CpuidFeatureFlagsC>();
@@ -596,7 +596,7 @@
   auto hypervisor = arch::HypervisorName(cpuid);
   EXPECT_TRUE(hypervisor.name() == "KVMKVMKVM"sv);
 
-  EXPECT_EQ(0x4000'0001, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().reg_value());
+  EXPECT_EQ(0x4000'0001, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().leaf());
 
   {
     auto features = cpuid.Read<arch::CpuidFeatureFlagsC>();
@@ -646,7 +646,7 @@
   auto hypervisor = arch::HypervisorName(cpuid);
   EXPECT_TRUE(hypervisor.name() == "VMwareVMware"sv);
 
-  EXPECT_EQ(0x4000'0010, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().reg_value());
+  EXPECT_EQ(0x4000'0010, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().leaf());
 
   {
     auto features = cpuid.Read<arch::CpuidFeatureFlagsC>();
@@ -694,7 +694,7 @@
   auto hypervisor = arch::HypervisorName(cpuid);
   EXPECT_TRUE(hypervisor.name() == "Microsoft Hv"sv);
 
-  EXPECT_EQ(0x4000'000b, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().reg_value());
+  EXPECT_EQ(0x4000'000b, cpuid.Read<arch::CpuidMaximumHypervisorLeaf>().leaf());
 
   {
     auto features = cpuid.Read<arch::CpuidFeatureFlagsC>();
diff --git a/zircon/kernel/lib/arch/test/fake-cpuid-tests.cc b/zircon/kernel/lib/arch/test/fake-cpuid-tests.cc
index 946489b..e848a7b 100644
--- a/zircon/kernel/lib/arch/test/fake-cpuid-tests.cc
+++ b/zircon/kernel/lib/arch/test/fake-cpuid-tests.cc
@@ -59,7 +59,7 @@
 
   // Read should be a shortcut to reading our the value type.
   EXPECT_EQ(0x0000'0014, io->values_[arch::CpuidIo::kEax]);
-  EXPECT_EQ(0x0000'0014, cpuid.Read<arch::CpuidMaximumLeaf>().reg_value());
+  EXPECT_EQ(0x0000'0014, cpuid.Read<arch::CpuidMaximumLeaf>().leaf());
 }
 
 TEST(FakeCpuidIoTests, PopulateOverwrites) {
diff --git a/zircon/kernel/lib/arch/testing/include/lib/arch/testing/x86/fake-cpuid.h b/zircon/kernel/lib/arch/testing/include/lib/arch/testing/x86/fake-cpuid.h
index c4a315f..a427656 100644
--- a/zircon/kernel/lib/arch/testing/include/lib/arch/testing/x86/fake-cpuid.h
+++ b/zircon/kernel/lib/arch/testing/include/lib/arch/testing/x86/fake-cpuid.h
@@ -17,25 +17,26 @@
 
 // FakeCpuidIo is a fake analogue to arch::BootCpuidIo, which may be provided
 // in its place for tests - in the kernel and on host - for logic templated
-// on any type the interface contract of the latter. Using `Populate`, test
-// authors can provide dummy data for specific (sub)leaves.
+// on any type the interface contract of the latter. A "CPUID I/O provider",
+// FakeCpuidIo's methods are expected to be instantiated by "CPUID value
+// types", defined in <lib/arch/x86/cpuid.h>.
+//
+// Using `Populate`, test authors can provide dummy data for specific
+// (sub)leaves.
 //
 // FakeCpuidIo is immovable and non-copyable; it is expected to be passed
 // around by const reference.
 class FakeCpuidIo {
  public:
   // Returns the cached CpuidIo object corresponding to the particular CPUID
-  // register type. This method mirrors that of arch::BootCpuidIo and is
-  // required to meet its interface contract.
+  // register type.
   template <typename CpuidValue>
   const CpuidIo* Get() const {
     return Get(CpuidValue::kLeaf, CpuidValue::kSubleaf);
   }
 
   // A convenience method to directly read a particular CPUID register type in
-  // consultation with the associated cached CpuidIo objects. This method
-  // mirrors that of arch::BootCpuidIo and is required to meet its interface
-  // contract.
+  // consultation with the associated cached CpuidIo objects.
   template <typename CpuidValue>
   auto Read() const {
     return CpuidValue::Get().ReadFrom(Get<CpuidValue>());
diff --git a/zircon/kernel/lib/arch/x86/include/lib/arch/x86/boot-cpuid.h b/zircon/kernel/lib/arch/x86/include/lib/arch/x86/boot-cpuid.h
index 6ae43c0..3e1fe4a 100644
--- a/zircon/kernel/lib/arch/x86/include/lib/arch/x86/boot-cpuid.h
+++ b/zircon/kernel/lib/arch/x86/include/lib/arch/x86/boot-cpuid.h
@@ -54,14 +54,32 @@
 
 }  // namespace internal
 
-// This can be instantiated for any type <lib/arch/x86/cpuid.h> defines.
+// A "CPUID I/O provider", BootCpuidIo's methods are expected to be
+// instantiated by "CPUID value types", defined in <lib/arch/x86/cpuid.h>.
+//
 // `BootCpuidIo<T>{}.Get()` returns a `const arch::CpuidIo*` that can be used
-// with the `hwreg` objects from `T::Get()`.  InitializeBootCpuid() fills in
+// with the `hwreg` objects from `T::Get()`. InitializeBootCpuid() fills in
 // the data for all the instantiations linked in.
 //
 // This template can be used as a parameter for template functions, e.g.
 // `arch::GetVendor(BootCpuidIo{})`.
-struct BootCpuidIo {
+class BootCpuidIo {
+ public:
+  // Most often just Get<Type> is used instead to reach a particular (sub)leaf.
+  // Multiple different CpuidValue types reach the same (sub)leaf, usually one
+  // type for each of the four registers.
+  template <typename CpuidValue>
+  const CpuidIo* Get() const {
+    return GetLeaf<CpuidValue::kLeaf, CpuidValue::kSubleaf>();
+  }
+
+  // Convenience accessor for the common case.
+  template <typename CpuidValue>
+  auto Read() const {
+    return CpuidValue::Get().ReadFrom(Get<CpuidValue>());
+  }
+
+ private:
   // The underlying instantiation is indexed by leaf and subleaf.
   template <uint32_t Leaf, uint32_t Subleaf = 0>
   const CpuidIo* GetLeaf() const {
@@ -101,20 +119,6 @@
 #endif  // __clang__
     return &gCpuidIo;
   }
-
-  // Most often just Get<Type> is used instead to reach a particular (sub)leaf.
-  // Multiple different CpuidValue types reach the same (sub)leaf, usually one
-  // type for each of the four registers.
-  template <typename CpuidValue>
-  const CpuidIo* Get() const {
-    return GetLeaf<CpuidValue::kLeaf, CpuidValue::kSubleaf>();
-  }
-
-  // Convenience accessor for the common case.
-  template <typename CpuidValue>
-  auto Read() const {
-    return CpuidValue::Get().ReadFrom(Get<CpuidValue>());
-  }
 };
 
 // Call this once early in startup, before any uses of arch::BootCpuIdIo.  It
diff --git a/zircon/system/ulib/fidl/BUILD.zircon.gn b/zircon/system/ulib/fidl/BUILD.zircon.gn
index 727a9e1..43b9460 100644
--- a/zircon/system/ulib/fidl/BUILD.zircon.gn
+++ b/zircon/system/ulib/fidl/BUILD.zircon.gn
@@ -139,6 +139,9 @@
 
       # <lib/fidl/llcpp/traits.h> has #include <lib/zx/object.h>.
       "$zx/system/ulib/zx:headers",
+
+      # <lib/fidl/llcpp/connect_service.h> has #include <lib/zx/status.h>.
+      "$zx/system/ulib/zxc:headers",
     ]
     deps = [
       ":fidl",
@@ -147,6 +150,7 @@
       "$zx/system/ulib/fit",
       "$zx/system/ulib/sync",
       "$zx/system/ulib/zircon",
+      "$zx/system/ulib/zxc",
     ]
   }
 } else {
@@ -175,12 +179,16 @@
       "llcpp_coding.c",
       "llcpp_message.cc",
     ]
-    public_deps = [ "$zx/system/ulib/zircon:headers" ]
+    public_deps = [
+      "$zx/system/ulib/zircon:headers",
+      "$zx/system/ulib/zxc:headers",
+    ]
     deps = [
       ":fidl_base",
       "$zx/system/ulib/fbl",
       "$zx/system/ulib/fit",
       "$zx/system/ulib/zircon",
+      "$zx/system/ulib/zxc",
     ]
   }
 }
diff --git a/zircon/system/ulib/fidl/include/lib/fidl/llcpp/connect_service.h b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/connect_service.h
index dcc8697..daafb46 100644
--- a/zircon/system/ulib/fidl/include/lib/fidl/llcpp/connect_service.h
+++ b/zircon/system/ulib/fidl/include/lib/fidl/llcpp/connect_service.h
@@ -5,12 +5,15 @@
 #ifndef LIB_FIDL_LLCPP_CONNECT_SERVICE_H_
 #define LIB_FIDL_LLCPP_CONNECT_SERVICE_H_
 
+#include <lib/fidl/llcpp/client_end.h>
+#include <lib/fidl/llcpp/server_end.h>
 #include <lib/fidl/llcpp/string_view.h>
 #include <lib/fit/result.h>
 #include <zircon/fidl.h>
 
 #ifdef __Fuchsia__
 #include <lib/zx/channel.h>
+#include <lib/zx/status.h>
 #endif  // __Fuchsia__
 
 namespace fidl {
@@ -22,6 +25,7 @@
 
 #ifdef __Fuchsia__
 
+// TODO(fxbug.dev/65212): ClientChannel may be replaced by fidl::ClientEnd.
 // A wrapper around a Zircon channel, strongly-typed on a FIDL protocol.
 template <typename FidlProtocol>
 class ClientChannel final {
@@ -46,6 +50,44 @@
   return typename FidlProtocol::SyncClient(channel.take_channel());
 }
 
+template <typename Protocol>
+struct Endpoints {
+  fidl::ClientEnd<Protocol> client;
+  fidl::ServerEnd<Protocol> server;
+};
+
+// Creates a pair of Zircon channel endpoints speaking the |Protocol| protocol.
+// Whenever interacting with LLCPP, using this method should be encouraged over
+// |zx::channel::create|, because this method encodes the precise protocol type
+// into its results at compile time.
+//
+// The return value is a result type wrapping the client and server endpoints.
+// Given the following:
+//
+//     auto endpoints = fidl::CreateEndpoints<MyProtocol>();
+//
+// The caller should first ensure that |endpoints.is_ok() == true|, after which
+// the channel endpoints may be accessed in one of two ways:
+//
+// - Direct:
+//     endpoints->client;
+//     endpoints->server;
+//
+// - Structured Binding:
+//     auto [client_end, server_end] = std::move(endpoints.value());
+template <typename Protocol>
+zx::status<Endpoints<Protocol>> CreateEndpoints() {
+  zx::channel local, remote;
+  zx_status_t status = zx::channel::create(0, &local, &remote);
+  if (status != ZX_OK) {
+    return zx::error_status(status);
+  }
+  return zx::ok(Endpoints<Protocol>{
+      fidl::ClientEnd<Protocol>(std::move(local)),
+      fidl::ServerEnd<Protocol>(std::move(remote)),
+  });
+}
+
 namespace internal {
 
 // The method signature required to implement the method that issues the Directory::Open
diff --git a/zircon/system/ulib/zbitl/include/lib/zbitl/items/mem_config.h b/zircon/system/ulib/zbitl/include/lib/zbitl/items/mem_config.h
index 6b6c23a..0977364 100644
--- a/zircon/system/ulib/zbitl/include/lib/zbitl/items/mem_config.h
+++ b/zircon/system/ulib/zbitl/include/lib/zbitl/items/mem_config.h
@@ -109,6 +109,103 @@
   View<ByteView> view_;
 };
 
+// Takes an iterator yielding a sorted list of zbi_mem_range_t items, and merges
+// together contiguous ranges of the same type.
+template <typename Iterator>
+class MemRangeMerger {
+ public:
+  MemRangeMerger() = default;
+  MemRangeMerger(Iterator begin, Iterator end) : begin_(begin), end_(end) {}
+
+  class iterator;
+  iterator begin() const { return iterator(begin_, end_); }
+  iterator end() const { return iterator(end_, end_); }
+
+  class iterator {
+   public:
+    iterator(Iterator begin, Iterator end) : it_(begin), next_(begin), end_(end) { Next(); }
+
+    // Iterator traits.
+    using iterator_category = std::input_iterator_tag;
+    using reference = const zbi_mem_range_t&;
+    using value_type = zbi_mem_range_t;
+    using pointer = const zbi_mem_range_t*;
+    using difference_type = size_t;
+
+    // Equality / inequality.
+    bool operator==(const iterator& other) const { return it_ == other.it_; }
+    bool operator!=(const iterator& other) const { return !(*this == other); }
+
+    // Return the current element.
+    const zbi_mem_range_t& operator*() const {
+      ZX_DEBUG_ASSERT_MSG(it_ != end_, "Attempted to dereference 'end' iterator.");
+      return current_;
+    }
+    const zbi_mem_range_t* operator->() {
+      ZX_DEBUG_ASSERT_MSG(it_ != end_, "Attempted to dereference 'end' iterator.");
+      return &current_;
+    }
+
+    // Increment operators: move iterator to next element.
+    iterator& operator++() {  // prefix
+      Next();
+      return *this;
+    }
+    iterator operator++(int) {  // postfix
+      iterator result = *this;
+      ++this;
+      return result;
+    }
+
+   private:
+    // Ensure iterator type is valid.
+    static_assert(std::is_same_v<zbi_mem_range_t, typename Iterator::value_type>,
+                  "Expected an iterator of type zbi_mem_range_t.");
+
+    // Fill `current_` with the next merged range.
+    void Next() {
+      // If we are at the end, do nothing.
+      it_ = next_;
+      if (it_ == end_) {
+        return;
+      }
+
+      // Keep merging entries together until we hit the end of our input
+      // or hit a discontinuity.
+      current_ = *next_;
+      ++next_;
+      while (next_ != end_) {
+        zbi_mem_range_t next = *next_;
+        // Ensure the end of this region is the start of the next.
+        if (current_.paddr + current_.length != next.paddr) {
+          break;
+        }
+        // Ensure the type of this region matches the next.
+        if (current_.type != next.type) {
+          break;
+        }
+        // Increase the size of this region.
+        current_.length += next.length;
+        ++next_;
+      }
+    }
+
+    // The merged memory range.
+    zbi_mem_range_t current_;
+
+    // The pair [it_, next_) represent the range of items merged into current_.
+    Iterator it_;
+    Iterator next_;
+
+    // The end_ iterator of the underlying container.
+    Iterator end_;
+  };
+
+ private:
+  Iterator begin_;
+  Iterator end_;
+};
+
 }  // namespace zbitl
 
 #endif  // LIB_ZBITL_ITEMS_MEM_CONFIG_H_
diff --git a/zircon/system/ulib/zbitl/test/mem_config_test.cc b/zircon/system/ulib/zbitl/test/mem_config_test.cc
index d78a191..79e96f2 100644
--- a/zircon/system/ulib/zbitl/test/mem_config_test.cc
+++ b/zircon/system/ulib/zbitl/test/mem_config_test.cc
@@ -528,4 +528,124 @@
       ranges.back()));
 }
 
+TEST(MemRangeMerger, Empty) {
+  std::vector<zbi_mem_range_t> input{};
+  zbitl::MemRangeMerger merger(input.begin(), input.end());
+  EXPECT_EQ(merger.begin(), merger.end());
+}
+
+TEST(MemRangeMerger, SingleItem) {
+  // Create an input with just a single range.
+  std::vector<zbi_mem_range_t> input = {
+      zbi_mem_range_t{
+          .paddr = 1,
+          .length = 2,
+          .type = 3,
+      },
+  };
+  zbitl::MemRangeMerger merger(input.begin(), input.end());
+  EXPECT_NE(merger.begin(), merger.end());
+
+  // Check first element.
+  auto it = merger.begin();
+  EXPECT_EQ(it->paddr, 1u);
+  EXPECT_EQ(it->length, 2u);
+  EXPECT_EQ(it->type, 3u);
+
+  // Should be no more elements.
+  ++it;
+  EXPECT_EQ(it, merger.end());
+}
+
+TEST(MemRangeMerger, MergeItems) {
+  // Create an input with multiple ranges to be merged.
+  std::vector<zbi_mem_range_t> input = {
+      zbi_mem_range_t{
+          .paddr = 0,
+          .length = 100,
+          .type = 1,
+      },
+      zbi_mem_range_t{
+          .paddr = 100,
+          .length = 200,
+          .type = 1,
+      },
+      zbi_mem_range_t{
+          .paddr = 300,
+          .length = 100,
+          .type = 1,
+      },
+  };
+  zbitl::MemRangeMerger merger(input.begin(), input.end());
+
+  // Merge the items.
+  std::vector<zbi_mem_range_t> result(merger.begin(), merger.end());
+
+  // Ensure we got the correctly merged results.
+  ASSERT_EQ(result.size(), 1u);
+  EXPECT_EQ(result[0].paddr, 0u);
+  EXPECT_EQ(result[0].length, 400u);
+  EXPECT_EQ(result[0].type, 1u);
+}
+
+TEST(MemRangeMerger, ShouldNotCombineNonContiguousItems) {
+  // Ensure we don't merge non-contiguous items.
+  std::vector<zbi_mem_range_t> input = {
+      zbi_mem_range_t{
+          .paddr = 0,
+          .length = 1,
+          .type = 1,
+      },
+      zbi_mem_range_t{
+          .paddr = 2,  // skips byte 1; should not be merged.
+          .length = 1,
+          .type = 1,
+      },
+      zbi_mem_range_t{
+          .paddr = 3,  // not the same type; should not be merged.
+          .length = 1,
+          .type = 2,
+      },
+  };
+  zbitl::MemRangeMerger merger(input.begin(), input.end());
+
+  // Merge the items.
+  std::vector<zbi_mem_range_t> result(merger.begin(), merger.end());
+
+  // Ensure the input matches the output.
+  ASSERT_EQ(result.size(), input.size());
+  for (size_t i = 0; i < input.size(); i++) {
+    EXPECT_EQ(input[i].paddr, result[i].paddr);
+    EXPECT_EQ(input[i].length, result[i].length);
+    EXPECT_EQ(input[i].type, result[i].type);
+  }
+}
+
+TEST(MemRangeMerger, MemRangeTableIterator) {
+  // Create a MemRangeTable, and use it as our input.
+  ZbiMemoryImage zbi = CreateImage();
+  AppendPayload(zbi, ZBI_TYPE_EFI_MEMORY_MAP,
+                JoinBytes(uint64_t{sizeof(efi_memory_descriptor)},
+                          efi_memory_descriptor{
+                              .Type = 1,
+                              .PhysicalStart = 0x1000,
+                              .NumberOfPages = 1,
+                          },
+                          efi_memory_descriptor{
+                              .Type = 1,
+                              .PhysicalStart = 0x2000,
+                              .NumberOfPages = 2,
+                          }));
+
+  // Merge elements together.
+  zbitl::MemRangeTable container{AsView(zbi)};
+  zbitl::MemRangeMerger merger(container.begin(), container.end());
+  std::vector<zbi_mem_range_t> ranges(merger.begin(), merger.end());
+  ASSERT_TRUE(container.take_error().is_ok());
+  ASSERT_EQ(ranges.size(), 1u);
+  EXPECT_EQ(ranges[0].paddr, 0x1000u);
+  EXPECT_EQ(ranges[0].length, 0x3000u);
+  EXPECT_EQ(ranges[0].type, 1u);
+}
+
 }  // namespace