blob: 4d65af2b5464c563ea0f2fb82475ec924299a0c5 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <audio-proto/audio-proto.h>
#include <ddktl/device.h>
#include <dispatcher-pool/dispatcher-channel.h>
#include <dispatcher-pool/dispatcher-execution-domain.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/vector.h>
#include <lib/zx/vmo.h>
#include <zircon/compiler.h>
#include <zircon/types.h>
#include <atomic>
#include <utility>
namespace audio {
struct SimpleAudioStreamProtocol : public ddk::internal::base_protocol {
explicit SimpleAudioStreamProtocol(bool is_input) {
ddk_proto_id_ = is_input
? ZX_PROTOCOL_AUDIO_INPUT
: ZX_PROTOCOL_AUDIO_OUTPUT;
}
bool is_input() const { return ddk_proto_id_ == ZX_PROTOCOL_AUDIO_INPUT; }
};
class SimpleAudioStream;
using SimpleAudioStreamBase = ddk::Device<SimpleAudioStream,
ddk::Ioctlable,
ddk::Unbindable>;
class SimpleAudioStream : public SimpleAudioStreamBase,
public SimpleAudioStreamProtocol,
public fbl::RefCounted<SimpleAudioStream> {
public:
// Create
//
// A general method which handles the construction/initialization of
// SimpleAudioStream implementation. Given an implementation called
// 'MyStream', invocation should look something like..
//
// auto stream = SimpleAudioStream::Create<MyStream>(arg1, arg2, ...);
//
// Note: Implementers are encouraged to keep their constructor/destructor
// protected/private. In order to do so, however, they will need to make
// sure to 'friend class SimpleAudioStream', and to 'friend class fbl::RefPtr<T>'
template <typename T, typename... ConstructorSignature>
static fbl::RefPtr<T> Create(ConstructorSignature&&... args) {
static_assert(fbl::is_base_of<SimpleAudioStream, T>::value,
"Class must derive from SimpleAudioStream!");
fbl::AllocChecker ac;
auto ret = fbl::AdoptRef(new (&ac) T(std::forward<ConstructorSignature>(args)...));
if (!ac.check()) {
return nullptr;
}
if (ret->CreateInternal() != ZX_OK) {
ret->Shutdown();
return nullptr;
}
return ret;
}
DISALLOW_COPY_ASSIGN_AND_MOVE(SimpleAudioStream);
// Public properties.
bool is_input() const { return SimpleAudioStreamProtocol::is_input(); }
// User facing shutdown method. Implementers with shutdown requirements
// should overload ShutdownHook.
void Shutdown() __TA_EXCLUDES(domain_->token());
// DDK device implementation
void DdkUnbind();
void DdkRelease();
zx_status_t DdkIoctl(uint32_t op,
const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* out_actual);
protected:
friend class fbl::RefPtr<SimpleAudioStream>;
SimpleAudioStream(zx_device_t* parent, bool is_input)
: SimpleAudioStreamBase(parent),
SimpleAudioStreamProtocol(is_input) {}
virtual ~SimpleAudioStream() = default;
// Hooks for driver implementation.
// Init - General hook
//
// Called once during device creation, before the execution domain has been
// created and before any device node has been published.
//
// During Init, devices *must*
// 1) Populate the supported_formats_ vector with at least one valid format
// range.
// 2) Report the stream's gain control capabilities and current gain control
// state in the cur_gain_state_ member.
// 3) Supply a valid, null-terminated, device node name in the device_name_
// member.
// 4) Supply a persistent unique ID in the unique_id_ member.
// 5) Call SetInitialPlugState to declare its plug detection capabilities
// and initial plug state, if the device is not exclusively hardwired.
//
// During Init, devices *should*
// 1) Supply a valid null-terminated UTF-8 encoded manufacturer name in the
// mfr_name_ member.
// 2) Supply a valid null-terminated UTF-8 encoded product name in the
// prod_name_ member.
//
// Note: The execution domain has not been created at this point. Because
// of this, it is safe to assert that we are holding the domain token and to
// access members and methods which are protected by the domain token.
// Users should *not* attempt to activate any event sources during Init as
// the domain has not been created and activation will fail. See InitPost.
//
virtual zx_status_t Init() __TA_REQUIRES(domain_->token()) = 0;
// InitPost - General hook
//
// Called once during device creation, after the execution domain has been
// created and after Init has succeeded, but before any device node has been
// published.
//
// This is the point at which the execution domain has been created and the
// point at which implementations should activate any custom event sources
// they need to use.
//
virtual zx_status_t InitPost() { return ZX_OK; }
// RingBufferShutdown - General hook
//
// Called any time the client ring buffer channel is closed, and only after
// the ring buffer is in the stopped state. Implementations may release
// their VMO and perform additional hardware shutdown tasks as needed here.
//
virtual void RingBufferShutdown() __TA_REQUIRES(domain_->token()) {}
// ShutdownHook - general hook
//
// Called during final shutdown, after the execution domain has been
// shutdown. All execution domain event sources have been deactivated and
// any callbacks have been completed. Implementations should finish
// completely shutting down all hardware and prepare for destruction.
virtual void ShutdownHook() __TA_REQUIRES(domain_->token()) {}
// EnableAsyncNotification - general hook
//
// Called whenever a client enables or disables notification of plug events.
// Subclass can override this, to remain aware of these requests.
virtual void EnableAsyncNotification(bool enable) __TA_REQUIRES(domain_->token()) {}
// Stream interface methods
//
// ChangeFormat - Stream interface method
//
// All drivers must implement ChangeFormat. When called, the following
// guarantees are provided.
//
// 1) Any existing ring buffer channel has been deactivated and the ring
// buffer (if it had existed previously) is in the stopped state.
// 2) The format request has been validated against the supported_formats_
// list supplied by the implementation.
// 3) The frame_size_ for the requested format has been computed.
//
// Drivers should take appropriate steps to prepare hardware for the
// requested format change. Depending on driver requirements, this may
// involve configuring hardware and starting clocks, or may simply involve
// deferring such operations until later.
//
// Upon success, drivers *must* have filled out the fifo_depth_ and
// external_delay_nsec fields with appropriate values.
//
virtual zx_status_t ChangeFormat(const audio_proto::StreamSetFmtReq& req)
__TA_REQUIRES(domain_->token()) = 0;
// SetGain - Stream interface method
//
// Drivers which support gain control may overload this method in order to
// receive a callback when a validated set gain request has been received by
// a client. After processing the request, drivers *must* update the
// cur_gain_state_ member to indicate the current gain state. This is what
// will be reported to users who request a callback from SetGain, as well as
// what will be reported for GetGain operations.
//
virtual zx_status_t SetGain(const audio_proto::SetGainReq& req)
__TA_REQUIRES(domain_->token()) {
return ZX_ERR_NOT_SUPPORTED;
}
// RingBuffer interface methods
//
// GetBuffer - RingBuffer interface method
//
// Called after a successful format change in order to establish the shared
// ring buffer. GetBuffer will never be called while the ring buffer is in
// the started state.
//
// Upon success, drivers should return a valid VMO with appropriate
// permissions (READ | MAP | TRANSFER for inputs, WRITE as well for outputs)
// as well as reporting the total number of usable frames in the ring.
//
virtual zx_status_t GetBuffer(const audio_proto::RingBufGetBufferReq& req,
uint32_t* out_num_rb_frames,
zx::vmo* out_buffer) __TA_REQUIRES(domain_->token()) = 0;
// Start - RingBuffer interface method
//
// Start the ring buffer. Will only be called after both a format and a
// buffer have been established, and only when the ring buffer is in the
// stopped state.
//
// Drivers *must* report the time at which the first frame will be clocked
// out on the CLOCK_MONOTONIC timeline, not including any external delay.
//
// TODO(johngro): Adapt this when we support alternate HW clock domains.
virtual zx_status_t Start(uint64_t* out_start_time) __TA_REQUIRES(domain_->token()) = 0;
// Stop - RingBuffer interface method
//
// Stop the ring buffer. Will only be called after both a format and a
// buffer have been established, and only when the ring buffer is in the
// started state.
//
virtual zx_status_t Stop() __TA_REQUIRES(domain_->token()) = 0;
// RingBuffer interface events
//
// NotifyPosition - RingBuffer interface event
//
// Send a position notification to the client over the ring buffer channel,
// if available. May be called from any thread. May return
// ZX_ERR_BAD_STATE if the ring buffer channel is currently closed, or if
// the active client has not requested that any position notifications be
// provided. Implementations may use this as a signal to stop notification
// production until the point in time at which GetBuffer is called again.
//
// TODO(johngro): Add support for IRQ event sources to the dispatcher pool
// library. With this is place, we can probably just demand that
// NotifyPosition is always called from within the context of the execution
// domain and not need to worry about any locking for the ring buffer
// channel.
zx_status_t NotifyPosition(const audio_proto::RingBufPositionNotify& notif);
// Incoming interfaces (callable from child classes into this class)
//
// SetInitialPlugState
//
// Must be called by child class during Init(), so that the device's Plug
// capabilities are correctly understood (and published) by the base class.
void SetInitialPlugState(audio_pd_notify_flags_t initial_state) __TA_REQUIRES(domain_->token());
// SetPlugState - asynchronous hook for child class
//
// Callable at any time after InitPost, if the device is not hardwired.
// Must be called from the same execution domain as other hooks listed here.
zx_status_t SetPlugState(bool plugged) __TA_REQUIRES(domain_->token());
// Callable any time after SetFormat while the RingBuffer channel is active,
// but only valid after GetBuffer is called. Can be called from any context.
uint32_t LoadNotificationsPerRing() const { return expected_notifications_per_ring_.load(); };
// The execution domain
fbl::RefPtr<dispatcher::ExecutionDomain> domain_;
// State and capabilities which need to be established and maintained by the
// driver implementation.
fbl::Vector<audio_stream_format_range_t> supported_formats_ __TA_GUARDED(domain_->token());
audio_proto::GetGainResp cur_gain_state_ __TA_GUARDED(domain_->token());
audio_stream_unique_id_t unique_id_ __TA_GUARDED(domain_->token()) = {};
char mfr_name_[64] __TA_GUARDED(domain_->token()) = {};
char prod_name_[64] __TA_GUARDED(domain_->token()) = {};
char device_name_[64] = {};
uint32_t frame_size_ __TA_GUARDED(domain_->token()) = 0;
uint32_t fifo_depth_ __TA_GUARDED(domain_->token()) = 0;
uint64_t external_delay_nsec_ __TA_GUARDED(domain_->token()) = 0;
private:
// Private subclass of dispatcher::Channel that adds DoublyLinkedListable for accounting
class AudioStreamChannel : public dispatcher::Channel,
public fbl::DoublyLinkedListable<fbl::RefPtr<AudioStreamChannel>> {
private:
friend class dispatcher::Channel;
friend class fbl::RefPtr<AudioStreamChannel>;
AudioStreamChannel() = default;
~AudioStreamChannel() = default;
};
// Internal method; called by the general Create template method.
zx_status_t CreateInternal();
// Internal method; called after all initialization is complete to actually
// publish the stream device node.
zx_status_t PublishInternal();
// Stream interface
zx_status_t ProcessStreamChannel(dispatcher::Channel* channel, bool privileged)
__TA_REQUIRES(domain_->token());
void DeactivateStreamChannel(const dispatcher::Channel* channel)
__TA_REQUIRES(domain_->token(), channel_lock_);
zx_status_t OnGetStreamFormats(dispatcher::Channel* channel,
const audio_proto::StreamGetFmtsReq& req) const
__TA_REQUIRES(domain_->token());
zx_status_t OnSetStreamFormat(dispatcher::Channel* channel,
const audio_proto::StreamSetFmtReq& req,
bool privileged)
__TA_REQUIRES(domain_->token());
zx_status_t OnGetGain(dispatcher::Channel* channel, const audio_proto::GetGainReq& req) const
__TA_REQUIRES(domain_->token());
zx_status_t OnSetGain(dispatcher::Channel* channel, const audio_proto::SetGainReq& req)
__TA_REQUIRES(domain_->token());
zx_status_t OnPlugDetect(dispatcher::Channel* channel, const audio_proto::PlugDetectReq& req)
__TA_REQUIRES(domain_->token());
zx_status_t OnGetUniqueId(dispatcher::Channel* channel,
const audio_proto::GetUniqueIdReq& req) const
__TA_REQUIRES(domain_->token());
zx_status_t OnGetString(dispatcher::Channel* channel,
const audio_proto::GetStringReq& req) const
__TA_REQUIRES(domain_->token());
zx_status_t NotifyPlugDetect() __TA_REQUIRES(domain_->token());
// Ring buffer interface
zx_status_t ProcessRingBufferChannel(dispatcher::Channel* channel)
__TA_REQUIRES(domain_->token());
void DeactivateRingBufferChannel(const dispatcher::Channel* channel)
__TA_REQUIRES(domain_->token(), channel_lock_);
zx_status_t OnGetFifoDepth(dispatcher::Channel* channel,
const audio_proto::RingBufGetFifoDepthReq& req)
__TA_REQUIRES(domain_->token());
zx_status_t OnGetBuffer(dispatcher::Channel* channel,
const audio_proto::RingBufGetBufferReq& req)
__TA_REQUIRES(domain_->token());
zx_status_t OnStart(dispatcher::Channel* channel, const audio_proto::RingBufStartReq& req)
__TA_REQUIRES(domain_->token());
zx_status_t OnStop(dispatcher::Channel* channel, const audio_proto::RingBufStopReq& req)
__TA_REQUIRES(domain_->token());
// Stream and ring buffer channel state.
fbl::Mutex channel_lock_ __TA_ACQUIRED_AFTER(domain_->token());
fbl::RefPtr<dispatcher::Channel> stream_channel_ __TA_GUARDED(channel_lock_);
fbl::RefPtr<dispatcher::Channel> rb_channel_ __TA_GUARDED(channel_lock_);
fbl::DoublyLinkedList<fbl::RefPtr<AudioStreamChannel>> plug_notify_channels_
__TA_GUARDED(domain_->token());
// Plug capabilities default to hardwired, if not changed by a child class.
audio_pd_notify_flags_t pd_flags_ __TA_GUARDED(domain_->token())
= AUDIO_PDNF_HARDWIRED | AUDIO_PDNF_PLUGGED;
zx_time_t plug_time_ __TA_GUARDED(domain_->token()) = 0;
// State used for protocol enforcement.
bool rb_started_ __TA_GUARDED(domain_->token()) = false;
bool rb_fetched_ __TA_GUARDED(domain_->token()) = false;
std::atomic<uint32_t> expected_notifications_per_ring_{0};
};
} // namespace audio