diff --git a/zircon/system/core/devmgr/devcoordinator/composite-device.cpp b/zircon/system/core/devmgr/devcoordinator/composite-device.cpp
index 02e09885eb047da12a81d86d264d4894274d4044..87836333d934930ed90ab2fc4aa7f900910f16b6 100644
--- a/zircon/system/core/devmgr/devcoordinator/composite-device.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/composite-device.cpp
@@ -5,6 +5,7 @@
 #include "composite-device.h"
 
 #include <utility>
+#include <zircon/status.h>
 #include "../shared/log.h"
 #include "binding-internal.h"
 #include "coordinator.h"
@@ -14,13 +15,9 @@ namespace devmgr {
 // CompositeDevice methods
 
 CompositeDevice::CompositeDevice(fbl::String name, fbl::Array<const zx_device_prop_t> properties,
-                                 uint32_t coresident_device_index)
+                                 uint32_t components_count, uint32_t coresident_device_index)
     : name_(std::move(name)), properties_(std::move(properties)),
-      coresident_device_index_(coresident_device_index) {
-    // TODO(teisenbe): Remove this when the index is used elsewhere.  Clang and
-    // GCC do not agree on whether this is unused, so either tagging it as used
-    // or unused causes one of them to error.
-    (void)coresident_device_index_;
+      components_count_(components_count), coresident_device_index_(coresident_device_index) {
 }
 
 CompositeDevice::~CompositeDevice() = default;
@@ -30,13 +27,17 @@ zx_status_t CompositeDevice::Create(const fbl::StringPiece& name,
                                     const fuchsia_device_manager_DeviceComponent* components,
                                     size_t components_count, uint32_t coresident_device_index,
                                     std::unique_ptr<CompositeDevice>* out) {
+    if (components_count > UINT32_MAX) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
     fbl::String name_obj(name);
     fbl::Array<zx_device_prop_t> properties(new zx_device_prop_t[props_count], props_count);
     memcpy(properties.get(), props_data, props_count * sizeof(props_data[0]));
 
     auto dev = std::make_unique<CompositeDevice>(std::move(name), std::move(properties),
-                                                 coresident_device_index);
-    for (size_t i = 0; i < components_count; ++i) {
+                                                 components_count, coresident_device_index);
+    for (uint32_t i = 0; i < components_count; ++i) {
         const auto& fidl_component = components[i];
         size_t parts_count = fidl_component.parts_count;
         fbl::Array<ComponentPartDescriptor> parts(new ComponentPartDescriptor[parts_count],
@@ -118,6 +119,9 @@ zx_status_t CompositeDevice::TryAssemble() {
         }
     }
 
+    Coordinator* coordinator = nullptr;
+    uint64_t component_local_ids[fuchsia_device_manager_COMPONENTS_MAX] = {};
+
     // Create all of the proxies for the component devices, in the same process
     for (auto& component : bound_) {
         const fbl::RefPtr<Device>& dev = component.component_device();
@@ -128,7 +132,8 @@ zx_status_t CompositeDevice::TryAssemble() {
             log(ERROR, "devcoordinator: cannot create composite, proxies in different processes\n");
             return ZX_ERR_BAD_STATE;
         }
-        zx_status_t status = dev->coordinator->PrepareProxy(dev, devhost);
+        coordinator = dev->coordinator;
+        zx_status_t status = coordinator->PrepareProxy(dev, devhost);
         if (status != ZX_OK) {
             return status;
         }
@@ -137,9 +142,39 @@ zx_status_t CompositeDevice::TryAssemble() {
             devhost = dev->proxy->host();
             ZX_ASSERT(devhost != nullptr);
         }
+        // Stash the local ID after the proxy has been created
+        component_local_ids[component.index()] = dev->proxy->local_id();
+    }
+
+    zx::channel rpc_local, rpc_remote;
+    zx_status_t status = zx::channel::create(0, &rpc_local, &rpc_remote);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    fbl::RefPtr<Device> new_device;
+    status = Device::CreateComposite(coordinator, devhost, *this, std::move(rpc_local),
+                                     &new_device);
+    if (status != ZX_OK) {
+        return status;
     }
-    // TODO: Create the composite device and wire everything up
-    return ZX_ERR_NOT_SUPPORTED;
+    coordinator->devices().push_back(new_device);
+
+    // Create the composite device in the devhost
+    status = dh_send_create_composite_device(devhost, new_device.get(), *this, component_local_ids,
+                                             std::move(rpc_remote));
+    if (status != ZX_OK) {
+        log(ERROR, "devcoordinator: create composite device request failed: %s\n",
+            zx_status_get_string(status));
+        return status;
+    }
+
+    status = new_device->SignalReadyForBind();
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    return ZX_OK;
 }
 
 // CompositeDeviceComponent methods
diff --git a/zircon/system/core/devmgr/devcoordinator/composite-device.h b/zircon/system/core/devmgr/devcoordinator/composite-device.h
index b4b575d5e323623663690e54c226157d1a0cd429..2f4e142666bbe3312f3538714caea520c3077771 100644
--- a/zircon/system/core/devmgr/devcoordinator/composite-device.h
+++ b/zircon/system/core/devmgr/devcoordinator/composite-device.h
@@ -92,7 +92,7 @@ class CompositeDevice {
 public:
     // Only public because of make_unique.  You probably want Create().
     CompositeDevice(fbl::String name, fbl::Array<const zx_device_prop_t> properties,
-                    uint32_t coresident_device_index);
+                    uint32_t components_count, uint32_t coresident_device_index);
 
     CompositeDevice(CompositeDevice&&) = delete;
     CompositeDevice& operator=(CompositeDevice&&) = delete;
@@ -114,6 +114,7 @@ public:
     const fbl::Array<const zx_device_prop_t>& properties() const {
         return properties_;
     }
+    uint32_t components_count() const { return components_count_; }
 
     // Attempt to match any of the unbound components against |dev|.  Returns true
     // if a component was match.  |*component_out| will be set to the index of
@@ -141,6 +142,7 @@ private:
 
     const fbl::String name_;
     const fbl::Array<const zx_device_prop_t> properties_;
+    const uint32_t components_count_;
     const uint32_t coresident_device_index_;
 
     ComponentList unbound_;
diff --git a/zircon/system/core/devmgr/devcoordinator/coordinator.cpp b/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
index 9c2fbaa712cd5c5e90989783dde3eec65eae6ced..1ffb4960e679d931a8e196c2127f37c856ba70f4 100644
--- a/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
@@ -929,10 +929,7 @@ zx_status_t Coordinator::AddCompositeDevice(
     return ZX_OK;
 
     // TODO:
-    // - Logic for creating the new bindpoint once the bookkeeping finds
-    //   everything is ready
     // - Logic for sending an unbind() when some component goes away
-    // - Implementation of Composite Banjo protocol
     //
     // Tests to write:
     // - Introducing a composite device when all components are already ready
diff --git a/zircon/system/core/devmgr/devcoordinator/coordinator.h b/zircon/system/core/devmgr/devcoordinator/coordinator.h
index 299776c118f5e1be7e34adca4545fac53b8d2e70..c98632666a5152ea9749e39e38ac85f7732fc3fc 100644
--- a/zircon/system/core/devmgr/devcoordinator/coordinator.h
+++ b/zircon/system/core/devmgr/devcoordinator/coordinator.h
@@ -338,5 +338,8 @@ zx_status_t dh_send_create_device_stub(Device* dev, Devhost* dh, zx::channel rpc
 zx_status_t dh_send_bind_driver(const Device* dev, const char* libname, zx::vmo driver);
 zx_status_t dh_send_connect_proxy(const Device* dev, zx::channel proxy);
 zx_status_t dh_send_suspend(const Device* dev, uint32_t flags);
+zx_status_t dh_send_create_composite_device(Devhost* dh, const Device* composite_dev,
+                                            const CompositeDevice& composite,
+                                            const uint64_t* component_local_ids, zx::channel rpc);
 
 } // namespace devmgr
diff --git a/zircon/system/core/devmgr/devcoordinator/device.cpp b/zircon/system/core/devmgr/devcoordinator/device.cpp
index 337ede8910126dd382ccfb2c01c61819f8e6fd9c..a83e2240986fdc9cce1292d67b5a856d8f57c5bc 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/device.cpp
@@ -4,6 +4,7 @@
 
 #include "device.h"
 
+#include <ddk/driver.h>
 #include "../shared/log.h"
 #include "coordinator.h"
 #include "devfs.h"
@@ -107,6 +108,51 @@ zx_status_t Device::Create(Coordinator* coordinator, const fbl::RefPtr<Device>&
     return ZX_OK;
 }
 
+zx_status_t Device::CreateComposite(Coordinator* coordinator, Devhost* devhost,
+                                    const CompositeDevice& composite, zx::channel rpc,
+                                    fbl::RefPtr<Device>* device) {
+    const auto& composite_props = composite.properties();
+    fbl::Array<zx_device_prop_t> props(new zx_device_prop_t[composite_props.size()],
+                                       composite_props.size());
+    memcpy(props.get(), composite_props.get(), props.size() * sizeof(props[0]));
+
+    auto dev = fbl::MakeRefCounted<Device>(coordinator);
+    if (!dev) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    zx_status_t status = dev->SetProps(std::move(props));
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    dev->name = composite.name();
+    dev->set_channel(std::move(rpc));
+    dev->set_protocol_id(ZX_PROTOCOL_COMPOSITE);
+    // We exist within our parent's device host
+    dev->set_host(devhost);
+
+    // TODO: Record composite membership
+
+    // TODO(teisenbe): Figure out how to manifest in devfs?  For now just hang it off of
+    // the root device?
+    if ((status = devfs_publish(coordinator->root_device(), dev)) < 0) {
+        return status;
+    }
+
+    if ((status = Device::BeginWait(dev, coordinator->dispatcher())) != ZX_OK) {
+        return status;
+    }
+
+    dev->host_->AddRef();
+    dev->host_->devices().push_back(dev.get());
+
+    log(DEVLC, "devcoordinator: composite dev created %p name='%s'\n", dev.get(), dev->name.data());
+
+    *device = std::move(dev);
+    return ZX_OK;
+}
+
 zx_status_t Device::CreateProxy() {
     ZX_ASSERT(this->proxy == nullptr);
 
diff --git a/zircon/system/core/devmgr/devcoordinator/device.h b/zircon/system/core/devmgr/devcoordinator/device.h
index 9da6b3e478f12838919e55803c9598c439fcd4b7..f1c45cabffb67291b4bc7a0d19f9047366fc0e95 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.h
+++ b/zircon/system/core/devmgr/devcoordinator/device.h
@@ -17,6 +17,7 @@
 
 namespace devmgr {
 
+class CompositeDevice;
 class CompositeDeviceComponent;
 class Coordinator;
 class Devhost;
@@ -66,6 +67,9 @@ struct Device : public fbl::RefCounted<Device>, public AsyncLoopRefCountedRpcHan
                               uint32_t protocol_id, fbl::Array<zx_device_prop_t> props,
                               zx::channel rpc, bool invisible, zx::channel client_remote,
                               fbl::RefPtr<Device>* device);
+    static zx_status_t CreateComposite(Coordinator* coordinator, Devhost* devhost,
+                                       const CompositeDevice& composite, zx::channel rpc,
+                                       fbl::RefPtr<Device>* device);
     zx_status_t CreateProxy();
 
     static void HandleRpc(fbl::RefPtr<Device>&& dev, async_dispatcher_t* dispatcher,
diff --git a/zircon/system/core/devmgr/devcoordinator/fidl.cpp b/zircon/system/core/devmgr/devcoordinator/fidl.cpp
index bb08a7d07b32216bece37a5d0674fb37e75460dd..ee8b26dc4e3eab5337d7a696f62f36b6020e0bd6 100644
--- a/zircon/system/core/devmgr/devcoordinator/fidl.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/fidl.cpp
@@ -150,4 +150,43 @@ zx_status_t dh_send_suspend(const Device* dev, uint32_t flags) {
     return msg.Write(dev->channel()->get(), 0);
 }
 
+zx_status_t dh_send_create_composite_device(Devhost* dh, const Device* composite_dev,
+                                            const CompositeDevice& composite,
+                                            const uint64_t* component_local_ids, zx::channel rpc) {
+    size_t components_size = composite.components_count() * sizeof(uint64_t);
+    size_t name_size = composite.name().size();
+    uint32_t wr_num_bytes = static_cast<uint32_t>(
+            sizeof(fuchsia_device_manager_ControllerCreateCompositeDeviceRequest) +
+            FIDL_ALIGN(components_size) + FIDL_ALIGN(name_size));
+    FIDL_ALIGNDECL char wr_bytes[wr_num_bytes];
+    fidl::Builder builder(wr_bytes, wr_num_bytes);
+
+    auto req = builder.New<fuchsia_device_manager_ControllerCreateCompositeDeviceRequest>();
+    uint64_t* components_data = builder.NewArray<uint64_t>(
+            static_cast<uint32_t>(composite.components_count()));
+    char* name_data = builder.NewArray<char>(static_cast<uint32_t>(name_size));
+    ZX_ASSERT(req != nullptr && components_data != nullptr && name_data != nullptr);
+    req->hdr.ordinal = fuchsia_device_manager_ControllerCreateCompositeDeviceOrdinal;
+    // TODO(teisenbe): Allocate and track txids
+    req->hdr.txid = 1;
+
+    req->rpc = FIDL_HANDLE_PRESENT;
+
+    req->components.count = composite.components_count();
+    req->components.data = reinterpret_cast<char*>(FIDL_ALLOC_PRESENT);
+    memcpy(components_data, component_local_ids, components_size);
+
+    req->name.size = name_size;
+    req->name.data = reinterpret_cast<char*>(FIDL_ALLOC_PRESENT);
+    memcpy(name_data, composite.name().data(), name_size);
+
+    req->local_device_id = composite_dev->local_id();
+
+    zx_handle_t handles[1] = {rpc.release()};
+    uint32_t num_handles = 1;
+
+    fidl::Message msg(builder.Finalize(), fidl::HandlePart(handles, num_handles, num_handles));
+    return msg.Write(dh->hrpc(), 0);
+}
+
 } // namespace devmgr
diff --git a/zircon/system/core/devmgr/devhost/BUILD.gn b/zircon/system/core/devmgr/devhost/BUILD.gn
index 356d28374cd59c389563997922f2848ac33b6342..a328c7e803f624439187e572e6aaadd5dfaac459 100644
--- a/zircon/system/core/devmgr/devhost/BUILD.gn
+++ b/zircon/system/core/devmgr/devhost/BUILD.gn
@@ -19,6 +19,7 @@ library("driver") {
   static = false
   sources = [
     "api.cpp",
+    "composite-device.cpp",
     "core.cpp",
     "devhost.cpp",
     "rpc-server.cpp",
@@ -27,6 +28,7 @@ library("driver") {
   ]
   configs += [ "$zx/public/gn/config:visibility_hidden" ]
   deps = [
+    "$zx/system/banjo/ddk-protocol-composite",
     "$zx/system/fidl/fuchsia-device:c",
     "$zx/system/fidl/fuchsia-device-manager:c",
     "$zx/system/fidl/fuchsia-io:c",
@@ -42,8 +44,8 @@ library("driver") {
     "$zx/system/ulib/sync",
     "$zx/system/ulib/trace:trace-driver",
     "$zx/system/ulib/trace-provider:static",
-    "$zx/system/ulib/zircon-internal",
     "$zx/system/ulib/zircon",
+    "$zx/system/ulib/zircon-internal",
     "$zx/system/ulib/zx",
     "$zx/system/ulib/zxcpp",
     "$zx/system/ulib/zxio",
diff --git a/zircon/system/core/devmgr/devhost/composite-device.cpp b/zircon/system/core/devmgr/devhost/composite-device.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..62d228a05247b071aa1a0b656d95778aeb99e4e8
--- /dev/null
+++ b/zircon/system/core/devmgr/devhost/composite-device.cpp
@@ -0,0 +1,88 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "composite-device.h"
+
+#include <algorithm>
+#include <ddk/protocol/composite.h>
+#include "device-internal.h"
+
+namespace devmgr {
+
+namespace {
+
+class CompositeDevice {
+public:
+    CompositeDevice(zx_device_t* zxdev, CompositeComponents&& components)
+            : zxdev_(zxdev), components_(std::move(components)) { }
+
+    static zx_status_t Create(zx_device_t* zxdev, CompositeComponents&& components,
+                              std::unique_ptr<CompositeDevice>* device) {
+        auto dev = std::make_unique<CompositeDevice>(zxdev, std::move(components));
+        *device = std::move(dev);
+        return ZX_OK;
+    }
+
+    uint32_t GetComponentCount() {
+        return static_cast<uint32_t>(components_.size());
+    }
+
+    void GetComponents(zx_device_t** comp_list, size_t comp_count, size_t* comp_actual) {
+        size_t actual = std::min(comp_count, components_.size());
+        for (size_t i = 0; i < actual; ++i) {
+            comp_list[i] = components_[i].get();
+        }
+        *comp_actual = actual;
+    }
+
+    void Release() {
+        delete this;
+    }
+
+    void Unbind() {
+        device_remove(zxdev_);
+    }
+private:
+    zx_device_t* zxdev_;
+    const CompositeComponents components_;
+};
+
+} // namespace
+
+zx_status_t InitializeCompositeDevice(const fbl::RefPtr<zx_device>& dev,
+                                      CompositeComponents&& components) {
+    static const zx_protocol_device_t composite_device_ops = []() {
+        zx_protocol_device_t ops = {};
+        ops.unbind = [](void* ctx) { static_cast<CompositeDevice*>(ctx)->Unbind(); };
+        ops.release = [](void* ctx) { static_cast<CompositeDevice*>(ctx)->Release(); };
+        return ops;
+    }();
+    static composite_protocol_ops_t composite_ops = []() {
+        composite_protocol_ops_t ops = {};
+        ops.get_component_count = [](void* ctx) {
+            return static_cast<CompositeDevice*>(ctx)->GetComponentCount();
+        };
+        ops.get_components = [](void* ctx, zx_device_t** comp_list, size_t comp_count,
+                                size_t* comp_actual) {
+            static_cast<CompositeDevice*>(ctx)->GetComponents(comp_list, comp_count, comp_actual);
+        };
+        return ops;
+    }();
+
+    std::unique_ptr<CompositeDevice> new_device;
+    zx_status_t status = CompositeDevice::Create(dev.get(), std::move(components), &new_device);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    dev->protocol_id = ZX_PROTOCOL_COMPOSITE;
+    dev->protocol_ops = &composite_ops;
+    dev->ops = &composite_device_ops;
+    dev->ctx = new_device.release();
+    // Flag that when this is cleaned up, we should run its release hook.
+    dev->flags |= DEV_FLAG_ADDED;
+    return ZX_OK;
+}
+
+} // namespace devmgr
diff --git a/zircon/system/core/devmgr/devhost/composite-device.h b/zircon/system/core/devmgr/devhost/composite-device.h
new file mode 100644
index 0000000000000000000000000000000000000000..aefec95c7748f5ef6cd00c87178f63a774787128
--- /dev/null
+++ b/zircon/system/core/devmgr/devhost/composite-device.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <ddk/driver.h>
+#include <fbl/array.h>
+#include <fbl/ref_ptr.h>
+#include "device-internal.h"
+
+namespace devmgr {
+
+typedef fbl::Array<fbl::RefPtr<zx_device>> CompositeComponents;
+
+// Modifies |device| to have the appropriate protocol_id, ctx, and ops tables
+// for a composite device
+zx_status_t InitializeCompositeDevice(const fbl::RefPtr<zx_device>& device,
+                                      CompositeComponents&& components);
+
+} // namespace devmgr
diff --git a/zircon/system/core/devmgr/devhost/devhost.cpp b/zircon/system/core/devmgr/devhost/devhost.cpp
index 3a3adf2bc74631e1bdbd58940f1776379ed04a7e..9fc0440f7e8e0323fcc4859b36e216b43a7589fe 100644
--- a/zircon/system/core/devmgr/devhost/devhost.cpp
+++ b/zircon/system/core/devmgr/devhost/devhost.cpp
@@ -44,6 +44,7 @@
 #include "../shared/env.h"
 #include "../shared/fidl_txn.h"
 #include "../shared/log.h"
+#include "composite-device.h"
 #include "main.h"
 #include "tracing.h"
 
@@ -477,6 +478,69 @@ static zx_status_t fidl_CreateDevice(void* raw_ctx, zx_handle_t raw_rpc,
     return ZX_OK;
 }
 
+static zx_status_t fidl_CreateCompositeDevice(void* raw_ctx, zx_handle_t raw_rpc,
+                                              const uint64_t* component_local_ids_data,
+                                              size_t component_local_ids_count,
+                                              const char* name_data, size_t name_size,
+                                              uint64_t device_local_id, fidl_txn_t* txn) {
+    auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
+    zx::channel rpc(raw_rpc);
+    fbl::StringPiece name(name_data, name_size);
+
+    log(RPC_IN, "devhost[%s] create composite device %.*s'\n", ctx->path,
+        static_cast<int>(name_size), name_data);
+
+    auto newconn = fbl::make_unique<DevcoordinatorConnection>();
+    if (!newconn) {
+        return fuchsia_device_manager_ControllerCreateCompositeDevice_reply(txn, ZX_ERR_NO_MEMORY);
+    }
+
+    // Convert the component IDs into zx_device references
+    CompositeComponents components_list(new fbl::RefPtr<zx_device>[component_local_ids_count],
+                                        component_local_ids_count);
+    {
+        // Acquire the API lock so that we don't have to worry about concurrent
+        // device removes
+        ApiAutoLock lock;
+
+        for (size_t i = 0; i < component_local_ids_count; ++i) {
+            uint64_t local_id = component_local_ids_data[i];
+            fbl::RefPtr<zx_device_t> dev = zx_device::GetDeviceFromLocalId(local_id);
+            if (dev == nullptr || (dev->flags & DEV_FLAG_DEAD)) {
+                return fuchsia_device_manager_ControllerCreateCompositeDevice_reply(
+                        txn, ZX_ERR_NOT_FOUND);
+            }
+            components_list[i] = std::move(dev);
+        }
+    }
+
+    fbl::RefPtr<zx_device_t> dev;
+    zx_status_t status = zx_device::Create(&dev);
+    if (status != ZX_OK) {
+        return fuchsia_device_manager_ControllerCreateCompositeDevice_reply(txn, status);
+    }
+    static_assert(fuchsia_device_manager_DEVICE_NAME_MAX + 1 >= sizeof(dev->name));
+    memcpy(dev->name, name_data, name_size);
+    dev->name[name_size] = 0;
+    dev->rpc = zx::unowned_channel(rpc);
+    dev->set_local_id(device_local_id);
+
+    status = InitializeCompositeDevice(dev, std::move(components_list));
+    if (status != ZX_OK) {
+        return fuchsia_device_manager_ControllerCreateCompositeDevice_reply(txn, status);
+    }
+
+    newconn->dev = dev;
+
+    newconn->set_channel(std::move(rpc));
+    log(RPC_IN, "devhost[%s] creating new composite conn=%p\n", ctx->path, newconn.get());
+    if ((status = DevcoordinatorConnection::BeginWait(std::move(newconn),
+                                                 DevhostAsyncLoop()->dispatcher())) != ZX_OK) {
+        return fuchsia_device_manager_ControllerCreateCompositeDevice_reply(txn, status);
+    }
+    return fuchsia_device_manager_ControllerCreateCompositeDevice_reply(txn, ZX_OK);
+}
+
 static zx_status_t fidl_BindDriver(void* raw_ctx, const char* driver_path_data,
                                    size_t driver_path_size, zx_handle_t raw_driver_vmo,
                                    fidl_txn_t* txn) {
@@ -566,6 +630,7 @@ static fuchsia_device_manager_Controller_ops_t fidl_ops = {
     .ConnectProxy = fidl_ConnectProxy,
     .Suspend = fidl_Suspend,
     .RemoveDevice = fidl_RemoveDevice,
+    .CreateCompositeDevice = fidl_CreateCompositeDevice,
 };
 
 static zx_status_t dh_handle_rpc_read(zx_handle_t h, DevcoordinatorConnection* conn) {
@@ -997,6 +1062,9 @@ zx_status_t devhost_remove(const fbl::RefPtr<zx_device_t>& dev) {
     zx_status_t status = fuchsia_device_manager_CoordinatorRemoveDevice(rpc.get(), &call_status);
     log_rpc_result("remove-device", status, call_status);
 
+    // Forget our local ID, to release the reference stored by the local ID map
+    dev->set_local_id(0);
+
     // Forget about our rpc channel since after the port_queue below it may be
     // closed.
     dev->rpc = zx::unowned_channel();
diff --git a/zircon/system/core/devmgr/devhost/device-internal.h b/zircon/system/core/devmgr/devhost/device-internal.h
index 50f96ad3952b1120643514c6b705c7bc706f1fbd..ce98d3e5f3c5b3645912297371b6731a3bb2b67b 100644
--- a/zircon/system/core/devmgr/devhost/device-internal.h
+++ b/zircon/system/core/devmgr/devhost/device-internal.h
@@ -7,6 +7,7 @@
 #include <atomic>
 #include <ddk/device.h>
 #include <fbl/intrusive_double_list.h>
+#include <fbl/intrusive_wavl_tree.h>
 #include <fbl/mutex.h>
 #include <fbl/recycler.h>
 #include <fbl/ref_counted_upgradeable.h>
@@ -83,8 +84,12 @@ struct zx_device : fbl::RefCountedUpgradeable<zx_device>, fbl::Recyclable<zx_dev
       return Dispatch(ops->message, ZX_ERR_NOT_SUPPORTED, msg, txn);
     }
 
+    // Check if this devhost has a device with the given ID, and if so returns a
+    // reference to it.
+    static fbl::RefPtr<zx_device> GetDeviceFromLocalId(uint64_t local_id);
+
     uint64_t local_id() const { return local_id_; }
-    void set_local_id(uint64_t id) { local_id_ = id; }
+    void set_local_id(uint64_t id);
 
     uintptr_t magic = DEV_MAGIC;
 
@@ -140,6 +145,18 @@ struct zx_device : fbl::RefCountedUpgradeable<zx_device>, fbl::Recyclable<zx_dev
 
     char name[ZX_DEVICE_NAME_MAX + 1] = {};
 
+    // Trait structures for the local ID map
+    struct LocalIdNode {
+        static fbl::WAVLTreeNodeState<fbl::RefPtr<zx_device>>& node_state(zx_device& obj) {
+            return obj.local_id_node_;
+        }
+    };
+    struct LocalIdKeyTraits {
+        static uint64_t GetKey(const zx_device& obj) { return obj.local_id_; }
+        static bool LessThan(const uint64_t& key1, const uint64_t& key2)  { return key1 <  key2; }
+        static bool EqualTo (const uint64_t& key1, const uint64_t& key2)  { return key1 == key2; }
+    };
+
 private:
     zx_device() = default;
 
@@ -162,6 +179,8 @@ private:
         }
     }
 
+    fbl::WAVLTreeNodeState<fbl::RefPtr<zx_device>> local_id_node_;
+
     // Identifier assigned by devmgr that can be used to assemble composite
     // devices.
     uint64_t local_id_ = 0;
diff --git a/zircon/system/core/devmgr/devhost/zx-device.cpp b/zircon/system/core/devmgr/devhost/zx-device.cpp
index e2d632a0534107319848cdc8b1d47ca98b3d3ded..97fe5c4b76d1e5c4a62649897eca67fde6d47760 100644
--- a/zircon/system/core/devmgr/devhost/zx-device.cpp
+++ b/zircon/system/core/devmgr/devhost/zx-device.cpp
@@ -6,6 +6,8 @@
 
 #include "devhost.h"
 #include <fbl/auto_call.h>
+#include <fbl/auto_lock.h>
+#include <fbl/mutex.h>
 
 zx_status_t zx_device::Create(fbl::RefPtr<zx_device>* out_dev) {
     *out_dev = fbl::AdoptRef(new zx_device());
@@ -58,3 +60,32 @@ void zx_device::fbl_recycle() TA_NO_THREAD_SAFETY_ANALYSIS {
         devmgr::devhost_finalize();
     }
 }
+
+static fbl::Mutex local_id_map_lock_;
+static fbl::WAVLTree<uint64_t, fbl::RefPtr<zx_device>, zx_device::LocalIdKeyTraits,
+        zx_device::LocalIdNode> local_id_map_ TA_GUARDED(local_id_map_lock_);
+
+void zx_device::set_local_id(uint64_t id) {
+    // If this is the last reference, we want it to go away outside of the lock
+    fbl::RefPtr<zx_device> old_entry;
+
+    fbl::AutoLock guard(&local_id_map_lock_);
+    if (local_id_ != 0) {
+        old_entry = local_id_map_.erase(*this);
+        ZX_ASSERT(old_entry.get() == this);
+    }
+
+    local_id_ = id;
+    if (id != 0) {
+        local_id_map_.insert(fbl::WrapRefPtr(this));
+    }
+}
+
+fbl::RefPtr<zx_device> zx_device::GetDeviceFromLocalId(uint64_t local_id) {
+    fbl::AutoLock guard(&local_id_map_lock_);
+    auto itr = local_id_map_.find(local_id);
+    if (itr == local_id_map_.end()) {
+        return nullptr;
+    }
+    return fbl::WrapRefPtr(&*itr);
+}
diff --git a/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl b/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl
index 072a17ec36680675231c6f2fb57a3aab9461d5fd..9352532c974b4c022d44888225b0bff4eeb38059 100644
--- a/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl
+++ b/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl
@@ -114,6 +114,19 @@ protocol Controller {
     /// Ask the devhost to remove this device.  On success, the remote end of
     /// this interface channel will close instead of returning a result.
     RemoveDevice();
+
+    /// Introduce a composite device that has the given name and properties.
+    /// |components| will be a list of all of the composite's components,
+    /// described using devhost local device ids.  The order of the components
+    /// will match the original composite creation request.  The new device will
+    /// communicate with devcoordinator via |rpc|.
+    ///
+    /// |local_device_id| will be a unique value within the device's devhost, identifying
+    /// the resulting composite device.
+    CreateCompositeDevice(handle<channel> rpc,
+                          vector<LocalDeviceId>:COMPONENTS_MAX components,
+                          string:DEVICE_NAME_MAX name, LocalDeviceId local_device_id)
+        -> (zx.status status);
 };
 
 /// Interface for the devices in devhosts to coordinate with the devcoordinator.