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

Commit 80260f99 authored by Grzegorz Kołodziejczyk's avatar Grzegorz Kołodziejczyk
Browse files

le_audio: Protect from unreliable autonomous disable operation

Both ASEs that are bonded with bi-directional ASEs should go to QoS
state once CIS is terminated. This change will guard for such
transition.

"If the server detects link loss of a CIS for an ASE in the Streaming
state or the Disabling state, the server shall immediately transition
that ASE to the QoS Configured state." ASCS 1.0 3.2
ASE state machine transitions.

Bug: 278621595
Tag: #feature
Test: atest bluetooth_le_audio_test
Change-Id: Ic6e91d63b9690771bd7da09f1a108a181369b861
parent 5fe550e4
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -518,6 +518,12 @@ class LeAudioClientImpl : public LeAudioClient {
    } while (leAudioDevice);
  }

  void OnDeviceAutonomousStateTransitionTimeout(LeAudioDevice* leAudioDevice) {
    LOG_ERROR("Device %s, failed to complete autonomous transition",
              ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
    DisconnectDevice(leAudioDevice, true);
  }

  void UpdateLocationsAndContextsAvailability(LeAudioDeviceGroup* group) {
    bool group_conf_changed = group->ReloadAudioLocations();
    group_conf_changed |= group->ReloadAudioDirections();
@@ -5530,6 +5536,12 @@ class CallbacksImpl : public LeAudioGroupStateMachine::Callbacks {
    if (instance) instance->OnLeAudioDeviceSetStateTimeout(group_id);
  }

  void OnDeviceAutonomousStateTransitionTimeout(
      LeAudioDevice* leAudioDevice) override {
    if (instance)
      instance->OnDeviceAutonomousStateTransitionTimeout(leAudioDevice);
  }

  void OnUpdatedCisConfiguration(int group_id, uint8_t direction) {
    if (instance) instance->OnUpdatedCisConfiguration(group_id, direction);
  }
+8 −0
Original line number Diff line number Diff line
@@ -335,6 +335,9 @@ void LeAudioDevice::ClearPACs(void) {

LeAudioDevice::~LeAudioDevice(void) {
  alarm_free(link_quality_timer);
  for (auto& ase : ases_) {
    alarm_free(ase.autonomous_operation_timer_);
  }
  this->ClearPACs();
}

@@ -946,6 +949,11 @@ void LeAudioDevice::DeactivateAllAses(void) {
          ase.cis_conn_hdl, bluetooth::common::ToString(ase.cis_state).c_str(),
          bluetooth::common::ToString(ase.data_path_state).c_str());
    }
    if (alarm_is_scheduled(ase.autonomous_operation_timer_)) {
      alarm_free(ase.autonomous_operation_timer_);
      ase.autonomous_operation_timer_ = NULL;
      ase.autonomous_target_state_ = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
    }
    ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
    ase.cis_state = CisState::IDLE;
    ase.data_path_state = DataPathState::IDLE;
+7 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@

#include "bta_le_audio_uuids.h"
#include "btm_iso_api_types.h"
#include "osi/include/alarm.h"

namespace le_audio {

@@ -722,6 +723,8 @@ struct ase {
        pres_delay_max(0),
        preferred_pres_delay_min(0),
        preferred_pres_delay_max(0),
        autonomous_operation_timer_(nullptr),
        autonomous_target_state_(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE),
        state(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {}

  struct hdl_pair hdls;
@@ -761,6 +764,10 @@ struct ase {

  std::vector<uint8_t> metadata;

  /* Autonomous change data */
  alarm_t* autonomous_operation_timer_;
  types::AseState autonomous_target_state_;

  AseState state;
};

+58 −2
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ using le_audio::types::LeAudioContextType;
namespace {

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

static void link_quality_cb(void* data) {
  // very ugly, but we need to pass just two bytes
@@ -2075,6 +2076,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        SetAseState(leAudioDevice, ase,
                    AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);

        /* Remote may autonomously bring ASEs to QoS configured state */
        if (group->GetTargetState() !=
            AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
          ProcessAutonomousDisable(leAudioDevice, ase);
        }

        /* Process the Disable Transition of the rest of group members if no
         * more ASE notifications has to come from this device. */
        if (leAudioDevice->IsReadyToSuspendStream()) ProcessGroupDisable(group);
@@ -2670,6 +2677,22 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
    }
  }

  void ScheduleAutonomousOperationTimer(AseState target_state,
                                        LeAudioDevice* leAudioDevice,
                                        struct ase* ase) {
    ase->autonomous_target_state_ = target_state;
    ase->autonomous_operation_timer_ =
        alarm_new("LeAudioAutonomousOperationTimeout");
    alarm_set_on_mloop(
        ase->autonomous_operation_timer_, kAutonomousTransitionTimeoutMs,
        [](void* data) {
          LeAudioDevice* leAudioDevice = static_cast<LeAudioDevice*>(data);
          instance->state_machine_callbacks_
              ->OnDeviceAutonomousStateTransitionTimeout(leAudioDevice);
        },
        leAudioDevice);
  }

  void AseStateMachineProcessDisabling(
      struct le_audio::client_parser::ascs::ase_rsp_hdr& arh, struct ase* ase,
      LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice) {
@@ -2696,10 +2719,15 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
        SetAseState(leAudioDevice, ase,
                    AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING);

        /* Remote may autonomously bring ASEs to QoS configured state */
        if (group->GetTargetState() !=
            AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
          ProcessAutonomousDisable(leAudioDevice, ase);
        }

        /* Process the Disable Transition of the rest of group members if no
         * more ASE notifications has to come from this device. */
        if (leAudioDevice->IsReadyToSuspendStream())
          ProcessGroupDisable(group);
        if (leAudioDevice->IsReadyToSuspendStream()) ProcessGroupDisable(group);

        break;

@@ -2895,6 +2923,34 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
      StopStream(group);
    }
  }

  void ProcessAutonomousDisable(LeAudioDevice* leAudioDevice, struct ase* ase) {
    auto bidirection_ase = leAudioDevice->GetAseToMatchBidirectionCis(ase);

    /* ASE is not a part of bi-directional CIS */
    if (!bidirection_ase) return;

    /* ASE is already disabled */
    if (bidirection_ase->state ==
        AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
      /* Bi-direction ASEs are now disabled */
      if ((ase->autonomous_target_state_ ==
           AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) &&
          alarm_is_scheduled(ase->autonomous_operation_timer_)) {
        alarm_free(ase->autonomous_operation_timer_);
        ase->autonomous_operation_timer_ = NULL;
        ase->autonomous_target_state_ = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
      }
      return;
    }

    /* Schedule alarm if first ASE is autonomously disabling */
    if (!alarm_is_scheduled(bidirection_ase->autonomous_operation_timer_)) {
      ScheduleAutonomousOperationTimer(
          AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED, leAudioDevice,
          bidirection_ase);
    }
  }
};
}  // namespace

+2 −0
Original line number Diff line number Diff line
@@ -39,6 +39,8 @@ class LeAudioGroupStateMachine {
        int group_id, bluetooth::le_audio::GroupStreamStatus status) = 0;
    virtual void OnStateTransitionTimeout(int group_id) = 0;
    virtual void OnUpdatedCisConfiguration(int group_id, uint8_t direction) = 0;
    virtual void OnDeviceAutonomousStateTransitionTimeout(
        LeAudioDevice* leAudioDevice) = 0;
  };

  virtual ~LeAudioGroupStateMachine() = default;
Loading