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

Commit 16dd9b05 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Gerrit Code Review
Browse files

Merge "Retry CIS on Failed to be established" into main

parents e5fef7aa dd9c038d
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -660,8 +660,9 @@ bool LeAudioDevice::HaveAllActiveAsesCisEst(void) const {
bool LeAudioDevice::HaveAnyCisConnected(void) {
  /* Pending and Disconnecting is considered as connected in this function */
  for (auto const ase : ases_) {
    if (ase.cis_state != CisState::ASSIGNED &&
        ase.cis_state != CisState::IDLE) {
    if (ase.cis_state == CisState::CONNECTED ||
        ase.cis_state == CisState::CONNECTING ||
        ase.cis_state == CisState::DISCONNECTING) {
      return true;
    }
  }
+2 −0
Original line number Diff line number Diff line
@@ -100,6 +100,7 @@ class LeAudioDevice {
  bool encrypted_;
  int group_id_;
  bool csis_member_;
  int cis_failed_to_be_established_retry_cnt_;
  std::bitset<16> tmap_role_;

  uint8_t audio_directions_;
@@ -135,6 +136,7 @@ class LeAudioDevice {
        encrypted_(false),
        group_id_(group_id),
        csis_member_(false),
        cis_failed_to_be_established_retry_cnt_(0),
        audio_directions_(0),
        model_name_(""),
        allowlist_flag_(false),
+33 −10
Original line number Diff line number Diff line
@@ -114,6 +114,7 @@ namespace {

constexpr int linkQualityCheckInterval = 4000;
constexpr int kAutonomousTransitionTimeoutMs = 5000;
constexpr int kNumberOfCisRetries = 2;

static void link_quality_cb(void* data) {
  // very ugly, but we need to pass just two bytes
@@ -819,10 +820,29 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        kLogCisEstablishedOp + "cis_h:" + loghex(event->cis_conn_hdl) +
            " STATUS=" + loghex(event->status));

    if (event->status) {
    if (event->status != HCI_SUCCESS) {
      if (ases_pair.sink) ases_pair.sink->cis_state = CisState::ASSIGNED;
      if (ases_pair.source) ases_pair.source->cis_state = CisState::ASSIGNED;

      LOG_WARN("%s: failed to create CIS 0x%04x, status: %s (0x%02x)",
               ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_),
               event->cis_conn_hdl,
               ErrorCodeText((ErrorCode)event->status).c_str(), event->status);

      if (event->status == HCI_ERR_CONN_FAILED_ESTABLISHMENT &&
          ((leAudioDevice->cis_failed_to_be_established_retry_cnt_++) <
           kNumberOfCisRetries) &&
          (CisCreateForDevice(group, leAudioDevice))) {
        LOG_INFO("Retrying (%d) to create CIS for %s ",
                 leAudioDevice->cis_failed_to_be_established_retry_cnt_,
                 ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
        return;
      }

      LOG_ERROR("CIS creation failed %d times, stopping the stream",
                leAudioDevice->cis_failed_to_be_established_retry_cnt_);
      leAudioDevice->cis_failed_to_be_established_retry_cnt_ = 0;

      /* CIS establishment failed. Remove CIG if no other CIS is already created
       * or pending. If CIS is established, this will be handled in disconnected
       * complete event
@@ -831,18 +851,18 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        RemoveCigForGroup(group);
      }

      LOG(ERROR) << __func__ << ", failed to create CIS, status: "
                 << ErrorCodeText((ErrorCode)event->status) << "("
                 << loghex(event->status) << ")";

      StopStream(group);
      return;
    }

    if (leAudioDevice->cis_failed_to_be_established_retry_cnt_ > 0) {
      /* Reset retry counter */
      leAudioDevice->cis_failed_to_be_established_retry_cnt_ = 0;
    }

    if (group->GetTargetState() != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
      LOG(ERROR) << __func__
                 << ", Unintended CIS establishement event came for group id:"
                 << group->group_id_;
      LOG_ERROR("Unintended CIS establishement event came for group id: %d",
                group->group_id_);
      StopStream(group);
      return;
    }
@@ -1396,8 +1416,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      /* First in ase pair is Sink, second Source */
      auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(ase->cis_conn_hdl);

      /* Already in pending state - bi-directional CIS */
      if (ase->cis_state == CisState::CONNECTING) continue;
      /* Already in pending state - bi-directional CIS or seconde CIS to same
       * device */
      if (ase->cis_state == CisState::CONNECTING ||
          ase->cis_state == CisState::CONNECTED)
        continue;

      if (ases_pair.sink) ases_pair.sink->cis_state = CisState::CONNECTING;
      if (ases_pair.source) ases_pair.source->cis_state = CisState::CONNECTING;
+131 −3
Original line number Diff line number Diff line
@@ -205,6 +205,8 @@ class StateMachineTestBase : public Test {

  /* Use to simulated error status on Cis creation */
  bool overwrite_cis_status_;
  bool use_cis_retry_cnt_;
  int retry_cis_established_cnt_;
  bool do_not_send_cis_establish_event_;
  uint8_t overwrite_cis_status_idx_;
  std::vector<uint8_t> cis_status_;
@@ -224,6 +226,8 @@ class StateMachineTestBase : public Test {
    gatt::SetMockBtaGattQueue(&gatt_queue);

    overwrite_cis_status_idx_ = 0;
    use_cis_retry_cnt_ = false;
    retry_cis_established_cnt_ = 0;
    overwrite_cis_status_ = false;
    do_not_send_cis_establish_event_ = false;
    stay_in_releasing_state_ = false;
@@ -455,8 +459,19 @@ class StateMachineTestBase : public Test {
                bluetooth::hci::iso_manager::cis_establish_cmpl_evt evt;

                // Fill proper values if needed
                if (overwrite_cis_status_) {
                if (use_cis_retry_cnt_) {
                  if (retry_cis_established_cnt_ > 0) {
                    evt.status = HCI_ERR_CONN_FAILED_ESTABLISHMENT;
                    retry_cis_established_cnt_--;
                  } else {
                    evt.status = 0;
                  }
                } else if (overwrite_cis_status_) {
                  evt.status = cis_status_[overwrite_cis_status_idx_++];
                  /* Reset the index */
                  if (cis_status_.size() == overwrite_cis_status_idx_) {
                    overwrite_cis_status_idx_ = 0;
                  }
                } else {
                  evt.status = 0;
                }
@@ -1796,6 +1811,119 @@ TEST_F(StateMachineTest, testStreamSingle) {
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

TEST_F(StateMachineTest, testStreamSingleRetryCisFailure) {
  /* Device is banded headphones with 1x snk + 0x src ase
   * (1xunidirectional CIS) with channel count 2 (for stereo
   */
  const auto context_type = kContextTypeRingtone;
  const int leaudio_group_id = 4;
  channel_count_ = kLeAudioCodecChannelCountSingleChannel |
                   kLeAudioCodecChannelCountTwoChannel;

  // Prepare fake connected device group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);

  /* Ringtone with channel count 1 for single device and 1 ASE sink will
   * end up with 1 Sink ASE being configured.
   */
  PrepareConfigureCodecHandler(group, 1);
  PrepareConfigureQosHandler(group, 1);
  PrepareEnableHandler(group, 1);
  PrepareReleaseHandler(group);

  use_cis_retry_cnt_ = true;
  retry_cis_established_cnt_ = 4;

  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
                                  GATT_WRITE_NO_RSP, _, _))
      .Times(4);

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(3);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);

  InjectInitialIdleNotification(group);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::RELEASING));
  EXPECT_CALL(mock_callbacks_,
              StatusReportCb(leaudio_group_id,
                             bluetooth::le_audio::GroupStreamStatus::IDLE));

  // Start the configuration and stream Media content
  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
      group, context_type,
      {.sink = types::AudioContexts(context_type),
       .source = types::AudioContexts(context_type)}));

  // Check if group has transitioned to a proper state
  ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
  ASSERT_EQ(2, get_func_call_count("alarm_cancel"));
}

TEST_F(StateMachineTest, testStreamSingleRetryCisSuccess) {
  /* Device is banded headphones with 1x snk + 0x src ase
   * (1xunidirectional CIS) with channel count 2 (for stereo
   */
  const auto context_type = kContextTypeRingtone;
  const int leaudio_group_id = 4;
  channel_count_ = kLeAudioCodecChannelCountSingleChannel |
                   kLeAudioCodecChannelCountTwoChannel;

  // Prepare fake connected device group
  auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);

  /* Ringtone with channel count 1 for single device and 1 ASE sink will
   * end up with 1 Sink ASE being configured.
   */
  PrepareConfigureCodecHandler(group, 1);
  PrepareConfigureQosHandler(group, 1);
  PrepareEnableHandler(group, 1);

  use_cis_retry_cnt_ = true;
  retry_cis_established_cnt_ = 2;

  auto* leAudioDevice = group->GetFirstDevice();
  EXPECT_CALL(gatt_queue,
              WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
                                  GATT_WRITE_NO_RSP, _, _))
      .Times(3);

  EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(3);
  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
  EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
  EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);

  InjectInitialIdleNotification(group);

  // Validate GroupStreamStatus
  EXPECT_CALL(
      mock_callbacks_,
      StatusReportCb(leaudio_group_id,
                     bluetooth::le_audio::GroupStreamStatus::STREAMING));

  // Start the configuration and stream Media content
  ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
      group, context_type,
      {.sink = types::AudioContexts(context_type),
       .source = types::AudioContexts(context_type)}));

  // Check if group has transitioned to a proper state
  ASSERT_EQ(group->GetState(),
            types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
  ASSERT_EQ(1, get_func_call_count("alarm_cancel"));
}

TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
  /* Device is banded headphones with 2x snk + none src ase
   * (2x unidirectional CIS)
@@ -2283,7 +2411,7 @@ TEST_F(StateMachineTest, testAttachToStreamWhileFirstDeviceIsStartingStream) {
}

TEST_F(StateMachineTest, testFailedStreamCreation) {
  /* Testing here CIS Failed to be established */
  /* Testing here different error than CIS Failed to be established */
  const auto context_type = kContextTypeConversational;
  const auto leaudio_group_id = 4;
  const auto num_devices = 2;
@@ -2343,7 +2471,7 @@ TEST_F(StateMachineTest, testFailedStreamCreation) {
       .source = types::AudioContexts(context_type)}));

  bluetooth::hci::iso_manager::cis_establish_cmpl_evt evt;
  evt.status = 0x3e;
  evt.status = HCI_ERR_LMP_RESPONSE_TIMEOUT;

  LeAudioGroupStateMachine::Get()->ProcessHciNotifCisEstablished(
      group, leAudioDevice, &evt);