[hidctl] Rewrite hidctl driver

The hidctl driver was never updated to use the hidbus protocol. This
rewrite leverages the DDKTL and provides a socket over which HID reports
may be injected into a virtual input device.

HID commands are not yet implemented, so only reading input reports is
supported so far.

Change-Id: I5938af3a89bc184e78060f4e61ec4280882bc5bb
diff --git a/system/dev/input/hidctl/binding.c b/system/dev/input/hidctl/binding.c
new file mode 100644
index 0000000..a18689c
--- /dev/null
+++ b/system/dev/input/hidctl/binding.c
@@ -0,0 +1,20 @@
+// 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/device.h>
+#include <ddk/driver.h>
+#include <ddk/binding.h>
+
+#include <zircon/types.h>
+
+extern zx_status_t hidctl_bind(void* ctx, zx_device_t* device, void** cookie);
+
+static zx_driver_ops_t hidctl_driver_ops = {
+    .version = DRIVER_OPS_VERSION,
+    .bind = hidctl_bind,
+};
+
+ZIRCON_DRIVER_BEGIN(hidctl, hidctl_driver_ops, "zircon", "0.1", 1)
+    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
+ZIRCON_DRIVER_END(hidctl)
diff --git a/system/dev/input/hidctl/hidctl.c b/system/dev/input/hidctl/hidctl.c
deleted file mode 100644
index 7e0ed6e..0000000
--- a/system/dev/input/hidctl/hidctl.c
+++ /dev/null
@@ -1,208 +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/device.h>
-#include <ddk/driver.h>
-#include <ddk/common/hid.h>
-
-#include <zircon/device/hidctl.h>
-#include <zircon/types.h>
-
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#define from_hid_device(d) containerof(d, hidctl_instance_t, hiddev)
-
-typedef struct hidctl_instance {
-    zx_device_t* zxdev;
-    zx_device_t* parent;
-    zx_hid_device_t hiddev;
-
-    uint8_t* hid_report_desc;
-    size_t hid_report_desc_len;
-} hidctl_instance_t;
-
-typedef struct hidctl_root {
-    zx_device_t* zxdev;
-} hidctl_root_t;
-
-zx_status_t hidctl_get_descriptor(zx_hid_device_t* dev, uint8_t desc_type, void** data, size_t* len) {
-    if (desc_type != HID_DESC_TYPE_REPORT) {
-        return ZX_ERR_NOT_SUPPORTED;
-    }
-    hidctl_instance_t* inst = from_hid_device(dev);
-    *data = malloc(inst->hid_report_desc_len);
-    memcpy(*data, inst->hid_report_desc, inst->hid_report_desc_len);
-    *len = inst->hid_report_desc_len;
-    return ZX_OK;
-}
-
-zx_status_t hidctl_get_report(zx_hid_device_t* dev, uint8_t rpt_type, uint8_t rpt_id, void* data,
-                              size_t len, size_t* out_len) {
-    return ZX_ERR_NOT_SUPPORTED;
-}
-
-zx_status_t hidctl_set_report(zx_hid_device_t* dev, uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len) {
-    return ZX_ERR_NOT_SUPPORTED;
-}
-
-zx_status_t hidctl_get_idle(zx_hid_device_t* dev, uint8_t rpt_id, uint8_t* duration) {
-    return ZX_ERR_NOT_SUPPORTED;
-}
-
-zx_status_t hidctl_set_idle(zx_hid_device_t* dev, uint8_t rpt_id, uint8_t duration) {
-    return ZX_OK;
-}
-
-zx_status_t hidctl_get_protocol(zx_hid_device_t* dev, uint8_t* protocol) {
-    return ZX_ERR_NOT_SUPPORTED;
-}
-
-zx_status_t hidctl_set_protocol(zx_hid_device_t* dev, uint8_t protocol) {
-    return ZX_OK;
-}
-
-static hid_bus_ops_t hidctl_hid_ops = {
-    .get_descriptor = hidctl_get_descriptor,
-    .get_report = hidctl_get_report,
-    .set_report = hidctl_set_report,
-    .get_idle = hidctl_get_idle,
-    .set_idle = hidctl_set_idle,
-    .get_protocol = hidctl_get_protocol,
-    .set_protocol = hidctl_set_protocol,
-};
-
-static ssize_t hidctl_set_config(hidctl_instance_t* dev, const void* in_buf, size_t in_len) {
-    const hid_ioctl_config_t* cfg = in_buf;
-    if (in_len < sizeof(hid_ioctl_config_t) || in_len != sizeof(hid_ioctl_config_t) + cfg->rpt_desc_len) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-
-    if (cfg->dev_class > HID_DEV_CLASS_LAST) {
-        return ZX_ERR_INVALID_ARGS;
-    }
-
-    hid_init_device(&dev->hiddev, &hidctl_hid_ops, cfg->dev_num, cfg->boot_device, cfg->dev_class);
-
-    dev->hid_report_desc_len = cfg->rpt_desc_len;
-    dev->hid_report_desc = malloc(cfg->rpt_desc_len);
-    memcpy(dev->hid_report_desc, cfg->rpt_desc, cfg->rpt_desc_len);
-
-    zx_status_t status = hid_add_device(&_driver_hidctl, &dev->hiddev, dev->parent);
-    if (status != ZX_OK) {
-        hid_release_device(&dev->hiddev);
-        free(dev->hid_report_desc);
-        dev->hid_report_desc = NULL;
-    } else {
-        printf("hidctl: device added\n");
-    }
-    return status;
-}
-
-static ssize_t hidctl_read(void* ctx, void* buf, size_t count, zx_off_t off) {
-    return 0;
-}
-
-static ssize_t hidctl_write(void* ctx, const void* buf, size_t count, zx_off_t off) {
-    hidctl_instance_t* inst = ctx;
-    hid_io_queue(&inst->hiddev, buf, count);
-    return count;
-}
-
-static ssize_t hidctl_ioctl(void* ctx, uint32_t op,
-        const void* in_buf, size_t in_len, void* out_buf, size_t out_len) {
-    hidctl_instance_t* inst = ctx;
-    switch (op) {
-    case IOCTL_HID_CTL_CONFIG:
-        return hidctl_set_config(inst, in_buf, in_len);
-        break;
-    }
-    return ZX_ERR_NOT_SUPPORTED;
-}
-
-static void hidctl_release(void* ctx) {
-    hidctl_instance_t* inst = ctx;
-    hid_release_device(&inst->hiddev);
-    if (inst->hid_report_desc) {
-        free(inst->hid_report_desc);
-        device_remove(&inst->hiddev.dev);
-    }
-    free(inst);
-}
-
-zx_protocol_device_t hidctl_instance_proto = {
-    .read = hidctl_read,
-    .write = hidctl_write,
-    .ioctl = hidctl_ioctl,
-    .release = hidctl_release,
-};
-
-static zx_status_t hidctl_open(void* ctx, zx_device_t** dev_out, uint32_t flags) {
-    hidctl_root_t* root = ctx;
-    hidctl_instance_t* inst = calloc(1, sizeof(hidctl_instance_t));
-    if (inst == NULL) {
-        return ZX_ERR_NO_MEMORY;
-    }
-    inst->parent = root->zxdev;
-
-    zx_status_t status;
-    if ((status = device_create("hidctl-inst", inst, &hidctl_instance_proto, &_driver_hidctl,
-                                &inst->zxdev)) < 0) {
-        free(inst);
-        return status;
-    }
-
-    status = device_add_instance(inst->zxdev, root->zxdev);
-    if (status != ZX_OK) {
-        printf("hidctl: could not open instance: %d\n", status);
-        device_destroy(inst->zxdev);
-        free(inst);
-        return status;
-    }
-    *dev_out = inst->zxdev;
-    return ZX_OK;
-}
-
-static void hidctl_root_release(void* ctx) {
-    hidctl_root_t* root = ctx;
-    device_destroy(root->zxdev);
-    free(root);
-}
-
-static zx_protocol_device_t hidctl_device_proto = {
-    .open = hidctl_open,
-    .release = hidctl_root_release,
-};
-
-static zx_status_t hidctl_bind(void* ctx, zx_device_t* parent, void** cookie) {
-    hidctl_root_t* root = calloc(1, sizeof(hidctl_root_t));
-    if (!root) {
-        return ZX_ERR_NO_MEMORY;
-    }
-
-    zx_status_t status;
-    if ((status = device_create("hidctl", root, &hidctl_device_proto, driver, &root->zxdev)) < 0) {
-        free(root);
-        return status;
-    }
-    if ((status = device_add(root->zxdev, parent)) < 0) {
-        device_destroy(root->zxdev);
-        free(root);
-        return status;
-    }
-
-    return ZX_OK;
-}
-
-static zx_driver_ops_t hidctl_driver_ops = {
-    .version = DRIVER_OPS_VERSION,
-    .bind = hidctl_bind,
-};
-
-ZIRCON_DRIVER_BEGIN(hidctl, hidctl_driver_ops, "zircon", "0.1", 1)
-    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
-ZIRCON_DRIVER_END(hidctl)
diff --git a/system/dev/input/hidctl/hidctl.cpp b/system/dev/input/hidctl/hidctl.cpp
new file mode 100644
index 0000000..ca80a82
--- /dev/null
+++ b/system/dev/input/hidctl/hidctl.cpp
@@ -0,0 +1,292 @@
+// 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 "hidctl.h"
+
+#include <ddk/debug.h>
+#include <zircon/compiler.h>
+#include <fbl/auto_lock.h>
+#include <fbl/type_support.h>
+#include <pretty/hexdump.h>
+
+#include <stdio.h>
+#include <string.h>
+
+namespace hidctl {
+
+HidCtl::HidCtl(zx_device_t* device) : ddk::Device<HidCtl, ddk::Ioctlable>(device) {}
+
+void HidCtl::DdkRelease() {
+    delete this;
+}
+
+zx_status_t HidCtl::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
+                             size_t out_len, size_t* out_actual) {
+    switch (op) {
+    case IOCTL_HIDCTL_CONFIG: {
+        if (in_buf == nullptr || in_len < sizeof(hid_ioctl_config_t) ||
+            out_buf == nullptr || out_len != sizeof(zx_handle_t) || out_actual == nullptr) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        auto config = static_cast<const hid_ioctl_config*>(in_buf);
+        if (in_len != sizeof(hid_ioctl_config_t) + config->rpt_desc_len) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        if (config->dev_class > HID_DEV_CLASS_LAST) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+
+        zx::socket local, remote;
+        zx_status_t status = zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &remote);
+        if (status != ZX_OK) {
+            return status;
+        }
+
+
+        auto hiddev = fbl::unique_ptr<hidctl::HidDevice>(
+                new hidctl::HidDevice(zxdev(), config, fbl::move(local)));
+
+        status = hiddev->DdkAdd("hidctl-dev");
+        if (status != ZX_OK) {
+            dprintf(ERROR, "hidctl: could not add hid device: %d\n", status);
+            hiddev->Shutdown();
+        } else {
+            // devmgr owns the memory until release is called
+            hiddev.release();
+
+            auto out = static_cast<zx_handle_t*>(out_buf);
+            *out = remote.release();
+            *out_actual = sizeof(zx_handle_t);
+            dprintf(INFO, "hidctl: created hid device\n");
+        }
+        return status;
+    }
+    default:
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+}
+
+int hid_device_thread(void* arg) {
+    HidDevice* device = reinterpret_cast<HidDevice*>(arg);
+    return device->Thread();
+}
+
+#define HID_SHUTDOWN ZX_USER_SIGNAL_7
+
+HidDevice::HidDevice(zx_device_t* device, const hid_ioctl_config* config, zx::socket data)
+  : ddk::Device<HidDevice, ddk::Unbindable>(device),
+    boot_device_(config->boot_device),
+    dev_class_(config->dev_class),
+    report_desc_(new uint8_t[config->rpt_desc_len]),
+    report_desc_len_(config->rpt_desc_len),
+    data_(fbl::move(data)) {
+    ZX_DEBUG_ASSERT(data_.is_valid());
+    memcpy(report_desc_.get(), config->rpt_desc, report_desc_len_);
+    int ret = thrd_create_with_name(&thread_, hid_device_thread, reinterpret_cast<void*>(this),
+                                    "hidctl-thread");
+    ZX_DEBUG_ASSERT(ret == thrd_success);
+}
+
+void HidDevice::DdkRelease() {
+    dprintf(TRACE, "hidctl: DdkRelease\n");
+    // Only the thread will call DdkRemove() when the loop exits. This detachs the thread before it
+    // exits, so no need to join.
+    delete this;
+}
+
+void HidDevice::DdkUnbind() {
+    dprintf(TRACE, "hidctl: DdkUnbind\n");
+    Shutdown();
+    // The thread will call DdkRemove when it exits the loop.
+}
+
+zx_status_t HidDevice::HidBusQuery(uint32_t options, hid_info_t* info) {
+    dprintf(TRACE, "hidctl: query\n");
+
+    info->dev_num = 0;
+    info->dev_class = dev_class_;
+    info->boot_device = boot_device_;
+    return ZX_OK;
+}
+
+zx_status_t HidDevice::HidBusStart(ddk::HidBusIfcProxy proxy) {
+    dprintf(TRACE, "hidctl: start\n");
+
+    fbl::AutoLock lock(&lock_);
+    if (proxy_.is_valid()) {
+        return ZX_ERR_ALREADY_BOUND;
+    }
+    proxy_ = proxy;
+    return ZX_OK;
+}
+
+void HidDevice::HidBusStop() {
+    dprintf(TRACE, "hidctl: stop\n");
+
+    fbl::AutoLock lock(&lock_);
+    proxy_.clear();
+}
+
+zx_status_t HidDevice::HidBusGetDescriptor(uint8_t desc_type, void** data, size_t* len) {
+    dprintf(TRACE, "hidctl: get descriptor %u\n", desc_type);
+
+    if (data == nullptr || len == nullptr) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    if (desc_type != HID_DESC_TYPE_REPORT) {
+        return ZX_ERR_NOT_FOUND;
+    }
+
+    *data = malloc(report_desc_len_);
+    if (*data == nullptr) {
+        return ZX_ERR_NO_MEMORY;
+    }
+    *len = report_desc_len_;
+    memcpy(*data, report_desc_.get(), report_desc_len_);
+    return ZX_OK;
+}
+
+zx_status_t HidDevice::HidBusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len,
+                            size_t* out_len) {
+    dprintf(TRACE, "hidctl: get report type=%u id=%u\n", rpt_type, rpt_id);
+
+    if (out_len == nullptr) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
+    // TODO: send get report message over socket
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t HidDevice::HidBusSetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len) {
+    dprintf(TRACE, "hidctl: set report type=%u id=%u\n", rpt_type, rpt_id);
+
+    // TODO: send set report message over socket
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t HidDevice::HidBusGetIdle(uint8_t rpt_id, uint8_t* duration) {
+    dprintf(TRACE, "hidctl: get idle\n");
+
+    // TODO: send get idle message over socket
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t HidDevice::HidBusSetIdle(uint8_t rpt_id, uint8_t duration) {
+    dprintf(TRACE, "hidctl: set idle\n");
+
+    // TODO: send set idle message over socket
+    return ZX_OK;
+}
+
+zx_status_t HidDevice::HidBusGetProtocol(uint8_t* protocol) {
+    dprintf(TRACE, "hidctl: get protocol\n");
+
+    // TODO: send get protocol message over socket
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t HidDevice::HidBusSetProtocol(uint8_t protocol) {
+    dprintf(TRACE, "hidctl: set protocol\n");
+
+    // TODO: send set protocol message over socket
+    return ZX_OK;
+}
+
+int HidDevice::Thread() {
+    dprintf(TRACE, "hidctl: starting main thread\n");
+    zx_signals_t pending;
+    fbl::unique_ptr<uint8_t[]> buf(new uint8_t[mtu_]);
+
+    zx_status_t status = ZX_OK;
+    const zx_signals_t wait = ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED | HID_SHUTDOWN;
+    while (true) {
+        status = data_.wait_one(wait, ZX_TIME_INFINITE, &pending);
+        if (status != ZX_OK) {
+            dprintf(ERROR, "hidctl: error waiting on data: %d\n", status);
+            break;
+        }
+
+        if (pending & ZX_SOCKET_READABLE) {
+            status = Recv(buf.get(), mtu_);
+            if (status != ZX_OK) {
+                break;
+            }
+        }
+        if (pending & ZX_SOCKET_PEER_CLOSED) {
+            dprintf(TRACE, "hidctl: socket closed (peer)\n");
+            break;
+        }
+        if (pending & HID_SHUTDOWN) {
+            dprintf(TRACE, "hidctl: socket closed (self)\n");
+            break;
+        }
+    }
+
+    dprintf(INFO, "hidctl: device destroyed\n");
+    {
+        fbl::AutoLock lock(&lock_);
+        data_.reset();
+        thrd_detach(thread_);
+    }
+    DdkRemove();
+
+    return static_cast<int>(status);
+}
+
+void HidDevice::Shutdown() {
+    fbl::AutoLock lock(&lock_);
+    if (data_.is_valid()) {
+        // Prevent further writes to the socket
+        zx_status_t status = data_.write(ZX_SOCKET_SHUTDOWN_READ, nullptr, 0, nullptr);
+        ZX_DEBUG_ASSERT(status == ZX_OK);
+        // Signal the thread to shutdown
+        status = data_.signal(0, HID_SHUTDOWN);
+        ZX_DEBUG_ASSERT(status == ZX_OK);
+    }
+}
+
+zx_status_t HidDevice::Recv(uint8_t* buffer, uint32_t capacity) {
+    size_t actual = 0;
+    zx_status_t status = ZX_OK;
+    // Read all the datagrams out of the socket.
+    while (status == ZX_OK) {
+        status = data_.read(0u, buffer, capacity, &actual);
+        if (status == ZX_ERR_SHOULD_WAIT || status == ZX_ERR_PEER_CLOSED) {
+            break;
+        }
+        if (status != ZX_OK) {
+            dprintf(ERROR, "hidctl: error reading data: %d\n", status);
+            return status;
+        }
+
+        fbl::AutoLock lock(&lock_);
+        if (unlikely(driver_get_log_flags() & DDK_LOG_TRACE)) {
+            dprintf(TRACE, "hidctl: received %zu bytes\n", actual);
+            hexdump8_ex(buffer, actual, 0);
+        }
+        if (proxy_.is_valid()) {
+            proxy_.IoQueue(buffer, actual);
+        }
+    }
+    return ZX_OK;
+}
+
+}  // namespace hidctl
+
+extern "C" zx_status_t hidctl_bind(void* ctx, zx_device_t* device, void** cookie) {
+    auto dev = fbl::unique_ptr<hidctl::HidCtl>(new hidctl::HidCtl(device));
+    zx_status_t status = dev->DdkAdd("hidctl");
+    if (status != ZX_OK) {
+        dprintf(ERROR, "%s: could not add device: %d\n", __func__, status);
+    } else {
+        // devmgr owns the memory now
+        dev.release();
+    }
+    return status;
+}
diff --git a/system/dev/input/hidctl/hidctl.h b/system/dev/input/hidctl/hidctl.h
new file mode 100644
index 0000000..aa01ba3
--- /dev/null
+++ b/system/dev/input/hidctl/hidctl.h
@@ -0,0 +1,67 @@
+// 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 <ddk/device.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/hidbus.h>
+#include <fbl/mutex.h>
+#include <fbl/unique_ptr.h>
+#include <threads.h>
+#include <zircon/compiler.h>
+#include <zircon/device/hidctl.h>
+#include <zircon/types.h>
+#include <zx/socket.h>
+
+namespace hidctl {
+
+class HidCtl : public ddk::Device<HidCtl, ddk::Ioctlable> {
+  public:
+    HidCtl(zx_device_t* device);
+
+    void DdkRelease();
+    zx_status_t DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, void* out_buf,
+                         size_t out_len, size_t* out_actual);
+};
+
+class HidDevice : public ddk::Device<HidDevice, ddk::Unbindable>,
+                  public ddk::HidBusProtocol<HidDevice> {
+  public:
+    HidDevice(zx_device_t* device, const hid_ioctl_config* config, zx::socket data);
+
+    void DdkRelease();
+    void DdkUnbind();
+
+    zx_status_t HidBusQuery(uint32_t options, hid_info_t* info);
+    zx_status_t HidBusStart(ddk::HidBusIfcProxy proxy);
+    void HidBusStop();
+    zx_status_t HidBusGetDescriptor(uint8_t desc_type, void** data, size_t* len);
+    zx_status_t HidBusGetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len,
+                                size_t* out_len);
+    zx_status_t HidBusSetReport(uint8_t rpt_type, uint8_t rpt_id, void* data, size_t len);
+    zx_status_t HidBusGetIdle(uint8_t rpt_id, uint8_t* duration);
+    zx_status_t HidBusSetIdle(uint8_t rpt_id, uint8_t duration);
+    zx_status_t HidBusGetProtocol(uint8_t* protocol);
+    zx_status_t HidBusSetProtocol(uint8_t protocol);
+
+    int Thread();
+    void Shutdown();
+
+  private:
+    zx_status_t Recv(uint8_t* buffer, uint32_t capacity);
+
+    bool boot_device_;
+    uint8_t dev_class_;
+    fbl::unique_ptr<uint8_t[]> report_desc_;
+    size_t report_desc_len_ = 0;
+    uint32_t mtu_ = 256;  // TODO: set this based on report_desc_
+
+    fbl::Mutex lock_;
+    ddk::HidBusIfcProxy proxy_ __TA_GUARDED(lock_);
+    zx::socket data_;
+    thrd_t thread_;
+};
+
+}  // namespace hidctl
diff --git a/system/dev/input/hidctl/rules.mk b/system/dev/input/hidctl/rules.mk
index e8c22b4..83691c7 100644
--- a/system/dev/input/hidctl/rules.mk
+++ b/system/dev/input/hidctl/rules.mk
@@ -2,26 +2,27 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-################################################################################
-#
-# TODO(tkilbourn)
-# This driver is disabled until we can update it to use the new
-# ZX_PROTOCOL_HIDBUS protocol, which requires rationalizing all of the device
-# lifecycle events.
-#
-################################################################################
+LOCAL_DIR := $(GET_LOCAL_DIR)
 
-#LOCAL_DIR := $(GET_LOCAL_DIR)
-#
-#MODULE := $(LOCAL_DIR)
-#
-#MODULE_TYPE := driver
-#
-#MODULE_SRCS := $(LOCAL_DIR)/hidctl.c
-#
-#MODULE_STATIC_LIBS := system/ulib/ddk
-#
-#MODULE_LIBS := system/ulib/driver system/ulib/zircon system/ulib/c
-#
-#include make/module.mk
+MODULE := $(LOCAL_DIR)
 
+MODULE_TYPE := driver
+
+MODULE_SRCS := \
+    $(LOCAL_DIR)/binding.c \
+    $(LOCAL_DIR)/hidctl.cpp \
+
+MODULE_STATIC_LIBS := \
+    system/ulib/ddk \
+    system/ulib/ddktl \
+    system/ulib/zx \
+    system/ulib/zxcpp \
+    system/ulib/fbl \
+    system/ulib/pretty \
+
+MODULE_LIBS := \
+    system/ulib/driver \
+    system/ulib/zircon \
+    system/ulib/c
+
+include make/module.mk
diff --git a/system/public/zircon/device/hidctl.h b/system/public/zircon/device/hidctl.h
index a5a4ed6..230f1fc 100644
--- a/system/public/zircon/device/hidctl.h
+++ b/system/public/zircon/device/hidctl.h
@@ -9,9 +9,11 @@
 #include <stdint.h>
 #include <zircon/device/ioctl.h>
 #include <zircon/device/ioctl-wrapper.h>
+#include <zircon/types.h>
 
-#define IOCTL_HID_CTL_CONFIG \
-    IOCTL(IOCTL_KIND_DEFAULT, IOCTL_FAMILY_HID, 0)
+// Returns a socket that can be used to send input reports and receive output reports.
+#define IOCTL_HIDCTL_CONFIG \
+    IOCTL(IOCTL_KIND_GET_HANDLE, IOCTL_FAMILY_HID, 0)
 
 typedef struct hid_ioctl_config {
     uint8_t dev_num;
@@ -21,5 +23,5 @@
     uint8_t rpt_desc[];
 } hid_ioctl_config_t;
 
-// ssize_t ioctl_hid_ctl_config(int fd, const hid_ioctl_config_t* in, size_t in_len);
-IOCTL_WRAPPER_VARIN(ioctl_hid_ctl_config, IOCTL_HID_CTL_CONFIG, hid_ioctl_config_t);
+// ssize_t ioctl_hidctl_config(int fd, const hid_ioctl_config_t* in, size_t in_len, zx_handle_t* out);
+IOCTL_WRAPPER_VARIN_OUT(ioctl_hidctl_config, IOCTL_HIDCTL_CONFIG, hid_ioctl_config_t, zx_handle_t);