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

Commit dd9c038d authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

Retry CIS on Failed to be established

State machine will retrie 2 times when CIS creation will fail due to
0x3e error

Bug: 312909605
Test: atest bluetooth_le_audio_test
Tag: #feature

Change-Id: Ibf1284ba0f8abd6184327681ec3f853f2249e6f9
parent cc87b9a9
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);