[bt][intel] WIP: hci_intel_tool load-firmware
Change-Id: Ie9dcfd8fbcd459426dfc260ed583a34f2527457b
diff --git a/bin/bluetooth_tools/bt_intel_tool/BUILD.gn b/bin/bluetooth_tools/bt_intel_tool/BUILD.gn
index 5ab4b0f..7aad0fe 100644
--- a/bin/bluetooth_tools/bt_intel_tool/BUILD.gn
+++ b/bin/bluetooth_tools/bt_intel_tool/BUILD.gn
@@ -6,8 +6,12 @@
output_name = "bt-intel-tool"
sources = [
+ "command_channel.cc",
+ "command_channel.h",
"commands.cc",
"commands.h",
+ "intel_firmware_loader.cc",
+ "intel_firmware_loader.h",
"main.cc",
]
diff --git a/bin/bluetooth_tools/bt_intel_tool/bt_intel.h b/bin/bluetooth_tools/bt_intel_tool/bt_intel.h
index 8b4db9d..260e33e 100644
--- a/bin/bluetooth_tools/bt_intel_tool/bt_intel.h
+++ b/bin/bluetooth_tools/bt_intel_tool/bt_intel.h
@@ -25,6 +25,9 @@
uint8_t fw_patch_num;
} __PACKED;
+constexpr bluetooth::hci::OpCode kSecureSend =
+ bluetooth::hci::VendorOpCode(0x0009);
+
constexpr bluetooth::hci::OpCode kReadBootParams =
bluetooth::hci::VendorOpCode(0x000D);
@@ -54,4 +57,32 @@
uint8_t data[8];
} __PACKED;
+constexpr bluetooth::hci::OpCode kMfgModeChange =
+ bluetooth::hci::VendorOpCode(0x0011);
+
+enum class MfgDisableMode : uint8_t {
+ kNoPatches = 0x00,
+ kPatchesDisabled = 0x01,
+ kPatchesEnabled = 0x02,
+};
+
+struct IntelMfgModeChangeCommandParams {
+ uint8_t enable;
+ MfgDisableMode disable_mode;
+} __PACKED;
+
+struct IntelSecureSendEventParams {
+ uint8_t vendor_event_code;
+ uint8_t result;
+ uint16_t opcode;
+ uint8_t status;
+} __PACKED;
+
+struct IntelBootloaderVendorEventParams {
+ FXL_DISALLOW_IMPLICIT_CONSTRUCTORS(IntelBootloaderVendorEventParams);
+
+ uint8_t vendor_event_code;
+ uint8_t vendor_params[];
+} __PACKED;
+
} // namespace bt_intel
diff --git a/bin/bluetooth_tools/bt_intel_tool/command_channel.cc b/bin/bluetooth_tools/bt_intel_tool/command_channel.cc
new file mode 100644
index 0000000..940d2a2
--- /dev/null
+++ b/bin/bluetooth_tools/bt_intel_tool/command_channel.cc
@@ -0,0 +1,240 @@
+// 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 "command_channel.h"
+
+#include <fcntl.h>
+
+#include <iostream>
+
+#include <async/default.h>
+#include <async/loop.h>
+#include <zircon/device/bt-hci.h>
+#include <zircon/status.h>
+#include <zx/event.h>
+#include <zx/time.h>
+#include <zx/timer.h>
+
+#include "garnet/drivers/bluetooth/lib/hci/slab_allocators.h"
+
+namespace {
+
+zx::channel GetCommandChannel(int fd) {
+ zx::channel channel;
+ ssize_t status =
+ ioctl_bt_hci_get_command_channel(fd, channel.reset_and_get_address());
+ if (status < 0) {
+ std::cerr << "hci: Failed to obtain command channel handle: "
+ << zx_status_get_string(status) << std::endl;
+ assert(!channel.is_valid());
+ }
+
+ return channel;
+}
+
+
+zx::channel GetAclChannel(int fd) {
+ zx::channel channel;
+ ssize_t status =
+ ioctl_bt_hci_get_acl_data_channel(fd, channel.reset_and_get_address());
+ if (status < 0) {
+ std::cerr << "hci: Failed to obtain ADL data channel handle: "
+ << zx_status_get_string(status) << std::endl;
+ assert(!channel.is_valid());
+ }
+
+ return channel;
+}
+
+} // namespace
+
+CommandChannel::CommandChannel(std::string hcidev_path) {
+ hci_fd_.reset(open(hcidev_path.c_str(), O_RDWR));
+ if (!bool(hci_fd_)) {
+ return;
+ }
+ channel_ = GetCommandChannel(hci_fd_.get());
+ channel_wait_.set_object(channel_.get());
+ channel_wait_.set_trigger(ZX_CHANNEL_READABLE);
+ channel_wait_.set_handler(
+ fbl::BindMember(this, &CommandChannel::OnChannelReady));
+ zx_status_t status = channel_wait_.Begin(async_get_default());
+ if (status != ZX_OK) {
+ std::cerr << "CommandChannel: problem setting up HCI command channel: "
+ << zx_status_get_string(status) << std::endl;
+ return;
+ }
+
+ acl_channel_ = GetAclChannel(hci_fd_.get());
+ acl_channel_wait_.set_object(acl_channel_.get());
+ acl_channel_wait_.set_trigger(ZX_CHANNEL_READABLE);
+ acl_channel_wait_.set_handler(
+ fbl::BindMember(this, &CommandChannel::OnAclChannelReady));
+ status = acl_channel_wait_.Begin(async_get_default());
+ if (status != ZX_OK) {
+ std::cerr << "CommandChannel: problem setting up ACL data channel: "
+ << zx_status_get_string(status) << std::endl;
+ }
+}
+
+CommandChannel::~CommandChannel() {
+ SetEventCallback(nullptr);
+ channel_wait_.Cancel(async_get_default());
+ acl_channel_wait_.Cancel(async_get_default());
+}
+
+void CommandChannel::SetEventCallback(const EventCallback& callback) {
+ event_callback_ = callback;
+}
+
+void CommandChannel::SendCommand(
+ const bluetooth::common::PacketView<bluetooth::hci::CommandHeader>&
+ command) {
+ // TODO(jamuraa): handle this in a non-shitty way later.
+ // If this is a 0xfc09 packet (and we're in bootloader mode, which is what we
+ // boot to - it needs to, nonsensically, be sent down the bulk URB instead of
+ // the standard one, so send it via the ACL channel, because that will do the
+ // right thing, since the ACL channel uses the bulk endpoint always.
+ zx_status_t status;
+ if (command.header().opcode == 0xfc09) {
+ status = acl_channel_.write(0, command.data().data(), command.size(), nullptr, 0);
+ } else {
+ status = channel_.write(0, command.data().data(), command.size(), nullptr, 0);
+ }
+ if (status < 0) {
+ // TODO(jamuraa): Maybe return the zx_status_t in this case?
+ std::cerr << "CommandChannel: Failed to send command: "
+ << zx_status_get_string(status) << std::endl;
+ }
+}
+
+void CommandChannel::SendCommandSync(
+ const bluetooth::common::PacketView<bluetooth::hci::CommandHeader>& command,
+ const EventCallback& callback) {
+ zx::event received;
+ zx::event::create(0, &received);
+
+ auto cb = [this, &received, callback](const auto& event_packet) {
+ if (callback) {
+ callback(event_packet);
+ }
+ received.signal(0, ZX_USER_SIGNAL_0);
+ };
+
+ SetEventCallback(cb);
+
+ SendCommand(command);
+
+ // Spin here until we get a response..
+ zx_status_t status;
+ zx::timer timeout;
+ zx::timer::create(0, ZX_CLOCK_MONOTONIC, &timeout);
+ timeout.set(zx::deadline_after(ZX_MSEC(200)), ZX_MSEC(50));
+ for (;;) {
+ async_loop_run(async_get_default(), zx::deadline_after(ZX_MSEC(10)), true);
+ status = received.wait_one(ZX_USER_SIGNAL_0, 0u, nullptr);
+ if (status != ZX_ERR_TIMED_OUT) {
+ break;
+ }
+ status = timeout.wait_one(ZX_TIMER_SIGNALED, 0u, nullptr);
+ if (status != ZX_ERR_TIMED_OUT) {
+ status = ZX_ERR_TIMED_OUT;
+ break;
+ }
+ }
+
+ SetEventCallback(nullptr);
+
+ if (status == ZX_OK) {
+ return;
+ }
+
+ std::cerr << "CommandChannel: error waiting for event "
+ << zx_status_get_string(status) << std::endl;
+}
+
+async_wait_result_t CommandChannel::HandleChannelReady(
+ const zx::channel& channel,
+ async_t* async,
+ zx_status_t status,
+ const zx_packet_signal_t* signal) {
+ FXL_DCHECK(signal->observed & ZX_CHANNEL_READABLE);
+
+ if (status != ZX_OK) {
+ std::cerr << "CommandChannel: channel error: "
+ << zx_status_get_string(status) << std::endl;
+ return ASYNC_WAIT_FINISHED;
+ }
+
+ // Allocate a buffer for the event. Since we don't know the size
+ // beforehand we allocate the largest possible buffer.
+
+ for (size_t count = 0; count < signal->count; count++) {
+ uint32_t read_size;
+ auto packet = bluetooth::hci::EventPacket::New(
+ bluetooth::hci::slab_allocators::kLargeControlPayloadSize);
+ if (!packet) {
+ std::cerr << "CommandChannel: Failed to allocate event packet!"
+ << std::endl;
+ return ASYNC_WAIT_FINISHED;
+ }
+ auto packet_bytes = packet->mutable_view()->mutable_data();
+ zx_status_t read_status =
+ channel.read(0u, packet_bytes.mutable_data(), packet_bytes.size(),
+ &read_size, nullptr, 0, nullptr);
+ if (read_status < 0) {
+ std::cerr << "CommandChannel: Failed to read event bytes: "
+ << zx_status_get_string(read_status) << std::endl;
+ // Clear the handler so that we stop receiving events from it.
+ return ASYNC_WAIT_FINISHED;
+ }
+
+ if (read_size < sizeof(bluetooth::hci::EventHeader)) {
+ std::cerr << "CommandChannel: Malformed event packet - "
+ << "expected at least " << sizeof(bluetooth::hci::EventHeader)
+ << " bytes, got " << read_size << std::endl;
+ continue;
+ }
+
+ // Compare the received payload size to what is in the header.
+ const size_t rx_payload_size =
+ read_size - sizeof(bluetooth::hci::EventHeader);
+ const size_t size_from_header =
+ packet->view().header().parameter_total_size;
+ if (size_from_header != rx_payload_size) {
+ std::cerr << "CommandChannel: Malformed event packet - "
+ << "payload size from header (" << size_from_header << ")"
+ << " does not match received payload size: " << rx_payload_size
+ << std::endl;
+ continue;
+ }
+
+ packet->InitializeFromBuffer();
+
+ if (event_callback_) {
+ event_callback_(*packet);
+ } else {
+ std::cerr << "CommandChannel: Event received with no handler:"
+ << packet->event_code() << std::endl;
+ }
+ }
+ return ASYNC_WAIT_AGAIN;
+}
+
+async_wait_result_t CommandChannel::OnChannelReady(
+ async_t* async,
+ zx_status_t status,
+ const zx_packet_signal_t* signal) {
+ // Just handle this.
+ return HandleChannelReady(channel_, async, status, signal);
+}
+
+async_wait_result_t CommandChannel::OnAclChannelReady(
+ async_t* async,
+ zx_status_t status,
+ const zx_packet_signal_t* signal) {
+ // This is probably a Command packet response from a Secure Send command.
+ std::cerr << "CommandChannel: ACL Data packet received, treating as a command packet.." << std::endl;
+ return HandleChannelReady(acl_channel_, async, status, signal);
+}
diff --git a/bin/bluetooth_tools/bt_intel_tool/command_channel.h b/bin/bluetooth_tools/bt_intel_tool/command_channel.h
new file mode 100644
index 0000000..0f190ce
--- /dev/null
+++ b/bin/bluetooth_tools/bt_intel_tool/command_channel.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 <fbl/unique_fd.h>
+#include <async/wait.h>
+#include <zircon/types.h>
+#include <zx/channel.h>
+
+#include "garnet/drivers/bluetooth/lib/hci/control_packets.h"
+
+// Sends and receives events from a command channel that it retrieves from a
+// Zircon Bluetooth HCI device. It parses the incoming event packets, only
+// returning complete and valid event packets on to the event handler set.
+class CommandChannel {
+ public:
+ // |hcidev_path| is a path to the hci device (e.g. /dev/class/bt-hci/000)
+ CommandChannel(std::string hcidev_path);
+ ~CommandChannel();
+
+ // Indicates whether this channel is valid. This should be checked after
+ // construction.
+ bool is_valid() { return channel_.is_valid(); }
+
+ using EventCallback =
+ std::function<void(const bluetooth::hci::EventPacket& event_packet)>;
+ // Sets the event callback to be called when an HCI Event arrives on the
+ // channel.
+ void SetEventCallback(const EventCallback& callback);
+
+ // Sends the command in |command| to the controller. The channel must
+ // be Ready when this is called.
+ void SendCommand(
+ const bluetooth::common::PacketView<bluetooth::hci::CommandHeader>&
+ command);
+
+ // Sends the command in |command| to the controller and waits for
+ // an Event, which is delivered to |callback| before this function
+ // returns.
+ void SendCommandSync(const bluetooth::common::PacketView<
+ bluetooth::hci::CommandHeader>& command,
+ const EventCallback& callback);
+
+ private:
+ // Common read handler for signalled channels
+ async_wait_result_t HandleChannelReady(const zx::channel& channel,
+ async_t* async,
+ zx_status_t status,
+ const zx_packet_signal_t* signal);
+
+ // Read ready handler for |channel_|
+ async_wait_result_t OnChannelReady(async_t* async,
+ zx_status_t status,
+ const zx_packet_signal_t* signal);
+
+ // Read ready handler for |acl_channel_|
+ async_wait_result_t OnAclChannelReady(async_t* async, zx_status_t status, const zx_packet_signal_t* signal);
+
+ EventCallback event_callback_;
+ fbl::unique_fd hci_fd_;
+ zx::channel channel_;
+ async::Wait channel_wait_;
+ zx::channel acl_channel_;
+ async::Wait acl_channel_wait_;
+};
diff --git a/bin/bluetooth_tools/bt_intel_tool/commands.cc b/bin/bluetooth_tools/bt_intel_tool/commands.cc
index 844d2fa..f74ee77 100644
--- a/bin/bluetooth_tools/bt_intel_tool/commands.cc
+++ b/bin/bluetooth_tools/bt_intel_tool/commands.cc
@@ -6,6 +6,7 @@
#include <endian.h>
+#include <sys/mman.h>
#include <cstring>
#include <iostream>
@@ -20,6 +21,7 @@
#include "lib/fxl/time/time_delta.h"
#include "bt_intel.h"
+#include "intel_firmware_loader.h"
using namespace bluetooth;
@@ -29,30 +31,45 @@
namespace bt_intel {
namespace {
-void StatusCallback(fxl::Closure complete_cb,
- bluetooth::hci::CommandChannel::TransactionId id,
- bluetooth::hci::Status status) {
- std::cout << " Command Status: " << fxl::StringPrintf("0x%02x", status)
- << " (id=" << id << ")" << std::endl;
- if (status != bluetooth::hci::Status::kSuccess)
- complete_cb();
-}
+class MfgModeEnabler {
+ public:
+ MfgModeEnabler(CommandChannel* channel)
+ : channel_(channel), patch_reset_needed_(false) {
+ auto packet = MakeMfgModePacket(true);
+ channel_->SendCommand(packet->view());
+ }
-hci::CommandChannel::TransactionId SendCommand(
- const CommandData* cmd_data,
- std::unique_ptr<hci::CommandPacket> packet,
- const hci::CommandChannel::CommandCompleteCallback& cb,
- const fxl::Closure& complete_cb) {
- return cmd_data->cmd_channel()->SendCommand(
- std::move(packet), cmd_data->task_runner(), cb,
- std::bind(&StatusCallback, complete_cb, _1, _2));
-}
+ ~MfgModeEnabler() {
+ MfgDisableMode disable_mode = MfgDisableMode::kNoPatches;
+ if (patch_reset_needed_)
+ disable_mode = MfgDisableMode::kPatchesEnabled;
-void LogCommandComplete(hci::Status status,
- hci::CommandChannel::TransactionId id) {
+ auto packet = MakeMfgModePacket(false, disable_mode);
+ channel_->SendCommand(packet->view());
+ }
+
+ void set_patch_reset(bool patch) { patch_reset_needed_ = patch; }
+
+ private:
+ CommandChannel* channel_;
+ bool patch_reset_needed_;
+
+ std::unique_ptr<hci::CommandPacket> MakeMfgModePacket(
+ bool enable,
+ MfgDisableMode disable_mode = MfgDisableMode::kNoPatches) {
+ auto packet = hci::CommandPacket::New(
+ kMfgModeChange, sizeof(IntelMfgModeChangeCommandParams));
+ auto params = packet->mutable_view()
+ ->mutable_payload<IntelMfgModeChangeCommandParams>();
+ params->enable = enable ? 1 : 0;
+ params->disable_mode = disable_mode;
+ return packet;
+ }
+};
+
+void LogCommandComplete(hci::Status status) {
std::cout << " Command Complete - status: "
- << fxl::StringPrintf("0x%02x", status) << " (id=" << id << ")"
- << std::endl;
+ << fxl::StringPrintf("0x%02x", status) << std::endl;
}
// Prints a byte in decimal and hex forms
@@ -84,10 +101,9 @@
return false;
}
- auto cb = [cmd_line, complete_cb](hci::CommandChannel::TransactionId id,
- const hci::EventPacket& event) {
+ auto cb = [cmd_line, complete_cb](const hci::EventPacket& event) {
auto params = event.return_params<IntelVersionReturnParams>();
- LogCommandComplete(params->status, id);
+ LogCommandComplete(params->status);
std::cout << fxl::StringPrintf(
" Firmware Summary: variant=%s - revision %u.%u build no: %u (week "
@@ -119,15 +135,15 @@
std::cout << " Firmware Patch No: " << PrintByte(params->fw_patch_num)
<< std::endl;
}
-
- complete_cb();
};
- auto packet = hci::CommandPacket::New(kReadVersion);
- auto id = SendCommand(cmd_data, std::move(packet), cb, complete_cb);
- std::cout << " Sent HCI Vendor (Intel) Read Version (id=" << id << ")"
- << std::endl;
+ //cmd_data->cmd_channel()->SetEventCallback(cb);
+ auto packet = hci::CommandPacket::New(kReadVersion);
+ std::cout << " Sending HCI Vendor (Intel) Read Version" << std::endl;
+ cmd_data->cmd_channel()->SendCommandSync(packet->view(), cb);
+
+ complete_cb();
return true;
}
@@ -139,10 +155,9 @@
return false;
}
- auto cb = [cmd_line, complete_cb](hci::CommandChannel::TransactionId id,
- const hci::EventPacket& event) {
+ auto cb = [cmd_line, complete_cb, cmd_data](const hci::EventPacket& event) {
auto params = event.return_params<IntelReadBootParamsReturnParams>();
- LogCommandComplete(params->status, id);
+ LogCommandComplete(params->status);
std::cout << " Intel Boot Parameters:" << std::endl;
std::cout << " Device Revision: " << le16toh(params->dev_revid)
@@ -166,13 +181,14 @@
2000 + params->min_fw_build_year)
<< std::endl;
+ cmd_data->cmd_channel()->SetEventCallback(nullptr);
complete_cb();
};
auto packet = hci::CommandPacket::New(kReadBootParams);
- auto id = SendCommand(cmd_data, std::move(packet), cb, complete_cb);
- std::cout << " Sent HCI Vendor (Intel) Read Boot Params (id=" << id << ")"
- << std::endl;
+ cmd_data->cmd_channel()->SetEventCallback(cb);
+ cmd_data->cmd_channel()->SendCommand(packet->view());
+ std::cout << " Sent HCI Vendor (Intel) Read Boot Params" << std::endl;
return true;
}
@@ -185,9 +201,6 @@
return false;
}
- auto cb = [](hci::CommandChannel::TransactionId id,
- const hci::EventPacket& event) {};
-
auto packet =
hci::CommandPacket::New(kReset, sizeof(IntelResetCommandParams));
auto params =
@@ -201,18 +214,57 @@
params->data[6] = 0x04;
params->data[7] = 0x00;
- auto id = SendCommand(cmd_data, std::move(packet), cb, complete_cb);
- std::cout << " Sent HCI Vendor (Intel) Reset (id=" << id << ")" << std::endl;
+ cmd_data->cmd_channel()->SendCommand(packet->view());
+ std::cout << " Sent HCI Vendor (Intel) Rese" << std::endl;
// Once the reset command is sent, the hardware will shut down and we won't be
// able to get a response back. Just exit the tool.
- // TODO(armansito): This needs to be implemented properly in the driver as
- // part of the controller boot sequence. We cannot reboot the controller from
- // userland since the hardware disappears so we'll never receive the
- // vendor-specific HCI event.
- cmd_data->task_runner()->PostDelayedTask(
- complete_cb, fxl::TimeDelta::FromMilliseconds(250));
+ complete_cb();
+ return true;
+}
+
+bool HandleLoadBseq(const CommandData* cmd_data,
+ const fxl::CommandLine& cmd_line,
+ const fxl::Closure& complete_cb) {
+ if (cmd_line.positional_args().size() != 1) {
+ std::cout << " Usage: load-bseq [--verbose] <filename>" << std::endl;
+ return false;
+ }
+
+ std::string firmware_fn = cmd_line.positional_args().front();
+
+ {
+ MfgModeEnabler enable(cmd_data->cmd_channel());
+
+ IntelFirmwareLoader loader(cmd_data->cmd_channel());
+
+ IntelFirmwareLoader::LoadStatus patched = loader.LoadBseq(firmware_fn);
+
+ if (patched == IntelFirmwareLoader::LoadStatus::kPatched) {
+ enable.set_patch_reset(true);
+ }
+ }
+
+ complete_cb();
+ return true;
+}
+
+bool HandleLoadSecure(const CommandData* cmd_data,
+ const fxl::CommandLine& cmd_line,
+ const fxl::Closure& complete_cb) {
+ if (cmd_line.positional_args().size() != 1) {
+ std::cout << " Usage: load-sfi [--verbose] <filename>" << std::endl;
+ return false;
+ }
+
+ std::string firmware_fn = cmd_line.positional_args().front();
+
+ IntelFirmwareLoader loader(cmd_data->cmd_channel());
+
+ loader.LoadSfi(firmware_fn);
+
+ complete_cb();
return true;
}
@@ -229,6 +281,11 @@
dispatcher->RegisterHandler("read-boot-params",
"Read hardware boot parameters",
BIND(HandleReadBootParams));
+ dispatcher->RegisterHandler("load-bseq", "Load bseq file onto device",
+ BIND(HandleLoadBseq));
+ dispatcher->RegisterHandler("load-sfi", "Load Secure Firmware onto device",
+ BIND(HandleLoadSecure));
+
dispatcher->RegisterHandler("reset", "Reset firmware", BIND(HandleReset));
#undef BIND
diff --git a/bin/bluetooth_tools/bt_intel_tool/commands.h b/bin/bluetooth_tools/bt_intel_tool/commands.h
index 57e8396..1c6850c 100644
--- a/bin/bluetooth_tools/bt_intel_tool/commands.h
+++ b/bin/bluetooth_tools/bt_intel_tool/commands.h
@@ -5,24 +5,21 @@
#pragma once
#include "garnet/bin/bluetooth_tools/lib/command_dispatcher.h"
-#include "garnet/drivers/bluetooth/lib/hci/command_channel.h"
#include "lib/fxl/memory/ref_ptr.h"
#include "lib/fxl/tasks/task_runner.h"
+#include "command_channel.h"
+
namespace bt_intel {
class CommandData final {
public:
- CommandData(bluetooth::hci::CommandChannel* cmd_channel,
- fxl::RefPtr<fxl::TaskRunner> task_runner)
- : cmd_channel_(cmd_channel), task_runner_(task_runner) {}
+ CommandData(CommandChannel* cmd_channel) : cmd_channel_(cmd_channel) {}
- bluetooth::hci::CommandChannel* cmd_channel() const { return cmd_channel_; }
- fxl::RefPtr<fxl::TaskRunner> task_runner() const { return task_runner_; }
+ CommandChannel* cmd_channel() const { return cmd_channel_; }
private:
- bluetooth::hci::CommandChannel* cmd_channel_;
- fxl::RefPtr<fxl::TaskRunner> task_runner_;
+ CommandChannel* cmd_channel_;
};
void RegisterCommands(const CommandData* data,
diff --git a/bin/bluetooth_tools/bt_intel_tool/intel_firmware_loader.cc b/bin/bluetooth_tools/bt_intel_tool/intel_firmware_loader.cc
new file mode 100644
index 0000000..1a878e0
--- /dev/null
+++ b/bin/bluetooth_tools/bt_intel_tool/intel_firmware_loader.cc
@@ -0,0 +1,251 @@
+// 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 "intel_firmware_loader.h"
+
+#include <fbl/string_printf.h>
+#include <fbl/unique_fd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <zircon/status.h>
+#include <zx/event.h>
+#include <zx/time.h>
+
+#include <iostream>
+#include <limits>
+
+#include "bt_intel.h"
+
+namespace bt_intel {
+
+namespace {
+
+// A file mapped into memory that we can grab chunks from.
+class MemoryFile {
+ public:
+ MemoryFile(const std::string& filename)
+ : fd_(open(filename.c_str(), O_RDONLY)), mapped_(nullptr) {
+ if (!bool(fd_)) {
+ std::cerr << "Failed to open file " << filename << " : "
+ << strerror(errno) << std::endl;
+ return;
+ }
+
+ struct stat file_stats;
+ fstat(fd_.get(), &file_stats);
+ size_ = file_stats.st_size;
+ std::cerr << "Mapping " << size_ << " bytes of " << filename << std::endl;
+
+ mapped_ = mmap(nullptr, size_, PROT_READ, MAP_PRIVATE, fd_.get(), 0);
+ if (mapped_ == MAP_FAILED) {
+ std::cerr << "Failed to map file to memory: " << strerror(errno)
+ << std::endl;
+ mapped_ = nullptr;
+ }
+ };
+
+ ~MemoryFile() {
+ if (mapped_) {
+ munmap(mapped_, size_);
+ }
+ }
+
+ size_t size() const { return size_; }
+
+ bool is_valid() const { return mapped_ != nullptr; }
+
+ const uint8_t* at(size_t offset) const {
+ return static_cast<uint8_t*>(mapped_) + offset;
+ }
+
+ bluetooth::common::BufferView view(
+ size_t offset,
+ size_t length = std::numeric_limits<size_t>::max()) const {
+ if (!is_valid() || (offset > size_)) {
+ return bluetooth::common::BufferView();
+ }
+ if (length > (size_ - offset)) {
+ length = (size_ - offset);
+ }
+ return bluetooth::common::BufferView(mapped_, length);
+ }
+
+ private:
+ // The actual file descriptor.
+ fbl::unique_fd fd_;
+
+ // pointer to the file's space in memory
+ void* mapped_;
+
+ // Size of the file in memory
+ size_t size_;
+};
+
+void SecureSend(CommandChannel* channel,
+ uint8_t type,
+ const bluetooth::common::BufferView& bytes) {
+ size_t left = bytes.size();
+ while (left > 0) {
+ size_t frag_len = left > 252 ? 252 : left;
+ std::cout << "IntelFirmwareLoader: Secure Sending " << frag_len << " of " << left << " bytes" << std::endl;
+ auto cmd = bluetooth::hci::CommandPacket::New(kSecureSend, frag_len + 1);
+ auto data = cmd->mutable_view()->mutable_payload_data();
+ data[0] = type;
+ data.Write(bytes.view(bytes.size() - left, frag_len), 1);
+
+ channel->SendCommandSync(cmd->view(), [](const auto& event) {
+ std::cout << "IntelFirmwareLoader: Secure Send response: "
+ << std::to_string(event.event_code()) << std::endl;
+ if (event.event_code() == 0xff) {
+ const auto& params = event.view().template payload<IntelSecureSendEventParams>();
+ std::cout << "IntelFirmwareLoader: Secure Send result: (" << params.result
+ << ", " << params.opcode << ", " << params.status << ")" << std::endl;
+ }
+ });
+ left -= frag_len;
+ }
+}
+
+} // namespace
+
+IntelFirmwareLoader::LoadStatus IntelFirmwareLoader::LoadBseq(
+ const std::string& filename) {
+ MemoryFile file(filename);
+
+ if (!file.is_valid()) {
+ std::cerr << "Failed to open firmware file." << std::endl;
+ return LoadStatus::kError;
+ }
+
+ size_t ptr = 0;
+
+ // A bseq file consists of a sequence of:
+ // - [0x01] [command w/params]
+ // - [0x02] [expected event w/params]
+ while (file.size() - ptr > sizeof(bluetooth::hci::CommandHeader)) {
+ // Parse the next items
+ if (*file.at(ptr) != 0x01) {
+ std::cerr << "IntelFirmwareLoader: Error: malformed file, expected "
+ "Command Packet marker"
+ << std::endl;
+ return LoadStatus::kError;
+ }
+ ptr++;
+ bluetooth::common::BufferView command_view = file.view(ptr);
+ bluetooth::common::PacketView<bluetooth::hci::CommandHeader> command(
+ &command_view);
+ command = bluetooth::common::PacketView<bluetooth::hci::CommandHeader>(
+ &command_view, command.header().parameter_total_size);
+ ptr += command.size();
+ if ((file.size() <= ptr) || (*file.at(ptr) != 0x02)) {
+ std::cerr << "IntelFirmwareLoader: Error: malformed file, expected Event "
+ "Packet marker"
+ << std::endl;
+ return LoadStatus::kError;
+ }
+ std::deque<std::unique_ptr<bluetooth::hci::EventPacket>> events;
+ while ((file.size() <= ptr) || (*file.at(ptr) == 0x02)) {
+ ptr++;
+ // TODO(jamuraa): we should probably do this without copying,
+ // maybe make a way to initialize event packets backed by unowned
+ // memor
+ auto event = bluetooth::hci::EventPacket::New(0u);
+ memcpy(event->mutable_view()->mutable_header(), file.at(ptr),
+ sizeof(bluetooth::hci::EventHeader));
+ ptr += event->view().size();
+ event->InitializeFromBuffer();
+ memcpy(event->mutable_view()->mutable_payload_bytes(), file.at(ptr),
+ event->view().payload_size());
+ ptr += event->view().payload_size();
+ events.push_back(std::move(event));
+ }
+
+ if (!RunCommandAndExpect(command, std::move(events))) {
+ return LoadStatus::kError;
+ }
+ }
+
+ return LoadStatus::kComplete;
+}
+
+bool IntelFirmwareLoader::LoadSfi(const std::string& filename) {
+ MemoryFile file(filename);
+
+ if (file.size() < 644) {
+ std::cerr << "IntelFirmwareLoader: SFI file not long enough: "
+ << file.size() << " < 644" << std::endl;
+ return false;
+ }
+
+ size_t ptr = 0;
+ // SFI File format:
+ // [128 bytes CSS Header]
+ SecureSend(channel_, 0x00, file.view(ptr, 128));
+ //ptr += 128;
+ // [256 bytes PKI]
+ SecureSend(channel_, 0x03, file.view(ptr, 256));
+ ptr += 256;
+ // [256 bytes signature info]
+ SecureSend(channel_, 0x02, file.view(ptr, 256));
+ ptr += 256;
+ // [N bytes of data]
+ // Note: this is actually a bunch of Command Packets, padded with
+ // NOP commands so they sit on 4-byte boundaries, but we write it to
+ // Secure Send area anyway so I don't see the point in parsing them.
+ SecureSend(channel_, 0x01, file.view(ptr, file.size() - 644));
+
+ return true;
+}
+
+bool IntelFirmwareLoader::RunCommandAndExpect(
+ const bluetooth::common::PacketView<bluetooth::hci::CommandHeader>& command,
+ std::deque<std::unique_ptr<bluetooth::hci::EventPacket>>&& events) {
+ zx::event events_done;
+ zx::event::create(0, &events_done);
+
+ size_t events_received = 0;
+ auto event_cb = [&events_received, &events, &events_done](
+ const bluetooth::hci::EventPacket& evt_packet) {
+ auto expected = std::move(events.front());
+ events.pop_front();
+ if (evt_packet.view().size() != expected->view().size()) {
+ events_done.signal(0, ZX_USER_SIGNAL_0);
+ return;
+ }
+ if (memcmp(evt_packet.view().data().data(), expected->view().data().data(),
+ expected->view().size()) != 0) {
+ events_done.signal(0, ZX_USER_SIGNAL_0);
+ return;
+ }
+ events_received++;
+ };
+
+ channel_->SetEventCallback(event_cb);
+
+ channel_->SendCommand(command);
+
+ zx_signals_t signalled;
+ zx_status_t status = events_done.wait_one(
+ ZX_USER_SIGNAL_0, zx::deadline_after(ZX_SEC(1)), &signalled);
+
+ channel_->SetEventCallback(nullptr);
+
+ if (status == ZX_OK) {
+ return true;
+ }
+
+ if (status == ZX_ERR_TIMED_OUT) {
+ std::cerr << "IntelFirmwareLoader: timed out waiting for events"
+ << std::endl;
+ } else {
+ std::cerr << "IntelFirmwareLoader: error waiting for events"
+ << zx_status_get_string(status) << std::endl;
+ }
+ return false;
+}
+
+} // namespace bt_intel
diff --git a/bin/bluetooth_tools/bt_intel_tool/intel_firmware_loader.h b/bin/bluetooth_tools/bt_intel_tool/intel_firmware_loader.h
new file mode 100644
index 0000000..f2e930f
--- /dev/null
+++ b/bin/bluetooth_tools/bt_intel_tool/intel_firmware_loader.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
+
+// A loader for Intel Bluetooth Firmware files.
+
+#include <deque>
+
+#include "garnet/drivers/bluetooth/lib/hci/control_packets.h"
+
+#include "command_channel.h"
+
+namespace bt_intel {
+
+class IntelFirmwareLoader {
+ public:
+ // |cmd_channel| is expected to outlive this object.
+ IntelFirmwareLoader(CommandChannel* cmd_channel) : channel_(cmd_channel) {}
+ ~IntelFirmwareLoader() = default;
+
+ enum class LoadStatus {
+ // Firmware is complete, no patch loaded, ready.
+ kComplete,
+ // Patch is loaded, reset the controller with patches enabled to continue
+ kPatched,
+ // An unexpected event was returned from the controller
+ kError,
+ // The file provided is in an invalid
+ kInvalidFile,
+ };
+
+ // Reads and loads a "bseq" file into the controller using the given command
+ // channel. Returns a LoadStatus indicating the result.
+ // - Complete if the firmware was loaded successfully
+ // - Patched if the firmware was loaded and a patch was added, meaning the
+ // controller should be reset.
+ // - Error otherwise.
+ LoadStatus LoadBseq(const std::string& filename);
+
+ // Reads and loads a "sfi" file into the controller using the given command
+ // channel. Returns true if the file was loaded, false otherwise
+ bool LoadSfi(const std::string& filename);
+
+ private:
+ bool ParseBseq();
+
+ // Sends the next command and waits for the next events.
+ // Returns true if the expected events got returned, false otherwise.
+ bool RunCommandAndExpect(
+ const bluetooth::common::PacketView<bluetooth::hci::CommandHeader>&
+ command,
+ std::deque<std::unique_ptr<bluetooth::hci::EventPacket>>&& events);
+
+ // The command channel to use
+ CommandChannel* channel_;
+};
+
+} // namespace bt_intel
diff --git a/bin/bluetooth_tools/bt_intel_tool/main.cc b/bin/bluetooth_tools/bt_intel_tool/main.cc
index de3fe76..21afdc9 100644
--- a/bin/bluetooth_tools/bt_intel_tool/main.cc
+++ b/bin/bluetooth_tools/bt_intel_tool/main.cc
@@ -20,6 +20,7 @@
#include "lib/fxl/log_settings_command_line.h"
#include "lib/fxl/strings/string_printf.h"
+#include "command_channel.h"
#include "commands.h"
using namespace bluetooth;
@@ -62,21 +63,11 @@
return EXIT_FAILURE;
}
- fxl::UniqueFD hci_dev_fd(open(hci_dev_path.c_str(), O_RDWR));
- if (!hci_dev_fd.is_valid()) {
- std::perror("Failed to open HCI device");
- return EXIT_FAILURE;
- }
-
- auto hci_dev =
- std::make_unique<hci::ZirconDeviceWrapper>(std::move(hci_dev_fd));
- auto hci = hci::Transport::Create(std::move(hci_dev));
- hci->Initialize();
fsl::MessageLoop message_loop;
+ CommandChannel channel(hci_dev_path);
tools::CommandDispatcher dispatcher;
- bt_intel::CommandData cmd_data(hci->command_channel(),
- message_loop.task_runner());
+ bt_intel::CommandData cmd_data(&channel);
RegisterCommands(&cmd_data, &dispatcher);
if (cl.positional_args().empty() || cl.positional_args()[0] == "help") {