proxy wip

Change-Id: Id6ca7ea54e4ad897da11b71df650de53a1364c6f
diff --git a/system/dev/board/astro/astro-display.c b/system/dev/board/astro/astro-display.c
index b9819e7..67baeba 100644
--- a/system/dev/board/astro/astro-display.c
+++ b/system/dev/board/astro/astro-display.c
@@ -91,6 +91,9 @@
     },
 };
 
+static const uint32_t display_protocols[] = {
+    ZX_PROTOCOL_CANVAS,
+};
 
 static pbus_dev_t display_dev = {
     .name = "display",
@@ -107,6 +110,8 @@
     .i2c_channel_count = countof(display_i2c_channels),
     .btis = display_btis,
     .bti_count = countof(display_btis),
+    .protocols = display_protocols,
+    .protocol_count = countof(display_protocols),
 };
 
 zx_status_t aml_display_init(aml_bus_t* bus) {
diff --git a/system/dev/board/hikey960/hikey960.c b/system/dev/board/hikey960/hikey960.c
index 6a7db4b..59c5ca5 100644
--- a/system/dev/board/hikey960/hikey960.c
+++ b/system/dev/board/hikey960/hikey960.c
@@ -79,12 +79,13 @@
     if (status != ZX_OK) {
         goto fail;
     }
-    status = pbus_register_protocol(&hikey->pbus, ZX_PROTOCOL_GPIO, &hikey->gpio);
+    status = pbus_register_protocol(&hikey->pbus, ZX_PROTOCOL_GPIO, &hikey->gpio, NULL);
     if (status != ZX_OK) {
         goto fail;
     }
 
-    status = pbus_register_protocol(&hikey->pbus, ZX_PROTOCOL_USB_MODE_SWITCH, &usb_mode_switch);
+    status = pbus_register_protocol(&hikey->pbus, ZX_PROTOCOL_USB_MODE_SWITCH, &usb_mode_switch,
+                                    NULL);
     if (status != ZX_OK) {
         goto fail;
     }
diff --git a/system/dev/board/imx8mevk/imx8mevk.c b/system/dev/board/imx8mevk/imx8mevk.c
index 416af25..7181445 100644
--- a/system/dev/board/imx8mevk/imx8mevk.c
+++ b/system/dev/board/imx8mevk/imx8mevk.c
@@ -148,7 +148,8 @@
         goto fail;
     }
 
-    status = pbus_register_protocol(&bus->pbus, ZX_PROTOCOL_USB_MODE_SWITCH, &bus->usb_mode_switch);
+    status = pbus_register_protocol(&bus->pbus, ZX_PROTOCOL_USB_MODE_SWITCH, &bus->usb_mode_switch,
+                                    NULL);
     if (status != ZX_OK) {
         goto fail;
     }
diff --git a/system/dev/board/vim/vim-display.c b/system/dev/board/vim/vim-display.c
index 8a5a72d..2a56c8b 100644
--- a/system/dev/board/vim/vim-display.c
+++ b/system/dev/board/vim/vim-display.c
@@ -71,6 +71,10 @@
     },
 };
 
+static const uint32_t vim_display_protocols[] = {
+    ZX_PROTOCOL_CANVAS,
+};
+
 static const pbus_dev_t display_dev = {
     .name = "display",
     .vid = PDEV_VID_KHADAS,
@@ -84,6 +88,8 @@
     .irq_count = countof(vim_display_irqs),
     .btis = vim_display_btis,
     .bti_count = countof(vim_display_btis),
+    .protocols = vim_display_protocols,
+    .protocol_count = countof(vim_display_protocols),
 };
 
 zx_status_t vim_display_init(vim_bus_t* bus) {
diff --git a/system/dev/bus/platform/device-resources.cpp b/system/dev/bus/platform/device-resources.cpp
index 91d166f..0054f6b 100644
--- a/system/dev/bus/platform/device-resources.cpp
+++ b/system/dev/bus/platform/device-resources.cpp
@@ -25,13 +25,17 @@
 namespace platform_bus {
 
 zx_status_t DeviceResources::Init(const pbus_dev_t* pdev, uint32_t* next_index) {
+    if (pdev->protocol_count > PROXY_MAX_PROTOCOLS) {
+        return ZX_ERR_INVALID_ARGS;
+    }
     if (!CopyResources(pdev->mmio_count, pdev->mmios, &mmios_) ||
         !CopyResources(pdev->irq_count, pdev->irqs, &irqs_) ||
         !CopyResources(pdev->gpio_count, pdev->gpios, &gpios_) ||
         !CopyResources(pdev->i2c_channel_count, pdev->i2c_channels, &i2c_channels_) ||
         !CopyResources(pdev->clk_count, pdev->clks, &clks_) ||
         !CopyResources(pdev->bti_count, pdev->btis, &btis_) ||
-        !CopyResources(pdev->metadata_count, pdev->metadata, &metadata_)) {
+        !CopyResources(pdev->metadata_count, pdev->metadata, &metadata_) ||
+        !CopyResources(pdev->protocol_count, pdev->protocols, &protocols_)) {
         return ZX_ERR_NO_MEMORY;
     }
 
diff --git a/system/dev/bus/platform/device-resources.h b/system/dev/bus/platform/device-resources.h
index 215812c..87519ec 100644
--- a/system/dev/bus/platform/device-resources.h
+++ b/system/dev/bus/platform/device-resources.h
@@ -42,6 +42,7 @@
     inline const pbus_clk_t& clk(size_t i) const { return clks_[i]; }
     inline const pbus_bti_t& bti(size_t i) const { return btis_[i]; }
     inline const pbus_metadata_t& metadata(size_t i) const { return metadata_[i]; }
+    inline const uint32_t* protocols() const { return protocols_.begin(); }
 
     // Counts for the above resource lists.
     inline size_t mmio_count() const { return mmios_.size(); }
@@ -52,10 +53,11 @@
     inline size_t bti_count() const { return btis_.size(); }
     inline size_t metadata_count() const { return metadata_.size(); }
     inline size_t child_count() const { return children_.size(); }
+    inline size_t protocol_count() const { return protocols_.size(); }
 
 private:
     // Index of this DeviceResources instance in PlatformDevice::device_index_.
-    uint32_t index_;
+    const uint32_t index_;
 
     // Platform bus resources copied from the pbus_dev_t struct from the board driver.
     fbl::Array<pbus_mmio_t> mmios_;
@@ -65,6 +67,7 @@
     fbl::Array<pbus_clk_t> clks_;
     fbl::Array<pbus_bti_t> btis_;
     fbl::Array<pbus_metadata_t> metadata_;
+    fbl::Array<uint32_t> protocols_;
 
     // Resources for children of this device.
     fbl::Vector<DeviceResources> children_;
diff --git a/system/dev/bus/platform/platform-bus.cpp b/system/dev/bus/platform/platform-bus.cpp
index 59df0a3..5d446e8 100644
--- a/system/dev/bus/platform/platform-bus.cpp
+++ b/system/dev/bus/platform/platform-bus.cpp
@@ -30,7 +30,8 @@
     return zx_bti_create(iommu_handle_.get(), 0, bti_id, out_handle);
 }
 
-zx_status_t PlatformBus::RegisterProtocol(uint32_t proto_id, void* protocol) {
+zx_status_t PlatformBus::RegisterProtocol(uint32_t proto_id, void* protocol,
+                                          platform_proxy_cb proxy_cb) {
     if (!protocol) {
         return ZX_ERR_INVALID_ARGS;
     }
@@ -89,8 +90,20 @@
         break;
     }
     default:
-        // TODO(voydanoff) consider having a registry of arbitrary protocols
-        return ZX_ERR_NOT_SUPPORTED;
+        if (proxy_cb == nullptr) {
+            return ZX_ERR_NOT_SUPPORTED;
+        }
+        fbl::AllocChecker ac;
+        fbl::unique_ptr<ProtoProxy> proxy(new (&ac) ProtoProxy(proto_id,
+                                          static_cast<ddk::AnyProtocol*>(protocol), proxy_cb));
+        if (!ac.check()) {
+            return ZX_ERR_NO_MEMORY;
+        }
+
+        fbl::AutoLock lock(&mutex_);
+        proto_proxys_.insert_or_replace(fbl::move(proxy));
+        sync_completion_signal(&proto_completion_);
+        return ZX_OK;
     }
 
     fbl::AutoLock lock(&mutex_);
@@ -192,44 +205,44 @@
     return ZX_OK;
 }
 
-zx_status_t PlatformBus::DdkGetProtocol(uint32_t proto_id, void* protocol) {
+zx_status_t PlatformBus::DdkGetProtocol(uint32_t proto_id, void* out) {
     switch (proto_id) {
     case ZX_PROTOCOL_PLATFORM_BUS: {
-        auto proto = static_cast<platform_bus_protocol_t*>(protocol);
+        auto proto = static_cast<platform_bus_protocol_t*>(out);
         proto->ctx = this;
         proto->ops = &pbus_proto_ops_;
         return ZX_OK;
     }
     case ZX_PROTOCOL_USB_MODE_SWITCH:
         if (ums_ != nullptr) {
-            ums_->GetProto(static_cast<usb_mode_switch_protocol_t*>(protocol));
+            ums_->GetProto(static_cast<usb_mode_switch_protocol_t*>(out));
             return ZX_OK;
         }
         break;
     case ZX_PROTOCOL_GPIO:
         if (gpio_ != nullptr) {
-            gpio_->GetProto(static_cast<gpio_protocol_t*>(protocol));
+            gpio_->GetProto(static_cast<gpio_protocol_t*>(out));
             return ZX_OK;
         }
         break;
     case ZX_PROTOCOL_I2C_IMPL:
         if (i2c_impl_ != nullptr) {
-            i2c_impl_->GetProto(static_cast<i2c_impl_protocol_t*>(protocol));
+            i2c_impl_->GetProto(static_cast<i2c_impl_protocol_t*>(out));
             return ZX_OK;
         }
         break;
     case ZX_PROTOCOL_CLK:
         if (clk_ != nullptr) {
-            clk_->GetProto(static_cast<clk_protocol_t*>(protocol));
+            clk_->GetProto(static_cast<clk_protocol_t*>(out));
             return ZX_OK;
         }
         break;
     case ZX_PROTOCOL_IOMMU:
         if (iommu_ != nullptr) {
-            iommu_->GetProto(static_cast<iommu_protocol_t*>(protocol));
+            iommu_->GetProto(static_cast<iommu_protocol_t*>(out));
         } else {
             // return default implementation
-            auto proto = static_cast<iommu_protocol_t*>(protocol);
+            auto proto = static_cast<iommu_protocol_t*>(out);
             proto->ctx = this;
             proto->ops = &iommu_proto_ops_;
             return ZX_OK;
@@ -237,13 +250,17 @@
         break;
     case ZX_PROTOCOL_CANVAS:
         if (canvas_ != nullptr) {
-            canvas_->GetProto(static_cast<canvas_protocol_t*>(protocol));
+            canvas_->GetProto(static_cast<canvas_protocol_t*>(out));
             return ZX_OK;
         }
         break;
     default:
-        // TODO(voydanoff) consider having a registry of arbitrary protocols
-        return ZX_ERR_NOT_SUPPORTED;
+        auto proto_proxy = proto_proxys_.find(proto_id);
+        if (!proto_proxy.IsValid()) {
+            return ZX_ERR_NOT_SUPPORTED;
+        }
+        proto_proxy->GetProtocol(out);
+        return ZX_OK;
     }
 
     return ZX_ERR_NOT_SUPPORTED;
diff --git a/system/dev/bus/platform/platform-bus.h b/system/dev/bus/platform/platform-bus.h
index 9fd870c..5f1b487 100644
--- a/system/dev/bus/platform/platform-bus.h
+++ b/system/dev/bus/platform/platform-bus.h
@@ -14,6 +14,7 @@
 #include <ddktl/protocol/platform-bus.h>
 #include <ddktl/protocol/usb-mode-switch.h>
 #include <fbl/array.h>
+#include <fbl/intrusive_wavl_tree.h>
 #include <fbl/mutex.h>
 #include <fbl/unique_ptr.h>
 #include <fbl/vector.h>
@@ -31,6 +32,25 @@
 
 namespace platform_bus {
 
+class ProtoProxy : public fbl::WAVLTreeContainable<fbl::unique_ptr<ProtoProxy>> {
+public:
+    ProtoProxy(uint32_t proto_id, ddk::AnyProtocol* protocol, platform_proxy_cb proxy_cb)
+        : proto_id_(proto_id), protocol_(*protocol), proxy_cb_(proxy_cb) {}
+
+    inline uint32_t GetKey() const { return proto_id_; }
+    inline void GetProtocol(void* out) const { memcpy(out, &protocol_, sizeof(protocol_)); }
+
+    inline zx_status_t Proxy(const void* req_buf, uint32_t req_size, void* rsp_buf,
+                             uint32_t rsp_buf_size, uint32_t* out_rsp_actual) {
+        return proxy_cb_(req_buf, req_size, rsp_buf, rsp_buf_size, out_rsp_actual);
+    }
+
+private:
+    const uint32_t proto_id_;
+    ddk::AnyProtocol protocol_;
+    const platform_proxy_cb proxy_cb_;
+};
+
 class PlatformBus;
 using PlatformBusType = ddk::Device<PlatformBus, ddk::GetProtocolable>;
 
@@ -47,7 +67,7 @@
     // Platform bus protocol implementation.
     zx_status_t DeviceAdd(const pbus_dev_t* dev);
     zx_status_t ProtocolDeviceAdd(uint32_t proto_id, const pbus_dev_t* dev);
-    zx_status_t RegisterProtocol(uint32_t proto_id, void* protocol);
+    zx_status_t RegisterProtocol(uint32_t proto_id, void* protocol, platform_proxy_cb proxy_cb);
     const char* GetBoardName();
     zx_status_t SetBoardInfo(const pbus_board_info_t* info);
 
@@ -111,6 +131,9 @@
 
     // Dummy IOMMU.
     zx::handle iommu_handle_;
+
+    fbl::WAVLTree<uint32_t, fbl::unique_ptr<ProtoProxy>> proto_proxys_ __TA_GUARDED(mutex_);
+
 };
 
 } // namespace platform_bus
diff --git a/system/dev/bus/platform/platform-device.cpp b/system/dev/bus/platform/platform-device.cpp
index 7dfa30b..aff7014 100644
--- a/system/dev/bus/platform/platform-device.cpp
+++ b/system/dev/bus/platform/platform-device.cpp
@@ -193,6 +193,15 @@
         }
         return status;
     }
+    return ZX_OK;
+}
+
+zx_status_t PlatformDevice::RpcGetProtocols(const DeviceResources* dr, uint32_t* out_protocols,
+                                            uint32_t* out_protocol_count) {
+    auto count = dr->protocol_count();
+    memcpy(out_protocols, dr->protocols(), count * sizeof(*out_protocols));
+    *out_protocol_count = static_cast<uint32_t>(count);
+    return ZX_OK;
 }
 
 zx_status_t PlatformDevice::RpcUmsSetMode(const DeviceResources* dr, usb_mode_t mode) {
@@ -423,6 +432,12 @@
             resp_len += resp->pdev.metadata_length;
             break;
         }
+        case PDEV_GET_PROTOCOLS: {
+            auto protos = reinterpret_cast<uint32_t*>(&resp[1]);
+            status = RpcGetProtocols(dr, protos, &resp->protocol_count);
+            resp_len += static_cast<uint32_t>(resp->protocol_count * sizeof(*protos));
+            break;
+        }
         default:
             zxlogf(ERROR, "platform_dev_rxrpc: unknown op %u\n", req_header->op);
             return ZX_ERR_INTERNAL;
diff --git a/system/dev/bus/platform/platform-device.h b/system/dev/bus/platform/platform-device.h
index a28e477..38539db 100644
--- a/system/dev/bus/platform/platform-device.h
+++ b/system/dev/bus/platform/platform-device.h
@@ -68,6 +68,8 @@
     zx_status_t RpcDeviceAdd(const DeviceResources* dr, uint32_t index, uint32_t* out_device_id);
     zx_status_t RpcGetMetadata(const DeviceResources* dr, uint32_t index, uint32_t* out_type,
                                uint8_t* buf, uint32_t buf_size, uint32_t* actual);
+    zx_status_t RpcGetProtocols(const DeviceResources* dr, uint32_t* out_protocols,
+                                uint32_t* out_protocol_count);
     zx_status_t RpcUmsSetMode(const DeviceResources* dr, usb_mode_t mode);
     zx_status_t RpcGpioConfig(const DeviceResources* dr, uint32_t index, uint32_t flags);
     zx_status_t RpcGpioSetAltFunction(const DeviceResources* dr, uint32_t index, uint64_t function);
diff --git a/system/dev/bus/platform/platform-proxy-device.h b/system/dev/bus/platform/platform-proxy-device.h
index 73d6974..85e3f1b 100644
--- a/system/dev/bus/platform/platform-proxy-device.h
+++ b/system/dev/bus/platform/platform-proxy-device.h
@@ -104,7 +104,7 @@
 
     DISALLOW_COPY_ASSIGN_AND_MOVE(ProxyDevice);
 
-    uint32_t device_id_;
+    const uint32_t device_id_;
     fbl::RefPtr<PlatformProxy> proxy_;
     fbl::Vector<Mmio> mmios_;
     fbl::Vector<Irq> irqs_;
diff --git a/system/dev/bus/platform/platform-proxy-host.cpp b/system/dev/bus/platform/platform-proxy-host.cpp
new file mode 100644
index 0000000..bb6acb0
--- /dev/null
+++ b/system/dev/bus/platform/platform-proxy-host.cpp
@@ -0,0 +1,63 @@
+// Copyright 2017 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 "platform-proxy-host.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <threads.h>
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <fbl/unique_ptr.h>
+
+#include "platform-proxy.h"
+
+namespace platform_bus {
+
+zx_status_t ProxyHost::Create(uint32_t proto_id, zx_device_t* parent,
+                              fbl::RefPtr<PlatformProxy> proxy) {
+    fbl::AllocChecker ac;
+    fbl::unique_ptr<platform_bus::ProxyHost> host(new (&ac)
+                                            platform_bus::ProxyHost(proto_id, parent, proxy));
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    char name[ZX_DEVICE_NAME_MAX];
+    snprintf(name, sizeof(name), "ProxyHost[%08x]", proto_id);
+
+    zx_device_prop_t props[] = {
+        {BIND_PLATFORM_PROTO, 0, proto_id},
+    };
+
+    auto status = host->DdkAdd(name, 0, props, countof(props));
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    // devmgr is now in charge of the device.
+    __UNUSED auto* dummy = host.release();
+    return ZX_OK;
+}
+
+void ProxyHost::DdkRelease() {
+    delete this;
+}
+
+zx_status_t ProxyHost::SetProtocol(uint32_t proto_id, void* protocol) {
+return -1;
+
+}
+
+zx_status_t ProxyHost::Proxy(uint32_t proto_id, const void* req_buf, uint32_t req_size,
+                             void* rsp_buf, uint32_t rsp_buf_size, uint32_t* out_rsp_actual) {
+return -1;
+}
+
+} // namespace platform_bus
diff --git a/system/dev/bus/platform/platform-proxy-host.h b/system/dev/bus/platform/platform-proxy-host.h
new file mode 100644
index 0000000..8209593
--- /dev/null
+++ b/system/dev/bus/platform/platform-proxy-host.h
@@ -0,0 +1,45 @@
+// Copyright 2017 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 <ddktl/device.h>
+#include <ddktl/protocol/platform-proxy.h>
+#include <fbl/ref_ptr.h>
+#include <fbl/vector.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/handle.h>
+
+#include "platform-proxy.h"
+
+namespace platform_bus {
+
+class ProxyHost;
+using ProxyHostType = ddk::Device<ProxyHost>;
+
+class ProxyHost : public ProxyHostType,
+                  public ddk::PlatformProxyProtocol<ProxyHost> {
+public:
+    static zx_status_t Create(uint32_t proto_id, zx_device_t* parent,
+                              fbl::RefPtr<PlatformProxy> proxy);
+
+    // Device protocol implementation.
+    void DdkRelease();
+
+    // Platform proxy protocol implementation.
+     zx_status_t SetProtocol(uint32_t proto_id, void* protocol);
+     zx_status_t Proxy(uint32_t proto_id, const void* req_buf, uint32_t req_size, void* rsp_buf,
+                       uint32_t rsp_buf_size, uint32_t* out_rsp_actual);
+
+private:
+    explicit ProxyHost(uint32_t proto_id, zx_device_t* parent, fbl::RefPtr<PlatformProxy> proxy)
+        :  ProxyHostType(parent), proto_id_(proto_id), proxy_(proxy) {}
+
+    DISALLOW_COPY_ASSIGN_AND_MOVE(ProxyHost);
+
+    uint32_t proto_id_;
+    fbl::RefPtr<PlatformProxy> proxy_;
+};
+
+} // namespace platform_bus
diff --git a/system/dev/bus/platform/platform-proxy.cpp b/system/dev/bus/platform/platform-proxy.cpp
index 00bc008..ada15a3 100644
--- a/system/dev/bus/platform/platform-proxy.cpp
+++ b/system/dev/bus/platform/platform-proxy.cpp
@@ -20,6 +20,7 @@
 #include <ddk/protocol/usb-mode-switch.h>
 
 #include "platform-proxy-device.h"
+#include "platform-proxy-host.h"
 
 // The implementation of the platform bus protocol in this file is for use by
 // drivers that exist in a proxy devhost and communicate with the platform bus
@@ -78,10 +79,34 @@
     return status;
 }
 
+zx_status_t PlatformProxy::LoadProtocols(zx_device_t* parent) {
+    // Get list of extra protocols to proxy.
+    rpc_pdev_req_t req = {};
+    struct {
+        rpc_pdev_rsp_t pdev;
+        uint32_t protocols[PROXY_MAX_PROTOCOLS];
+    } resp = {};
+    req.header.protocol = ZX_PROTOCOL_PLATFORM_DEV;
+    req.header.op = PDEV_GET_PROTOCOLS;
+
+    auto status = Rpc(ROOT_DEVICE_ID, &req.header, sizeof(req), &resp.pdev.header, sizeof(resp));
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    uint32_t protocol_count = resp.pdev.protocol_count;
+    for (uint32_t i = 0; i < protocol_count; i++) {
+        printf("PROTOCOL %08x\n", resp.protocols[i]);
+        status = ProxyHost::Create(resp.protocols[i], parent, fbl::RefPtr<PlatformProxy>(this));
+    }
+
+    return ZX_OK;
+}
+
 zx_status_t PlatformProxy::Create(zx_device_t* parent, zx_handle_t rpc_channel) {
     fbl::AllocChecker ac;
 
-    auto proxy = fbl::MakeRefCountedChecked<PlatformProxy>(&ac, rpc_channel);
+    auto proxy = fbl::MakeRefCountedChecked<PlatformProxy>(&ac, parent, rpc_channel);
     if (!ac.check()) {
         return ZX_ERR_NO_MEMORY;
     }
diff --git a/system/dev/bus/platform/platform-proxy.h b/system/dev/bus/platform/platform-proxy.h
index 9b770df..27148f7 100644
--- a/system/dev/bus/platform/platform-proxy.h
+++ b/system/dev/bus/platform/platform-proxy.h
@@ -19,6 +19,8 @@
 public:
     static zx_status_t Create(zx_device_t* parent, zx_handle_t rpc_channel);
 
+    zx_status_t LoadProtocols(zx_device_t* parent);
+
     zx_status_t Rpc(uint32_t device_id, rpc_req_header_t* req, uint32_t req_length,
                     rpc_rsp_header_t* resp, uint32_t resp_length,
                     zx_handle_t* in_handles, uint32_t in_handle_count,
@@ -34,12 +36,13 @@
     friend class fbl::RefPtr<PlatformProxy>;
     friend class fbl::internal::MakeRefCountedHelper<PlatformProxy>;
 
-    explicit PlatformProxy(zx_handle_t rpc_channel)
-        : rpc_channel_(rpc_channel) {}
+    explicit PlatformProxy(zx_device_t* parent, zx_handle_t rpc_channel)
+        :  parent_(parent), rpc_channel_(rpc_channel) {}
 
     DISALLOW_COPY_ASSIGN_AND_MOVE(PlatformProxy);
 
-    zx::channel rpc_channel_;
+    zx_device_t* parent_;
+    const zx::channel rpc_channel_;
 };
 
 } // namespace platform_bus
diff --git a/system/dev/bus/platform/proxy-protocol.h b/system/dev/bus/platform/proxy-protocol.h
index ad799de..e47fe05 100644
--- a/system/dev/bus/platform/proxy-protocol.h
+++ b/system/dev/bus/platform/proxy-protocol.h
@@ -40,6 +40,7 @@
     PDEV_GET_BOARD_INFO,
     PDEV_DEVICE_ADD,
     PDEV_GET_METADATA,
+    PDEV_GET_PROTOCOLS,
 };
 
 typedef struct {
@@ -59,6 +60,7 @@
     uint32_t device_id;
     uint32_t metadata_type;
     uint32_t metadata_length;
+    uint32_t protocol_count;
 } rpc_pdev_rsp_t;
 
 // Maximum metadata size that can be returned via PDEV_DEVICE_GET_METADATA.
@@ -70,6 +72,10 @@
     uint8_t metadata[PROXY_MAX_METADATA_SIZE];
 } rpc_pdev_metadata_rsp_t;
 
+// Maximum number of protocols that can be returned via PDEV_GET_PROTOCOLS.
+static constexpr size_t PROXY_MAX_PROTOCOLS = ((PROXY_MAX_TRANSFER_SIZE - sizeof(rpc_pdev_rsp_t))
+                                                / sizeof(uint32_t));
+
 // Maximum I2C transfer is I2C_MAX_TRANSFER_SIZE minus size of largest header.
 static constexpr uint32_t I2C_MAX_TRANSFER_SIZE = (PROXY_MAX_TRANSFER_SIZE -
             (sizeof(rpc_pdev_req_t) > sizeof(rpc_pdev_rsp_t) ?
diff --git a/system/dev/bus/platform/rules.mk b/system/dev/bus/platform/rules.mk
index 0fae0b2..2626d75 100644
--- a/system/dev/bus/platform/rules.mk
+++ b/system/dev/bus/platform/rules.mk
@@ -43,6 +43,7 @@
     $(LOCAL_DIR)/platform-proxy.cpp \
     $(LOCAL_DIR)/platform-proxy-bind.c \
     $(LOCAL_DIR)/platform-proxy-device.cpp \
+    $(LOCAL_DIR)/platform-proxy-host.cpp \
 
 MODULE_STATIC_LIBS := \
     system/ulib/ddk \
diff --git a/system/dev/clk/amlogic-clk/aml-clk.cpp b/system/dev/clk/amlogic-clk/aml-clk.cpp
index f34dc24..0816462 100644
--- a/system/dev/clk/amlogic-clk/aml-clk.cpp
+++ b/system/dev/clk/amlogic-clk/aml-clk.cpp
@@ -157,7 +157,7 @@
         .ctx = this,
     };
 
-    status = pbus_register_protocol(&pbus, ZX_PROTOCOL_CLK, &clk_proto);
+    status = pbus_register_protocol(&pbus, ZX_PROTOCOL_CLK, &clk_proto, NULL);
     if (status != ZX_OK) {
         zxlogf(ERROR, "meson_clk_bind: pbus_register_protocol failed, st = %d\n", status);
         return status;
diff --git a/system/dev/clk/hisi-lib/hisi-clk.c b/system/dev/clk/hisi-lib/hisi-clk.c
index 35c46a9..79baa6b 100644
--- a/system/dev/clk/hisi-lib/hisi-clk.c
+++ b/system/dev/clk/hisi-lib/hisi-clk.c
@@ -224,7 +224,7 @@
     hisi_clk->clk.ops = &clk_ops;
     hisi_clk->clk.ctx = hisi_clk;
 
-    st = pbus_register_protocol(&pbus, ZX_PROTOCOL_CLK, &hisi_clk->clk);
+    st = pbus_register_protocol(&pbus, ZX_PROTOCOL_CLK, &hisi_clk->clk, NULL);
     if (st != ZX_OK) {
         zxlogf(ERROR, "hisi_clk_bind: pbus_register_protocol failed, st = %d\n", st);
         goto fail;
diff --git a/system/dev/clk/meson-lib/meson_clk.c b/system/dev/clk/meson-lib/meson_clk.c
new file mode 100644
index 0000000..e277e64
--- /dev/null
+++ b/system/dev/clk/meson-lib/meson_clk.c
@@ -0,0 +1,166 @@
+// Copyright 2018 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 <hw/reg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <threads.h>
+
+#include <zircon/assert.h>
+#include <zircon/threads.h>
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/protocol/clk.h>
+#include <ddk/protocol/platform-bus.h>
+#include <ddk/protocol/platform-defs.h>
+#include <ddk/protocol/platform-device.h>
+
+#include <dev/clk/meson-lib/meson.h>
+
+typedef struct meson_clk {
+    platform_device_protocol_t pdev;
+    clk_protocol_t clk;
+    zx_device_t* zxdev;
+    io_buffer_t mmio;
+
+    meson_clk_gate_t* gates;
+    size_t gate_count;
+
+    // Serialize access to clocks.
+    mtx_t lock;
+} meson_clk_t;
+
+static zx_status_t meson_clk_toggle(void* ctx, const uint32_t idx,
+                                    const bool enable) {
+    meson_clk_t* const meson_clk = ctx;
+
+    if (idx >= meson_clk->gate_count) return ZX_ERR_INVALID_ARGS;
+
+    const meson_clk_gate_t* const gate = &meson_clk->gates[idx];
+
+    volatile uint32_t* regs = (volatile uint32_t*)io_buffer_virt(&meson_clk->mmio);
+
+    mtx_lock(&meson_clk->lock);
+
+    uint32_t val = readl(regs + gate->reg);
+
+    if (enable) {
+        val |= (1 << gate->bit);
+    } else {
+        val &= ~(1 << gate->bit);
+    }
+
+    writel(val, regs + gate->reg);
+
+    mtx_unlock(&meson_clk->lock);
+
+    return ZX_OK;
+}
+
+static zx_status_t meson_clk_enable(void* ctx, uint32_t clk) {
+    return meson_clk_toggle(ctx, clk, true);
+}
+
+static zx_status_t meson_clk_disable(void* ctx, uint32_t clk) {
+    return meson_clk_toggle(ctx, clk, false);
+}
+
+clk_protocol_ops_t clk_ops = {
+    .enable = meson_clk_enable,
+    .disable = meson_clk_disable,
+};
+
+static void meson_clk_release(void* ctx) {
+    meson_clk_t* clk = ctx;
+    io_buffer_release(&clk->mmio);
+    mtx_destroy(&clk->lock);
+    free(clk);
+}
+
+
+static zx_protocol_device_t meson_clk_device_proto = {
+    .version = DEVICE_OPS_VERSION,
+    .release = meson_clk_release,
+};
+
+zx_status_t meson_clk_init(const char* name, meson_clk_gate_t* gates,
+                           const size_t gate_count, zx_device_t* parent) {
+    zx_status_t st = ZX_ERR_INTERNAL;
+
+    meson_clk_t* meson_clk = calloc(1, sizeof(*meson_clk));
+    if (!meson_clk) {
+    zxlogf(ERROR, "meson_clk_bind: failed to allocate meson_clk, "
+                      "st = %d\n", ZX_ERR_NO_MEMORY);
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    meson_clk->gates = gates;
+    meson_clk->gate_count = gate_count;
+
+    int ret = mtx_init(&meson_clk->lock, mtx_plain);
+    if (ret != thrd_success) {
+        st = thrd_status_to_zx_status(ret);
+        zxlogf(ERROR, "meson_clk_bind: failed to initialize mutex, st = %d\n",
+               st);
+        goto fail;
+    }
+
+    st = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &meson_clk->pdev);
+    if (st != ZX_OK) {
+        zxlogf(ERROR, "meson_clk_bind: failed to get ZX_PROTOCOL_PLATFORM_DEV, "
+                      "st = %d\n", st);
+        goto fail;
+    }
+
+    platform_bus_protocol_t pbus;
+    st = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_BUS, &pbus);
+    if (st != ZX_OK) {
+        zxlogf(ERROR, "meson_clk_bind: failed to get ZX_PROTOCOL_PLATFORM_BUS, "
+               "st = %d\n", st);
+        goto fail;
+    }
+
+    st = pdev_map_mmio_buffer(&meson_clk->pdev, 0,
+                              ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                              &meson_clk->mmio);
+    if (st != ZX_OK) {
+        zxlogf(ERROR, "meson_clk_bind: failed to map clk mmio, st = %d\n", st);
+        goto fail;
+    }
+
+    device_add_args_t args = {
+        .version = DEVICE_ADD_ARGS_VERSION,
+        .name = name,
+        .ctx = meson_clk,
+        .ops = &meson_clk_device_proto,
+        .flags = DEVICE_ADD_NON_BINDABLE,
+    };
+
+    st = device_add(parent, &args, &meson_clk->zxdev);
+    if (st != ZX_OK) {
+        zxlogf(ERROR, "meson_clk_bind: device_add failed, st = %d\n", st);
+        goto fail;
+    }
+
+    meson_clk->clk.ops = &clk_ops;
+    meson_clk->clk.ctx = meson_clk;
+
+    st = pbus_set_protocol(&pbus, ZX_PROTOCOL_CLK, &meson_clk->clk, NULL);
+    if (st != ZX_OK) {
+        zxlogf(ERROR, "meson_clk_bind: pbus_set_protocol failed, st = %d\n", st);
+        goto fail;
+    }
+
+    return ZX_OK;
+
+fail:
+    meson_clk_release(meson_clk);
+
+    // Make sure we don't accidentally return ZX_OK if the device has failed
+    // to bind for some reason
+    ZX_DEBUG_ASSERT(st != ZX_OK);
+    return st;
+}
\ No newline at end of file
diff --git a/system/dev/display/aml-canvas/aml-canvas.c b/system/dev/display/aml-canvas/aml-canvas.c
index 18c84aa..df00ab3 100644
--- a/system/dev/display/aml-canvas/aml-canvas.c
+++ b/system/dev/display/aml-canvas/aml-canvas.c
@@ -158,12 +158,30 @@
     .unbind = aml_canvas_unbind,
 };
 
+static zx_protocol_device_t empty_device_protocol = {
+    .version = DEVICE_OPS_VERSION,
+};
+
 static canvas_protocol_ops_t canvas_ops = {
     .config = aml_canvas_config,
     .free = aml_canvas_free,
 };
 
 static zx_status_t aml_canvas_bind(void* ctx, zx_device_t* parent) {
+    platform_proxy_protocol_t proxy;
+    if (device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_PROXY, &proxy) == ZX_OK) {
+printf("bind as proxy\n");
+ 
+         device_add_args_t args = {
+            .version = DEVICE_ADD_ARGS_VERSION,
+            .name = "aml-canvas-proxy",
+            .ops = &empty_device_protocol,
+            .flags = DEVICE_ADD_NON_BINDABLE,
+        };
+
+        return device_add(parent, &args, NULL);
+    }
+
     zx_status_t status = ZX_OK;
 
     aml_canvas_t* canvas = calloc(1, sizeof(aml_canvas_t));
@@ -222,7 +240,7 @@
     canvas->canvas.ctx = canvas;
 
     // Set the canvas protocol on the platform bus
-    pbus_register_protocol(&pbus, ZX_PROTOCOL_CANVAS, &canvas->canvas);
+    pbus_register_protocol(&pbus, ZX_PROTOCOL_CANVAS, &canvas->canvas, NULL);
     return ZX_OK;
 fail:
     aml_canvas_release(canvas);
@@ -234,9 +252,15 @@
     .bind = aml_canvas_bind,
 };
 
-ZIRCON_DRIVER_BEGIN(aml_canvas, aml_canvas_driver_ops, "zircon", "0.1", 4)
-    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV),
+ZIRCON_DRIVER_BEGIN(aml_canvas, aml_canvas_driver_ops, "zircon", "0.1", 7)
+    // platform device binding support
+    BI_GOTO_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_DEV, 0),
     BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
     BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GENERIC),
     BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_CANVAS),
+
+    // platform proxy binding support
+    BI_LABEL(0),
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PLATFORM_PROXY),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_PROTO, ZX_PROTOCOL_CANVAS),
 ZIRCON_DRIVER_END(aml_canvas)
diff --git a/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c b/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c
index 5f5384d..b7a992b 100644
--- a/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c
+++ b/system/dev/gpio/aml-axg-gpio/aml-axg-gpio.c
@@ -491,7 +491,7 @@
     gpio->gpio_interrupt->irq_status = 0;
     gpio->gpio.ops = &gpio_ops;
     gpio->gpio.ctx = gpio;
-    pbus_register_protocol(&pbus, ZX_PROTOCOL_GPIO, &gpio->gpio);
+    pbus_register_protocol(&pbus, ZX_PROTOCOL_GPIO, &gpio->gpio, NULL);
     gpio->gpio_interrupt->irq_info = calloc(gpio->gpio_interrupt->irq_count,
                                      sizeof(uint8_t));
     if (!gpio->gpio_interrupt->irq_info) {
diff --git a/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c b/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c
index 52ac69a..4b14422 100644
--- a/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c
+++ b/system/dev/gpio/aml-gxl-gpio/aml-gxl-gpio.c
@@ -535,7 +535,7 @@
         goto fail;
     }
 
-    pbus_register_protocol(&pbus, ZX_PROTOCOL_GPIO, &gpio->gpio);
+    pbus_register_protocol(&pbus, ZX_PROTOCOL_GPIO, &gpio->gpio, NULL);
 
     return ZX_OK;
 
diff --git a/system/dev/gpio/imx8/imx8-gpio.c b/system/dev/gpio/imx8/imx8-gpio.c
index a3b86b4..79fb441 100644
--- a/system/dev/gpio/imx8/imx8-gpio.c
+++ b/system/dev/gpio/imx8/imx8-gpio.c
@@ -472,7 +472,7 @@
 
     gpio->gpio.ops = &gpio_ops;
     gpio->gpio.ctx = gpio;
-    pbus_register_protocol(&gpio->pbus, ZX_PROTOCOL_GPIO, &gpio->gpio);
+    pbus_register_protocol(&gpio->pbus, ZX_PROTOCOL_GPIO, &gpio->gpio, NULL);
 
     return ZX_OK;
 
diff --git a/system/dev/i2c/aml-i2c/aml-i2c.c b/system/dev/i2c/aml-i2c/aml-i2c.c
index a3cfcdc..50d346b 100644
--- a/system/dev/i2c/aml-i2c/aml-i2c.c
+++ b/system/dev/i2c/aml-i2c/aml-i2c.c
@@ -410,7 +410,7 @@
 
     i2c->i2c.ops = &i2c_ops;
     i2c->i2c.ctx = i2c;
-    pbus_register_protocol(&pbus, ZX_PROTOCOL_I2C_IMPL, &i2c->i2c);
+    pbus_register_protocol(&pbus, ZX_PROTOCOL_I2C_IMPL, &i2c->i2c, NULL);
 
     return ZX_OK;
 
diff --git a/system/dev/i2c/dw-i2c/dw-i2c.c b/system/dev/i2c/dw-i2c/dw-i2c.c
index 78744d5..c201629 100644
--- a/system/dev/i2c/dw-i2c/dw-i2c.c
+++ b/system/dev/i2c/dw-i2c/dw-i2c.c
@@ -491,7 +491,7 @@
 
     i2c->i2c.ops = &i2c_ops;
     i2c->i2c.ctx = i2c;
-    pbus_register_protocol(&pbus, ZX_PROTOCOL_I2C_IMPL, &i2c->i2c);
+    pbus_register_protocol(&pbus, ZX_PROTOCOL_I2C_IMPL, &i2c->i2c, NULL);
 
     return ZX_OK;
 
diff --git a/system/public/zircon/driver/binding.h b/system/public/zircon/driver/binding.h
index b0974b2..8b60e50 100644
--- a/system/public/zircon/driver/binding.h
+++ b/system/public/zircon/driver/binding.h
@@ -94,10 +94,11 @@
 #define BIND_USB_SUBCLASS     0x0203
 #define BIND_USB_PROTOCOL     0x0204
 
-// Platform device binding variables at 0x03XX
+// Platform bus binding variables at 0x03XX
 #define BIND_PLATFORM_DEV_VID 0x0300
 #define BIND_PLATFORM_DEV_PID 0x0301
 #define BIND_PLATFORM_DEV_DID 0x0302
+#define BIND_PLATFORM_PROTO   0x0303
 
 // ACPI binding variables at 0x04XX
 // The _HID is a 7- or 8-byte string. Because a bind property is 32-bit, use 2
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-bus.h b/system/ulib/ddk/include/ddk/protocol/platform-bus.h
index 35b4316..5285d60 100644
--- a/system/ulib/ddk/include/ddk/protocol/platform-bus.h
+++ b/system/ulib/ddk/include/ddk/protocol/platform-bus.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <ddk/protocol/platform-proxy.h>
 #include <zircon/compiler.h>
 #include <zircon/types.h>
 
@@ -74,6 +75,10 @@
     // platform bus resources.
     const pbus_dev_t* children;
     uint32_t child_count;
+    // Extra protocols to be provided to this platform device and its children.
+    // These fields are only used for the top level pbus_dev_t,
+    const uint32_t* protocols;
+    uint32_t protocol_count;
 };
 
 // Subset of pdev_board_info_t to be set by the board driver.
@@ -85,7 +90,8 @@
 typedef struct {
     zx_status_t (*device_add)(void* ctx, const pbus_dev_t* dev);
     zx_status_t (*protocol_device_add)(void* ctx, uint32_t proto_id, const pbus_dev_t* dev);
-    zx_status_t (*register_protocol)(void* ctx, uint32_t proto_id, void* protocol);
+    zx_status_t (*register_protocol)(void* ctx, uint32_t proto_id, void* protocol,
+                                     platform_proxy_cb proxy_cb);
     const char* (*get_board_name)(void* ctx);
     zx_status_t (*set_board_info)(void* ctx, const pbus_board_info_t* info);
 } platform_bus_protocol_ops_t;
@@ -116,8 +122,9 @@
 // Called by protocol implementation drivers to register their protocol
 // with the platform bus.
 static inline zx_status_t pbus_register_protocol(const platform_bus_protocol_t* pbus,
-                                                 uint32_t proto_id, void* protocol) {
-    return pbus->ops->register_protocol(pbus->ctx, proto_id, protocol);
+                                                 uint32_t proto_id, void* protocol,
+                                                 platform_proxy_cb proxy_cb) {
+    return pbus->ops->register_protocol(pbus->ctx, proto_id, protocol, proxy_cb);
 }
 
 // Returns the board name for the underlying hardware.
diff --git a/system/ulib/ddk/include/ddk/protocol/platform-proxy.h b/system/ulib/ddk/include/ddk/protocol/platform-proxy.h
new file mode 100644
index 0000000..98bacac
--- /dev/null
+++ b/system/ulib/ddk/include/ddk/protocol/platform-proxy.h
@@ -0,0 +1,26 @@
+// Copyright 2017 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 <zircon/compiler.h>
+#include <zircon/types.h>
+
+__BEGIN_CDECLS;
+
+typedef zx_status_t (*platform_proxy_cb)(const void* req_buf, uint32_t req_size, void* rsp_buf,
+                                             uint32_t rsp_buf_size, uint32_t* out_rsp_actual);
+
+typedef struct {
+    zx_status_t (*set_protocol)(void* ctx, uint32_t proto_id, void* protocol);
+    zx_status_t (*proxy)(void* ctx, uint32_t proto_id, const void* req_buf, uint32_t req_size,
+                         void* rsp_buf, uint32_t rsp_buf_size, uint32_t* out_rsp_actual);
+} platform_proxy_protocol_ops_t;
+
+typedef struct {
+    platform_proxy_protocol_ops_t* ops;
+    void* ctx;
+} platform_proxy_protocol_t;
+
+__END_CDECLS;
diff --git a/system/ulib/ddk/include/ddk/protodefs.h b/system/ulib/ddk/include/ddk/protodefs.h
index a41d313..8942812 100644
--- a/system/ulib/ddk/include/ddk/protodefs.h
+++ b/system/ulib/ddk/include/ddk/protodefs.h
@@ -73,6 +73,7 @@
 DDK_PROTOCOL_DEF(TEST_PARENT,    'pTSP', "test-parent", PF_NOPUB)
 DDK_PROTOCOL_DEF(PLATFORM_BUS,   'pPBU', "platform-bus", 0)
 DDK_PROTOCOL_DEF(PLATFORM_DEV,   'pPDV', "platform-dev", 0)
+DDK_PROTOCOL_DEF(PLATFORM_PROXY, 'pPPR', "platform-proxy", PF_NOPUB)
 DDK_PROTOCOL_DEF(I2C_HID,        'pIHD', "i2c-hid", 0)
 DDK_PROTOCOL_DEF(SERIAL,         'pSer', "serial", 0)
 DDK_PROTOCOL_DEF(SERIAL_IMPL,    'pSri', "serial-impl", 0)
diff --git a/system/ulib/ddktl/include/ddktl/protocol/platform-bus-internal.h b/system/ulib/ddktl/include/ddktl/protocol/platform-bus-internal.h
index 057df2a..e2e457f 100644
--- a/system/ulib/ddktl/include/ddktl/protocol/platform-bus-internal.h
+++ b/system/ulib/ddktl/include/ddktl/protocol/platform-bus-internal.h
@@ -14,7 +14,7 @@
 DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_pbus_protocol_device_add, ProtocolDeviceAdd,
         zx_status_t (C::*)(uint32_t proto_id, const pbus_dev_t* dev));
 DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_pbus_register_protocol, RegisterProtocol,
-        zx_status_t (C::*)(uint32_t proto_id, void* protocol));
+        zx_status_t (C::*)(uint32_t proto_id, void* protocol, platform_proxy_cb proxy_cb));
 DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_pbus_get_board_name, GetBoardName,
         const char* (C::*)());
 DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_pbus_set_board_info, SetBoardInfo,
@@ -30,7 +30,7 @@
                   "ProtocolAdd(uint32_t proto_id, const pbus_dev_t* dev)");
     static_assert(internal::has_pbus_register_protocol<D>::value,
                   "PlatformBusProtocol subclasses must implement "
-                  "RegisterProtocol(uint32_t proto_id, void* protocol)");
+                  "RegisterProtocol(uint32_t proto_id, void* protocol, platform_proxy_cb proxy_cb)");
     static_assert(internal::has_pbus_get_board_name<D>::value,
                   "PlatformBusProtocol subclasses must implement "
                   "GetBoardName()");
diff --git a/system/ulib/ddktl/include/ddktl/protocol/platform-bus.h b/system/ulib/ddktl/include/ddktl/protocol/platform-bus.h
index 6ab475b..880908c 100644
--- a/system/ulib/ddktl/include/ddktl/protocol/platform-bus.h
+++ b/system/ulib/ddktl/include/ddktl/protocol/platform-bus.h
@@ -36,7 +36,7 @@
 //
 //    zx_status_t DeviceAdd(const pbus_dev_t* dev);
 //    zx_status_t ProtocolDeviceAdd(uint32_t proto_id, const pbus_dev_t* dev);
-//    zx_status_t RegisterProtocol(uint32_t proto_id, void* protocol);
+//    zx_status_t RegisterProtocol(uint32_t proto_id, void* protocol, platform_proxy_cb proxy_cb);
 //    const char* GetBoardName();
 //    zx_status_t SetBoardInfo(const pbus_board_info_t* info);
 //     ...
@@ -73,8 +73,9 @@
         return static_cast<D*>(ctx)->ProtocolDeviceAdd(proto_id, dev);
     }
 
-    static zx_status_t RegisterProtocol(void* ctx, uint32_t proto_id, void* protocol) {
-        return static_cast<D*>(ctx)->RegisterProtocol(proto_id, protocol);
+    static zx_status_t RegisterProtocol(void* ctx, uint32_t proto_id, void* protocol,
+                                        platform_proxy_cb proxy_cb) {
+        return static_cast<D*>(ctx)->RegisterProtocol(proto_id, protocol, proxy_cb);
     }
 
     static const char* GetBoardName(void* ctx) {
@@ -100,8 +101,8 @@
         return ops_->protocol_device_add(ctx_, proto_id, dev);
     }
 
-    zx_status_t RegisterProtocol(uint32_t proto_id, void* protocol) {
-        return ops_->register_protocol(ctx_, proto_id, protocol);
+    zx_status_t RegisterProtocol(uint32_t proto_id, void* protocol, platform_proxy_cb proxy_cb) {
+        return ops_->register_protocol(ctx_, proto_id, protocol, proxy_cb);
     }
 
     const char* GetBoardName() {
diff --git a/system/ulib/ddktl/include/ddktl/protocol/platform-proxy-internal.h b/system/ulib/ddktl/include/ddktl/protocol/platform-proxy-internal.h
new file mode 100644
index 0000000..9bb53a2
--- /dev/null
+++ b/system/ulib/ddktl/include/ddktl/protocol/platform-proxy-internal.h
@@ -0,0 +1,30 @@
+// Copyright 2018 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 <fbl/type_support.h>
+
+namespace ddk {
+namespace internal {
+
+DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_platform_proxy_set_protocol, SetProtocol,
+        zx_status_t (C::*)(uint32_t proto_id, void* protocol));
+DECLARE_HAS_MEMBER_FN_WITH_SIGNATURE(has_platform_proxy_proxy, Proxy,
+        zx_status_t (C::*)(uint32_t proto_id, const void* req_buf, uint32_t req_size, void* rsp_buf,
+                           uint32_t rsp_buf_size, uint32_t* out_rsp_actual));
+
+template <typename D>
+constexpr void CheckPlatformProxyProtocolSubclass() {
+    static_assert(internal::has_platform_proxy_set_protocol<D>::value,
+                  "PlatformProxyProtocol subclasses must implement "
+                  "SetProtocol(uint32_t proto_id, void* protocol)");
+    static_assert(internal::has_platform_proxy_proxy<D>::value,
+                  "PlatformProxyProtocol subclasses must implement "
+                  "Proxy(uint32_t proto_id, const void* req_buf, uint32_t req_size, void* rsp_buf, "
+                  "uint32_t rsp_buf_size, uint32_t* out_rsp_actual)");
+ }
+
+}  // namespace internal
+}  // namespace ddk
diff --git a/system/ulib/ddktl/include/ddktl/protocol/platform-proxy.h b/system/ulib/ddktl/include/ddktl/protocol/platform-proxy.h
new file mode 100644
index 0000000..fcb6fb3
--- /dev/null
+++ b/system/ulib/ddktl/include/ddktl/protocol/platform-proxy.h
@@ -0,0 +1,94 @@
+// Copyright 2018 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/protocol/platform-proxy.h>
+#include <ddktl/device-internal.h>
+#include <zircon/assert.h>
+
+#include "platform-proxy-internal.h"
+
+// DDK platform proxy protocol support.
+//
+// :: Proxies ::
+//
+// ddk::PlatformProxyProtocolProxy is a simple wrappers around platform_proxy_protocol_t. It does
+// not own the pointers passed to it.
+//
+// :: Mixins ::
+//
+// ddk::PlatformProxyProtocol is a mixin class that simplifies writing DDK drivers that
+// implement the platform proxy protocol.
+//
+// :: Examples ::
+//
+// // A driver that implements a ZX_PROTOCOL_PLATFORM_PROXY device.
+// class PlatformProxyDevice;
+// using PlatformProxyDeviceType = ddk::Device<PlatformProxyDevice, /* ddk mixins */>;
+//
+// class PlatformProxyDevice : public PlatformProxyDeviceType,
+//                             public ddk::PlatformProxyProtocol<PlatformProxyDevice> {
+//   public:
+//     PlatformProxyDevice(zx_device_t* parent)
+//       : PlatformProxyDeviceType("my-platform-proxy", parent) {}
+//
+//     zx_status_t SetProtocol(uint32_t proto_id, void* protocol);
+//     zx_status_t Proxy(uint32_t proto_id, const void* req_buf, uint32_t req_size, void* rsp_buf,
+//                       uint32_t rsp_buf_size, uint32_t* out_rsp_actual);
+//     ...
+// };
+
+namespace ddk {
+
+template <typename D>
+class PlatformProxyProtocol : public internal::base_protocol {
+public:
+    PlatformProxyProtocol() {
+        internal::CheckPlatformProxyProtocolSubclass<D>();
+        platform_proxy_proto_ops_.set_protocol = SetProtocol;
+        platform_proxy_proto_ops_.proxy = Proxy;
+
+       // Can only inherit from one base_protocol implementation.
+        ZX_ASSERT(ddk_proto_id_ == 0);
+        ddk_proto_id_ = ZX_PROTOCOL_PLATFORM_PROXY;
+        ddk_proto_ops_ = &platform_proxy_proto_ops_;
+    }
+
+protected:
+    platform_proxy_protocol_ops_t platform_proxy_proto_ops_ = {};
+
+private:
+    static zx_status_t SetProtocol(void* ctx, uint32_t proto_id, void* protocol) {
+        return static_cast<D*>(ctx)->SetProtocol(proto_id, protocol);
+    }
+
+    static zx_status_t Proxy(void* ctx, uint32_t proto_id, const void* req_buf, uint32_t req_size,
+                         void* rsp_buf, uint32_t rsp_buf_size, uint32_t* out_rsp_actual) {
+        return static_cast<D*>(ctx)->Proxy(proto_id, req_buf, req_size, rsp_buf, rsp_buf_size,
+                                           out_rsp_actual);
+    }
+};
+
+class PlatformProxyProtocolProxy {
+public:
+    PlatformProxyProtocolProxy(platform_proxy_protocol_t* proto)
+        : ops_(proto->ops), ctx_(proto->ctx) {}
+
+    zx_status_t SetProtocol(uint32_t proto_id, void* protocol) {
+        return ops_->set_protocol(ctx_, proto_id, protocol);
+    }
+
+    zx_status_t Proxy(uint32_t proto_id, const void* req_buf, uint32_t req_size, void* rsp_buf,
+                      uint32_t rsp_buf_size, uint32_t* out_rsp_actual) {
+        return ops_->proxy(ctx_, proto_id, req_buf, req_size, rsp_buf, rsp_buf_size,
+                           out_rsp_actual);
+    }
+
+private:
+    platform_proxy_protocol_ops_t* ops_;
+    void* ctx_;
+};
+
+} // namespace ddk