[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") {