blob: f9f4e354c0e7bf8e44cb37e5db69e722454a02ab [file] [log] [blame]
// Copyright 2017 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.
#include <lib/fidl/llcpp/server.h>
#include <lib/fit/defer.h>
#include <lib/zx/clock.h>
#include <string.h>
#include <algorithm>
#include <limits>
#include <utility>
#include <audio-proto-utils/format-utils.h>
#include <audio-proto/audio-proto.h>
#include <fbl/algorithm.h>
#include <intel-hda/codec-utils/codec-driver-base.h>
#include <intel-hda/codec-utils/stream-base.h>
#include <intel-hda/utils/intel-hda-proto.h>
#include "debug-logging.h"
namespace {
namespace audio_fidl = fuchsia_hardware_audio;
} // namespace
namespace audio {
namespace intel_hda {
namespace codecs {
zx_protocol_device_t IntelHDAStreamBase::STREAM_DEVICE_THUNKS = []() {
zx_protocol_device_t sdt = {};
sdt.version = DEVICE_OPS_VERSION;
sdt.message = [](void* ctx, fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
IntelHDAStreamBase* thiz = static_cast<IntelHDAStreamBase*>(ctx);
DdkTransaction transaction(txn);
fidl::WireDispatch<fuchsia_hardware_audio::Device>(thiz, msg, &transaction);
return transaction.Status();
};
return sdt;
}();
IntelHDAStreamBase::IntelHDAStreamBase(uint32_t id, bool is_input)
: id_(id), is_input_(is_input), loop_(&kAsyncLoopConfigNeverAttachToThread) {
plug_time_ = zx::clock::get_monotonic().get();
snprintf(dev_name_, sizeof(dev_name_), "%s-stream-%03u", is_input_ ? "input" : "output", id_);
loop_.StartThread("intel-hda-stream-loop");
}
IntelHDAStreamBase::~IntelHDAStreamBase() {}
void IntelHDAStreamBase::PrintDebugPrefix() const { printf("[%s] ", dev_name_); }
void IntelHDAStreamBase::SetPersistentUniqueId(const audio_stream_unique_id_t& id) {
fbl::AutoLock obj_lock(&obj_lock_);
SetPersistentUniqueIdLocked(id);
}
void IntelHDAStreamBase::SetPersistentUniqueIdLocked(const audio_stream_unique_id_t& id) {
persistent_unique_id_ = id;
}
zx_status_t IntelHDAStreamBase::Activate(fbl::RefPtr<IntelHDACodecDriverBase>&& parent_codec,
const fbl::RefPtr<Channel>& codec_channel) {
ZX_DEBUG_ASSERT(codec_channel != nullptr);
fbl::AutoLock obj_lock(&obj_lock_);
if (is_active() || (codec_channel_ != nullptr))
return ZX_ERR_BAD_STATE;
ZX_DEBUG_ASSERT(parent_codec_ == nullptr);
// Remember our parent codec and our codec channel. If something goes wrong
// during activation, make sure we let go of these references.
//
// Note; the cleanup lambda needs to have thread analysis turned off because
// the compiler is not quite smart enough to figure out that the obj_lock
// AutoLock will destruct (and release the lock) after the AutoCall runs,
// and that the AutoCall will never leave this scope.
auto cleanup = fit::defer([this]() __TA_NO_THREAD_SAFETY_ANALYSIS {
parent_codec_.reset();
codec_channel_.reset();
});
parent_codec_ = std::move(parent_codec);
codec_channel_ = codec_channel;
// Allow our implementation to send its initial stream setup commands to the
// codec.
zx_status_t res = OnActivateLocked();
if (res != ZX_OK)
return res;
// Request a DMA context
ihda_proto::RequestStreamReq req;
req.hdr.transaction_id = id();
req.hdr.cmd = IHDA_CODEC_REQUEST_STREAM;
req.input = is_input();
res = codec_channel_->Write(&req, sizeof(req));
if (res != ZX_OK)
return res;
cleanup.cancel();
return ZX_OK;
}
void IntelHDAStreamBase::Deactivate() {
{
fbl::AutoLock obj_lock(&obj_lock_);
DEBUG_LOG("Deactivating stream\n");
// Let go of any unsolicited stream tags we may be holding.
if (unsol_tag_count_) {
ZX_DEBUG_ASSERT(parent_codec_ != nullptr);
parent_codec_->ReleaseAllUnsolTags(*this);
unsol_tag_count_ = 0;
}
// Clear out our parent_codec_ pointer. This will mark us as being
// inactive and prevent any new connections from being made.
parent_codec_.reset();
// We should already have been removed from our codec's active stream list
// at this point.
ZX_DEBUG_ASSERT(!this->InContainer());
}
loop_.Shutdown();
{
fbl::AutoLock obj_lock(&obj_lock_);
ZX_DEBUG_ASSERT(stream_channel_ == nullptr);
// Allow our implementation to send the commands needed to tear down the
// widgets which make up this stream.
OnDeactivateLocked();
// If we have been given a DMA stream by the IHDA controller, attempt to
// return it now.
if ((dma_stream_id_ != IHDA_INVALID_STREAM_ID) && (codec_channel_ != nullptr)) {
ihda_proto::ReleaseStreamReq req;
req.hdr.transaction_id = id();
req.hdr.cmd = IHDA_CODEC_RELEASE_STREAM_NOACK, req.stream_id = dma_stream_id_;
codec_channel_->Write(&req, sizeof(req));
dma_stream_id_ = IHDA_INVALID_STREAM_ID;
dma_stream_tag_ = IHDA_INVALID_STREAM_TAG;
}
// Let go of our reference to the codec device channel.
codec_channel_ = nullptr;
// If we had published a device node, remove it now.
if (parent_device_ != nullptr) {
device_async_remove(stream_device_);
parent_device_ = nullptr;
}
}
DEBUG_LOG("Deactivate complete\n");
}
zx_status_t IntelHDAStreamBase::PublishDeviceLocked() {
if (!is_active() || (parent_device_ != nullptr))
return ZX_ERR_BAD_STATE;
ZX_DEBUG_ASSERT(parent_codec_ != nullptr);
// Initialize our device and fill out the protocol hooks
device_add_args_t args = {};
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = dev_name_;
args.ctx = this;
args.ops = &STREAM_DEVICE_THUNKS;
args.proto_id = (is_input() ? ZX_PROTOCOL_AUDIO_INPUT : ZX_PROTOCOL_AUDIO_OUTPUT);
// Publish the device.
zx_status_t res = device_add(parent_codec_->codec_device(), &args, &stream_device_);
if (res != ZX_OK) {
LOG("Failed to add stream device for \"%s\" (res %d)\n", dev_name_, res);
return res;
}
// Record our parent.
parent_device_ = parent_codec_->codec_device();
return ZX_OK;
}
zx_status_t IntelHDAStreamBase::ProcessResponse(const CodecResponse& resp) {
fbl::AutoLock obj_lock(&obj_lock_);
if (!is_active()) {
DEBUG_LOG("Ignoring codec response (0x%08x, 0x%08x) for inactive stream id %u\n", resp.data,
resp.data_ex, id());
return ZX_OK;
}
return resp.unsolicited() ? OnUnsolicitedResponseLocked(resp) : OnSolicitedResponseLocked(resp);
}
zx_status_t IntelHDAStreamBase::ProcessRequestStream(const ihda_proto::RequestStreamResp& resp) {
fbl::AutoLock obj_lock(&obj_lock_);
zx_status_t res;
if (!is_active())
return ZX_ERR_BAD_STATE;
res = SetDMAStreamLocked(resp.stream_id, resp.stream_tag);
if (res != ZX_OK) {
// TODO(johngro) : If we failed to set the DMA info because this stream
// is in the process of shutting down, we really should return the
// stream to the controller.
//
// Right now, we are going to return an error which will cause the lower
// level infrastructure to close the codec device channel. This will
// prevent a leak (the core controller driver will re-claim the stream),
// but it will also ruin all of the other streams in this codec are
// going to end up being destroyed. For simple codec driver who never
// change stream topology, this is probably fine, but for more
// complicated ones it probably is not.
return res;
}
return OnDMAAssignedLocked();
}
zx_status_t IntelHDAStreamBase::ProcessSetStreamFmt(
const ihda_proto::SetStreamFmtResp& codec_resp) {
fbl::AutoLock obj_lock(&obj_lock_);
zx_status_t res = ZX_OK;
// Are we shutting down?
if (!is_active())
return ZX_ERR_BAD_STATE;
// If we don't have a set format operation in flight, or the stream channel
// has been closed, this set format operation has been canceled. Do not
// return an error up the stack; we don't want to close the connection to
// our codec device.
if ((!IsFormatChangeInProgress()) || (stream_channel_ == nullptr))
goto finished;
// Let the implementation send the commands required to finish changing the
// stream format.
res = FinishChangeStreamFormatLocked(encoded_fmt_);
if (res != ZX_OK) {
DEBUG_LOG("Failed to finish set format (enc fmt 0x%04hx res %d)\n", encoded_fmt_, res);
goto finished;
}
finished:
// Something went fatally wrong when trying to send the result back to the
// caller. Close the stream channel.
if ((res != ZX_OK) && (stream_channel_ != nullptr)) {
OnChannelDeactivateLocked(*stream_channel_);
stream_channel_ = nullptr;
}
// Set format operation is finished. There is no reply sent in CreateRingBuffer.
SetFormatChangeInProgress(false);
return ZX_OK;
}
// TODO(johngro) : Refactor this; this sample_format of parameters is 95% the same
// between both the codec and stream base classes.
zx_status_t IntelHDAStreamBase::SendCodecCommandLocked(uint16_t nid, CodecVerb verb, Ack do_ack) {
if (codec_channel_ == nullptr)
return ZX_ERR_BAD_STATE;
ihda_codec_send_corb_cmd_req_t cmd;
cmd.hdr.cmd = (do_ack == Ack::NO) ? IHDA_CODEC_SEND_CORB_CMD_NOACK : IHDA_CODEC_SEND_CORB_CMD;
cmd.hdr.transaction_id = id();
cmd.nid = nid;
cmd.verb = verb.val;
return codec_channel_->Write(&cmd, sizeof(cmd));
}
zx_status_t IntelHDAStreamBase::SetDMAStreamLocked(uint16_t id, uint8_t tag) {
if ((id == IHDA_INVALID_STREAM_ID) || (tag == IHDA_INVALID_STREAM_TAG))
return ZX_ERR_INVALID_ARGS;
ZX_DEBUG_ASSERT((dma_stream_id_ == IHDA_INVALID_STREAM_ID) ==
(dma_stream_tag_ == IHDA_INVALID_STREAM_TAG));
if (dma_stream_id_ != IHDA_INVALID_STREAM_ID)
return ZX_ERR_BAD_STATE;
dma_stream_id_ = id;
dma_stream_tag_ = tag;
return ZX_OK;
}
void IntelHDAStreamBase::GetChannel(GetChannelCompleter::Sync& completer) {
fbl::AutoLock obj_lock(&obj_lock_);
// Do not allow any new connections if we are in the process of shutting down
if (!is_active()) {
completer.Close(ZX_ERR_BAD_STATE);
return;
}
// For now, block new connections if we currently have no privileged
// connection, but there is a SetFormat request in flight to the codec
// driver. We are trying to avoid the following sequence...
//
// 1) A privileged connection starts a set format.
// 2) After we ask the controller to set the format, our privileged channel
// is closed.
// 3) A new user connects.
// 4) The response to the first client's request arrives and gets sent
// to the second client.
// 5) Confusion ensues.
//
// Denying new connections while the old request is in flight avoids this,
// but is generally a terrible solution. What we should really do is tag
// the requests to the codec driver with a unique ID which we can use to
// filter responses. One option might be to split the transaction ID so
// that a portion of the TID is used for stream routing, while another
// portion is used for requests like this.
bool privileged = (stream_channel_ == nullptr);
if (privileged && IsFormatChangeInProgress()) {
completer.Close(ZX_ERR_SHOULD_WAIT);
return;
}
// Attempt to allocate a new driver channel and bind it to us. If we don't
// already have a stream_channel_, flag this channel is the privileged
// connection (The connection which is allowed to do things like change
// formats).
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfig>();
if (!endpoints.is_ok()) {
completer.Close(ZX_ERR_NO_MEMORY);
return;
}
auto [stream_channel_remote, stream_channel_local] = *std::move(endpoints);
fbl::RefPtr<StreamChannel> stream_channel = StreamChannel::Create(this);
if (stream_channel == nullptr) {
completer.Close(ZX_ERR_NO_MEMORY);
return;
}
stream_channels_.push_back(stream_channel);
fidl::OnUnboundFn<fidl::WireInterface<audio_fidl::StreamConfig>> on_unbound =
[this, stream_channel](fidl::WireInterface<audio_fidl::StreamConfig>*, fidl::UnbindInfo,
fidl::ServerEnd<fuchsia_hardware_audio::StreamConfig>) {
fbl::AutoLock channel_lock(&this->obj_lock_);
this->ProcessClientDeactivateLocked(stream_channel.get());
};
fidl::BindServer<fidl::WireInterface<audio_fidl::StreamConfig>>(
loop_.dispatcher(), std::move(stream_channel_local), stream_channel.get(),
std::move(on_unbound));
if (privileged) {
stream_channel_ = stream_channel;
}
completer.Reply(std::move(stream_channel_remote));
}
void IntelHDAStreamBase::GetSupportedFormats(
StreamChannel::GetSupportedFormatsCompleter::Sync& completer) {
fbl::AutoLock channel_lock(&obj_lock_);
if (supported_formats_.size() > std::numeric_limits<uint16_t>::max()) {
LOG("Too many formats (%zu) to send during AUDIO_STREAM_CMD_GET_FORMATS request!\n",
supported_formats_.size());
return;
}
// Build formats compatible with FIDL from a vector of audio_stream_format_range_t.
// Needs to be alive until the reply is sent.
struct FidlCompatibleFormats {
fbl::Vector<uint8_t> number_of_channels;
fbl::Vector<audio_fidl::wire::SampleFormat> sample_formats;
fbl::Vector<uint32_t> frame_rates;
fbl::Vector<uint8_t> valid_bits_per_sample;
fbl::Vector<uint8_t> bytes_per_sample;
};
fbl::Vector<FidlCompatibleFormats> fidl_compatible_formats;
for (auto& i : supported_formats_) {
auto formats = audio::utils::GetAllFormats(i.sample_formats);
ZX_ASSERT(formats.size() >= 1);
for (auto& j : formats) {
fbl::Vector<uint32_t> rates;
// Ignore flags if min and max are equal.
if (i.min_frames_per_second == i.max_frames_per_second) {
rates.push_back(i.min_frames_per_second);
} else {
ZX_DEBUG_ASSERT(!(i.flags & ASF_RANGE_FLAG_FPS_CONTINUOUS));
audio::utils::FrameRateEnumerator enumerator(i);
for (uint32_t rate : enumerator) {
rates.push_back(rate);
}
}
fbl::Vector<uint8_t> number_of_channels;
for (uint8_t j = i.min_channels; j <= i.max_channels; ++j) {
number_of_channels.push_back(j);
}
fidl_compatible_formats.push_back({
std::move(number_of_channels),
{j.format},
std::move(rates),
{j.valid_bits_per_sample},
{j.bytes_per_sample},
});
}
}
fidl::FidlAllocator allocator;
fidl::VectorView<audio_fidl::wire::SupportedFormats> fidl_formats(allocator,
fidl_compatible_formats.size());
for (size_t i = 0; i < fidl_compatible_formats.size(); ++i) {
// Get FIDL PcmSupportedFormats from FIDL compatible vectors.
// Needs to be alive until the reply is sent.
FidlCompatibleFormats& src = fidl_compatible_formats[i];
audio_fidl::wire::PcmSupportedFormats formats;
formats.number_of_channels = ::fidl::VectorView<uint8_t>::FromExternal(
src.number_of_channels.data(), src.number_of_channels.size());
formats.sample_formats = ::fidl::VectorView<audio_fidl::wire::SampleFormat>::FromExternal(
src.sample_formats.data(), src.sample_formats.size());
formats.frame_rates =
::fidl::VectorView<uint32_t>::FromExternal(src.frame_rates.data(), src.frame_rates.size());
formats.bytes_per_sample = ::fidl::VectorView<uint8_t>::FromExternal(
src.bytes_per_sample.data(), src.bytes_per_sample.size());
formats.valid_bits_per_sample = ::fidl::VectorView<uint8_t>::FromExternal(
src.valid_bits_per_sample.data(), src.valid_bits_per_sample.size());
fidl_formats[i].Allocate(allocator);
fidl_formats[i].set_pcm_supported_formats(allocator, std::move(formats));
}
completer.Reply(std::move(fidl_formats));
}
void IntelHDAStreamBase::CreateRingBuffer(
StreamChannel* channel, audio_fidl::wire::Format format,
::fidl::ServerEnd<audio_fidl::RingBuffer> ring_buffer,
StreamChannel::CreateRingBufferCompleter::Sync& completer) {
ZX_DEBUG_ASSERT(channel != nullptr);
ihda_proto::SetStreamFmtReq req;
uint16_t encoded_fmt;
zx_status_t res;
fbl::AutoLock channel_lock(&obj_lock_);
// Only the privileged stream channel is allowed to change the format.
if (channel != stream_channel_.get()) {
LOG("Unprivileged channel cannot set the format");
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
// If we don't have a DMA stream assigned to us, or there is already a set
// format operation in flight, we cannot proceed.
if ((dma_stream_id_ == IHDA_INVALID_STREAM_ID) || IsFormatChangeInProgress()) {
completer.Close(ZX_ERR_BAD_STATE);
return;
}
auto format_pcm = format.pcm_format();
audio_sample_format_t sample_format = audio::utils::GetSampleFormat(
format_pcm.valid_bits_per_sample, 8 * format_pcm.bytes_per_sample);
if (sample_format == 0) {
LOG("Unsupported format: Invalid bits per sample (%u/%u)\n", format_pcm.valid_bits_per_sample,
8 * format_pcm.bytes_per_sample);
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
if (format_pcm.sample_format == audio_fidl::wire::SampleFormat::kPcmFloat) {
sample_format = AUDIO_SAMPLE_FORMAT_32BIT_FLOAT;
if (format_pcm.valid_bits_per_sample != 32 || format_pcm.bytes_per_sample != 4) {
LOG("Unsupported format: Not 32 per sample/channel for float\n");
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
}
if (format_pcm.sample_format == audio_fidl::wire::SampleFormat::kPcmUnsigned) {
sample_format |= AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED;
}
bool found_one = false;
// Check the format for compatibility
for (const auto& fmt : supported_formats_) {
if (audio::utils::FormatIsCompatible(format_pcm.frame_rate,
static_cast<uint16_t>(format_pcm.number_of_channels),
sample_format, fmt)) {
found_one = true;
break;
}
}
if (!found_one) {
LOG("Could not find a suitable format in %s", __PRETTY_FUNCTION__);
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
audio_proto::StreamSetFmtReq fmt = {};
fmt.sample_format = sample_format;
fmt.channels = format_pcm.number_of_channels;
fmt.frames_per_second = format_pcm.frame_rate;
// The upper level stream told us that they support this format, we had
// better be able to encode it into an IHDA format specifier.
res = EncodeStreamFormat(fmt, &encoded_fmt);
if (res != ZX_OK) {
DEBUG_LOG("Failed to encode stream format %u:%hu:%s (res %d)\n", fmt.frames_per_second,
fmt.channels, audio_proto::SampleFormatToString(fmt.sample_format), res);
completer.Close(res);
return;
}
// Let our implementation start the process of a format change. This gives
// it a chance to check the format for compatibility, and send commands to
// quiesce the converters and amplifiers if it approves of the format.
res = BeginChangeStreamFormatLocked(fmt);
if (res != ZX_OK) {
DEBUG_LOG("Stream impl rejected stream format %u:%hu:%s (res %d)\n", fmt.frames_per_second,
fmt.channels, audio_proto::SampleFormatToString(fmt.sample_format), res);
completer.Close(res);
return;
}
// Set the format of DMA stream. This will stop any stream in progress and
// close any connection to its clients. At this point, all of our checks
// are done and we expect success. If anything goes wrong, consider it to
// be a fatal internal error and close the connection to our client by
// returning an error.
ZX_DEBUG_ASSERT(codec_channel_ != nullptr);
req.hdr.cmd = IHDA_CODEC_SET_STREAM_FORMAT;
req.hdr.transaction_id = id();
req.stream_id = dma_stream_id_;
req.format = encoded_fmt;
res = codec_channel_->Write(&req, sizeof(req), ring_buffer.TakeChannel());
if (res != ZX_OK) {
DEBUG_LOG("Failed to write set stream format %u:%hu:%s to codec channel (res %d)\n",
fmt.frames_per_second, fmt.channels,
audio_proto::SampleFormatToString(fmt.sample_format), res);
completer.Close(res);
return;
}
// Success! Record that the format change is in progress.
SetFormatChangeInProgress(true);
encoded_fmt_ = encoded_fmt;
}
void IntelHDAStreamBase::WatchGainState(StreamChannel* channel,
StreamChannel::WatchGainStateCompleter::Sync& completer) {
ZX_DEBUG_ASSERT(!channel->gain_completer_);
channel->gain_completer_ = completer.ToAsync();
fbl::AutoLock obj_lock(&obj_lock_);
OnGetGainLocked(&cur_gain_state_);
// Reply is delayed if there is no change since the last reported gain state.
if (channel->last_reported_gain_state_ != cur_gain_state_) {
fidl::FidlAllocator allocator;
audio_fidl::wire::GainState gain_state(allocator);
if (cur_gain_state_.can_mute) {
gain_state.set_muted(allocator, cur_gain_state_.cur_mute);
}
if (cur_gain_state_.can_agc) {
gain_state.set_agc_enabled(allocator, cur_gain_state_.cur_mute);
}
gain_state.set_gain_db(allocator, cur_gain_state_.cur_gain);
channel->last_reported_gain_state_ = cur_gain_state_;
channel->gain_completer_->Reply(std::move(gain_state));
channel->gain_completer_.reset();
}
}
void IntelHDAStreamBase::SetGain(audio_fidl::wire::GainState target_state,
StreamChannel::SetGainCompleter::Sync& completer) {
fbl::AutoLock obj_lock(&obj_lock_);
OnGetGainLocked(&cur_gain_state_);
// Sanity check the request before passing it along
if (target_state.has_muted() && target_state.muted() && !cur_gain_state_.can_mute) {
LOG("Can't mute\n");
return;
}
if (target_state.has_agc_enabled() && target_state.agc_enabled() && !cur_gain_state_.can_agc) {
LOG("Can't enable AGC\n");
return;
}
if (target_state.has_gain_db() && ((target_state.gain_db() < cur_gain_state_.min_gain) ||
(target_state.gain_db() > cur_gain_state_.max_gain))) {
LOG("Can't set gain outside valid range\n");
return;
}
audio_stream_cmd_set_gain_req_t req = {};
cur_gain_state_.can_mute = target_state.has_muted();
if (cur_gain_state_.can_mute) {
req.flags |= AUDIO_SGF_MUTE_VALID;
if (target_state.muted()) {
req.flags |= AUDIO_SGF_MUTE;
}
cur_gain_state_.cur_mute = target_state.muted();
}
cur_gain_state_.can_agc = target_state.has_agc_enabled();
if (cur_gain_state_.can_agc) {
req.flags |= AUDIO_SGF_AGC_VALID;
if (target_state.agc_enabled()) {
req.flags |= AUDIO_SGF_AGC;
}
cur_gain_state_.cur_agc = target_state.agc_enabled();
}
if (target_state.has_gain_db()) {
req.flags |= AUDIO_SGF_GAIN_VALID;
req.gain = target_state.gain_db();
cur_gain_state_.cur_gain = req.gain;
}
audio_proto::SetGainResp out = {};
OnSetGainLocked(req, &out);
if (out.result != ZX_OK && out.result != ZX_ERR_NOT_SUPPORTED) {
LOG("Error setting the gain state %d\n", out.result);
}
for (auto& channel : stream_channels_) {
if (channel.gain_completer_) {
channel.gain_completer_->Reply(std::move(target_state));
channel.gain_completer_.reset();
}
}
}
void IntelHDAStreamBase::WatchPlugState(StreamChannel* channel,
StreamChannel::WatchPlugStateCompleter::Sync& completer) {
ZX_DEBUG_ASSERT(!channel->plug_completer_);
channel->plug_completer_ = completer.ToAsync();
audio_proto::PlugDetectResp plug = {};
fbl::AutoLock lock(obj_lock());
OnPlugDetectLocked(channel, &plug);
bool plugged = plug.flags & AUDIO_PDNF_PLUGGED;
// Reply is delayed if there is no change since the last reported plugged state.
if (channel->last_reported_plugged_state_ == StreamChannel::Plugged::kNotReported ||
(channel->last_reported_plugged_state_ == StreamChannel::Plugged::kPlugged) != plugged) {
fidl::FidlAllocator allocator;
audio_fidl::wire::PlugState plug_state(allocator);
plug_state.set_plugged(allocator, plugged).set_plug_state_time(allocator, plug.plug_state_time);
channel->last_reported_plugged_state_ =
plugged ? StreamChannel::Plugged::kPlugged : StreamChannel::Plugged::kUnplugged;
channel->plug_completer_->Reply(std::move(plug_state));
channel->plug_completer_.reset();
}
}
void IntelHDAStreamBase::NotifyPlugStateLocked(bool plugged, int64_t plug_time) {
for (auto& channel : stream_channels_) {
if (channel.plug_completer_) {
fidl::FidlAllocator allocator;
audio_fidl::wire::PlugState plug_state(allocator);
plug_state.set_plugged(allocator, plugged).set_plug_state_time(allocator, plug_time);
channel.plug_completer_->Reply(std::move(plug_state));
channel.plug_completer_.reset();
}
}
}
void IntelHDAStreamBase::GetProperties(
StreamChannel* channel,
fidl::WireInterface<audio_fidl::StreamConfig>::GetPropertiesCompleter::Sync& completer) {
fbl::AutoLock obj_lock(&obj_lock_);
fidl::FidlAllocator allocator;
audio_fidl::wire::StreamProperties response(allocator);
fidl::Array<uint8_t, audio_fidl::wire::kUniqueIdSize> unique_id = {};
for (size_t i = 0; i < audio_fidl::wire::kUniqueIdSize; ++i) {
unique_id.data_[i] = persistent_unique_id_.data[i];
}
response.set_unique_id(allocator, unique_id);
response.set_is_input(allocator, is_input());
OnGetGainLocked(&cur_gain_state_);
response.set_can_mute(allocator, cur_gain_state_.can_mute);
response.set_can_agc(allocator, cur_gain_state_.can_agc);
response.set_min_gain_db(allocator, cur_gain_state_.min_gain);
response.set_max_gain_db(allocator, cur_gain_state_.max_gain);
response.set_gain_step_db(allocator, cur_gain_state_.gain_step);
audio_proto::GetStringResp resp_product = {};
audio_proto::GetStringReq req = {};
req.id = AUDIO_STREAM_STR_ID_PRODUCT;
OnGetStringLocked(req, &resp_product);
auto product = fidl::StringView::FromExternal(reinterpret_cast<char*>(resp_product.str),
resp_product.strlen);
response.set_product(fidl::ObjectView<fidl::StringView>::FromExternal(&product));
req.id = AUDIO_STREAM_STR_ID_MANUFACTURER;
audio_proto::GetStringResp resp_manufacturer = {};
OnGetStringLocked(req, &resp_manufacturer);
auto manufacturer = fidl::StringView::FromExternal(reinterpret_cast<char*>(resp_manufacturer.str),
resp_manufacturer.strlen);
response.set_manufacturer(fidl::ObjectView<fidl::StringView>::FromExternal(&manufacturer));
audio_proto::GetClockDomainResp domain_resp = {};
OnGetClockDomainLocked(&domain_resp);
response.set_clock_domain(allocator, domain_resp.clock_domain);
audio_proto::PlugDetectResp plug = {};
OnPlugDetectLocked(channel, &plug);
if (plug.flags & AUDIO_PDNF_CAN_NOTIFY) {
response.set_plug_detect_capabilities(
allocator, audio_fidl::wire::PlugDetectCapabilities::kCanAsyncNotify);
} else if (plug.flags & AUDIO_PDNF_HARDWIRED) {
response.set_plug_detect_capabilities(allocator,
audio_fidl::wire::PlugDetectCapabilities::kHardwired);
}
completer.Reply(std::move(response));
}
void IntelHDAStreamBase::ProcessClientDeactivateLocked(StreamChannel* channel) {
ZX_DEBUG_ASSERT(channel != nullptr);
// Let our subclass know that this channel is going away.
OnChannelDeactivateLocked(*channel);
// Is this the privileged stream channel?
if (stream_channel_.get() == channel) {
stream_channel_.reset();
}
stream_channels_.erase(*channel);
}
zx_status_t IntelHDAStreamBase::AllocateUnsolTagLocked(uint8_t* out_tag) {
if (!parent_codec_)
return ZX_ERR_BAD_STATE;
zx_status_t res = parent_codec_->AllocateUnsolTag(*this, out_tag);
if (res == ZX_OK)
unsol_tag_count_++;
return res;
}
void IntelHDAStreamBase::ReleaseUnsolTagLocked(uint8_t tag) {
ZX_DEBUG_ASSERT(unsol_tag_count_ > 0);
ZX_DEBUG_ASSERT(parent_codec_ != nullptr);
parent_codec_->ReleaseUnsolTag(*this, tag);
unsol_tag_count_--;
}
// TODO(johngro) : Move this out to a utils library?
#define MAKE_RATE(_rate, _base, _mult, _div) \
{ .rate = _rate, .encoded = (_base << 14) | ((_mult - 1) << 11) | ((_div - 1) << 8) }
zx_status_t IntelHDAStreamBase::EncodeStreamFormat(const audio_proto::StreamSetFmtReq& fmt,
uint16_t* encoded_fmt_out) {
ZX_DEBUG_ASSERT(encoded_fmt_out != nullptr);
// See section 3.7.1
// Start with the channel count. Intel HDA DMA streams support between 1
// and 16 channels.
uint32_t channels = fmt.channels - 1;
if ((fmt.channels < 1) || (fmt.channels > 16))
return ZX_ERR_NOT_SUPPORTED;
// Next determine the bit sample_format format
uint32_t bits;
switch (fmt.sample_format) {
case AUDIO_SAMPLE_FORMAT_8BIT:
bits = 0;
break;
case AUDIO_SAMPLE_FORMAT_16BIT:
bits = 1;
break;
case AUDIO_SAMPLE_FORMAT_20BIT_IN32:
bits = 2;
break;
case AUDIO_SAMPLE_FORMAT_24BIT_IN32:
bits = 3;
break;
case AUDIO_SAMPLE_FORMAT_32BIT:
case AUDIO_SAMPLE_FORMAT_32BIT_FLOAT:
bits = 4;
break;
default:
return ZX_ERR_NOT_SUPPORTED;
}
// Finally, determine the base frame rate, as well as the multiplier and
// divisor.
static const struct {
uint32_t rate;
uint32_t encoded;
} RATE_ENCODINGS[] = {
// 48 KHz family
MAKE_RATE(6000, 0, 1, 8),
MAKE_RATE(8000, 0, 1, 6),
MAKE_RATE(9600, 0, 1, 5),
MAKE_RATE(16000, 0, 1, 3),
MAKE_RATE(24000, 0, 1, 2),
MAKE_RATE(32000, 0, 2, 3),
MAKE_RATE(48000, 0, 1, 1),
MAKE_RATE(96000, 0, 2, 1),
MAKE_RATE(144000, 0, 3, 1),
MAKE_RATE(192000, 0, 4, 1),
// 44.1 KHz family
MAKE_RATE(11025, 1, 1, 4),
MAKE_RATE(22050, 1, 1, 2),
MAKE_RATE(44100, 1, 1, 1),
MAKE_RATE(88200, 1, 2, 1),
MAKE_RATE(176400, 1, 4, 1),
};
for (const auto& r : RATE_ENCODINGS) {
if (r.rate == fmt.frames_per_second) {
*encoded_fmt_out = static_cast<uint16_t>(r.encoded | channels | (bits << 4));
return ZX_OK;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
#undef MAKE_RATE
/////////////////////////////////////////////////////////////////////
//
// Default handlers
//
/////////////////////////////////////////////////////////////////////
zx_status_t IntelHDAStreamBase::OnActivateLocked() { return ZX_OK; }
void IntelHDAStreamBase::OnDeactivateLocked() {}
void IntelHDAStreamBase::OnChannelDeactivateLocked(const StreamChannel& channel) {}
zx_status_t IntelHDAStreamBase::OnDMAAssignedLocked() { return PublishDeviceLocked(); }
zx_status_t IntelHDAStreamBase::OnSolicitedResponseLocked(const CodecResponse& resp) {
return ZX_OK;
}
zx_status_t IntelHDAStreamBase::OnUnsolicitedResponseLocked(const CodecResponse& resp) {
return ZX_OK;
}
zx_status_t IntelHDAStreamBase::BeginChangeStreamFormatLocked(
const audio_proto::StreamSetFmtReq& fmt) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t IntelHDAStreamBase::FinishChangeStreamFormatLocked(uint16_t encoded_fmt) {
return ZX_ERR_INTERNAL;
}
void IntelHDAStreamBase::OnGetGainLocked(audio_proto::GainState* out_resp) {
ZX_DEBUG_ASSERT(out_resp != nullptr);
// By default we claim to have a fixed, un-mute-able gain stage.
out_resp->cur_mute = false;
out_resp->cur_agc = false;
out_resp->cur_gain = 0.0;
out_resp->can_mute = false;
out_resp->can_agc = false;
out_resp->min_gain = 0.0;
out_resp->max_gain = 0.0;
out_resp->gain_step = 0.0;
}
void IntelHDAStreamBase::OnSetGainLocked(const audio_proto::SetGainReq& req,
audio_proto::SetGainResp* out_resp) {
// Nothing to do if no response is expected.
if (out_resp == nullptr) {
ZX_DEBUG_ASSERT(req.hdr.cmd & AUDIO_FLAG_NO_ACK);
return;
}
bool illegal_mute = (req.flags & AUDIO_SGF_MUTE_VALID) && (req.flags & AUDIO_SGF_MUTE);
bool illegal_agc = (req.flags & AUDIO_SGF_AGC_VALID) && (req.flags & AUDIO_SGF_AGC);
bool illegal_gain = (req.flags & AUDIO_SGF_GAIN_VALID) && (req.gain != 0.0f);
out_resp->cur_mute = false;
out_resp->cur_gain = 0.0;
out_resp->result = (illegal_mute || illegal_agc || illegal_gain) ? ZX_ERR_INVALID_ARGS : ZX_OK;
}
void IntelHDAStreamBase::OnPlugDetectLocked(StreamChannel* response_channel,
audio_proto::PlugDetectResp* out_resp) {
// Nothing to do if no response is expected.
if (out_resp == nullptr) {
return;
}
ZX_DEBUG_ASSERT(parent_codec_ != nullptr);
out_resp->flags = static_cast<audio_pd_notify_flags_t>(AUDIO_PDNF_HARDWIRED | AUDIO_PDNF_PLUGGED);
out_resp->plug_state_time = parent_codec_->create_time();
}
void IntelHDAStreamBase::OnGetStringLocked(const audio_proto::GetStringReq& req,
audio_proto::GetStringResp* out_resp) {
ZX_DEBUG_ASSERT(out_resp);
switch (req.id) {
case AUDIO_STREAM_STR_ID_MANUFACTURER:
case AUDIO_STREAM_STR_ID_PRODUCT: {
int res =
snprintf(reinterpret_cast<char*>(out_resp->str), sizeof(out_resp->str), "<unknown>");
ZX_DEBUG_ASSERT(res >= 0); // there should be no way for snprintf to fail here.
out_resp->strlen = std::min<uint32_t>(res, sizeof(out_resp->str) - 1);
out_resp->result = ZX_OK;
break;
}
default:
out_resp->strlen = 0;
out_resp->result = ZX_ERR_NOT_FOUND;
break;
}
}
void IntelHDAStreamBase::OnGetClockDomainLocked(audio_proto::GetClockDomainResp* out_resp) {
ZX_DEBUG_ASSERT(out_resp != nullptr);
// By default we claim to be in the MONOTONIC clock domain.
// TODO(mpuryear): if the audio clock might possibly ever be in a different domain than the local
// system clock (either because it is trimmable [unlikely] or uses a different oscillator [even
// less likely]), handle that case here.
out_resp->clock_domain = 0;
}
} // namespace codecs
} // namespace intel_hda
} // namespace audio