Merge changes Ic8eaee53,Ide961d91,Ie97f3ce9 am: 99d4458c3b am: d6ccd8631f
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2374735
Change-Id: I345f22a9f5cd8df41f200e8c0da3d4c3c228b65f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
index e25b15a..501dc01 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
@@ -30,6 +30,7 @@
STANDBY -> PAUSED [label="burst"]; // producer -> active
IDLE -> STANDBY [label="standby"]; // consumer -> passive
IDLE -> TRANSFERRING [label="burst"]; // producer -> active
+ IDLE -> ACTIVE [label="burst"]; // full write
ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming)
ACTIVE -> DRAINING [label="drain"]; // producer -> passive
ACTIVE -> TRANSFERRING [label="burst"]; // early unblocking
@@ -45,6 +46,7 @@
PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared
DRAINING -> IDLE [label="←IStreamCallback.onDrainReady"];
DRAINING -> TRANSFERRING [label="burst"]; // producer -> active
+ DRAINING -> ACTIVE [label="burst"]; // full write
DRAINING -> DRAIN_PAUSED [label="pause"]; // consumer -> passive (not consuming)
DRAIN_PAUSED -> DRAINING [label="start"]; // consumer -> active
DRAIN_PAUSED -> TRANSFER_PAUSED [label="burst"]; // producer -> active
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
index 6aa5c61..47e7fda 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
@@ -31,6 +31,7 @@
ACTIVE -> ACTIVE [label="burst"];
ACTIVE -> PAUSED [label="pause"]; // consumer -> passive (not consuming)
ACTIVE -> DRAINING [label="drain"]; // producer -> passive
+ ACTIVE -> IDLE [label="drain"]; // synchronous drain
PAUSED -> PAUSED [label="burst"];
PAUSED -> ACTIVE [label="start"]; // consumer -> active
PAUSED -> IDLE [label="flush"]; // producer -> passive, buffer is cleared
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index e8b5bfc..13b04cd 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -46,6 +46,7 @@
using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioProfile;
+using aidl::android::media::audio::common::Boolean;
using aidl::android::media::audio::common::Int;
using aidl::android::media::audio::common::PcmType;
using android::hardware::audio::common::getFrameSizeInBytes;
@@ -138,12 +139,15 @@
(flags.getTag() == AudioIoFlags::Tag::output &&
!isBitPositionFlagSet(flags.get<AudioIoFlags::Tag::output>(),
AudioOutputFlags::MMAP_NOIRQ))) {
+ StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
+ mVendorDebug.forceTransientBurst,
+ mVendorDebug.forceSynchronousDrain};
StreamContext temp(
std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
portConfigIt->format.value(), portConfigIt->channelMask.value(),
std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
- asyncCallback, mDebug.streamTransientStateDelayMs);
+ asyncCallback, params);
if (temp.isValid()) {
*out_context = std::move(temp);
} else {
@@ -976,18 +980,69 @@
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
+const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
+const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
+
ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
std::vector<VendorParameter>* _aidl_return) {
LOG(DEBUG) << __func__ << ": id count: " << in_ids.size();
- (void)_aidl_return;
- return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ bool allParametersKnown = true;
+ for (const auto& id : in_ids) {
+ if (id == VendorDebug::kForceTransientBurstName) {
+ VendorParameter forceTransientBurst{.id = id};
+ forceTransientBurst.ext.setParcelable(Boolean{mVendorDebug.forceTransientBurst});
+ _aidl_return->push_back(std::move(forceTransientBurst));
+ } else if (id == VendorDebug::kForceSynchronousDrainName) {
+ VendorParameter forceSynchronousDrain{.id = id};
+ forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
+ _aidl_return->push_back(std::move(forceSynchronousDrain));
+ } else {
+ allParametersKnown = false;
+ LOG(ERROR) << __func__ << ": unrecognized parameter \"" << id << "\"";
+ }
+ }
+ if (allParametersKnown) return ndk::ScopedAStatus::ok();
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
+namespace {
+
+template <typename W>
+bool extractParameter(const VendorParameter& p, decltype(W::value)* v) {
+ std::optional<W> value;
+ binder_status_t result = p.ext.getParcelable(&value);
+ if (result == STATUS_OK && value.has_value()) {
+ *v = value.value().value;
+ return true;
+ }
+ LOG(ERROR) << __func__ << ": failed to read the value of the parameter \"" << p.id
+ << "\": " << result;
+ return false;
+}
+
+} // namespace
+
ndk::ScopedAStatus Module::setVendorParameters(const std::vector<VendorParameter>& in_parameters,
bool in_async) {
LOG(DEBUG) << __func__ << ": parameter count " << in_parameters.size()
<< ", async: " << in_async;
- return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ bool allParametersKnown = true;
+ for (const auto& p : in_parameters) {
+ if (p.id == VendorDebug::kForceTransientBurstName) {
+ if (!extractParameter<Boolean>(p, &mVendorDebug.forceTransientBurst)) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ } else if (p.id == VendorDebug::kForceSynchronousDrainName) {
+ if (!extractParameter<Boolean>(p, &mVendorDebug.forceSynchronousDrain)) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ } else {
+ allParametersKnown = false;
+ LOG(ERROR) << __func__ << ": unrecognized parameter \"" << p.id << "\"";
+ }
+ }
+ if (allParametersKnown) return ndk::ScopedAStatus::ok();
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
ndk::ScopedAStatus Module::addDeviceEffect(
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index a490a2a..0520cba 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -402,7 +402,11 @@
usleep(1000); // Simulate a blocking call into the driver.
populateReply(&reply, mIsConnected);
// Can switch the state to ERROR if a driver error occurs.
- switchToTransientState(StreamDescriptor::State::DRAINING);
+ if (mState == StreamDescriptor::State::ACTIVE && mForceSynchronousDrain) {
+ mState = StreamDescriptor::State::IDLE;
+ } else {
+ switchToTransientState(StreamDescriptor::State::DRAINING);
+ }
} else if (mState == StreamDescriptor::State::TRANSFER_PAUSED) {
mState = StreamDescriptor::State::DRAIN_PAUSED;
populateReply(&reply, mIsConnected);
@@ -467,14 +471,19 @@
bool StreamOutWorkerLogic::write(size_t clientSize, StreamDescriptor::Reply* reply) {
const size_t readByteCount = mDataMQ->availableToRead();
- // Amount of data that the HAL module is going to actually use.
- const size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
bool fatal = false;
if (bool success = readByteCount > 0 ? mDataMQ->read(&mDataBuffer[0], readByteCount) : true) {
const bool isConnected = mIsConnected;
LOG(DEBUG) << __func__ << ": reading of " << readByteCount << " bytes from data MQ"
<< " succeeded; connected? " << isConnected;
- // Frames are consumed and counted regardless of connection status.
+ // Amount of data that the HAL module is going to actually use.
+ size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
+ if (byteCount >= mFrameSize && mForceTransientBurst) {
+ // In order to prevent the state machine from going to ACTIVE state,
+ // simulate partial write.
+ byteCount -= mFrameSize;
+ }
+ // Frames are consumed and counted regardless of the connection status.
reply->fmqByteCount += byteCount;
mFrameCount += byteCount / mFrameSize;
populateReply(reply, isConnected);
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index e9f43d8..000a704 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -36,6 +36,13 @@
explicit Module(Type type) : mType(type) {}
private:
+ struct VendorDebug {
+ static const std::string kForceTransientBurstName;
+ static const std::string kForceSynchronousDrainName;
+ bool forceTransientBurst = false;
+ bool forceSynchronousDrain = false;
+ };
+
ndk::ScopedAStatus setModuleDebug(
const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override;
ndk::ScopedAStatus getTelephony(std::shared_ptr<ITelephony>* _aidl_return) override;
@@ -128,6 +135,7 @@
const Type mType;
std::unique_ptr<internal::Configuration> mConfig;
ModuleDebug mDebug;
+ VendorDebug mVendorDebug;
// For the interfaces requiring to return the same instance, we need to hold them
// via a strong pointer. The binder token is retained for a call to 'setMinSchedulerPolicy'.
std::shared_ptr<ITelephony> mTelephony;
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 5abd4de..2cf5951 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -62,12 +62,21 @@
// Ensure that this value is not used by any of StreamDescriptor.State enums
static constexpr int32_t STATE_CLOSED = -1;
+ struct DebugParameters {
+ // An extra delay for transient states, in ms.
+ int transientStateDelayMs = 0;
+ // Force the "burst" command to move the SM to the TRANSFERRING state.
+ bool forceTransientBurst = false;
+ // Force the "drain" command to be synchronous, going directly to the IDLE state.
+ bool forceSynchronousDrain = false;
+ };
+
StreamContext() = default;
StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
const ::aidl::android::media::audio::common::AudioFormatDescription& format,
const ::aidl::android::media::audio::common::AudioChannelLayout& channelLayout,
std::unique_ptr<DataMQ> dataMQ, std::shared_ptr<IStreamCallback> asyncCallback,
- int transientStateDelayMs)
+ DebugParameters debugParameters)
: mCommandMQ(std::move(commandMQ)),
mInternalCommandCookie(std::rand()),
mReplyMQ(std::move(replyMQ)),
@@ -75,7 +84,7 @@
mChannelLayout(channelLayout),
mDataMQ(std::move(dataMQ)),
mAsyncCallback(asyncCallback),
- mTransientStateDelayMs(transientStateDelayMs) {}
+ mDebugParameters(debugParameters) {}
StreamContext(StreamContext&& other)
: mCommandMQ(std::move(other.mCommandMQ)),
mInternalCommandCookie(other.mInternalCommandCookie),
@@ -83,8 +92,8 @@
mFormat(other.mFormat),
mChannelLayout(other.mChannelLayout),
mDataMQ(std::move(other.mDataMQ)),
- mAsyncCallback(other.mAsyncCallback),
- mTransientStateDelayMs(other.mTransientStateDelayMs) {}
+ mAsyncCallback(std::move(other.mAsyncCallback)),
+ mDebugParameters(std::move(other.mDebugParameters)) {}
StreamContext& operator=(StreamContext&& other) {
mCommandMQ = std::move(other.mCommandMQ);
mInternalCommandCookie = other.mInternalCommandCookie;
@@ -92,8 +101,8 @@
mFormat = std::move(other.mFormat);
mChannelLayout = std::move(other.mChannelLayout);
mDataMQ = std::move(other.mDataMQ);
- mAsyncCallback = other.mAsyncCallback;
- mTransientStateDelayMs = other.mTransientStateDelayMs;
+ mAsyncCallback = std::move(other.mAsyncCallback);
+ mDebugParameters = std::move(other.mDebugParameters);
return *this;
}
@@ -107,10 +116,12 @@
::aidl::android::media::audio::common::AudioFormatDescription getFormat() const {
return mFormat;
}
+ bool getForceTransientBurst() const { return mDebugParameters.forceTransientBurst; }
+ bool getForceSynchronousDrain() const { return mDebugParameters.forceSynchronousDrain; }
size_t getFrameSize() const;
int getInternalCommandCookie() const { return mInternalCommandCookie; }
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
- int getTransientStateDelayMs() const { return mTransientStateDelayMs; }
+ int getTransientStateDelayMs() const { return mDebugParameters.transientStateDelayMs; }
bool isValid() const;
void reset();
@@ -122,7 +133,7 @@
::aidl::android::media::audio::common::AudioChannelLayout mChannelLayout;
std::unique_ptr<DataMQ> mDataMQ;
std::shared_ptr<IStreamCallback> mAsyncCallback;
- int mTransientStateDelayMs;
+ DebugParameters mDebugParameters;
};
class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
@@ -141,7 +152,9 @@
mReplyMQ(context.getReplyMQ()),
mDataMQ(context.getDataMQ()),
mAsyncCallback(context.getAsyncCallback()),
- mTransientStateDelayMs(context.getTransientStateDelayMs()) {}
+ mTransientStateDelayMs(context.getTransientStateDelayMs()),
+ mForceTransientBurst(context.getForceTransientBurst()),
+ mForceSynchronousDrain(context.getForceSynchronousDrain()) {}
std::string init() override;
void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
void populateReplyWrongState(StreamDescriptor::Reply* reply,
@@ -164,6 +177,8 @@
std::shared_ptr<IStreamCallback> mAsyncCallback;
const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
+ const bool mForceTransientBurst;
+ const bool mForceSynchronousDrain;
// We use an array and the "size" field instead of a vector to be able to detect
// memory allocation issues.
std::unique_ptr<int8_t[]> mDataBuffer;
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 8da475e..d4f2811 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -18,6 +18,7 @@
#include <chrono>
#include <cmath>
#include <condition_variable>
+#include <forward_list>
#include <limits>
#include <memory>
#include <mutex>
@@ -85,6 +86,7 @@
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioSource;
using aidl::android::media::audio::common::AudioUsage;
+using aidl::android::media::audio::common::Boolean;
using aidl::android::media::audio::common::Float;
using aidl::android::media::audio::common::Int;
using aidl::android::media::audio::common::Void;
@@ -126,7 +128,7 @@
return WithDebugFlags(parent.mFlags);
}
- WithDebugFlags() {}
+ WithDebugFlags() = default;
explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {}
WithDebugFlags(const WithDebugFlags&) = delete;
WithDebugFlags& operator=(const WithDebugFlags&) = delete;
@@ -135,7 +137,10 @@
EXPECT_IS_OK(mModule->setModuleDebug(mInitial));
}
}
- void SetUp(IModule* module) { ASSERT_IS_OK(module->setModuleDebug(mFlags)); }
+ void SetUp(IModule* module) {
+ ASSERT_IS_OK(module->setModuleDebug(mFlags));
+ mModule = module;
+ }
ModuleDebug& flags() { return mFlags; }
private:
@@ -144,13 +149,65 @@
IModule* mModule = nullptr;
};
+template <typename T>
+class WithModuleParameter {
+ public:
+ WithModuleParameter(const std::string parameterId, const T& value)
+ : mParameterId(parameterId), mValue(value) {}
+ WithModuleParameter(const WithModuleParameter&) = delete;
+ WithModuleParameter& operator=(const WithModuleParameter&) = delete;
+ ~WithModuleParameter() {
+ if (mModule != nullptr) {
+ VendorParameter parameter{.id = mParameterId};
+ parameter.ext.setParcelable(mInitial);
+ EXPECT_IS_OK(mModule->setVendorParameters({parameter}, false));
+ }
+ }
+ ScopedAStatus SetUpNoChecks(IModule* module, bool failureExpected) {
+ std::vector<VendorParameter> parameters;
+ ScopedAStatus result = module->getVendorParameters({mParameterId}, ¶meters);
+ if (result.isOk() && parameters.size() == 1) {
+ std::optional<T> maybeInitial;
+ binder_status_t status = parameters[0].ext.getParcelable(&maybeInitial);
+ if (status == STATUS_OK && maybeInitial.has_value()) {
+ mInitial = maybeInitial.value();
+ VendorParameter parameter{.id = mParameterId};
+ parameter.ext.setParcelable(mValue);
+ result = module->setVendorParameters({parameter}, false);
+ if (result.isOk()) {
+ LOG(INFO) << __func__ << ": overriding parameter \"" << mParameterId
+ << "\" with " << mValue.toString()
+ << ", old value: " << mInitial.toString();
+ mModule = module;
+ }
+ } else {
+ LOG(ERROR) << __func__ << ": error while retrieving the value of \"" << mParameterId
+ << "\"";
+ return ScopedAStatus::fromStatus(status);
+ }
+ }
+ if (!result.isOk()) {
+ LOG(failureExpected ? INFO : ERROR)
+ << __func__ << ": can not override vendor parameter \"" << mParameterId << "\""
+ << result;
+ }
+ return result;
+ }
+
+ private:
+ const std::string mParameterId;
+ const T mValue;
+ IModule* mModule = nullptr;
+ T mInitial;
+};
+
// For consistency, WithAudioPortConfig can start both with a non-existent
// port config, and with an existing one. Existence is determined by the
// id of the provided config. If it's not 0, then WithAudioPortConfig is
// essentially a no-op wrapper.
class WithAudioPortConfig {
public:
- WithAudioPortConfig() {}
+ WithAudioPortConfig() = default;
explicit WithAudioPortConfig(const AudioPortConfig& config) : mInitialConfig(config) {}
WithAudioPortConfig(const WithAudioPortConfig&) = delete;
WithAudioPortConfig& operator=(const WithAudioPortConfig&) = delete;
@@ -302,26 +359,31 @@
void SetUpImpl(const std::string& moduleName) {
ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName));
- debug.flags().simulateDeviceConnections = true;
- ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get()));
}
- void TearDownImpl() {
- if (module != nullptr) {
- EXPECT_IS_OK(module->setModuleDebug(ModuleDebug{}));
- }
- }
+ void TearDownImpl() { debug.reset(); }
void ConnectToService(const std::string& moduleName) {
+ ASSERT_EQ(module, nullptr);
+ ASSERT_EQ(debug, nullptr);
module = IModule::fromBinder(binderUtil.connectToService(moduleName));
ASSERT_NE(module, nullptr);
+ ASSERT_NO_FATAL_FAILURE(SetUpDebug());
}
void RestartService() {
ASSERT_NE(module, nullptr);
moduleConfig.reset();
+ debug.reset();
module = IModule::fromBinder(binderUtil.restartService());
ASSERT_NE(module, nullptr);
+ ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+ }
+
+ void SetUpDebug() {
+ debug.reset(new WithDebugFlags());
+ debug->flags().simulateDeviceConnections = true;
+ ASSERT_NO_FATAL_FAILURE(debug->SetUp(module.get()));
}
void ApplyEveryConfig(const std::vector<AudioPortConfig>& configs) {
@@ -390,7 +452,7 @@
std::shared_ptr<IModule> module;
std::unique_ptr<ModuleConfig> moduleConfig;
AudioHalBinderServiceUtil binderUtil;
- WithDebugFlags debug;
+ std::unique_ptr<WithDebugFlags> debug;
};
class AudioCoreModule : public AudioCoreModuleBase, public testing::TestWithParam<std::string> {
@@ -465,6 +527,7 @@
size_t getBufferSizeFrames() const { return mBufferSizeFrames; }
CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
DataMQ* getDataMQ() const { return mDataMQ.get(); }
+ size_t getFrameSizeBytes() const { return mFrameSizeBytes; }
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
private:
@@ -504,10 +567,48 @@
return std::to_string(static_cast<int32_t>(event));
}
+// Note: we use a reference wrapper, not a pointer, because methods of std::*list
+// return references to inserted elements. This way, we can put a returned reference
+// into the children vector without any type conversions, and this makes DAG creation
+// code more clear.
+template <typename T>
+struct DagNode : public std::pair<T, std::vector<std::reference_wrapper<DagNode<T>>>> {
+ using Children = std::vector<std::reference_wrapper<DagNode>>;
+ DagNode(const T& t, const Children& c) : std::pair<T, Children>(t, c) {}
+ DagNode(T&& t, Children&& c) : std::pair<T, Children>(std::move(t), std::move(c)) {}
+ const T& datum() const { return this->first; }
+ Children& children() { return this->second; }
+ const Children& children() const { return this->second; }
+};
+// Since DagNodes do contain references to next nodes, node links provided
+// by the list are not used. Thus, the order of the nodes in the list is not
+// important, except that the starting node must be at the front of the list,
+// which means, it must always be added last.
+template <typename T>
+struct Dag : public std::forward_list<DagNode<T>> {
+ Dag() = default;
+ // We prohibit copying and moving Dag instances because implementing that
+ // is not trivial due to references between nodes.
+ Dag(const Dag&) = delete;
+ Dag(Dag&&) = delete;
+ Dag& operator=(const Dag&) = delete;
+ Dag& operator=(Dag&&) = delete;
+};
+
// Transition to the next state happens either due to a command from the client,
// or after an event received from the server.
using TransitionTrigger = std::variant<StreamDescriptor::Command, StreamEventReceiver::Event>;
-using StateTransition = std::pair<TransitionTrigger, StreamDescriptor::State>;
+std::string toString(const TransitionTrigger& trigger) {
+ if (std::holds_alternative<StreamDescriptor::Command>(trigger)) {
+ return std::string("'")
+ .append(toString(std::get<StreamDescriptor::Command>(trigger).getTag()))
+ .append("' command");
+ }
+ return std::string("'")
+ .append(toString(std::get<StreamEventReceiver::Event>(trigger)))
+ .append("' event");
+}
+
struct StateSequence {
virtual ~StateSequence() = default;
virtual void rewind() = 0;
@@ -517,6 +618,10 @@
virtual void advance(StreamDescriptor::State state) = 0;
};
+// Defines the current state and the trigger to transfer to the next one,
+// thus "state" is the "from" state.
+using StateTransitionFrom = std::pair<StreamDescriptor::State, TransitionTrigger>;
+
static const StreamDescriptor::Command kGetStatusCommand =
StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::getStatus>(Void{});
static const StreamDescriptor::Command kStartCommand =
@@ -542,66 +647,65 @@
StreamEventReceiver::Event::TransferReady;
static const StreamEventReceiver::Event kDrainReadyEvent = StreamEventReceiver::Event::DrainReady;
-// Handle possible bifurcations:
-// - on burst and on start: 'TRANSFERRING' -> {'ACTIVE', 'TRANSFERRING'}
-// - on pause: 'TRANSFER_PAUSED' -> {'PAUSED', 'TRANSFER_PAUSED'}
-// It is assumed that the 'steps' provided on the construction contain the sequence
-// for the async case, which gets corrected in the case when the HAL decided to do
-// a synchronous transfer.
-class SmartStateSequence : public StateSequence {
+struct StateDag : public Dag<StateTransitionFrom> {
+ using Node = StateDag::reference;
+ using NextStates = StateDag::value_type::Children;
+
+ template <typename... Next>
+ Node makeNode(StreamDescriptor::State s, TransitionTrigger t, Next&&... next) {
+ return emplace_front(std::make_pair(s, t), NextStates{std::forward<Next>(next)...});
+ }
+ Node makeNodes(const std::vector<StateTransitionFrom>& v, Node last) {
+ auto helper = [&](auto i, auto&& h) -> Node {
+ if (i == v.end()) return last;
+ return makeNode(i->first, i->second, h(++i, h));
+ };
+ return helper(v.begin(), helper);
+ }
+ Node makeNodes(const std::vector<StateTransitionFrom>& v, StreamDescriptor::State f) {
+ return makeNodes(v, makeFinalNode(f));
+ }
+ Node makeFinalNode(StreamDescriptor::State s) {
+ // The actual command used here is irrelevant. Since it's the final node
+ // in the test sequence, no commands sent after reaching it.
+ return emplace_front(std::make_pair(s, kGetStatusCommand), NextStates{});
+ }
+};
+
+class StateSequenceFollower : public StateSequence {
public:
- explicit SmartStateSequence(const std::vector<StateTransition>& steps) : mSteps(steps) {}
- explicit SmartStateSequence(std::vector<StateTransition>&& steps) : mSteps(std::move(steps)) {}
- void rewind() override { mCurrentStep = 0; }
- bool done() const override { return mCurrentStep >= mSteps.size(); }
- TransitionTrigger getTrigger() override { return mSteps[mCurrentStep].first; }
+ explicit StateSequenceFollower(std::unique_ptr<StateDag> steps)
+ : mSteps(std::move(steps)), mCurrent(mSteps->front()) {}
+ void rewind() override { mCurrent = mSteps->front(); }
+ bool done() const override { return current().children().empty(); }
+ TransitionTrigger getTrigger() override { return current().datum().second; }
std::set<StreamDescriptor::State> getExpectedStates() override {
- std::set<StreamDescriptor::State> result = {getState()};
- if (isBurstBifurcation() || isStartBifurcation()) {
- result.insert(StreamDescriptor::State::ACTIVE);
- } else if (isPauseBifurcation()) {
- result.insert(StreamDescriptor::State::PAUSED);
- }
+ std::set<StreamDescriptor::State> result;
+ std::transform(current().children().cbegin(), current().children().cend(),
+ std::inserter(result, result.begin()),
+ [](const auto& node) { return node.get().datum().first; });
+ LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(result);
return result;
}
void advance(StreamDescriptor::State state) override {
- if (isBurstBifurcation() && state == StreamDescriptor::State::ACTIVE &&
- mCurrentStep + 1 < mSteps.size() &&
- mSteps[mCurrentStep + 1].first == TransitionTrigger{kTransferReadyEvent}) {
- mCurrentStep++;
+ if (auto it = std::find_if(
+ current().children().cbegin(), current().children().cend(),
+ [&](const auto& node) { return node.get().datum().first == state; });
+ it != current().children().cend()) {
+ LOG(DEBUG) << __func__ << ": " << toString(mCurrent.get().datum().first) << " -> "
+ << toString(it->get().datum().first);
+ mCurrent = *it;
+ } else {
+ LOG(FATAL) << __func__ << ": state " << toString(state) << " is unexpected";
}
- mCurrentStep++;
}
private:
- StreamDescriptor::State getState() const { return mSteps[mCurrentStep].second; }
- bool isBurstBifurcation() {
- return getTrigger() == TransitionTrigger{kBurstCommand} &&
- getState() == StreamDescriptor::State::TRANSFERRING;
- }
- bool isPauseBifurcation() {
- return getTrigger() == TransitionTrigger{kPauseCommand} &&
- getState() == StreamDescriptor::State::TRANSFER_PAUSED;
- }
- bool isStartBifurcation() {
- return getTrigger() == TransitionTrigger{kStartCommand} &&
- getState() == StreamDescriptor::State::TRANSFERRING;
- }
- const std::vector<StateTransition> mSteps;
- size_t mCurrentStep = 0;
+ StateDag::const_reference current() const { return mCurrent.get(); }
+ std::unique_ptr<StateDag> mSteps;
+ std::reference_wrapper<StateDag::value_type> mCurrent;
};
-std::string toString(const TransitionTrigger& trigger) {
- if (std::holds_alternative<StreamDescriptor::Command>(trigger)) {
- return std::string("'")
- .append(toString(std::get<StreamDescriptor::Command>(trigger).getTag()))
- .append("' command");
- }
- return std::string("'")
- .append(toString(std::get<StreamEventReceiver::Event>(trigger)))
- .append("' event");
-}
-
struct StreamLogicDriver {
virtual ~StreamLogicDriver() = default;
// Return 'true' to stop the worker.
@@ -927,7 +1031,7 @@
return common->close();
}
- WithStream() {}
+ WithStream() = default;
explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {}
WithStream(const WithStream&) = delete;
WithStream& operator=(const WithStream&) = delete;
@@ -1032,7 +1136,7 @@
class WithAudioPatch {
public:
- WithAudioPatch() {}
+ WithAudioPatch() = default;
WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig)
: mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {}
WithAudioPatch(bool sinkIsCfg1, const AudioPortConfig& portConfig1,
@@ -1473,7 +1577,7 @@
GTEST_SKIP() << "No external devices in the module.";
}
AudioPort ignored;
- WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(debug);
+ WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(*debug);
doNotSimulateConnections.flags().simulateDeviceConnections = false;
ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
for (const auto& port : ports) {
@@ -1493,7 +1597,7 @@
}
WithDevicePortConnectedState portConnected(*ports.begin(), GenerateUniqueDeviceAddress());
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
- ModuleDebug midwayDebugChange = debug.flags();
+ ModuleDebug midwayDebugChange = debug->flags();
midwayDebugChange.simulateDeviceConnections = false;
EXPECT_STATUS(EX_ILLEGAL_STATE, module->setModuleDebug(midwayDebugChange))
<< "when trying to disable connections simulation while having a connected device";
@@ -2716,8 +2820,8 @@
class StreamLogicDefaultDriver : public StreamLogicDriver {
public:
- explicit StreamLogicDefaultDriver(std::shared_ptr<StateSequence> commands)
- : mCommands(commands) {
+ StreamLogicDefaultDriver(std::shared_ptr<StateSequence> commands, size_t frameSizeBytes)
+ : mCommands(commands), mFrameSizeBytes(frameSizeBytes) {
mCommands->rewind();
}
@@ -2736,7 +2840,10 @@
if (actualSize != nullptr) {
// In the output scenario, reduce slightly the fmqByteCount to verify
// that the HAL module always consumes all data from the MQ.
- if (maxDataSize > 1) maxDataSize--;
+ if (maxDataSize > static_cast<int>(mFrameSizeBytes)) {
+ LOG(DEBUG) << __func__ << ": reducing data size by " << mFrameSizeBytes;
+ maxDataSize -= mFrameSizeBytes;
+ }
*actualSize = maxDataSize;
}
command->set<StreamDescriptor::Command::Tag::burst>(maxDataSize);
@@ -2782,6 +2889,7 @@
protected:
std::shared_ptr<StateSequence> mCommands;
+ const size_t mFrameSizeBytes;
std::optional<StreamDescriptor::State> mPreviousState;
std::optional<int64_t> mPreviousFrames;
bool mObservablePositionIncrease = false;
@@ -2830,7 +2938,7 @@
(!isNonBlocking && streamType == StreamTypeFilter::ASYNC)) {
continue;
}
- WithDebugFlags delayTransientStates = WithDebugFlags::createNested(debug);
+ WithDebugFlags delayTransientStates = WithDebugFlags::createNested(*debug);
delayTransientStates.flags().streamTransientStateDelayMs =
std::get<NAMED_CMD_DELAY_MS>(std::get<PARAM_CMD_SEQ>(GetParam()));
ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get()));
@@ -2841,6 +2949,40 @@
} else {
ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
}
+ if (isNonBlocking) {
+ // Also try running the same sequence with "aosp.forceTransientBurst" set.
+ // This will only work with the default implementation. When it works, the stream
+ // tries always to move to the 'TRANSFERRING' state after a burst.
+ // This helps to check more paths for our test scenarios.
+ WithModuleParameter forceTransientBurst("aosp.forceTransientBurst", Boolean{true});
+ if (forceTransientBurst.SetUpNoChecks(module.get(), true /*failureExpected*/)
+ .isOk()) {
+ if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(
+ RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
+ }
+ }
+ } else if (!IOTraits<Stream>::is_input) {
+ // Also try running the same sequence with "aosp.forceSynchronousDrain" set.
+ // This will only work with the default implementation. When it works, the stream
+ // tries always to move to the 'IDLE' state after a drain.
+ // This helps to check more paths for our test scenarios.
+ WithModuleParameter forceSynchronousDrain("aosp.forceSynchronousDrain",
+ Boolean{true});
+ if (forceSynchronousDrain.SetUpNoChecks(module.get(), true /*failureExpected*/)
+ .isOk()) {
+ if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
+ ASSERT_NO_FATAL_FAILURE(
+ RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(
+ RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
+ }
+ }
+ }
}
}
@@ -2861,7 +3003,8 @@
WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
- StreamLogicDefaultDriver driver(commandsAndStates);
+ StreamLogicDefaultDriver driver(commandsAndStates,
+ stream.getContext()->getFrameSizeBytes());
typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
stream.getEventReceiver());
@@ -2882,7 +3025,8 @@
std::shared_ptr<StateSequence> commandsAndStates) {
WithStream<Stream> stream(portConfig);
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
- StreamLogicDefaultDriver driver(commandsAndStates);
+ StreamLogicDefaultDriver driver(commandsAndStates,
+ stream.getContext()->getFrameSizeBytes());
typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
stream.getEventReceiver());
@@ -3219,38 +3363,52 @@
// TODO: Add async test cases for input once it is implemented.
-std::shared_ptr<StateSequence> makeBurstCommands(bool isSync, size_t burstCount) {
- const auto burst =
- isSync ? std::vector<StateTransition>{std::make_pair(kBurstCommand,
- StreamDescriptor::State::ACTIVE)}
- : std::vector<StateTransition>{
- std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
- std::make_pair(kTransferReadyEvent, StreamDescriptor::State::ACTIVE)};
- std::vector<StateTransition> result{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE)};
- for (size_t i = 0; i < burstCount; ++i) {
- result.insert(result.end(), burst.begin(), burst.end());
+std::shared_ptr<StateSequence> makeBurstCommands(bool isSync) {
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ StateDag::Node last = d->makeFinalNode(State::ACTIVE);
+ StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, last);
+ StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+ if (!isSync) {
+ // Allow optional routing via the TRANSFERRING state on bursts.
+ active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, last));
+ idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
}
- return std::make_shared<SmartStateSequence>(result);
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kReadSeq =
- std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true, 3));
-static const NamedCommandSequence kWriteSyncSeq = std::make_tuple(
- std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true, 3));
-static const NamedCommandSequence kWriteAsyncSeq = std::make_tuple(
- std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false, 3));
+ std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true));
+static const NamedCommandSequence kWriteSyncSeq =
+ std::make_tuple(std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true));
+static const NamedCommandSequence kWriteAsyncSeq =
+ std::make_tuple(std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false));
std::shared_ptr<StateSequence> makeAsyncDrainCommands(bool isInput) {
- return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, isInput ? StreamDescriptor::State::ACTIVE
- : StreamDescriptor::State::TRANSFERRING),
- std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
- StreamDescriptor::State::DRAINING),
- isInput ? std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE)
- : std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
- std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
- StreamDescriptor::State::DRAINING)});
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ if (isInput) {
+ d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+ std::make_pair(State::IDLE, kBurstCommand),
+ std::make_pair(State::ACTIVE, kDrainInCommand),
+ std::make_pair(State::DRAINING, kStartCommand),
+ std::make_pair(State::ACTIVE, kDrainInCommand)},
+ State::DRAINING);
+ } else {
+ StateDag::Node draining =
+ d->makeNodes({std::make_pair(State::DRAINING, kBurstCommand),
+ std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)},
+ State::DRAINING);
+ StateDag::Node idle =
+ d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
+ std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)},
+ draining);
+ // If we get straight into ACTIVE on burst, no further testing is possible.
+ draining.children().push_back(d->makeFinalNode(State::ACTIVE));
+ idle.children().push_back(d->makeFinalNode(State::ACTIVE));
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ }
+ return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kWriteDrainAsyncSeq =
std::make_tuple(std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs,
@@ -3259,58 +3417,92 @@
std::string("Drain"), 0, StreamTypeFilter::ANY, makeAsyncDrainCommands(true));
std::shared_ptr<StateSequence> makeDrainOutCommands(bool isSync) {
- return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
- std::make_pair(isSync ? TransitionTrigger(kGetStatusCommand)
- : TransitionTrigger(kDrainReadyEvent),
- StreamDescriptor::State::IDLE)});
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ StateDag::Node last = d->makeFinalNode(State::IDLE);
+ StateDag::Node active = d->makeNodes(
+ {std::make_pair(State::ACTIVE, kDrainOutAllCommand),
+ std::make_pair(State::DRAINING, isSync ? TransitionTrigger(kGetStatusCommand)
+ : TransitionTrigger(kDrainReadyEvent))},
+ last);
+ StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+ if (!isSync) {
+ idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
+ } else {
+ active.children().push_back(last);
+ }
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainOutSyncSeq = std::make_tuple(
std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true));
static const NamedCommandSequence kDrainOutAsyncSeq = std::make_tuple(
std::string("Drain"), 0, StreamTypeFilter::ASYNC, makeDrainOutCommands(false));
-std::shared_ptr<StateSequence> makeDrainOutPauseCommands(bool isSync) {
- return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
- : StreamDescriptor::State::TRANSFERRING),
- std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
- std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
- std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING),
- std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
- std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::PAUSED
- : StreamDescriptor::State::TRANSFER_PAUSED)});
+std::shared_ptr<StateSequence> makeDrainPauseOutCommands(bool isSync) {
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
+ std::make_pair(State::DRAIN_PAUSED, kStartCommand),
+ std::make_pair(State::DRAINING, kPauseCommand),
+ std::make_pair(State::DRAIN_PAUSED, kBurstCommand)},
+ isSync ? State::PAUSED : State::TRANSFER_PAUSED);
+ StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining);
+ StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+ if (!isSync) {
+ idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
+ } else {
+ // If we get straight into IDLE on drain, no further testing is possible.
+ active.children().push_back(d->makeFinalNode(State::IDLE));
+ }
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainPauseOutSyncSeq =
std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
- StreamTypeFilter::SYNC, makeDrainOutPauseCommands(true));
+ StreamTypeFilter::SYNC, makeDrainPauseOutCommands(true));
static const NamedCommandSequence kDrainPauseOutAsyncSeq =
std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
- StreamTypeFilter::ASYNC, makeDrainOutPauseCommands(false));
+ StreamTypeFilter::ASYNC, makeDrainPauseOutCommands(false));
// This sequence also verifies that the capture / presentation position is not reset on standby.
std::shared_ptr<StateSequence> makeStandbyCommands(bool isInput, bool isSync) {
- return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY),
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, isInput || isSync
- ? StreamDescriptor::State::ACTIVE
- : StreamDescriptor::State::TRANSFERRING),
- std::make_pair(kPauseCommand, isInput || isSync
- ? StreamDescriptor::State::PAUSED
- : StreamDescriptor::State::TRANSFER_PAUSED),
- std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
- : StreamDescriptor::State::IDLE),
- std::make_pair(isInput ? kGetStatusCommand : kStandbyCommand, // no-op for input
- StreamDescriptor::State::STANDBY),
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, isInput || isSync
- ? StreamDescriptor::State::ACTIVE
- : StreamDescriptor::State::TRANSFERRING)});
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ if (isInput) {
+ d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+ std::make_pair(State::IDLE, kStandbyCommand),
+ std::make_pair(State::STANDBY, kStartCommand),
+ std::make_pair(State::IDLE, kBurstCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kFlushCommand),
+ std::make_pair(State::STANDBY, kStartCommand),
+ std::make_pair(State::IDLE, kBurstCommand)},
+ State::ACTIVE);
+ } else {
+ StateDag::Node idle3 =
+ d->makeNode(State::IDLE, kBurstCommand, d->makeFinalNode(State::ACTIVE));
+ StateDag::Node idle2 = d->makeNodes({std::make_pair(State::IDLE, kStandbyCommand),
+ std::make_pair(State::STANDBY, kStartCommand)},
+ idle3);
+ StateDag::Node active = d->makeNodes({std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kFlushCommand)},
+ idle2);
+ StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+ if (!isSync) {
+ idle3.children().push_back(d->makeFinalNode(State::TRANSFERRING));
+ StateDag::Node transferring =
+ d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
+ std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)},
+ idle2);
+ idle.children().push_back(transferring);
+ }
+ d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+ std::make_pair(State::IDLE, kStandbyCommand),
+ std::make_pair(State::STANDBY, kStartCommand)},
+ idle);
+ }
+ return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kStandbyInSeq = std::make_tuple(
std::string("Standby"), 0, StreamTypeFilter::ANY, makeStandbyCommands(true, false));
@@ -3320,50 +3512,71 @@
std::make_tuple(std::string("Standby"), kStreamTransientStateTransitionDelayMs,
StreamTypeFilter::ASYNC, makeStandbyCommands(false, false));
-static const NamedCommandSequence kPauseInSeq =
- std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY,
- std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}));
-static const NamedCommandSequence kPauseOutSyncSeq =
- std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC,
- std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED),
- std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
- std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)}));
-/* TODO: Figure out a better way for testing sync/async bursts
-static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple(
- std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
- std::make_shared<StaticStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
- std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
- std::make_pair(kStartCommand, StreamDescriptor::State::TRANSFERRING),
- std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
- std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAIN_PAUSED),
- std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFER_PAUSED)}));
-*/
+std::shared_ptr<StateSequence> makePauseCommands(bool isInput, bool isSync) {
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ if (isInput) {
+ d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+ std::make_pair(State::IDLE, kBurstCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kBurstCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kFlushCommand)},
+ State::STANDBY);
+ } else {
+ StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kStartCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kBurstCommand),
+ std::make_pair(State::PAUSED, kStartCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand)},
+ State::PAUSED);
+ if (!isSync) {
+ idle.children().push_back(
+ d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
+ std::make_pair(State::TRANSFER_PAUSED, kStartCommand),
+ std::make_pair(State::TRANSFERRING, kPauseCommand),
+ std::make_pair(State::TRANSFER_PAUSED, kDrainOutAllCommand),
+ std::make_pair(State::DRAIN_PAUSED, kBurstCommand)},
+ State::TRANSFER_PAUSED));
+ }
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ }
+ return std::make_shared<StateSequenceFollower>(std::move(d));
+}
+static const NamedCommandSequence kPauseInSeq = std::make_tuple(
+ std::string("Pause"), 0, StreamTypeFilter::ANY, makePauseCommands(true, false));
+static const NamedCommandSequence kPauseOutSyncSeq = std::make_tuple(
+ std::string("Pause"), 0, StreamTypeFilter::SYNC, makePauseCommands(false, true));
+static const NamedCommandSequence kPauseOutAsyncSeq =
+ std::make_tuple(std::string("Pause"), kStreamTransientStateTransitionDelayMs,
+ StreamTypeFilter::ASYNC, makePauseCommands(false, false));
std::shared_ptr<StateSequence> makeFlushCommands(bool isInput, bool isSync) {
- return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, isInput || isSync
- ? StreamDescriptor::State::ACTIVE
- : StreamDescriptor::State::TRANSFERRING),
- std::make_pair(kPauseCommand, isInput || isSync
- ? StreamDescriptor::State::PAUSED
- : StreamDescriptor::State::TRANSFER_PAUSED),
- std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
- : StreamDescriptor::State::IDLE)});
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ if (isInput) {
+ d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+ std::make_pair(State::IDLE, kBurstCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kFlushCommand)},
+ State::STANDBY);
+ } else {
+ StateDag::Node last = d->makeFinalNode(State::IDLE);
+ StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
+ std::make_pair(State::ACTIVE, kPauseCommand),
+ std::make_pair(State::PAUSED, kFlushCommand)},
+ last);
+ if (!isSync) {
+ idle.children().push_back(
+ d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
+ std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)},
+ last));
+ }
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ }
+ return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kFlushInSeq = std::make_tuple(
std::string("Flush"), 0, StreamTypeFilter::ANY, makeFlushCommands(true, false));
@@ -3374,13 +3587,21 @@
StreamTypeFilter::ASYNC, makeFlushCommands(false, false));
std::shared_ptr<StateSequence> makeDrainPauseFlushOutCommands(bool isSync) {
- return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
- std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
- std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
- : StreamDescriptor::State::TRANSFERRING),
- std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
- std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
- std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)});
+ using State = StreamDescriptor::State;
+ auto d = std::make_unique<StateDag>();
+ StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
+ std::make_pair(State::DRAIN_PAUSED, kFlushCommand)},
+ State::IDLE);
+ StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining);
+ StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+ if (!isSync) {
+ idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
+ } else {
+ // If we get straight into IDLE on drain, no further testing is possible.
+ active.children().push_back(d->makeFinalNode(State::IDLE));
+ }
+ d->makeNode(State::STANDBY, kStartCommand, idle);
+ return std::make_shared<StateSequenceFollower>(std::move(d));
}
static const NamedCommandSequence kDrainPauseFlushOutSyncSeq =
std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,