blob: 5711a566695c69e5dff9fa888a642a1e5095bfee [file] [log] [blame]
// 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 <assert.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/bt/hci.h>
#include <ddk/protocol/serial.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>
#include <threads.h>
#include <zircon/assert.h>
#include <zircon/device/serial.h>
#include <zircon/status.h>
#include <zircon/threads.h>
#include <fuchsia/hardware/bluetooth/c/fidl.h>
// TODO: how can we parameterize this?
#define TARGET_BAUD_RATE 2000000
#define MAC_ADDR_LEN 6
// TODO: Determine firmware name based on controller version.
#define FIRMWARE_PATH "BCM4345C5.hcd"
#define FIRMWARE_DOWNLOAD_DELAY ZX_MSEC(50)
// Hardcoded. Better to parameterize on chipset.
// Broadcom chips need a few hundred msec delay
// after firmware load
#define BAUD_RATE_SWITCH_DELAY ZX_MSEC(200)
typedef struct {
uint16_t opcode;
uint8_t parameter_total_size;
} __PACKED hci_command_header_t;
typedef struct {
uint8_t event_code;
uint8_t parameter_total_size;
} __PACKED hci_event_header_t;
typedef struct {
hci_event_header_t header;
uint8_t num_hci_command_packets;
uint16_t command_opcode;
uint8_t return_code;
} __PACKED hci_command_complete_t;
typedef struct {
hci_event_header_t header;
uint8_t num_hci_command_packets;
uint16_t command_opcode;
uint8_t return_code;
uint8_t bdaddr[MAC_ADDR_LEN];
} __PACKED hci_read_bdaddr_command_complete_t;
// HCI reset command
const hci_command_header_t RESET_CMD = {
.opcode = 0x0c03,
.parameter_total_size = 0,
};
// vendor command to begin firmware download
const hci_command_header_t START_FIRMWARE_DOWNLOAD_CMD = {
.opcode = 0xfc2e,
.parameter_total_size = 0,
};
// HCI command to read BDADDR from controller
const hci_command_header_t READ_BDADDR_CMD = {
.opcode = 0x1009,
.parameter_total_size = 0,
};
typedef struct {
hci_command_header_t header;
uint16_t unused;
uint32_t baud_rate;
} __PACKED bcm_set_baud_rate_cmd_t;
#define BCM_SET_BAUD_RATE_CMD 0xfc18
typedef struct {
hci_command_header_t header;
uint8_t bdaddr[MAC_ADDR_LEN];
} __PACKED bcm_set_bdaddr_cmd_t;
#define BCM_SET_BDADDR_CMD 0xfc01
#define HCI_EVT_COMMAND_COMPLETE 0x0e
typedef struct {
zx_device_t* zxdev;
zx_device_t* transport_dev;
bt_hci_protocol_t hci;
serial_protocol_t serial;
zx_handle_t command_channel;
bool is_uart; // true if underlying transport is UART
} bcm_hci_t;
static zx_status_t bcm_hci_get_protocol(void* ctx, uint32_t proto_id, void* out_proto) {
if (proto_id != ZX_PROTOCOL_BT_HCI) {
return ZX_ERR_NOT_SUPPORTED;
}
bcm_hci_t* hci = ctx;
bt_hci_protocol_t* hci_proto = out_proto;
// Forward the underlying bt-transport ops.
hci_proto->ops = hci->hci.ops;
hci_proto->ctx = hci->hci.ctx;
return ZX_OK;
}
static void bcm_hci_unbind(void* ctx) {
bcm_hci_t* hci = ctx;
device_remove(hci->zxdev);
}
static void bcm_hci_release(void* ctx) {
bcm_hci_t* hci = ctx;
if (hci->command_channel != ZX_HANDLE_INVALID) {
zx_handle_close(hci->command_channel);
}
free(hci);
}
zx_status_t fidl_bt_hci_open_command_channel(void* ctx, zx_handle_t channel) {
bcm_hci_t* hci = ctx;
zx_status_t status = bt_hci_open_command_channel(&hci->hci, channel);
if (status != ZX_OK) {
zx_handle_close(channel);
}
return status;
}
zx_status_t fidl_bt_hci_open_acl_data_channel(void* ctx, zx_handle_t channel) {
bcm_hci_t* hci = ctx;
zx_status_t status = bt_hci_open_acl_data_channel(&hci->hci, channel);
if (status != ZX_OK) {
zx_handle_close(channel);
}
return status;
}
zx_status_t fidl_bt_hci_open_snoop_channel(void* ctx, zx_handle_t channel) {
bcm_hci_t* hci = ctx;
zx_status_t status = bt_hci_open_snoop_channel(&hci->hci, channel);
if (status != ZX_OK) {
zx_handle_close(channel);
}
return status;
}
static fuchsia_hardware_bluetooth_Hci_ops_t fidl_ops = {
.OpenCommandChannel = fidl_bt_hci_open_command_channel,
.OpenAclDataChannel = fidl_bt_hci_open_acl_data_channel,
.OpenSnoopChannel = fidl_bt_hci_open_snoop_channel,
};
static zx_status_t fuchsia_bt_hci_message_instance(void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) {
return fuchsia_hardware_bluetooth_Hci_dispatch(ctx, txn, msg, &fidl_ops);
}
static zx_protocol_device_t bcm_hci_device_proto = {
.version = DEVICE_OPS_VERSION,
.get_protocol = bcm_hci_get_protocol,
.message = fuchsia_bt_hci_message_instance,
.unbind = bcm_hci_unbind,
.release = bcm_hci_release,
};
static zx_status_t bcm_hci_send_command(bcm_hci_t* hci, const hci_command_header_t* command,
size_t length, void* out_buf, size_t out_buf_len) {
#define CHAN_READ_BUF_LEN 257
uint8_t read_buf[CHAN_READ_BUF_LEN];
if (out_buf_len > CHAN_READ_BUF_LEN) {
zxlogf(ERROR, "bcm_hci_send_command provided |out_buf| is too large");
return ZX_ERR_INVALID_ARGS;
}
// send HCI command
zx_status_t status = zx_channel_write(hci->command_channel, 0, command, length, NULL, 0);
if (status != ZX_OK) {
zxlogf(ERROR, "bcm_hci_send_command zx_channel_write failed %s\n",
zx_status_get_string(status));
return status;
}
// wait for an HCI Command Complete event
uint32_t actual;
do {
status = zx_channel_read(hci->command_channel, 0, read_buf, NULL, CHAN_READ_BUF_LEN, 0,
&actual, NULL);
if (status == ZX_ERR_SHOULD_WAIT) {
zx_object_wait_one(hci->command_channel, ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
zx_deadline_after(ZX_SEC(5)), NULL);
}
} while (status == ZX_ERR_SHOULD_WAIT);
if (status != ZX_OK) {
zxlogf(ERROR, "bcm_hci_send_command zx_channel_read failed %s\n",
zx_status_get_string(status));
return status;
}
hci_event_header_t* header = (hci_event_header_t *)read_buf;
if (header->event_code != HCI_EVT_COMMAND_COMPLETE || header->parameter_total_size
!= sizeof(hci_command_complete_t) - sizeof(hci_event_header_t)) {
zxlogf(ERROR, "bcm_hci_send_command did not receive command complete\n");
return ZX_ERR_INTERNAL;
}
hci_command_complete_t* event = (hci_command_complete_t *)read_buf;
if (event->return_code != 0) {
zxlogf(ERROR, "bcm_hci_send_command got command complete error %u\n", event->return_code);
return ZX_ERR_INTERNAL;
}
if (out_buf) {
memcpy(out_buf, read_buf, out_buf_len);
}
return ZX_OK;
}
static zx_status_t bcm_hci_set_baud_rate(bcm_hci_t* hci, uint32_t baud_rate) {
bcm_set_baud_rate_cmd_t command = {
.header = {
.opcode = BCM_SET_BAUD_RATE_CMD,
.parameter_total_size = sizeof(bcm_set_baud_rate_cmd_t) - sizeof(hci_command_header_t),
},
.unused = 0,
.baud_rate = htole32(baud_rate),
};
zx_status_t status = bcm_hci_send_command(hci, &command.header, sizeof(command), NULL, 0);
if (status != ZX_OK) {
return status;
}
return serial_config(&hci->serial, TARGET_BAUD_RATE, SERIAL_SET_BAUD_RATE_ONLY);
}
static zx_status_t bcm_hci_set_bdaddr(bcm_hci_t* hci, uint8_t bdaddr[MAC_ADDR_LEN]) {
bcm_set_bdaddr_cmd_t command = {
.header = {
.opcode = BCM_SET_BDADDR_CMD,
.parameter_total_size = sizeof(bcm_set_bdaddr_cmd_t) - sizeof(hci_command_header_t),
},
.bdaddr = {
// HCI expects little endian. Swap bytes
bdaddr[5], bdaddr[4], bdaddr[3], bdaddr[2], bdaddr[1], bdaddr[0]
},
};
return bcm_hci_send_command(hci, &command.header, sizeof(command), NULL, 0);
}
static zx_status_t bcm_get_bdaddr_from_bootloader(bcm_hci_t* hci, uint8_t macaddr[MAC_ADDR_LEN]) {
uint8_t bootloader_macaddr[8];
size_t actual_len;
zx_status_t status = device_get_metadata(hci->zxdev, DEVICE_METADATA_MAC_ADDRESS,
bootloader_macaddr, sizeof(bootloader_macaddr),
&actual_len);
if (status != ZX_OK) {
return status;
} else if (actual_len < MAC_ADDR_LEN) {
return ZX_ERR_INTERNAL;
}
memcpy(macaddr, bootloader_macaddr, MAC_ADDR_LEN);
zxlogf(INFO, "bcm-hci: got bootloader mac address %02x:%02x:%02x:%02x:%02x:%02x\n",
macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
return ZX_OK;
}
static int bcm_hci_start_thread(void* arg) {
bcm_hci_t* hci = arg;
zx_handle_t fw_vmo;
zx_handle_t theirs;
zx_status_t status = zx_channel_create(0, &hci->command_channel, &theirs);
if (status != ZX_OK) {
goto fail;
}
status = bt_hci_open_command_channel(&hci->hci, theirs);
if (status != ZX_OK) {
goto fail;
}
// Send Reset command
status = bcm_hci_send_command(hci, &RESET_CMD, sizeof(RESET_CMD), NULL, 0);
if (status != ZX_OK) {
goto fail;
}
// set BDADDR to value in bootloader
uint8_t macaddr[MAC_ADDR_LEN];
status = bcm_get_bdaddr_from_bootloader(hci, macaddr);
if (status == ZX_OK) {
// send Set BDADDR command
status = bcm_hci_set_bdaddr(hci, macaddr);
if (status != ZX_OK) {
goto fail;
}
} else {
// log error and fallback mac address
hci_read_bdaddr_command_complete_t event;
memset(&event, 0, sizeof(event));
char fallback_addr[18] = "<unknown>";
zx_status_t read_cmd_status = bcm_hci_send_command(hci, &READ_BDADDR_CMD,
sizeof(READ_BDADDR_CMD),
(void *)(&event), sizeof(event));
if (read_cmd_status == ZX_OK) {
// HCI returns data as little endian. Swap bytes
snprintf(fallback_addr, 18, "%02x:%02x:%02x:%02x:%02x:%02x", event.bdaddr[5],
event.bdaddr[4], event.bdaddr[3], event.bdaddr[2], event.bdaddr[1],
event.bdaddr[0]);
}
zxlogf(ERROR, "bcm-hci: error getting mac address from bootloader: %s. "
"Fallback address: %s.\n",
zx_status_get_string(status), fallback_addr);
}
if (hci->is_uart) {
// switch baud rate to TARGET_BAUD_RATE
status = bcm_hci_set_baud_rate(hci, TARGET_BAUD_RATE);
if (status != ZX_OK) {
goto fail;
}
}
size_t fw_size;
status = load_firmware(hci->zxdev, FIRMWARE_PATH, &fw_vmo, &fw_size);
if (status == ZX_OK) {
status = bcm_hci_send_command(hci, &START_FIRMWARE_DOWNLOAD_CMD,
sizeof(START_FIRMWARE_DOWNLOAD_CMD), NULL, 0);
if (status != ZX_OK) {
goto fail;
}
// give time for placing firmware in download mode
zx_nanosleep(zx_deadline_after(FIRMWARE_DOWNLOAD_DELAY));
zx_off_t offset = 0;
while (offset < fw_size) {
uint8_t buffer[255 + 3];
size_t remaining = fw_size - offset;
size_t read_amount = (remaining > sizeof(buffer) ? sizeof(buffer) : remaining);
if (read_amount < 3) {
zxlogf(ERROR, "short HCI command in firmware download\n");
status = ZX_ERR_INTERNAL;
goto vmo_close_fail;
}
status = zx_vmo_read(fw_vmo, buffer, offset, read_amount);
if (status != ZX_OK) {
goto vmo_close_fail;
}
hci_command_header_t* header = (hci_command_header_t *)buffer;
size_t length = header->parameter_total_size + sizeof(*header);
if (read_amount < length) {
zxlogf(ERROR, "short HCI command in firmware download\n");
status = ZX_ERR_INTERNAL;
goto vmo_close_fail;
}
status = bcm_hci_send_command(hci, header, length, NULL, 0);
if (status != ZX_OK) {
zxlogf(ERROR, "bcm_hci_send_command failed in firmware download: %s\n",
zx_status_get_string(status));
goto vmo_close_fail;
}
offset += length;
}
zx_handle_close(fw_vmo);
if (hci->is_uart) {
// firmware switched us back to 115200. switch back to TARGET_BAUD_RATE
status = serial_config(&hci->serial, 115200, SERIAL_SET_BAUD_RATE_ONLY);
if (status != ZX_OK) {
goto fail;
}
// switch baud rate to TARGET_BAUD_RATE after DELAY
zx_nanosleep(zx_deadline_after(BAUD_RATE_SWITCH_DELAY));
status = bcm_hci_set_baud_rate(hci, TARGET_BAUD_RATE);
if (status != ZX_OK) {
goto fail;
}
}
zxlogf(INFO, "bcm-hci: firmware loaded\n");
} else {
zxlogf(ERROR, "bcm-hci: no firmware file found\n");
}
// We're done with the command channel. Close it so that it can be opened by
// the host stack after the device becomes visible.
zx_handle_close(hci->command_channel);
hci->command_channel = ZX_HANDLE_INVALID;
device_make_visible(hci->zxdev);
return 0;
vmo_close_fail:
zx_handle_close(fw_vmo);
fail:
zxlogf(ERROR, "bcm_hci_start_thread: device initialization failed: %s\n",
zx_status_get_string(status));
device_remove(hci->zxdev);
return -1;
}
static zx_status_t bcm_hci_bind(void* ctx, zx_device_t* device) {
bcm_hci_t* hci = calloc(1, sizeof(bcm_hci_t));
if (!hci) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = device_get_protocol(device, ZX_PROTOCOL_BT_HCI, &hci->hci);
if (status != ZX_OK) {
zxlogf(ERROR, "bcm_hci_bind: get protocol ZX_PROTOCOL_BT_HCI failed\n");
return status;
}
status = device_get_protocol(device, ZX_PROTOCOL_SERIAL, &hci->serial);
if (status == ZX_OK) {
hci->is_uart = true;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bcm-hci",
.ctx = hci,
.ops = &bcm_hci_device_proto,
.proto_id = ZX_PROTOCOL_BT_HCI,
.flags = DEVICE_ADD_INVISIBLE,
};
hci->transport_dev = device;
status = device_add(device, &args, &hci->zxdev);
if (status != ZX_OK) {
bcm_hci_release(hci);
return status;
}
// create thread to continue initialization
thrd_t t;
int thrd_rc = thrd_create_with_name(&t, bcm_hci_start_thread, hci, "bcm_hci_start_thread");
if (thrd_rc != thrd_success) {
device_remove(hci->zxdev);
bcm_hci_release(hci);
return thrd_status_to_zx_status(thrd_rc);
}
return ZX_OK;
}
static zx_driver_ops_t bcm_hci_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = bcm_hci_bind,
};
// clang-format off
ZIRCON_DRIVER_BEGIN(bcm_hci, bcm_hci_driver_ops, "zircon", "0.1", 2)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_BT_TRANSPORT),
BI_MATCH_IF(EQ, BIND_SERIAL_VID, PDEV_VID_BROADCOM),
ZIRCON_DRIVER_END(bcm_hci)