Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a15c3f48 authored by Myles Watson's avatar Myles Watson
Browse files

HCI: HCI command credit and timeout handling

Test: bluetooth_gd_test
Change-Id: Iff11fd2e21f71c23d716f7ca5f920490dd7c79f3
parent 882007ff
Loading
Loading
Loading
Loading
+117 −34
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */

#include "hci/hci_layer.h"
#include "os/alarm.h"

#include "common/bind.h"
#include "common/callback.h"
@@ -45,12 +46,17 @@ class EventHandler {
class CommandQueueEntry {
 public:
  CommandQueueEntry(std::unique_ptr<CommandPacketBuilder> command_packet,
                    OnceCallback<void(CommandStatusView)> on_status_function,
                    OnceCallback<void(CommandCompleteView)> on_complete_function, Handler* handler)
      : command(std::move(command_packet)), on_status(std::move(on_status_function)),
        on_complete(std::move(on_complete_function)), caller_handler(handler) {}
      : command(std::move(command_packet)), waiting_for_status_(false), on_complete(std::move(on_complete_function)),
        caller_handler(handler) {}

  CommandQueueEntry(std::unique_ptr<CommandPacketBuilder> command_packet,
                    OnceCallback<void(CommandStatusView)> on_status_function, Handler* handler)
      : command(std::move(command_packet)), waiting_for_status_(true), on_status(std::move(on_status_function)),
        caller_handler(handler) {}

  std::unique_ptr<CommandPacketBuilder> command;
  bool waiting_for_status_;
  OnceCallback<void(CommandStatusView)> on_status;
  OnceCallback<void(CommandCompleteView)> on_complete;
  Handler* caller_handler;
@@ -63,13 +69,32 @@ namespace hci {
using common::Address;
using common::BidiQueue;
using common::BidiQueueEnd;
using os::Alarm;
using os::Handler;

namespace {
using hci::OpCode;
using hci::ResetCompleteView;

void fail_if_reset_complete_not_success(CommandCompleteView complete) {
  auto reset_complete = ResetCompleteView::Create(complete);
  ASSERT(reset_complete.IsValid());
  ASSERT(reset_complete.GetStatus() == ErrorCode::SUCCESS);
}

void on_hci_timeout(OpCode op_code) {
  ASSERT_LOG(false, "Timed out waiting for 0x%02hx (%s)", op_code, OpCodeText(op_code).c_str());
}
}  // namespace

struct HciLayer::impl : public hal::HciHalCallbacks {
  impl(HciLayer& module) : hal_(nullptr), module_(module) {}

  ~impl() {}

  void Start(hal::HciHal* hal) {
    hal_ = hal;
    hci_timeout_alarm_ = new Alarm(module_.GetHandler());

    auto queue_end = acl_queue_.GetDownEnd();
    Handler* handler = module_.GetHandler();
@@ -78,6 +103,7 @@ struct HciLayer::impl : public hal::HciHalCallbacks {
                         handler);
    RegisterEventHandler(EventCode::COMMAND_STATUS, Bind(&impl::command_status_callback, common::Unretained(this)),
                         handler);
    EnqueueCommand(ResetBuilder::Create(), BindOnce(&fail_if_reset_complete_not_success), handler);
    hal_->registerIncomingPacketCallback(this);
  }

@@ -86,6 +112,13 @@ struct HciLayer::impl : public hal::HciHalCallbacks {
    send_acl(std::move(packet));
  }

  void Stop() {
    acl_queue_.GetDownEnd()->UnregisterDequeue();
    delete hci_timeout_alarm_;
    command_queue_.clear();
    hal_ = nullptr;
  }

  void send_acl(std::unique_ptr<hci::BasePacketBuilder> packet) {
    std::vector<uint8_t> bytes;
    BitInserter bi(bytes);
@@ -100,37 +133,52 @@ struct HciLayer::impl : public hal::HciHalCallbacks {
    hal_->sendScoData(bytes);
  }

  void Stop() {
    acl_queue_.GetDownEnd()->UnregisterDequeue();
    hal_ = nullptr;
  }

  void command_status_callback(EventPacketView event) {
    CommandStatusView status_view = CommandStatusView::Create(event);
    ASSERT(status_view.IsValid());
    if (command_queue_.size() == 0) {
      ASSERT_LOG(status_view.GetCommandOpCode() == OpCode::NONE, "Unexpected status event with OpCode 0x%02hx",
                 status_view.GetCommandOpCode());
    command_credits_ = status_view.GetNumHciCommandPackets();
    OpCode op_code = status_view.GetCommandOpCode();
    if (op_code == OpCode::NONE) {
      send_next_command();
      return;
    }
    // TODO: Check whether this is the CommandOpCode we're looking for.
    ASSERT_LOG(!command_queue_.empty(), "Unexpected status event with OpCode 0x%02hx (%s)", op_code,
               OpCodeText(op_code).c_str());
    ASSERT_LOG(waiting_command_ == op_code, "Waiting for 0x%02hx (%s), got 0x%02hx (%s)", waiting_command_,
               OpCodeText(waiting_command_).c_str(), op_code, OpCodeText(op_code).c_str());
    ASSERT_LOG(command_queue_.front().waiting_for_status_,
               "Waiting for command complete 0x%02hx (%s), got command status for 0x%02hx (%s)", waiting_command_,
               OpCodeText(waiting_command_).c_str(), op_code, OpCodeText(op_code).c_str());
    auto caller_handler = command_queue_.front().caller_handler;
    caller_handler->Post(BindOnce(std::move(command_queue_.front().on_status), std::move(status_view)));
    command_queue_.pop();
    command_queue_.pop_front();
    waiting_command_ = OpCode::NONE;
    hci_timeout_alarm_->Cancel();
    send_next_command();
  }

  void command_complete_callback(EventPacketView event) {
    CommandCompleteView complete_view = CommandCompleteView::Create(event);
    ASSERT(complete_view.IsValid());
    if (command_queue_.size() == 0) {
      ASSERT_LOG(complete_view.GetCommandOpCode() == OpCode::NONE,
                 "Unexpected command complete event with OpCode 0x%02hx", complete_view.GetCommandOpCode());
    command_credits_ = complete_view.GetNumHciCommandPackets();
    OpCode op_code = complete_view.GetCommandOpCode();
    if (op_code == OpCode::NONE) {
      send_next_command();
      return;
    }
    // TODO: Check whether this is the CommandOpCode we're looking for.
    ASSERT_LOG(command_queue_.size() > 0, "Unexpected command complete with OpCode 0x%02hx (%s)", op_code,
               OpCodeText(op_code).c_str());
    ASSERT_LOG(waiting_command_ == op_code, "Waiting for 0x%02hx (%s), got 0x%02hx (%s)", waiting_command_,
               OpCodeText(waiting_command_).c_str(), op_code, OpCodeText(op_code).c_str());
    ASSERT_LOG(!command_queue_.front().waiting_for_status_,
               "Waiting for command status 0x%02hx (%s), got command complete for 0x%02hx (%s)", waiting_command_,
               OpCodeText(waiting_command_).c_str(), op_code, OpCodeText(op_code).c_str());
    auto caller_handler = command_queue_.front().caller_handler;
    caller_handler->Post(BindOnce(std::move(command_queue_.front().on_complete), std::move(complete_view)));
    command_queue_.pop();
    command_queue_.pop_front();
    waiting_command_ = OpCode::NONE;
    hci_timeout_alarm_->Cancel();
    send_next_command();
  }

  void hciEventReceived(hal::HciPacket event_bytes) override {
@@ -147,7 +195,6 @@ struct HciLayer::impl : public hal::HciHalCallbacks {
               event.GetEventCode());
    auto& registered_handler = event_handlers_[event_code].event_handler;
    event_handlers_[event_code].handler->Post(BindOnce(registered_handler, std::move(event)));
    // TODO: Credits
  }

  void aclDataReceived(hal::HciPacket data_bytes) override {
@@ -174,24 +221,53 @@ struct HciLayer::impl : public hal::HciHalCallbacks {
    ScoPacketView sco = ScoPacketView::Create(packet);
  }

  void EnqueueCommand(std::unique_ptr<CommandPacketBuilder> command, OnceCallback<void(CommandStatusView)> on_status,
  void EnqueueCommand(std::unique_ptr<CommandPacketBuilder> command,
                      OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) {
    module_.GetHandler()->Post(common::BindOnce(&impl::handle_enqueue_command, common::Unretained(this),
                                                std::move(command), std::move(on_status), std::move(on_complete),
    module_.GetHandler()->Post(common::BindOnce(&impl::handle_enqueue_command_with_complete, common::Unretained(this),
                                                std::move(command), std::move(on_complete),
                                                common::Unretained(handler)));
  }

  void handle_enqueue_command(std::unique_ptr<CommandPacketBuilder> command,
                              OnceCallback<void(CommandStatusView)> on_status,
  void EnqueueCommand(std::unique_ptr<CommandPacketBuilder> command, OnceCallback<void(CommandStatusView)> on_status,
                      os::Handler* handler) {
    module_.GetHandler()->Post(common::BindOnce(&impl::handle_enqueue_command_with_status, common::Unretained(this),
                                                std::move(command), std::move(on_status), common::Unretained(handler)));
  }

  void handle_enqueue_command_with_complete(std::unique_ptr<CommandPacketBuilder> command,
                                            OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) {
    command_queue_.emplace(std::move(command), std::move(on_status), std::move(on_complete), handler);
    command_queue_.emplace_back(std::move(command), std::move(on_complete), handler);

    if (command_queue_.size() == 1) {
      std::vector<uint8_t> bytes;
      BitInserter bi(bytes);
      command_queue_.front().command->Serialize(bi);
      hal_->sendHciCommand(bytes);
    send_next_command();
  }

  void handle_enqueue_command_with_status(std::unique_ptr<CommandPacketBuilder> command,
                                          OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) {
    command_queue_.emplace_back(std::move(command), std::move(on_status), handler);

    send_next_command();
  }

  void send_next_command() {
    if (command_credits_ == 0) {
      return;
    }
    if (waiting_command_ != OpCode::NONE) {
      return;
    }
    if (command_queue_.size() == 0) {
      return;
    }
    std::shared_ptr<std::vector<uint8_t>> bytes = std::make_shared<std::vector<uint8_t>>();
    BitInserter bi(*bytes);
    command_queue_.front().command->Serialize(bi);
    hal_->sendHciCommand(*bytes);
    auto cmd_view = CommandPacketView::Create(bytes);
    ASSERT(cmd_view.IsValid());
    OpCode op_code = cmd_view.GetOpCode();
    waiting_command_ = op_code;
    command_credits_ = 0;  // Only allow one outstanding command
    hci_timeout_alarm_->Schedule(BindOnce(&on_hci_timeout, op_code), kHciTimeoutMs);
  }

  BidiQueueEnd<AclPacketBuilder, AclPacketView>* GetAclQueueEnd() {
@@ -227,9 +303,12 @@ struct HciLayer::impl : public hal::HciHalCallbacks {
  HciLayer& module_;

  // Command Handling
  std::queue<CommandQueueEntry> command_queue_;
  std::list<CommandQueueEntry> command_queue_;

  std::map<EventCode, EventHandler> event_handlers_;
  OpCode waiting_command_{OpCode::NONE};
  uint8_t command_credits_{1};  // Send reset first
  Alarm* hci_timeout_alarm_{nullptr};

  // Acl packets
  BidiQueue<AclPacketView, AclPacketBuilder> acl_queue_{3 /* TODO: Set queue depth */};
@@ -242,9 +321,13 @@ HciLayer::~HciLayer() {
}

void HciLayer::EnqueueCommand(std::unique_ptr<CommandPacketBuilder> command,
                              common::OnceCallback<void(CommandStatusView)> on_status,
                              common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler) {
  impl_->EnqueueCommand(std::move(command), std::move(on_status), std::move(on_complete), handler);
  impl_->EnqueueCommand(std::move(command), std::move(on_complete), handler);
}

void HciLayer::EnqueueCommand(std::unique_ptr<CommandPacketBuilder> command,
                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler) {
  impl_->EnqueueCommand(std::move(command), std::move(on_status), handler);
}

common::BidiQueueEnd<AclPacketBuilder, AclPacketView>* HciLayer::GetAclQueueEnd() {
+5 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

#pragma once

#include <chrono>
#include <map>

#include "common/address.h"
@@ -37,9 +38,11 @@ class HciLayer : public Module {
  DISALLOW_COPY_AND_ASSIGN(HciLayer);

  virtual void EnqueueCommand(std::unique_ptr<CommandPacketBuilder> command,
                              common::OnceCallback<void(CommandStatusView)> on_status,
                              common::OnceCallback<void(CommandCompleteView)> on_complete, os::Handler* handler);

  virtual void EnqueueCommand(std::unique_ptr<CommandPacketBuilder> command,
                              common::OnceCallback<void(CommandStatusView)> on_status, os::Handler* handler);

  virtual common::BidiQueueEnd<AclPacketBuilder, AclPacketView>* GetAclQueueEnd();

  virtual void RegisterEventHandler(EventCode event_code, common::Callback<void(EventPacketView)> event_handler,
@@ -54,6 +57,7 @@ class HciLayer : public Module {
  void Start() override;

  void Stop() override;
  static constexpr std::chrono::milliseconds kHciTimeoutMs = std::chrono::milliseconds(2000);

 private:
  struct impl;
+149 −9
Original line number Diff line number Diff line
@@ -75,12 +75,16 @@ class TestHciHal : public hal::HciHal {
    return PacketView<kLittleEndian>(shared);
  }

  PacketView<kLittleEndian> GetSentCommand() {
  size_t GetNumSentCommands() {
    return outgoing_commands_.size();
  }

  CommandPacketView GetSentCommand() {
    while (outgoing_commands_.size() == 0)
      ;
    auto packetview = GetPacketView(std::move(outgoing_commands_.front()));
    outgoing_commands_.pop_front();
    return packetview;
    return CommandPacketView::Create(packetview);
  }

  PacketView<kLittleEndian> GetSentAcl() {
@@ -111,10 +115,16 @@ class DependsOnHci : public Module {
 public:
  DependsOnHci() : Module() {}

  void SendHciCommand(std::unique_ptr<CommandPacketBuilder> command) {
    hci_->EnqueueCommand(
        std::move(command), common::Bind(&DependsOnHci::handle_event<CommandStatusView>, common::Unretained(this)),
        common::Bind(&DependsOnHci::handle_event<CommandCompleteView>, common::Unretained(this)), GetHandler());
  void SendHciCommandExpectingStatus(std::unique_ptr<CommandPacketBuilder> command) {
    hci_->EnqueueCommand(std::move(command),
                         common::Bind(&DependsOnHci::handle_event<CommandStatusView>, common::Unretained(this)),
                         GetHandler());
  }

  void SendHciCommandExpectingComplete(std::unique_ptr<CommandPacketBuilder> command) {
    hci_->EnqueueCommand(std::move(command),
                         common::Bind(&DependsOnHci::handle_event<CommandCompleteView>, common::Unretained(this)),
                         GetHandler());
  }

  void SendAclData(std::unique_ptr<AclPacketBuilder> acl) {
@@ -192,6 +202,23 @@ class HciTest : public ::testing::Test {
    hci = static_cast<HciLayer*>(fake_registry_.GetModuleUnderTest(&HciLayer::Factory));
    upper = static_cast<DependsOnHci*>(fake_registry_.GetModuleUnderTest(&DependsOnHci::Factory));
    ASSERT(fake_registry_.IsStarted<HciLayer>());
    // Wait for the reset
    while (hal->GetNumSentCommands() == 0)
      ;
    // Verify that reset was received
    ASSERT_EQ(1, hal->GetNumSentCommands());

    auto sent_command = hal->GetSentCommand();
    auto reset_view = ResetView::Create(CommandPacketView::Create(sent_command));
    ASSERT_TRUE(reset_view.IsValid());

    // Verify that only one was sent
    ASSERT_EQ(0, hal->GetNumSentCommands());

    // Send the response event
    uint8_t num_packets = 1;
    ErrorCode error_code = ErrorCode::SUCCESS;
    hal->callbacks->hciEventReceived(GetPacketBytes(ResetCompleteBuilder::Create(num_packets, error_code)));
  }

  void TearDown() override {
@@ -214,6 +241,118 @@ class HciTest : public ::testing::Test {

TEST_F(HciTest, initAndClose) {}

TEST_F(HciTest, noOpCredits) {
  ASSERT_EQ(0, hal->GetNumSentCommands());

  // Send 0 credits
  uint8_t num_packets = 0;
  hal->callbacks->hciEventReceived(GetPacketBytes(NoCommandCompleteBuilder::Create(num_packets)));

  upper->SendHciCommandExpectingComplete(ReadLocalVersionInformationBuilder::Create());

  // Verify that nothing was sent
  ASSERT_EQ(0, hal->GetNumSentCommands());

  num_packets = 1;
  hal->callbacks->hciEventReceived(GetPacketBytes(NoCommandCompleteBuilder::Create(num_packets)));
  // Verify that one was sent
  while (hal->GetNumSentCommands() == 0)
    ;
  ASSERT_EQ(1, hal->GetNumSentCommands());

  // Send the response event
  ErrorCode error_code = ErrorCode::SUCCESS;
  HciVersion hci_version = HciVersion::V_5_0;
  uint16_t hci_subversion = 0x1234;
  LmpVersion lmp_version = LmpVersion::V_4_2;
  uint16_t manufacturer_name = 0xBAD;
  uint16_t lmp_subversion = 0x5678;
  hal->callbacks->hciEventReceived(GetPacketBytes(ReadLocalVersionInformationCompleteBuilder::Create(
      num_packets, error_code, hci_version, hci_subversion, lmp_version, manufacturer_name, lmp_subversion)));
  auto event = upper->GetReceivedEvent();
  ASSERT(ReadLocalVersionInformationCompleteView::Create(CommandCompleteView::Create(EventPacketView::Create(event)))
             .IsValid());
}

TEST_F(HciTest, creditsTest) {
  ASSERT_EQ(0, hal->GetNumSentCommands());

  // Send all three commands
  upper->SendHciCommandExpectingComplete(ReadLocalVersionInformationBuilder::Create());
  upper->SendHciCommandExpectingComplete(ReadLocalSupportedCommandsBuilder::Create());
  upper->SendHciCommandExpectingComplete(ReadLocalSupportedFeaturesBuilder::Create());

  while (hal->GetNumSentCommands() == 0)
    ;

  // Verify that the first one is sent
  ASSERT_EQ(1, hal->GetNumSentCommands());

  auto sent_command = hal->GetSentCommand();
  auto version_view = ReadLocalVersionInformationView::Create(CommandPacketView::Create(sent_command));
  ASSERT_TRUE(version_view.IsValid());

  // Verify that only one was sent
  ASSERT_EQ(0, hal->GetNumSentCommands());

  // Send the response event
  uint8_t num_packets = 1;
  ErrorCode error_code = ErrorCode::SUCCESS;
  HciVersion hci_version = HciVersion::V_5_0;
  uint16_t hci_subversion = 0x1234;
  LmpVersion lmp_version = LmpVersion::V_4_2;
  uint16_t manufacturer_name = 0xBAD;
  uint16_t lmp_subversion = 0x5678;
  hal->callbacks->hciEventReceived(GetPacketBytes(ReadLocalVersionInformationCompleteBuilder::Create(
      num_packets, error_code, hci_version, hci_subversion, lmp_version, manufacturer_name, lmp_subversion)));
  auto event = upper->GetReceivedEvent();
  ASSERT(ReadLocalVersionInformationCompleteView::Create(CommandCompleteView::Create(EventPacketView::Create(event)))
             .IsValid());

  // Verify that the second one is sent
  while (hal->GetNumSentCommands() == 0)
    ;
  ASSERT_EQ(1, hal->GetNumSentCommands());

  sent_command = hal->GetSentCommand();
  auto supported_commands_view = ReadLocalSupportedCommandsView::Create(CommandPacketView::Create(sent_command));
  ASSERT_TRUE(supported_commands_view.IsValid());

  // Verify that only one was sent
  ASSERT_EQ(0, hal->GetNumSentCommands());

  // Send the response event
  std::vector<uint8_t> supported_commands;
  for (uint8_t i = 0; i < 64; i++) {
    supported_commands.push_back(i);
  }
  hal->callbacks->hciEventReceived(
      GetPacketBytes(ReadLocalSupportedCommandsCompleteBuilder::Create(num_packets, error_code, supported_commands)));
  event = upper->GetReceivedEvent();
  ASSERT(ReadLocalSupportedCommandsCompleteView::Create(CommandCompleteView::Create(EventPacketView::Create(event)))
             .IsValid());

  // Verify that the third one is sent
  while (hal->GetNumSentCommands() == 0)
    ;
  ASSERT_EQ(1, hal->GetNumSentCommands());

  sent_command = hal->GetSentCommand();
  auto supported_features_view = ReadLocalSupportedFeaturesView::Create(CommandPacketView::Create(sent_command));
  ASSERT_TRUE(supported_features_view.IsValid());

  // Verify that only one was sent
  ASSERT_EQ(0, hal->GetNumSentCommands());

  // Send the response event
  uint64_t lmp_features = 0x012345678abcdef;
  hal->callbacks->hciEventReceived(
      GetPacketBytes(ReadLocalSupportedFeaturesCompleteBuilder::Create(num_packets, error_code, lmp_features)));
  event = upper->GetReceivedEvent();
  ASSERT(ReadLocalSupportedFeaturesCompleteView::Create(CommandCompleteView::Create(EventPacketView::Create(event)))
             .IsValid());
}

TEST_F(HciTest, createConnectionTest) {
  // Send CreateConnection to the controller
  common::Address bd_addr;
@@ -223,8 +362,8 @@ TEST_F(HciTest, createConnectionTest) {
  uint16_t clock_offset = 0x3456;
  ClockOffsetValid clock_offset_valid = ClockOffsetValid::VALID;
  CreateConnectionRoleSwitch allow_role_switch = CreateConnectionRoleSwitch::ALLOW_ROLE_SWITCH;
  upper->SendHciCommand(CreateConnectionBuilder::Create(bd_addr, packet_type, page_scan_repetition_mode, clock_offset,
                                                        clock_offset_valid, allow_role_switch));
  upper->SendHciCommandExpectingStatus(CreateConnectionBuilder::Create(
      bd_addr, packet_type, page_scan_repetition_mode, clock_offset, clock_offset_valid, allow_role_switch));

  // Check the command
  auto sent_command = hal->GetSentCommand();
@@ -239,7 +378,7 @@ TEST_F(HciTest, createConnectionTest) {
  ASSERT_EQ(clock_offset_valid, view.GetClockOffsetValid());
  ASSERT_EQ(allow_role_switch, view.GetAllowRoleSwitch());

  // Send a ConnectionComplete to the host
  // Send a Command Status to the host
  ErrorCode status = ErrorCode::SUCCESS;
  uint16_t handle = 0x123;
  LinkType link_type = LinkType::ACL;
@@ -251,6 +390,7 @@ TEST_F(HciTest, createConnectionTest) {
  ASSERT_TRUE(event.IsValid());
  ASSERT_EQ(EventCode::COMMAND_STATUS, event.GetEventCode());

  // Send a ConnectionComplete to the host
  hal->callbacks->hciEventReceived(
      GetPacketBytes(ConnectionCompleteBuilder::Create(status, handle, bd_addr, link_type, encryption_enabled)));

+61 −1
Original line number Diff line number Diff line
@@ -473,6 +473,10 @@ packet CommandStatus : EventPacket (event_code = COMMAND_STATUS){
  _payload_,
}

  // Credits
packet NoCommandComplete : CommandComplete (command_op_code = NONE){
}

  // LINK_CONTROL
packet Inquiry : DiscoveryCommand (op_code = INQUIRY) {
  lap0 : 8, // 0x00 - 0x3F
@@ -1434,12 +1438,59 @@ packet WriteSecureConnectionsHostSupportComplete : CommandComplete (command_op_c
packet ReadLocalVersionInformation : CommandPacket (op_code = READ_LOCAL_VERSION_INFORMATION) {
}

enum HciVersion : 8 {
  V_1_0b = 0x00,
  V_1_1 = 0x01,
  V_1_2 = 0x02,
  V_2_0 = 0x03, //  + EDR
  V_2_1 = 0x04, //  + EDR
  V_3_0 = 0x05, //  + HS
  V_4_0 = 0x06,
  V_4_1 = 0x07,
  V_4_2 = 0x08,
  V_5_0 = 0x09,
  V_5_1 = 0x0a,
}

enum LmpVersion : 8 {
  V_1_0b = 0x00, // withdrawn
  V_1_1 = 0x01, // withdrawn
  V_1_2 = 0x02, // withdrawn
  V_2_0 = 0x03, //  + EDR
  V_2_1 = 0x04, //  + EDR
  V_3_0 = 0x05, //  + HS
  V_4_0 = 0x06,
  V_4_1 = 0x07,
  V_4_2 = 0x08,
  V_5_0 = 0x09,
  V_5_1 = 0x0a,
}

packet ReadLocalVersionInformationComplete : CommandComplete (command_op_code = READ_LOCAL_VERSION_INFORMATION) {
  status : ErrorCode,
  hci_version : HciVersion,
  hci_revision : 16,
  lmp_version : LmpVersion,
  manufacturer_name : 16,
  lmp_subversion : 16,
}

packet ReadLocalSupportedCommands : CommandPacket (op_code = READ_LOCAL_SUPPORTED_COMMANDS) {
}

packet ReadLocalSupportedCommandsComplete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_COMMANDS) {
  status : ErrorCode,
  supported_commands : 8[64],
}

packet ReadLocalSupportedFeatures : CommandPacket (op_code = READ_LOCAL_SUPPORTED_FEATURES) {
}

packet ReadLocalSupportedFeaturesComplete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_FEATURES) {
  status : ErrorCode,
  lmp_features : 64,
}

packet ReadLocalExtendedFeatures : CommandPacket (op_code = READ_LOCAL_EXTENDED_FEATURES) {
  page_number : 8,
}
@@ -1447,6 +1498,14 @@ packet ReadLocalExtendedFeatures : CommandPacket (op_code = READ_LOCAL_EXTENDED_
packet ReadBufferSize : CommandPacket (op_code = READ_BUFFER_SIZE) {
}

packet ReadBufferSizeComplete : CommandComplete (command_op_code = READ_BUFFER_SIZE) {
  status : ErrorCode,
  acl_data_packet_length : 16,
  synchronous_data_packet_length : 8,
  total_num_acl_data_packets : 16,
  total_num_synchronous_data_packets : 16,
}

packet ReadBdAddr : CommandPacket (op_code = READ_BD_ADDR) {
}

@@ -2200,7 +2259,8 @@ packet RoleChange : EventPacket (event_code = ROLE_CHANGE){
}

packet NumberOfCompletedPackets : EventPacket (event_code = NUMBER_OF_COMPLETED_PACKETS){
  _payload_,
  _count_(handles_and_completed_packets) : 8,
  handles_and_completed_packets : 32[], // connection_handle[i] : 12, _reserved_[i] : 4, hc_num_of_completed_packets[i] : 16
}

enum Mode : 8 {