Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 75 additions & 81 deletions ASFWDriver/Audio/Core/AudioCoordinator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ void AudioCoordinator::OnDeviceResumed(std::shared_ptr<Discovery::FWDevice> devi
bool recoverActiveStream = false;
if (lock_) {
IOLockLock(lock_);
recoverActiveStream = (activeGuid_ == guid);
recoverActiveStream = streamReservation_.Guid() == guid &&
!streamReservation_.BlocksNewWork(guid);
IOLockUnlock(lock_);
}

Expand All @@ -109,7 +110,8 @@ void AudioCoordinator::OnDeviceSuspended(std::shared_ptr<Discovery::FWDevice> de
bool suspendedActiveStream = false;
if (lock_) {
IOLockLock(lock_);
suspendedActiveStream = (activeGuid_ == guid);
suspendedActiveStream = streamReservation_.Guid() == guid &&
!streamReservation_.BlocksNewWork(guid);
IOLockUnlock(lock_);
}

Expand All @@ -132,9 +134,9 @@ void AudioCoordinator::OnDeviceRemoved(Discovery::Guid64 guid) {
if (lock_) {
IOLockLock(lock_);
firstRemoval = remoteLostGuids_.insert(guid).second;
wasActive = (activeGuid_ == guid);
wasActive = (streamReservation_.Guid() == guid);
if (wasActive) {
activeGuid_ = 0;
streamReservation_.Clear(guid);
}
IOLockUnlock(lock_);
}
Expand Down Expand Up @@ -194,7 +196,8 @@ void AudioCoordinator::HandleCycleInconsistent() noexcept {
uint64_t guid = 0;
if (lock_) {
IOLockLock(lock_);
guid = activeGuid_;
guid = streamReservation_.Guid();
if (streamReservation_.BlocksNewWork(guid)) guid = 0;
IOLockUnlock(lock_);
}

Expand Down Expand Up @@ -238,61 +241,48 @@ IAudioBackend* AudioCoordinator::BackendForGuid(uint64_t guid) noexcept {

IOReturn AudioCoordinator::StartStreaming(uint64_t guid) noexcept {
if (guid == 0) return kIOReturnBadArgument;

bool setActive = false;
if (lock_) {
IOLockLock(lock_);
if (remoteLostGuids_.contains(guid)) {
IOLockUnlock(lock_);
return kIOReturnNoDevice;
}
if (activeGuid_ == 0) {
activeGuid_ = guid;
setActive = true;
} else if (activeGuid_ == guid) {
IOLockUnlock(lock_);
// Idempotent start: avoid reconfiguring already-running IR/IT contexts.
return kIOReturnSuccess;
} else {
const uint64_t active = activeGuid_;
IOLockUnlock(lock_);

ASFW_LOG_WARNING(Audio,
"AudioCoordinator: StartStreaming busy requested=0x%016llx active=0x%016llx",
guid,
active);
// TODO(ASFW-MULTIDEVICE): Multi-device streaming is not implemented.
// This is the explicit v1 multi-device boundary: multiple GUIDs may
// publish nubs/runtimes, but only one GUID may own isoch transport.
// Simultaneous streaming starts here and requires per-GUID IR/IT
// contexts, timing bridge, IRM/channel allocation, and backend sessions.
return kIOReturnBusy;
}
if (teardownRequested_.load(std::memory_order_acquire)) return kIOReturnAborted;
if (!lock_) return kIOReturnNoResources;
IOLockLock(lock_);
if (remoteLostGuids_.contains(guid)) {
IOLockUnlock(lock_);
return kIOReturnNoDevice;
}
const auto decision = streamReservation_.BeginStart(guid);
const uint64_t reservedGuid = streamReservation_.Guid();
IOLockUnlock(lock_);
if (decision.admission == AudioStreamReservation::Admission::AlreadyRunning) {
return kIOReturnSuccess;
}
if (decision.admission != AudioStreamReservation::Admission::Begin) {
const bool cleanupFailed = decision.admission == AudioStreamReservation::Admission::CleanupFailed;
ASFW_LOG_WARNING(Audio,
"AudioCoordinator: StartStreaming refused requested=0x%016llx reserved=0x%016llx cleanupFailed=%u",
guid, reservedGuid, cleanupFailed ? 1U : 0U);
return cleanupFailed ? kIOReturnNotReady : kIOReturnBusy;
}

auto* backend = BackendForGuid(guid);
if (!backend) {
if (setActive && lock_) {
IOLockLock(lock_);
if (activeGuid_ == guid) activeGuid_ = 0;
IOLockUnlock(lock_);
}
IOLockLock(lock_);
streamReservation_.CompleteStart(decision.token, false);
IOLockUnlock(lock_);
return kIOReturnNotReady;
}

const IOReturn kr = backend->StartStreaming(guid);
IOLockLock(lock_);
const bool completionAccepted = streamReservation_.CompleteStart(decision.token, kr == kIOReturnSuccess);
IOLockUnlock(lock_);
// Removal, teardown, or a superseding stop invalidated this operation.
// Never report a late backend success as a newly running stream.
if (!completionAccepted) return kIOReturnAborted;
if (kr != kIOReturnSuccess) {
ASFW_LOG_ERROR(Audio,
"AudioCoordinator: StartStreaming failed backend=%{public}s GUID=0x%016llx kr=0x%x",
backend->Name(),
guid,
kr);
if (setActive && lock_) {
IOLockLock(lock_);
if (activeGuid_ == guid) activeGuid_ = 0;
IOLockUnlock(lock_);
}
return kr;
}

Expand All @@ -305,29 +295,37 @@ IOReturn AudioCoordinator::StartStreaming(uint64_t guid) noexcept {

IOReturn AudioCoordinator::StopStreaming(uint64_t guid) noexcept {
if (guid == 0) return kIOReturnBadArgument;

if (lock_) {
IOLockLock(lock_);
if (remoteLostGuids_.contains(guid)) {
IOLockUnlock(lock_);
return kIOReturnSuccess;
}
if (activeGuid_ != 0 && activeGuid_ != guid) {
const uint64_t active = activeGuid_;
IOLockUnlock(lock_);
ASFW_LOG_WARNING(Audio,
"AudioCoordinator: StopStreaming busy requested=0x%016llx active=0x%016llx",
guid,
active);
return kIOReturnBusy;
}
if (teardownRequested_.load(std::memory_order_acquire)) return kIOReturnAborted;
if (!lock_) return kIOReturnNoResources;
IOLockLock(lock_);
if (remoteLostGuids_.contains(guid)) {
IOLockUnlock(lock_);
return kIOReturnSuccess;
}
const auto decision = streamReservation_.BeginStop(guid);
const uint64_t reservedGuid = streamReservation_.Guid();
IOLockUnlock(lock_);
if (decision.admission != AudioStreamReservation::Admission::Begin) {
const bool cleanupFailed = decision.admission == AudioStreamReservation::Admission::CleanupFailed;
ASFW_LOG_WARNING(Audio,
"AudioCoordinator: StopStreaming refused requested=0x%016llx reserved=0x%016llx cleanupFailed=%u",
guid, reservedGuid, cleanupFailed ? 1U : 0U);
return cleanupFailed ? kIOReturnNotReady : kIOReturnBusy;
}

auto* backend = BackendForGuid(guid);
if (!backend) return kIOReturnNotReady;
if (!backend) {
IOLockLock(lock_);
streamReservation_.CompleteStop(decision.token, false);
IOLockUnlock(lock_);
return kIOReturnNotReady;
}

const IOReturn kr = backend->StopStreaming(guid);
IOLockLock(lock_);
const bool completionAccepted = streamReservation_.CompleteStop(decision.token, kr == kIOReturnSuccess);
IOLockUnlock(lock_);
if (!completionAccepted) return kIOReturnAborted;
if (kr != kIOReturnSuccess) {
ASFW_LOG_ERROR(Audio,
"AudioCoordinator: StopStreaming failed backend=%{public}s GUID=0x%016llx kr=0x%x",
Expand All @@ -337,12 +335,6 @@ IOReturn AudioCoordinator::StopStreaming(uint64_t guid) noexcept {
return kr;
}

if (lock_) {
IOLockLock(lock_);
if (activeGuid_ == guid) activeGuid_ = 0;
IOLockUnlock(lock_);
}

ASFW_LOG(Audio,
"AudioCoordinator: StopStreaming ok backend=%{public}s GUID=0x%016llx",
backend->Name(),
Expand All @@ -357,20 +349,21 @@ IOReturn AudioCoordinator::RequestClockConfig(
if (guid == 0) {
return kIOReturnBadArgument;
}
if (teardownRequested_.load(std::memory_order_acquire)) return kIOReturnAborted;
if (!lock_) return kIOReturnNoResources;

if (lock_) {
IOLockLock(lock_);
if (activeGuid_ != 0 && activeGuid_ != guid) {
const uint64_t active = activeGuid_;
IOLockUnlock(lock_);
ASFW_LOG_WARNING(Audio,
"AudioCoordinator: RequestClockConfig busy requested=0x%016llx active=0x%016llx",
guid,
active);
return kIOReturnBusy;
}
IOLockLock(lock_);
if ((streamReservation_.Guid() != 0 && streamReservation_.Guid() != guid) ||
streamReservation_.BlocksNewWork(guid)) {
const uint64_t active = streamReservation_.Guid();
IOLockUnlock(lock_);
ASFW_LOG_WARNING(Audio,
"AudioCoordinator: RequestClockConfig busy requested=0x%016llx active=0x%016llx",
guid,
active);
return kIOReturnBusy;
}
IOLockUnlock(lock_);

const auto record = registry_.SnapshotByGuid(guid);
if (!record.has_value()) {
Expand Down Expand Up @@ -427,7 +420,7 @@ void AudioCoordinator::BeginTeardown() noexcept {

if (lock_) {
IOLockLock(lock_);
activeGuid_ = 0;
streamReservation_.ClearAll();
IOLockUnlock(lock_);
}
}
Expand All @@ -448,8 +441,9 @@ void AudioCoordinator::HandleHostTimingLoss(uint64_t guid) noexcept {
if (lock_) {
IOLockLock(lock_);
const bool remoteLost = remoteLostGuids_.contains(guid);
const bool cleanupBlocked = streamReservation_.BlocksNewWork(guid);
IOLockUnlock(lock_);
if (remoteLost) {
if (remoteLost || cleanupBlocked) {
return;
}
}
Expand Down
3 changes: 2 additions & 1 deletion ASFWDriver/Audio/Core/AudioCoordinator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "IAVCAudioConfigListener.hpp"
#include "AudioNubPublisher.hpp"
#include "AudioStreamReservation.hpp"
#include "../Protocols/Backends/AVCAudioBackend.hpp"
#include "../Protocols/Backends/DiceAudioBackend.hpp"
#include "../Protocols/Backends/IsochDuplexHostTransport.hpp"
Expand Down Expand Up @@ -89,7 +90,7 @@ class AudioCoordinator final : public Discovery::IDeviceObserver,
AVCAudioBackend avc_;

IOLock* lock_{nullptr};
uint64_t activeGuid_{0};
AudioStreamReservation streamReservation_;
// A CoreAudio StopIO can arrive after discovery has retired the GUID. Keep
// that callback from re-entering a backend that now has no remote device.
std::unordered_set<uint64_t> remoteLostGuids_{};
Expand Down
89 changes: 89 additions & 0 deletions ASFWDriver/Audio/Core/AudioStreamReservation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <cstdint>

namespace ASFW::Audio {

// Controller-global host-stream reservation. The caller serializes every
// access with its control-plane lock; backend work runs outside that lock.
// Failed cleanup keeps ownership but must never be mistaken for running audio.
class AudioStreamReservation final {
public:
enum class State { Idle, Starting, Running, Stopping, CleanupFailed };
enum class Admission { Begin, AlreadyRunning, Busy, CleanupFailed };

struct Token {
uint64_t guid{0};
uint64_t epoch{0};
};
struct Decision {
Admission admission{Admission::Busy};
Token token{};
};

[[nodiscard]] Decision BeginStart(uint64_t guid) noexcept {
if (guid == 0) return {};
if (guid_ == 0) return Begin(guid, State::Starting);
if (guid_ != guid) return {};
if (state_ == State::Running) return {Admission::AlreadyRunning, {}};
if (state_ == State::CleanupFailed) return {Admission::CleanupFailed, {}};
return {};
}

[[nodiscard]] Decision BeginStop(uint64_t guid) noexcept {
if (guid == 0 || (guid_ != 0 && guid_ != guid)) return {};
if (state_ == State::CleanupFailed) return {Admission::CleanupFailed, {}};
if (state_ == State::Stopping) return {};
// A stop may supersede an in-flight start. Its new epoch prevents a
// late start completion from publishing Running over the stop state.
return Begin(guid, State::Stopping);
}

bool CompleteStart(Token token, bool success) noexcept {
if (!Matches(token, State::Starting)) return false;
if (success) state_ = State::Running;
else Clear(token.guid);
return true;
}

bool CompleteStop(Token token, bool success) noexcept {
if (!Matches(token, State::Stopping)) return false;
if (success) Clear(token.guid);
else state_ = State::CleanupFailed;
return true;
}

// Only confirmed removal or service teardown clears failed cleanup. A bus
// reset/resume alone does not prove outstanding device operations finished.
void Clear(uint64_t guid) noexcept {
if (guid_ != guid) return;
guid_ = 0;
state_ = State::Idle;
++epoch_;
}
void ClearAll() noexcept { Clear(guid_); }

[[nodiscard]] uint64_t Guid() const noexcept { return guid_; }
[[nodiscard]] State GetState() const noexcept { return state_; }
[[nodiscard]] bool BlocksNewWork(uint64_t guid) const noexcept {
return guid_ == guid &&
(state_ == State::Stopping || state_ == State::CleanupFailed);
}

private:
[[nodiscard]] Decision Begin(uint64_t guid, State state) noexcept {
guid_ = guid;
state_ = state;
return {Admission::Begin, {guid_, ++epoch_}};
}
[[nodiscard]] bool Matches(Token token, State state) const noexcept {
return token.guid != 0 && token.guid == guid_ && token.epoch == epoch_ && state_ == state;
}

uint64_t guid_{0};
uint64_t epoch_{0};
State state_{State::Idle};
};

} // namespace ASFW::Audio
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "Isoch/Profiles/FocusriteSaffireProfile.hpp"
#include "Isoch/Profiles/GenericDiceProfile.hpp"
#include "Isoch/Profiles/MidasVeniceProfile.hpp"
#include "Isoch/Profiles/PreSonusFireStudioProjectProfile.hpp"
#include "Isoch/Profiles/PreSonusStudioLiveProfile.hpp"
#include "Isoch/Profiles/WeissIntProfile.hpp"

Expand All @@ -18,6 +19,7 @@ namespace {
Profiles::GenericDiceProfile gGenericProfile{};
Profiles::FocusriteSaffireProfile gFocusriteProfile{};
Profiles::MidasVeniceProfile gMidasVeniceProfile{};
Profiles::PreSonusFireStudioProjectProfile gFireStudioProjectProfile{};
Profiles::PreSonusStudioLiveProfile gPreSonusStudioLiveProfile{};
Profiles::AlesisMultiMixProfile gAlesisMultiMixProfile{};
Profiles::WeissIntProfile gWeissIntProfile{};
Expand All @@ -27,6 +29,7 @@ DiceProfileRegistry::DiceProfileRegistry() noexcept {
(void)RegisterProfile(&gFocusriteProfile);
(void)RegisterProfile(&gMidasVeniceProfile);
(void)RegisterProfile(&gPreSonusStudioLiveProfile);
(void)RegisterProfile(&gFireStudioProjectProfile);
(void)RegisterProfile(&gAlesisMultiMixProfile);
(void)RegisterProfile(&gWeissIntProfile);
}
Expand Down
Loading