blob: 05f121ba2513e0c86bb019d451b2b8d60b6582a1 [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 "device.h"
#include <ddk/protocol/usb.h>
#include <usb/usb.h>
#include <usb/usb-request.h>
#include <fbl/auto_lock.h>
#include <fbl/string_printf.h>
#include <lib/zx/vmo.h>
#include <usb/usb-request.h>
#include <zircon/device/bt-hci.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include "logging.h"
namespace btatheros {
using ::btlib::common::BufferView;
using ::btlib::common::PacketView;
// hard coded for Qualcomm Atheros chipset 0CF3:E300
static constexpr size_t GET_TARGET_VERSION = 0x09;
static constexpr size_t GET_STATUS = 0x05;
static constexpr size_t DFU_DOWNLOAD = 0x01;
static constexpr size_t DFU_PACKET_LEN = 4096;
static constexpr size_t PATCH_UPDATED = 0x80;
static constexpr size_t SYSCFG_UPDATED = 0x40;
static constexpr size_t RAMPATCH_HDR = 28;
static constexpr size_t NVM_HDR = 4;
static zx_protocol_device_t dev_proto = {
.version = DEVICE_OPS_VERSION,
.get_protocol = [](void* ctx, uint32_t proto_id,
void* protocol) -> zx_status_t {
return static_cast<Device*>(ctx)->DdkGetProtocol(proto_id, protocol);
},
.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) -> zx_status_t {
return static_cast<Device*>(ctx)->DdkIoctl(op, in_buf, in_len, out_buf,
out_len, out_actual);
},
};
Device::Device(zx_device_t* device, bt_hci_protocol_t* hci, usb_protocol_t* usb)
: parent_(device), hci_(*hci), usb_(*usb), firmware_loaded_(false) {}
zx_status_t Device::Bind() {
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bt_hci_atheros",
.ctx = this,
.ops = &dev_proto,
.proto_id = ZX_PROTOCOL_BT_HCI,
.flags = DEVICE_ADD_INVISIBLE,
};
return device_add(parent_, &args, &zxdev_);
}
static void interrupt_complete(void* ctx, usb_request_t* req) {
if (ctx != nullptr) {
sync_completion_t* completion = (sync_completion_t*)ctx;
sync_completion_signal(completion);
}
}
zx_status_t Device::LoadNVM(const qca_version& version) {
zx_status_t result = 0;
zx::vmo fw_vmo;
uintptr_t fw_addr;
size_t fw_size;
fbl::String fw_filename;
fbl::AutoLock lock(&mutex_);
fw_filename = fbl::StringPrintf("nvm_usb_%08X.bin", version.rom_version);
fw_vmo.reset(MapFirmware(fw_filename.c_str(), &fw_addr, &fw_size));
infof("Loading nvm: %s\n", fw_filename.c_str());
BufferView file(reinterpret_cast<void*>(fw_addr), fw_size);
size_t count = fw_size;
size_t size = std::min(count, NVM_HDR);
size_t sent = 0;
result = usb_control_out(&usb_, USB_TYPE_VENDOR, DFU_DOWNLOAD, 0, 0,
ZX_TIME_INFINITE,
(void*)file.view(0, size).data(), size);
if (result != ZX_OK) {
return result;
}
usb_request_t* req;
result = usb_request_alloc(&req, size, bulk_out_addr_, parent_req_size_);
if (result != ZX_OK) {
zxlogf(ERROR, "LoadNVM: Failed to allocate usb request: %d\n", result);
return result;
}
count -= size;
sent += size;
while (count) {
size = std::min(count, DFU_PACKET_LEN);
req->size = size;
req->header.length = size;
usb_request_copy_to(req, file.view(sent, size).data(), size, 0);
sync_completion_reset(&completion_);
usb_request_complete_t complete = {
.callback = interrupt_complete,
.ctx = &completion_,
};
usb_request_queue(&usb_, req, &complete);
sync_completion_wait(&completion_, ZX_TIME_INFINITE);
if (req->response.status != ZX_OK) {
result = req->response.status;
break;
}
count -= size;
sent += size;
}
zx_vmar_unmap(zx_vmar_root_self(), fw_addr, fw_size);
usb_request_release(req);
return result;
}
zx_status_t Device::LoadRAM(const qca_version& version) {
zx_status_t result;
zx::vmo fw_vmo;
uintptr_t fw_addr;
size_t fw_size;
fbl::String fw_filename;
fbl::AutoLock lock(&mutex_);
fw_filename = fbl::StringPrintf("rampatch_usb_%08X.bin", version.rom_version);
fw_vmo.reset(MapFirmware(fw_filename.c_str(), &fw_addr, &fw_size));
infof("Loading rampatch: %s\n", fw_filename.c_str());
size_t count = fw_size;
size_t size = std::min(count, RAMPATCH_HDR);
size_t sent = 0;
BufferView file(reinterpret_cast<void*>(fw_addr), fw_size);
result = usb_control_out(&usb_, USB_TYPE_VENDOR, DFU_DOWNLOAD, 0, 0,
ZX_TIME_INFINITE,
(void*)file.view(0, size).data(), size);
usb_request_t* req;
result = usb_request_alloc(&req, size, bulk_out_addr_, parent_req_size_);
if (result != ZX_OK) {
zxlogf(ERROR, "LoadRAM: Failed to allocate usb request: %d\n", result);
return result;
}
count -= size;
sent += size;
while (count) {
size = std::min(count, DFU_PACKET_LEN);
req->size = size;
req->header.length = size;
usb_request_copy_to(req, file.view(sent, size).data(), size, 0);
sync_completion_reset(&completion_);
usb_request_complete_t complete = {
.callback = interrupt_complete,
.ctx = &completion_,
};
usb_request_queue(&usb_, req, &complete);
sync_completion_wait(&completion_, ZX_TIME_INFINITE);
if (req->response.status != ZX_OK) {
result = req->response.status;
break;
}
count -= size;
sent += size;
}
zx_vmar_unmap(zx_vmar_root_self(), fw_addr, fw_size);
usb_request_release(req);
return result;
}
zx_status_t Device::LoadFirmware() {
zx_status_t result;
usb_device_descriptor_t dev_desc;
parent_req_size_ = usb_get_request_size(&usb_);
ZX_DEBUG_ASSERT(parent_req_size_ != 0);
usb_get_device_descriptor(&usb_, &dev_desc);
struct qca_version ver;
size_t actual_read;
result = usb_control_in(&usb_, USB_TYPE_VENDOR | USB_DIR_IN, GET_TARGET_VERSION,
0, 0, ZX_TIME_INFINITE, &ver, sizeof(ver), &actual_read);
if (result != ZX_OK) {
errorf("couldn't get version");
return result;
}
uint8_t status;
result = usb_control_in(&usb_, USB_TYPE_VENDOR | USB_DIR_IN, GET_STATUS, 0, 0,
ZX_TIME_INFINITE, &status, sizeof(status), &actual_read);
usb_desc_iter_t iter;
result = usb_desc_iter_init(&usb_, &iter);
if (result < 0) {
errorf("usb iterator failed: %s\n", zx_status_get_string(result));
return result;
}
usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
if (!intf || intf->bNumEndpoints != 3) {
usb_desc_iter_release(&iter);
return ZX_ERR_NOT_SUPPORTED;
}
usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
while (endp) {
if (usb_ep_direction(endp) == USB_ENDPOINT_OUT) {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_out_addr_ = endp->bEndpointAddress;
}
}
endp = usb_desc_iter_next_endpoint(&iter);
}
usb_desc_iter_release(&iter);
if (!bulk_out_addr_) {
errorf("bind could not find bulk out endpoint\n");
return ZX_ERR_NOT_SUPPORTED;
}
if (!(status & PATCH_UPDATED)) {
result = LoadRAM(ver);
if (result != ZX_OK) {
return Remove(result, "Failed to load Qualcomm Atheros RAM patches");
}
}
if (!(status & SYSCFG_UPDATED)) {
result = LoadNVM(ver);
if (result != ZX_OK) {
return Remove(result, "Failed to load Qualcomm Atheros NVM patches");
}
}
auto note = fbl::StringPrintf("loaded successfully");
return Appear(note.c_str());
}
zx_status_t Device::Remove(zx_status_t status, const char* note) {
device_remove(zxdev_);
errorf("%s: %s", note, zx_status_get_string(status));
return status;
}
zx_status_t Device::Appear(const char* note) {
fbl::AutoLock lock(&mutex_);
errorf("Making visible\n");
device_make_visible(zxdev_);
infof("%s\n", note);
firmware_loaded_ = true;
return ZX_OK;
}
zx_handle_t Device::MapFirmware(const char* name, uintptr_t* fw_addr,
size_t* fw_size) {
zx_handle_t vmo = ZX_HANDLE_INVALID;
size_t size;
zx_status_t status = load_firmware(zxdev_, name, &vmo, &size);
if (status != ZX_OK) {
return ZX_HANDLE_INVALID;
}
status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ, 0, vmo, 0, size,
fw_addr);
if (status != ZX_OK) {
return ZX_HANDLE_INVALID;
}
*fw_size = size;
return vmo;
}
void Device::DdkUnbind() { device_remove(zxdev_); }
void Device::DdkRelease() { delete this; }
zx_status_t Device::DdkGetProtocol(uint32_t proto_id, void* out_proto) {
if (proto_id != ZX_PROTOCOL_BT_HCI) {
return ZX_ERR_NOT_SUPPORTED;
}
bt_hci_protocol_t* hci_proto = static_cast<bt_hci_protocol_t*>(out_proto);
// Forward the underlying bt-transport ops.
hci_proto->ops = hci_.ops;
hci_proto->ctx = hci_.ctx;
return ZX_OK;
}
zx_status_t Device::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* actual) {
fbl::AutoLock lock(&mutex_);
ZX_DEBUG_ASSERT(firmware_loaded_);
if (out_len < sizeof(zx_handle_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
zx_handle_t* reply = (zx_handle_t*)out_buf;
zx_status_t status = ZX_ERR_NOT_SUPPORTED;
if (op == IOCTL_BT_HCI_GET_COMMAND_CHANNEL) {
status = bt_hci_open_command_channel(&hci_, reply);
} else if (op == IOCTL_BT_HCI_GET_ACL_DATA_CHANNEL) {
status = bt_hci_open_acl_data_channel(&hci_, reply);
} else if (op == IOCTL_BT_HCI_GET_SNOOP_CHANNEL) {
status = bt_hci_open_snoop_channel(&hci_, reply);
}
if (status != ZX_OK) {
return status;
}
*actual = sizeof(*reply);
return ZX_OK;
}
} // namespace btatheros