blob: 50c46e920beca2648b4c6fb9d82611dcc48b650b [file] [log] [blame]
// 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 <assert.h>
#include <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/bt/hci.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/device/bt-hci.h>
#include <zircon/status.h>
typedef struct {
zx_device_t* dev;
zx_device_t* transport_dev;
bt_hci_protocol_t hci;
} passthrough_t;
static zx_status_t bt_hci_passthrough_get_protocol(void* ctx, uint32_t proto_id,
void* out_proto) {
if (proto_id != ZX_PROTOCOL_BT_HCI) {
return ZX_ERR_NOT_SUPPORTED;
}
passthrough_t* passthrough = ctx;
bt_hci_protocol_t* hci_proto = out_proto;
// Forward the underlying bt-transport ops.
hci_proto->ops = passthrough->hci.ops;
hci_proto->ctx = passthrough->hci.ctx;
return ZX_OK;
}
static zx_status_t bt_hci_passthrough_ioctl(void* ctx, uint32_t op,
const void* in_buf, size_t in_len,
void* out_buf, size_t out_len,
size_t* out_actual) {
passthrough_t* passthrough = ctx;
if (out_len < sizeof(zx_handle_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
zx_handle_t* reply = out_buf;
zx_status_t status = ZX_ERR_NOT_SUPPORTED;
if (op == IOCTL_BT_HCI_GET_COMMAND_CHANNEL) {
status = bt_hci_open_command_channel(&passthrough->hci, reply);
} else if (op == IOCTL_BT_HCI_GET_ACL_DATA_CHANNEL) {
status = bt_hci_open_acl_data_channel(&passthrough->hci, reply);
} else if (op == IOCTL_BT_HCI_GET_SNOOP_CHANNEL) {
status = bt_hci_open_snoop_channel(&passthrough->hci, reply);
}
if (status != ZX_OK) {
return status;
}
*out_actual = sizeof(*reply);
return ZX_OK;
}
static void bt_hci_passthrough_unbind(void* ctx) {
passthrough_t* passthrough = ctx;
device_remove(passthrough->dev);
}
static void bt_hci_passthrough_release(void* ctx) { free(ctx); }
static zx_protocol_device_t passthrough_device_proto = {
.version = DEVICE_OPS_VERSION,
.get_protocol = bt_hci_passthrough_get_protocol,
.ioctl = bt_hci_passthrough_ioctl,
.unbind = bt_hci_passthrough_unbind,
.release = bt_hci_passthrough_release,
};
static zx_status_t bt_hci_passthrough_bind(void* ctx, zx_device_t* device) {
printf("bt_hci_passthrough_bind: starting\n");
passthrough_t* passthrough = calloc(1, sizeof(passthrough_t));
if (!passthrough) {
printf("bt_hci_passthrough_bind: not enough memory\n");
return ZX_ERR_NO_MEMORY;
}
zx_status_t status =
device_get_protocol(device, ZX_PROTOCOL_BT_HCI, &passthrough->hci);
if (status != ZX_OK) {
printf("bt_hci_passthrough_bind: failed protocol: %s\n",
zx_status_get_string(status));
return status;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bt_hci_passthrough",
.ctx = passthrough,
.ops = &passthrough_device_proto,
.proto_id = ZX_PROTOCOL_BT_HCI,
};
passthrough->transport_dev = device;
status = device_add(device, &args, &passthrough->dev);
if (status == ZX_OK) {
return status;
}
printf("bt_hci_passthrough_bind failed: %s\n", zx_status_get_string(status));
bt_hci_passthrough_release(passthrough);
return status;
};
static zx_driver_ops_t bt_hci_passthrough_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = bt_hci_passthrough_bind,
};
// This should be the last driver queried, so we match any transport.
// clang-format off
ZIRCON_DRIVER_BEGIN(bt_hci_passthrough, bt_hci_passthrough_driver_ops, "fuchsia", "*0.1", 1)
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_BT_TRANSPORT),
ZIRCON_DRIVER_END(bt_hci_passthrough)