[dev][intel-i915] Convert driver to c++/ddktl

Change-Id: I927ef64122402fee3e661c8a473b24423dca63d9
diff --git a/system/dev/display/intel-i915/bind.c b/system/dev/display/intel-i915/bind.c
new file mode 100644
index 0000000..9486e2d
--- /dev/null
+++ b/system/dev/display/intel-i915/bind.c
@@ -0,0 +1,22 @@
+// 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 <ddk/binding.h>
+#include <ddk/driver.h>
+
+#include "intel-i915.h"
+
+#define INTEL_I915_VID (0x8086)
+
+static zx_driver_ops_t intel_i915_driver_ops = {
+    .version = DRIVER_OPS_VERSION,
+    .bind = intel_i915_bind,
+};
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(intel_i915, intel_i915_driver_ops, "zircon", "*0.1", 3)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
+    BI_ABORT_IF(NE, BIND_PCI_VID, INTEL_I915_VID),
+    BI_MATCH_IF(EQ, BIND_PCI_CLASS, 0x3), // Display class
+ZIRCON_DRIVER_END(intel_i915)
diff --git a/system/dev/display/intel-i915/intel-i915.c b/system/dev/display/intel-i915/intel-i915.c
deleted file mode 100644
index a7242ea..0000000
--- a/system/dev/display/intel-i915/intel-i915.c
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2016 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 <ddk/binding.h>
-#include <ddk/debug.h>
-#include <ddk/device.h>
-#include <ddk/driver.h>
-#include <ddk/protocol/display.h>
-#include <ddk/protocol/pci.h>
-#include <hw/pci.h>
-
-#include <assert.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <zircon/syscalls.h>
-#include <zircon/types.h>
-
-#define INTEL_I915_VID (0x8086)
-#define INTEL_I915_BROADWELL_DID (0x1616)
-
-#define INTEL_I915_REG_WINDOW_SIZE (0x1000000u)
-#define INTEL_I915_FB_WINDOW_SIZE (0x10000000u)
-
-#define BACKLIGHT_CTRL_OFFSET (0xc8250)
-#define BACKLIGHT_CTRL_BIT ((uint32_t)(1u << 31))
-
-typedef struct intel_i915_device {
-    void* regs;
-    uint64_t regs_size;
-    zx_handle_t regs_handle;
-
-    void* framebuffer;
-    uint64_t framebuffer_size;
-    zx_handle_t framebuffer_handle;
-
-    zx_display_info_t info;
-    uint32_t flags;
-} intel_i915_device_t;
-
-#define FLAGS_BACKLIGHT 1
-
-static void intel_i915_enable_backlight(intel_i915_device_t* dev, bool enable) {
-    if (dev->flags & FLAGS_BACKLIGHT) {
-        void* backlight_ctrl = (uint8_t*)dev->regs + BACKLIGHT_CTRL_OFFSET;
-        uint32_t tmp = pcie_read32(backlight_ctrl);
-
-        if (enable)
-            tmp |= BACKLIGHT_CTRL_BIT;
-        else
-            tmp &= ~BACKLIGHT_CTRL_BIT;
-
-        pcie_write32(backlight_ctrl, tmp);
-    }
-}
-
-// implement display protocol
-
-static zx_status_t intel_i915_set_mode(void* ctx, zx_display_info_t* info) {
-    return ZX_ERR_NOT_SUPPORTED;
-}
-
-static zx_status_t intel_i915_get_mode(void* ctx, zx_display_info_t* info) {
-    assert(info);
-    intel_i915_device_t* device = ctx;
-    memcpy(info, &device->info, sizeof(zx_display_info_t));
-    return ZX_OK;
-}
-
-static zx_status_t intel_i915_get_framebuffer(void* ctx, void** framebuffer) {
-    assert(framebuffer);
-    intel_i915_device_t* device = ctx;
-    (*framebuffer) = device->framebuffer;
-    return ZX_OK;
-}
-
-static display_protocol_ops_t intel_i915_display_proto = {
-    .set_mode = intel_i915_set_mode,
-    .get_mode = intel_i915_get_mode,
-    .get_framebuffer = intel_i915_get_framebuffer,
-};
-
-// implement device protocol
-
-static zx_status_t intel_i915_open(void* ctx, zx_device_t** out, uint32_t flags) {
-    intel_i915_device_t* device = ctx;
-    intel_i915_enable_backlight(device, true);
-    return ZX_OK;
-}
-
-static zx_status_t intel_i915_close(void* ctx, uint32_t flags) {
-    return ZX_OK;
-}
-
-static void intel_i915_release(void* ctx) {
-    intel_i915_device_t* device = ctx;
-    intel_i915_enable_backlight(device, false);
-
-    if (device->regs) {
-        zx_handle_close(device->regs_handle);
-        device->regs_handle = -1;
-    }
-
-    if (device->framebuffer) {
-        zx_handle_close(device->framebuffer_handle);
-        device->framebuffer_handle = -1;
-    }
-
-    free(device);
-}
-
-static zx_protocol_device_t intel_i915_device_proto = {
-    .version = DEVICE_OPS_VERSION,
-    .open = intel_i915_open,
-    .close = intel_i915_close,
-    .release = intel_i915_release,
-};
-
-// implement driver object:
-
-static zx_status_t intel_i915_bind(void* ctx, zx_device_t* dev) {
-    pci_protocol_t pci;
-    if (device_get_protocol(dev, ZX_PROTOCOL_PCI, &pci))
-        return ZX_ERR_NOT_SUPPORTED;
-
-    // map resources and initialize the device
-    intel_i915_device_t* device = calloc(1, sizeof(intel_i915_device_t));
-    if (!device)
-        return ZX_ERR_NO_MEMORY;
-
-    const pci_config_t* pci_config;
-    size_t config_size;
-    zx_handle_t cfg_handle = ZX_HANDLE_INVALID;
-    zx_status_t status = pci_map_resource(&pci, PCI_RESOURCE_CONFIG,
-                                          ZX_CACHE_POLICY_UNCACHED_DEVICE,
-                                          (void**)&pci_config,
-                                          &config_size, &cfg_handle);
-    if (status == ZX_OK) {
-        if (pci_config->device_id == INTEL_I915_BROADWELL_DID) {
-            // TODO: this should be based on the specific target
-            device->flags |= FLAGS_BACKLIGHT;
-        }
-        zx_handle_close(cfg_handle);
-    }
-
-    // map register window
-    status = pci_map_resource(&pci, PCI_RESOURCE_BAR_0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
-                              &device->regs, &device->regs_size, &device->regs_handle);
-    if (status != ZX_OK) {
-        zxlogf(ERROR, "i915: failed to map bar 0: %d\n", status);
-        goto fail;
-    }
-
-    // map framebuffer window
-    status = pci_map_resource(&pci, PCI_RESOURCE_BAR_2, ZX_CACHE_POLICY_WRITE_COMBINING,
-                              &device->framebuffer,
-                              &device->framebuffer_size,
-                              &device->framebuffer_handle);
-    if (status != ZX_OK) {
-        zxlogf(ERROR, "i915: failed to map bar 2: %d\n", status);
-        goto fail;
-    }
-
-    zx_display_info_t* di = &device->info;
-    uint32_t format, width, height, stride;
-    status = zx_bootloader_fb_get_info(&format, &width, &height, &stride);
-    if (status == ZX_OK) {
-        di->format = format;
-        di->width = width;
-        di->height = height;
-        di->stride = stride;
-    } else {
-        di->format = ZX_PIXEL_FORMAT_RGB_565;
-        di->width = 2560 / 2;
-        di->height = 1700 / 2;
-        di->stride = 2560 / 2;
-    }
-    di->flags = ZX_DISPLAY_FLAG_HW_FRAMEBUFFER;
-
-    // TODO remove when the gfxconsole moves to user space
-    intel_i915_enable_backlight(device, true);
-    zx_set_framebuffer(get_root_resource(), device->framebuffer, device->framebuffer_size,
-                       format, width, height, stride);
-
-    // create and add the display (char) device
-    device_add_args_t args = {
-        .version = DEVICE_ADD_ARGS_VERSION,
-        .name = "intel_i915_disp",
-        .ctx = device,
-        .ops = &intel_i915_device_proto,
-        .proto_id = ZX_PROTOCOL_DISPLAY,
-        .proto_ops = &intel_i915_display_proto,
-    };
-
-    status = device_add(dev, &args, NULL);
-    if (status != ZX_OK) {
-        goto fail;
-    }
-
-    zxlogf(SPEW, "i915: reg=%p regsize=0x%" PRIx64 " fb=%p fbsize=0x%" PRIx64 "\n",
-            device->regs, device->regs_size, device->framebuffer, device->framebuffer_size);
-
-    return ZX_OK;
-
-fail:
-    free(device);
-    return status;
-}
-
-static zx_driver_ops_t intel_i915_driver_ops = {
-    .version = DRIVER_OPS_VERSION,
-    .bind = intel_i915_bind,
-};
-
-// clang-format off
-ZIRCON_DRIVER_BEGIN(intel_i915, intel_i915_driver_ops, "zircon", "*0.1", 3)
-    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
-    BI_ABORT_IF(NE, BIND_PCI_VID, INTEL_I915_VID),
-    BI_MATCH_IF(EQ, BIND_PCI_CLASS, 0x3), // Display class
-ZIRCON_DRIVER_END(intel_i915)
diff --git a/system/dev/display/intel-i915/intel-i915.cpp b/system/dev/display/intel-i915/intel-i915.cpp
new file mode 100644
index 0000000..a289846
--- /dev/null
+++ b/system/dev/display/intel-i915/intel-i915.cpp
@@ -0,0 +1,200 @@
+// Copyright 2016 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 <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/protocol/display.h>
+#include <ddk/protocol/pci.h>
+#include <hw/pci.h>
+
+#include <assert.h>
+#include <fbl/unique_ptr.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
+
+#include "intel-i915.h"
+
+#define INTEL_I915_BROADWELL_DID (0x1616)
+
+#define INTEL_I915_REG_WINDOW_SIZE (0x1000000u)
+#define INTEL_I915_FB_WINDOW_SIZE (0x10000000u)
+
+#define BACKLIGHT_CTRL_OFFSET (0xc8250)
+#define BACKLIGHT_CTRL_BIT ((uint32_t)(1u << 31))
+
+#define FLAGS_BACKLIGHT 1
+
+namespace i915 {
+
+void Device::EnableBacklight(bool enable) {
+    if (flags_ & FLAGS_BACKLIGHT) {
+        auto* backlight_ctrl = reinterpret_cast<volatile uint32_t*>(regs_ + BACKLIGHT_CTRL_OFFSET);
+        uint32_t tmp = pcie_read32(backlight_ctrl);
+
+        if (enable) {
+            tmp |= BACKLIGHT_CTRL_BIT;
+        } else {
+            tmp &= ~BACKLIGHT_CTRL_BIT;
+        }
+
+        pcie_write32(backlight_ctrl, tmp);
+    }
+}
+
+// implement display protocol
+
+zx_status_t Device::SetMode(zx_display_info_t* info) {
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t Device::GetMode(zx_display_info_t* info) {
+    assert(info);
+    memcpy(info, &info_, sizeof(zx_display_info_t));
+    return ZX_OK;
+}
+
+zx_status_t Device::GetFramebuffer(void** framebuffer) {
+    assert(framebuffer);
+    *framebuffer = framebuffer_;
+    return ZX_OK;
+}
+
+void Device::Flush() {
+    // no-op
+}
+
+void Device::AcquireOrReleaseDisplay(bool acquire) {
+    // no-op
+}
+
+void Device::SetOwnershipChangeCallback(zx_display_cb_t callback, void* cookie) {
+    // no-op
+}
+
+// implement device protocol
+
+zx_status_t Device::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
+    EnableBacklight(true);
+    return ZX_OK;
+}
+
+zx_status_t Device::DdkClose(uint32_t flags) {
+    return ZX_OK;
+}
+
+void Device::DdkRelease() {
+    delete this;
+}
+
+zx_status_t Device::Bind() {
+    pci_protocol_t pci;
+    if (device_get_protocol(parent_, ZX_PROTOCOL_PCI, &pci))
+        return ZX_ERR_NOT_SUPPORTED;
+
+    const pci_config_t* pci_config;
+    size_t config_size;
+    zx_handle_t cfg_handle = ZX_HANDLE_INVALID;
+    zx_status_t status = pci_map_resource(&pci, PCI_RESOURCE_CONFIG,
+                                          ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                                          (void**)&pci_config,
+                                          &config_size, &cfg_handle);
+    uint32_t flags = 0;
+    if (status == ZX_OK) {
+        if (pci_config->device_id == INTEL_I915_BROADWELL_DID) {
+            // TODO: this should be based on the specific target
+            flags |= FLAGS_BACKLIGHT;
+        }
+        zx_handle_close(cfg_handle);
+    }
+
+    // map register window
+    void** addr = reinterpret_cast<void**>(&regs_);
+    status = pci_map_resource(&pci, PCI_RESOURCE_BAR_0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+                              addr, &regs_size_, &regs_handle_);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "i915: failed to map bar 0: %d\n", status);
+        return status;
+    }
+
+    // map framebuffer window
+    status = pci_map_resource(&pci, PCI_RESOURCE_BAR_2, ZX_CACHE_POLICY_WRITE_COMBINING,
+                              &framebuffer_,
+                              &framebuffer_size_,
+                              &framebuffer_handle_);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "i915: failed to map bar 2: %d\n", status);
+        return status;
+    }
+
+    zx_display_info_t* di = &info_;
+    uint32_t format, width, height, stride;
+    status = zx_bootloader_fb_get_info(&format, &width, &height, &stride);
+    if (status == ZX_OK) {
+        di->format = format;
+        di->width = width;
+        di->height = height;
+        di->stride = stride;
+    } else {
+        di->format = ZX_PIXEL_FORMAT_RGB_565;
+        di->width = 2560 / 2;
+        di->height = 1700 / 2;
+        di->stride = 2560 / 2;
+    }
+    di->flags = ZX_DISPLAY_FLAG_HW_FRAMEBUFFER;
+
+    // TODO remove when the gfxconsole moves to user space
+    EnableBacklight(true);
+    zx_set_framebuffer(get_root_resource(), framebuffer_,
+                       (uint32_t) framebuffer_size_,
+                       format, width, height, stride);
+
+    status = DdkAdd("intel_i915_disp", flags);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    zxlogf(SPEW, "i915: reg=%08lx regsize=0x%" PRIx64 " fb=%p fbsize=0x%" PRIx64 "\n",
+            regs_, regs_size_, framebuffer_, framebuffer_size_);
+
+    return ZX_OK;
+}
+
+Device::Device(zx_device_t* parent) : DeviceType(parent) { }
+
+Device::~Device() {
+    if (regs_) {
+        EnableBacklight(false);
+
+        zx_handle_close(regs_handle_);
+        regs_handle_ = ZX_HANDLE_INVALID;
+    }
+
+    if (framebuffer_) {
+        zx_handle_close(framebuffer_handle_);
+        framebuffer_handle_ = ZX_HANDLE_INVALID;
+    }
+}
+
+} // namespace i915
+
+zx_status_t intel_i915_bind(void* ctx, zx_device_t* parent) {
+    fbl::AllocChecker ac;
+    fbl::unique_ptr<i915::Device> device(new (&ac) i915::Device(parent));
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    zx_status_t status = device->Bind();
+    if (status == ZX_OK) {
+        device.release();
+    }
+    return status;
+}
diff --git a/system/dev/display/intel-i915/intel-i915.h b/system/dev/display/intel-i915/intel-i915.h
new file mode 100644
index 0000000..229c79f
--- /dev/null
+++ b/system/dev/display/intel-i915/intel-i915.h
@@ -0,0 +1,55 @@
+// 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
+
+#if __cplusplus
+
+#include <ddktl/device.h>
+#include <ddktl/protocol/display.h>
+
+namespace i915 {
+
+class Device;
+using DeviceType = ddk::Device<Device, ddk::Openable, ddk::Closable>;
+
+class Device: public DeviceType, public ddk::DisplayProtocol<Device> {
+public:
+    Device(zx_device_t* parent);
+    ~Device();
+
+    zx_status_t DdkOpen(zx_device_t** dev_out, uint32_t flags);
+    zx_status_t DdkClose(uint32_t flags);
+    void DdkRelease();
+    zx_status_t Bind();
+
+    zx_status_t SetMode(zx_display_info_t* info);
+    zx_status_t GetMode(zx_display_info_t* info);
+    zx_status_t GetFramebuffer(void** framebuffer);
+    void Flush();
+    void AcquireOrReleaseDisplay(bool acquire);
+    void SetOwnershipChangeCallback(zx_display_cb_t callback, void* cookie);
+
+private:
+    void EnableBacklight(bool enable);
+
+    uintptr_t regs_;
+    uint64_t regs_size_;
+    zx_handle_t regs_handle_;
+
+    void* framebuffer_;
+    uint64_t framebuffer_size_;
+    zx_handle_t framebuffer_handle_;
+
+    zx_display_info_t info_;
+    uint32_t flags_;
+};
+
+}
+
+#endif // __cplusplus
+
+__BEGIN_CDECLS
+zx_status_t intel_i915_bind(void* ctx, zx_device_t* parent);
+__END_CDECLS
diff --git a/system/dev/display/intel-i915/rules.mk b/system/dev/display/intel-i915/rules.mk
index ce40d58..7dd1de3 100644
--- a/system/dev/display/intel-i915/rules.mk
+++ b/system/dev/display/intel-i915/rules.mk
@@ -10,12 +10,18 @@
 
 MODULE_TYPE := driver
 
-MODULE_SRCS := $(LOCAL_DIR)/intel-i915.c
+MODULE_SRCS := \
+    $(LOCAL_DIR)/bind.c \
+    $(LOCAL_DIR)/intel-i915.cpp
 
-MODULE_STATIC_LIBS := system/ulib/ddk
+MODULE_STATIC_LIBS := \
+    system/ulib/ddk \
+    system/ulib/ddktl \
+    system/ulib/zxcpp \
+    system/ulib/fbl
 
 MODULE_LIBS := system/ulib/driver system/ulib/zircon system/ulib/c
 
 include make/module.mk
 
-endif
\ No newline at end of file
+endif
diff --git a/system/ulib/ddktl/BUILD.gn b/system/ulib/ddktl/BUILD.gn
index 1f6c8c7..4ff01e8 100644
--- a/system/ulib/ddktl/BUILD.gn
+++ b/system/ulib/ddktl/BUILD.gn
@@ -15,6 +15,8 @@
     "include/ddktl/protocol/block.h",
     "include/ddktl/protocol/ethernet-internal.h",
     "include/ddktl/protocol/ethernet.h",
+    "include/ddktl/protocol/display.h",
+    "include/ddktl/protocol/display-internal.h",
     "include/ddktl/protocol/pci.h",
     "include/ddktl/protocol/test.h",
     "include/ddktl/protocol/tpm.h",
diff --git a/system/ulib/ddktl/include/ddktl/protocol/display-internal.h b/system/ulib/ddktl/include/ddktl/protocol/display-internal.h
new file mode 100644
index 0000000..f523f62
--- /dev/null
+++ b/system/ulib/ddktl/include/ddktl/protocol/display-internal.h
@@ -0,0 +1,77 @@
+// 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-internal.h>
+#include <zircon/types.h>
+#include <fbl/type_support.h>
+#include <fbl/unique_ptr.h>
+
+#include <stdint.h>
+
+namespace ddk {
+namespace internal {
+
+DECLARE_HAS_MEMBER_FN(has_set_mode, SetMode);
+DECLARE_HAS_MEMBER_FN(has_get_mode, GetMode);
+DECLARE_HAS_MEMBER_FN(has_get_framebuffer, GetFramebuffer);
+DECLARE_HAS_MEMBER_FN(has_flush, Flush);
+DECLARE_HAS_MEMBER_FN(has_acquire_or_release_display, AcquireOrReleaseDisplay);
+DECLARE_HAS_MEMBER_FN(has_set_ownership_change_callback, SetOwnershipChangeCallback);
+
+template <typename D>
+constexpr void CheckDisplayProtocolSubclass() {
+    static_assert(internal::has_set_mode<D>::value,
+                  "DisplayProtocol subclasses must implement SetMode");
+    static_assert(fbl::is_same<decltype(&D::SetMode),
+                                zx_status_t (D::*)(zx_display_info_t*)>::value,
+                  "SetMode must be a non-static member function with signature "
+                  "'zx_status_t SetMode(zx_display_info_t* info)', and be visible to "
+                  "ddk::DisplayProtocol<D> (either because they are public, or because of "
+                  "friendship).");
+    static_assert(internal::has_get_mode<D>::value,
+                  "DisplayProtocol subclasses must implement GetMode");
+    static_assert(fbl::is_same<decltype(&D::GetMode),
+                                zx_status_t (D::*)(zx_display_info_t*)>::value,
+                  "GetMode must be a non-static member function with signature "
+                  "'zx_status_t GetMode(zx_display_info_t* info)', and be visible to "
+                  "ddk::DisplayProtocol<D> (either because they are public, or because of "
+                  "friendship).");
+    static_assert(internal::has_get_framebuffer<D>::value,
+                  "DisplayProtocol subclasses must implement GetFramebuffer");
+    static_assert(fbl::is_same<decltype(&D::GetFramebuffer),
+                                zx_status_t (D::*)(void**)>::value,
+                  "GetFramebuffer must be a non-static member function with signature "
+                  "'zx_status_t GetFramebuffer(void** framebuffer)', and be visible to "
+                  "ddk::DisplayProtocol<D> (either because they are public, or because of "
+                  "friendship).");
+    static_assert(internal::has_flush<D>::value,
+                  "DisplayProtocol subclasses must implement Flush");
+    static_assert(fbl::is_same<decltype(&D::Flush),
+                                void (D::*)()>::value,
+                  "Flush must be a non-static member function with signature "
+                  "'void Flush()', and be visible to "
+                  "ddk::DisplayProtocol<D> (either because they are public, or because of "
+                  "friendship).");
+    static_assert(internal::has_acquire_or_release_display<D>::value,
+                  "DisplayProtocol subclasses must implement AcquireOrReleaseDisplay");
+    static_assert(fbl::is_same<decltype(&D::AcquireOrReleaseDisplay),
+                                void (D::*)(bool)>::value,
+                  "AcquireOrReleaseDisplay must be a non-static member function with signature "
+                  "'void AcquireOrReleaseDisplay(bool acquire)', and be visible to "
+                  "ddk::DisplayProtocol<D> (either because they are public, or because of "
+                  "friendship).");
+    static_assert(internal::has_set_ownership_change_callback<D>::value,
+                  "DisplayProtocol subclasses must implement SetOwnershipChangeCallback");
+    static_assert(fbl::is_same<decltype(&D::SetOwnershipChangeCallback),
+                                void (D::*)(zx_display_cb_t, void*)>::value,
+                  "SetOwnershipChangeCallback must be a non-static member function with signature "
+                  "'void SetOwnershipChangeCallback(zx_display_cb_t cb, void* cookie)', and be "
+                  "visible to ddk::DisplayProtocol<D> (either because they are public, or because "
+                  "of friendship).");
+}
+
+}  // namespace internal
+}  // namespace ddk
diff --git a/system/ulib/ddktl/include/ddktl/protocol/display.h b/system/ulib/ddktl/include/ddktl/protocol/display.h
new file mode 100644
index 0000000..32d725a
--- /dev/null
+++ b/system/ulib/ddktl/include/ddktl/protocol/display.h
@@ -0,0 +1,60 @@
+// 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/protocol/display-internal.h"
+
+#include <ddk/protocol/display.h>
+#include <zircon/assert.h>
+
+namespace ddk {
+
+template <typename D>
+class DisplayProtocol : public internal::base_protocol {
+  public:
+    DisplayProtocol() {
+        internal::CheckDisplayProtocolSubclass<D>();
+        ops_.set_mode = SetMode;
+        ops_.get_mode = GetMode;
+        ops_.get_framebuffer = GetFramebuffer;
+        ops_.flush = FlushThunk;
+        ops_.acquire_or_release_display = AcquireOrReleaseDisplay;
+        ops_.set_ownership_change_callback = SetOwnershipChangeCallback;
+
+        // Can only inherit from one base_protocol implemenation
+        ZX_ASSERT(ddk_proto_ops_ == nullptr);
+        ddk_proto_id_ = ZX_PROTOCOL_DISPLAY;
+        ddk_proto_ops_ = &ops_;
+    }
+
+  private:
+    static zx_status_t SetMode(void* ctx, zx_display_info_t* info) {
+        return static_cast<D*>(ctx)->SetMode(info);
+    }
+
+    static zx_status_t GetMode(void* ctx, zx_display_info_t* info) {
+        return static_cast<D*>(ctx)->GetMode(info);
+    }
+
+    static zx_status_t GetFramebuffer(void* ctx, void** framebuffer) {
+        return static_cast<D*>(ctx)->GetFramebuffer(framebuffer);
+    }
+
+    static void FlushThunk(void* ctx) {
+        static_cast<D*>(ctx)->Flush();
+    }
+
+    static void AcquireOrReleaseDisplay(void* ctx, bool acquire) {
+        static_cast<D*>(ctx)->AcquireOrReleaseDisplay(acquire);
+    }
+
+    static void SetOwnershipChangeCallback(void* ctx, zx_display_cb_t callback, void* cookie) {
+        static_cast<D*>(ctx)->SetOwnershipChangeCallback(callback, cookie);
+    }
+
+    display_protocol_ops_t ops_ = {};
+};
+
+}  // namespace ddk