blob: 76389abc117e9441b1d7ad3f08eb048ae98c9ab2 [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 "garnet/bin/media/audio_core/audio_driver.h"
#include <audio-proto-utils/format-utils.h>
#include <cstdio>
#include "garnet/bin/media/audio_core/driver_utils.h"
#include "lib/fidl/cpp/clone.h"
#include "lib/fxl/logging.h"
namespace media::audio {
static constexpr zx_txid_t TXID = 1;
static constexpr zx_duration_t kDefaultShortCmdTimeout = ZX_MSEC(250);
static constexpr zx_duration_t kDefaultLongCmdTimeout = ZX_SEC(3);
AudioDriver::AudioDriver(AudioDevice* owner) : owner_(owner) {
FXL_DCHECK(owner_ != nullptr);
stream_channel_ = ::dispatcher::Channel::Create();
rb_channel_ = ::dispatcher::Channel::Create();
cmd_timeout_ = ::dispatcher::Timer::Create();
}
zx_status_t AudioDriver::Init(zx::channel stream_channel) {
FXL_DCHECK(state_ == State::Uninitialized);
if ((stream_channel_ == nullptr) || (rb_channel_ == nullptr) ||
(cmd_timeout_ == nullptr)) {
return ZX_ERR_NO_RESOURCES;
}
// Fetch the KOID of our stream channel. We will end up using this unique ID
// as our device's device token.
zx_status_t res;
zx_info_handle_basic_t sc_info;
res = zx_object_get_info(stream_channel.get(), ZX_INFO_HANDLE_BASIC, &sc_info,
sizeof(sc_info), nullptr, nullptr);
if (res != ZX_OK) {
FXL_LOG(ERROR) << "Failed to to fetch stream channel KOID (res " << res
<< ")";
return res;
}
stream_channel_koid_ = sc_info.koid;
// Activate the stream channel.
::dispatcher::Channel::ProcessHandler process_handler(
[this](::dispatcher::Channel* channel) -> zx_status_t {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
FXL_DCHECK(stream_channel_.get() == channel);
return ProcessStreamChannelMessage();
});
::dispatcher::Channel::ChannelClosedHandler channel_closed_handler(
[this](const ::dispatcher::Channel* channel) {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
FXL_DCHECK(stream_channel_.get() == channel);
ShutdownSelf("Stream channel closed unexpectedly");
});
res = stream_channel_->Activate(
std::move(stream_channel), owner_->mix_domain_,
std::move(process_handler), std::move(channel_closed_handler));
if (res != ZX_OK) {
FXL_LOG(ERROR) << "Failed to activate stream channel for AudioDriver! "
<< "(res " << res << ")";
return res;
}
// Activate the command timeout timer.
::dispatcher::Timer::ProcessHandler cmd_timeout_handler(
[this](::dispatcher::Timer* timer) -> zx_status_t {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
FXL_DCHECK(cmd_timeout_.get() == timer);
ShutdownSelf("Unexpected command timeout");
return ZX_OK;
});
res = cmd_timeout_->Activate(owner_->mix_domain_,
std::move(cmd_timeout_handler));
if (res != ZX_OK) {
FXL_LOG(ERROR)
<< "Failed to activate command timeout timer for AudioDriver! "
<< "(res " << res << ")";
return res;
}
// We are now initialized, but we don't know any of our fundamental driver
// level info. Things like...
//
// 1) This device's persistent unique ID.
// 2) The list of formats supported by this device.
// 3) The user visible strings for this device (manufacturer, product, etc...)
state_ = State::MissingDriverInfo;
return ZX_OK;
}
void AudioDriver::Cleanup() {
fbl::RefPtr<DriverRingBuffer> ring_buffer;
{
std::lock_guard<std::mutex> lock(ring_buffer_state_lock_);
ring_buffer = std::move(ring_buffer_);
clock_mono_to_ring_pos_bytes_ = TimelineFunction();
ring_buffer_state_gen_.Next();
}
ring_buffer.reset();
stream_channel_->Deactivate();
rb_channel_->Deactivate();
cmd_timeout_->Deactivate();
}
void AudioDriver::SnapshotRingBuffer(RingBufferSnapshot* snapshot) const {
FXL_DCHECK(snapshot);
std::lock_guard<std::mutex> lock(ring_buffer_state_lock_);
snapshot->ring_buffer = ring_buffer_;
snapshot->clock_mono_to_ring_pos_bytes = clock_mono_to_ring_pos_bytes_;
snapshot->position_to_end_fence_frames =
owner_->is_input() ? fifo_depth_frames() : 0;
snapshot->end_fence_to_start_fence_frames = end_fence_to_start_fence_frames_;
snapshot->gen_id = ring_buffer_state_gen_.get();
}
fuchsia::media::AudioStreamTypePtr AudioDriver::GetSourceFormat() const {
std::lock_guard<std::mutex> lock(configured_format_lock_);
if (!configured_format_)
return nullptr;
fuchsia::media::AudioStreamTypePtr result;
fidl::Clone(configured_format_, &result);
return result;
}
zx_status_t AudioDriver::GetDriverInfo() {
// TODO(johngro) : Figure out a better way to assert this!
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
// We have to be operational in order to fetch supported formats.
if (!operational()) {
FXL_LOG(ERROR)
<< "Cannot fetch supported formats while non-operational (state = "
<< static_cast<uint32_t>(state_) << ")";
return ZX_ERR_BAD_STATE;
}
// If we are already in the process of fetching our initial driver info, just
// get out now. We will inform our owner when the process completes.
if (fetching_driver_info()) {
return ZX_OK;
}
// Send the commands to do the following.
//
// 1) Fetch our persistent unique ID.
// 2) Fetch our manufacturer string.
// 3) Fetch our product string.
// 4) Fetch our current gain state/caps
// 5) Fetch our supported format list.
// Step #1, fetch unique IDs.
{
audio_stream_cmd_get_formats_req_t req;
req.hdr.cmd = AUDIO_STREAM_CMD_GET_UNIQUE_ID;
req.hdr.transaction_id = TXID;
zx_status_t res = stream_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to request unique ID.", res);
return res;
}
}
// Steps #2-3, fetch strings.
static const audio_stream_string_id_t kStringsToFetch[] = {
AUDIO_STREAM_STR_ID_MANUFACTURER,
AUDIO_STREAM_STR_ID_PRODUCT,
};
for (const auto string_id : kStringsToFetch) {
audio_stream_cmd_get_string_req_t req;
req.hdr.cmd = AUDIO_STREAM_CMD_GET_STRING;
req.hdr.transaction_id = TXID;
req.id = string_id;
zx_status_t res = stream_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to request unique ID.", res);
return res;
}
}
// Step #4. Fetch our current gain state.
{
audio_stream_cmd_get_gain_req_t req;
req.hdr.cmd = AUDIO_STREAM_CMD_GET_GAIN;
req.hdr.transaction_id = TXID;
zx_status_t res = stream_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to request gain state.", res);
return res;
}
}
// Step #5. Fetch our gain state.
{
FXL_DCHECK(format_ranges_.empty());
// Actually send the request to the driver.
audio_stream_cmd_get_formats_req_t req;
req.hdr.cmd = AUDIO_STREAM_CMD_GET_FORMATS;
req.hdr.transaction_id = TXID;
zx_status_t res = stream_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to request supported format list.", res);
return res;
}
}
// Setup our command timeout.
fetch_driver_info_timeout_ = zx_deadline_after(kDefaultShortCmdTimeout);
SetupCommandTimeout();
return ZX_OK;
}
zx_status_t AudioDriver::Configure(uint32_t frames_per_second,
uint32_t channels,
fuchsia::media::AudioSampleFormat fmt,
zx_duration_t min_ring_buffer_duration) {
// TODO(johngro) : Figure out a better way to assert this!
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
// Sanity check arguments.
audio_sample_format_t driver_format;
if (!driver_utils::AudioSampleFormatToDriverSampleFormat(fmt,
&driver_format)) {
FXL_LOG(ERROR) << "Failed to convert Fmt 0x" << std::hex
<< static_cast<uint32_t>(fmt) << " to driver format.";
return ZX_ERR_INVALID_ARGS;
}
if (channels > std::numeric_limits<uint16_t>::max()) {
FXL_LOG(ERROR) << "Bad channel count: " << channels;
return ZX_ERR_INVALID_ARGS;
}
// TODO(johngro) : sanity check the min_ring_buffer_sz.
// Check our known format list for compatibility.
bool found_format = false;
for (const auto& fmt_range : format_ranges_) {
found_format = ::audio::utils::FormatIsCompatible(
frames_per_second, channels, driver_format, fmt_range);
if (found_format) {
break;
}
}
if (!found_format) {
FXL_LOG(ERROR) << "No compatible format range found when setting format to "
<< frames_per_second << " Hz " << channels << " Ch Fmt 0x"
<< std::hex << static_cast<uint32_t>(fmt);
return ZX_ERR_INVALID_ARGS;
}
// We must be in the Unconfigured state in order to change formats.
// TODO(johngro): Improve this. We should permit changing formats if we are
// in either the Unconfigured or Configured state.
if (state_ != State::Unconfigured) {
FXL_LOG(ERROR) << "Bad state while attempting to configure for "
<< frames_per_second << " Hz " << channels << " Ch Fmt 0x"
<< std::hex << static_cast<uint32_t>(fmt)
<< " (state = " << static_cast<uint32_t>(state_) << ")";
return ZX_ERR_BAD_STATE;
}
// Record the details of our intended target format
frames_per_sec_ = frames_per_second;
channel_count_ = static_cast<uint16_t>(channels);
sample_format_ = driver_format;
bytes_per_frame_ =
::audio::utils::ComputeFrameSize(channel_count_, sample_format_);
;
min_ring_buffer_duration_ = min_ring_buffer_duration;
// Start the process of configuring by sending the message to set the format.
audio_stream_cmd_set_format_req_t req;
req.hdr.cmd = AUDIO_STREAM_CMD_SET_FORMAT;
req.hdr.transaction_id = TXID;
req.frames_per_second = frames_per_sec_;
req.channels = channel_count_;
req.sample_format = sample_format_;
{
std::lock_guard<std::mutex> lock(configured_format_lock_);
configured_format_ = fuchsia::media::AudioStreamType::New();
configured_format_->sample_format = fmt;
configured_format_->channels = channels;
configured_format_->frames_per_second = frames_per_second;
}
zx_status_t res = stream_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to send set format command", res);
return res;
}
// Change state, setup our command timeout and we are finished.
state_ = State::Configuring_SettingFormat;
configuration_timeout_ = zx_deadline_after(kDefaultLongCmdTimeout);
SetupCommandTimeout();
return ZX_OK;
}
zx_status_t AudioDriver::Start() {
// TODO(johngro) : Figure out a better way to assert this!
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
// In order to start, we must be in the Configured state.
//
// Note: Attempting to start while already started is considered to be an
// error because (since we are already started) we will never deliver the
// OnDriverStartComplete callback, and it would be confisuing to do so from
// within the call to start itself (before the user's call to Start even
// returned)
if (state_ != State::Configured) {
FXL_LOG(ERROR) << "Bad state while attempting start (state = "
<< static_cast<uint32_t>(state_) << ")";
return ZX_ERR_BAD_STATE;
}
// Send the command to start the ring buffer.
audio_rb_cmd_start_req_t req;
req.hdr.cmd = AUDIO_RB_CMD_START;
req.hdr.transaction_id = TXID;
zx_status_t res = rb_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to send set start command", res);
return res;
}
// Change state, setup our command timeout and we are finished.
state_ = State::Starting;
configuration_timeout_ = zx_deadline_after(kDefaultShortCmdTimeout);
SetupCommandTimeout();
return ZX_OK;
}
zx_status_t AudioDriver::Stop() {
// TODO(johngro) : Figure out a better way to assert this!
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
// In order to stop, we must be in the Started state.
// TODO(johngro): consider relaxing this restriction to make Stop completely
// idempotent. Care would to be taken to handle the case where a user
// attempts to stop while a start operation is in flight but has not
// completed. Allowing multiple start/stop operations to be in flight
// simultaneously could get quite confusing.
if (state_ != State::Started) {
FXL_LOG(ERROR) << "Bad state while attempting stop (state = "
<< static_cast<uint32_t>(state_) << ")";
return ZX_ERR_BAD_STATE;
}
// Invalidate our timeline transformation here. To outside observers, we are
// now stopped.
{
std::lock_guard<std::mutex> lock(ring_buffer_state_lock_);
clock_mono_to_ring_pos_bytes_ = TimelineFunction();
ring_buffer_state_gen_.Next();
}
// Send the command to stop the ring buffer.
audio_rb_cmd_start_req_t req;
req.hdr.cmd = AUDIO_RB_CMD_STOP;
req.hdr.transaction_id = TXID;
zx_status_t res = rb_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to send set stop command", res);
return res;
}
// Since we were just recently in steady state, we should be able to assert
// that we have no configuration timeout at this point.
FXL_DCHECK(configuration_timeout_ == ZX_TIME_INFINITE);
// We are now in the process of stopping.
state_ = State::Stopping;
configuration_timeout_ = zx_deadline_after(kDefaultShortCmdTimeout);
SetupCommandTimeout();
return ZX_OK;
}
zx_status_t AudioDriver::SetPlugDetectEnabled(bool enabled) {
// TODO(johngro) : Figure out a better way to assert this!
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
if (enabled == pd_enabled_) {
return ZX_OK;
}
audio_stream_cmd_plug_detect_req_t req;
if (enabled) {
req.hdr.cmd = AUDIO_STREAM_CMD_PLUG_DETECT;
req.flags = AUDIO_PDF_ENABLE_NOTIFICATIONS;
pd_enable_timeout_ = zx_deadline_after(kDefaultShortCmdTimeout);
} else {
req.hdr.cmd = static_cast<audio_cmd_t>(AUDIO_STREAM_CMD_PLUG_DETECT |
AUDIO_FLAG_NO_ACK);
req.flags = AUDIO_PDF_DISABLE_NOTIFICATIONS;
pd_enable_timeout_ = ZX_TIME_INFINITE;
}
req.hdr.transaction_id = TXID;
zx_status_t res = stream_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to request send plug state request", res);
return res;
}
pd_enabled_ = enabled;
SetupCommandTimeout();
return ZX_OK;
}
zx_status_t AudioDriver::ReadMessage(
const fbl::RefPtr<::dispatcher::Channel>& channel, void* buf,
uint32_t buf_size, uint32_t* bytes_read_out, zx::handle* handle_out) {
FXL_DCHECK(buf != nullptr);
FXL_DCHECK(bytes_read_out != nullptr);
FXL_DCHECK(handle_out != nullptr);
FXL_DCHECK(buf_size >= sizeof(audio_cmd_hdr_t));
if (!operational()) {
return ZX_ERR_BAD_STATE;
}
zx_status_t res;
res = channel->Read(buf, buf_size, bytes_read_out, handle_out);
if (res != ZX_OK) {
ShutdownSelf("Error attempting to read channel response", res);
return res;
}
if (*bytes_read_out < sizeof(audio_cmd_hdr_t)) {
FXL_LOG(ERROR) << "Channel response is too small to hold even a "
<< "message header (" << *bytes_read_out << " < "
<< sizeof(audio_cmd_hdr_t) << ").";
ShutdownSelf();
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
#define CHECK_RESP(_ioctl, _payload, _expect_handle, _is_notif) \
do { \
if ((_expect_handle) != rxed_handle.is_valid()) { \
FXL_LOG(ERROR) << ((_expect_handle) ? "Missing" : "Unexpected") \
<< " handle in " #_ioctl " response"; \
return ZX_ERR_INVALID_ARGS; \
} \
if ((msg.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID) != \
(_is_notif)) { \
FXL_LOG(ERROR) << "Bad txn id " << msg.hdr.transaction_id \
<< " in " #_ioctl " response"; \
return ZX_ERR_INVALID_ARGS; \
} \
if (bytes_read != sizeof(msg._payload)) { \
FXL_LOG(ERROR) << "Bad " #_ioctl " response length (" << bytes_read \
<< " != " << sizeof(msg._payload) << ")"; \
return ZX_ERR_INVALID_ARGS; \
} \
} while (0)
zx_status_t AudioDriver::ProcessStreamChannelMessage() {
zx_status_t res;
zx::handle rxed_handle;
uint32_t bytes_read;
union {
audio_cmd_hdr_t hdr;
audio_stream_cmd_get_unique_id_resp_t get_unique_id;
audio_stream_cmd_get_string_resp_t get_string;
audio_stream_cmd_get_gain_resp_t get_gain;
audio_stream_cmd_get_formats_resp_t get_formats;
audio_stream_cmd_set_format_resp_t set_format;
audio_stream_cmd_plug_detect_resp_t pd_resp;
audio_stream_plug_detect_notify_t pd_notify;
} msg;
static_assert(sizeof(msg) <= 256,
"Message buffer is becoming too large to hold on the stack!");
res = ReadMessage(stream_channel_, &msg, sizeof(msg), &bytes_read,
&rxed_handle);
if (res != ZX_OK) {
return res;
}
bool plug_state;
switch (msg.hdr.cmd) {
case AUDIO_STREAM_CMD_GET_UNIQUE_ID:
CHECK_RESP(AUDIO_STREAM_CMD_GET_UNIQUE_ID, get_unique_id, false, false);
persistent_unique_id_ = msg.get_unique_id.unique_id;
res = OnDriverInfoFetched(kDriverInfoHasUniqueId);
break;
case AUDIO_STREAM_CMD_GET_STRING:
CHECK_RESP(AUDIO_STREAM_CMD_GET_STRING, get_string, false, false);
res = ProcessGetStringResponse(msg.get_string);
break;
case AUDIO_STREAM_CMD_GET_GAIN:
CHECK_RESP(AUDIO_STREAM_CMD_GET_GAIN, get_gain, false, false);
res = ProcessGetGainResponse(msg.get_gain);
break;
case AUDIO_STREAM_CMD_GET_FORMATS:
CHECK_RESP(AUDIO_STREAM_CMD_GET_FORMATS, get_formats, false, false);
res = ProcessGetFormatsResponse(msg.get_formats);
break;
case AUDIO_STREAM_CMD_SET_FORMAT:
CHECK_RESP(AUDIO_STREAM_CMD_SET_FORMAT, set_format, true, false);
res = ProcessSetFormatResponse(msg.set_format,
zx::channel(rxed_handle.release()));
break;
case AUDIO_STREAM_CMD_PLUG_DETECT:
CHECK_RESP(AUDIO_STREAM_CMD_PLUG_DETECT, pd_resp, false, false);
if ((msg.pd_resp.flags & AUDIO_PDNF_HARDWIRED) != 0) {
plug_state = true;
} else {
plug_state = ((msg.pd_resp.flags & AUDIO_PDNF_PLUGGED) != 0);
if ((msg.pd_resp.flags & AUDIO_PDNF_CAN_NOTIFY) == 0) {
// TODO(johngro) : If we ever encounter hardware which must be polled
// in order for plug detection to function properly, we should set up
// a timer to periodically poll the plug state instead of just
// assuming that the output is always plugged in.
FXL_LOG(WARNING)
<< "Stream is incapable of async plug detection notifications. "
"Assuming that the stream is always plugged in for now.";
plug_state = true;
}
}
ReportPlugStateChange(plug_state, msg.pd_resp.plug_state_time);
pd_enable_timeout_ = ZX_TIME_INFINITE;
SetupCommandTimeout();
break;
case AUDIO_STREAM_PLUG_DETECT_NOTIFY:
CHECK_RESP(AUDIO_STREAM_CMD_PLUG_DETECT, pd_resp, false, true);
plug_state = ((msg.pd_resp.flags & AUDIO_PDNF_PLUGGED) != 0);
ReportPlugStateChange(plug_state, msg.pd_resp.plug_state_time);
break;
default:
FXL_LOG(ERROR) << "Unrecognized stream channel response 0x" << std::hex
<< msg.hdr.cmd;
return ZX_ERR_BAD_STATE;
}
if (res != ZX_OK) {
ShutdownSelf("Error while processing stream channel message", res);
}
return res;
}
zx_status_t AudioDriver::ProcessRingBufferChannelMessage() {
zx_status_t res;
zx::handle rxed_handle;
uint32_t bytes_read;
union {
audio_cmd_hdr_t hdr;
audio_rb_cmd_get_fifo_depth_resp_t get_fifo_depth;
audio_rb_cmd_get_buffer_resp_t get_buffer;
audio_rb_cmd_start_resp_t start;
audio_rb_cmd_stop_resp_t stop;
} msg;
static_assert(sizeof(msg) <= 256,
"Message buffer is becoming too large to hold on the stack!");
res = ReadMessage(rb_channel_, &msg, sizeof(msg), &bytes_read, &rxed_handle);
if (res != ZX_OK) {
return res;
}
switch (msg.hdr.cmd) {
case AUDIO_RB_CMD_GET_FIFO_DEPTH:
CHECK_RESP(AUDIO_RB_CMD_GET_FIFO_DEPTH, get_fifo_depth, false, false);
res = ProcessGetFifoDepthResponse(msg.get_fifo_depth);
break;
case AUDIO_RB_CMD_GET_BUFFER:
CHECK_RESP(AUDIO_RB_CMD_GET_BUFFER, get_buffer, true, false);
res = ProcessGetBufferResponse(msg.get_buffer,
zx::vmo(rxed_handle.release()));
break;
case AUDIO_RB_CMD_START:
CHECK_RESP(AUDIO_RB_CMD_START, start, false, false);
res = ProcessStartResponse(msg.start);
break;
case AUDIO_RB_CMD_STOP:
CHECK_RESP(AUDIO_RB_CMD_STOP, stop, false, false);
res = ProcessStopResponse(msg.stop);
break;
default:
FXL_LOG(ERROR) << "Unrecognized ring buffer channel response 0x"
<< std::hex << msg.hdr.cmd;
return ZX_ERR_BAD_STATE;
}
if (res != ZX_OK) {
ShutdownSelf("Error while processing ring buffer message", res);
}
return res;
}
#undef CHECK_RESP
zx_status_t AudioDriver::ProcessGetStringResponse(
audio_stream_cmd_get_string_resp_t& resp) {
std::string* tgt_string;
uint32_t info_bit;
if (state_ != State::MissingDriverInfo) {
FXL_LOG(ERROR) << "Bad state (" << static_cast<uint32_t>(state_)
<< ") while handling get string response.";
return ZX_ERR_BAD_STATE;
}
if (resp.result != ZX_OK) {
FXL_LOG(WARNING) << "Error ( " << resp.result
<< ") attempting to fetch string id " << resp.id
<< ". Replacing with <unknown>.";
resp.strlen = static_cast<uint32_t>(snprintf(
reinterpret_cast<char*>(resp.str), sizeof(resp.str), "<unknown>"));
}
switch (resp.id) {
case AUDIO_STREAM_STR_ID_MANUFACTURER:
info_bit = kDriverInfoHasMfrStr;
tgt_string = &manufacturer_name_;
break;
case AUDIO_STREAM_STR_ID_PRODUCT:
info_bit = kDriverInfoHasProdStr;
tgt_string = &product_name_;
break;
default:
FXL_LOG(ERROR) << "Unrecognized string id (" << resp.id << ").";
return ZX_ERR_INVALID_ARGS;
}
if (resp.strlen > sizeof(resp.str)) {
FXL_LOG(ERROR) << "Bad string length " << resp.strlen
<< " attempting to fetch string id " << resp.id << ".";
return ZX_ERR_INTERNAL;
}
// Stash the string we just received and update our progress in fetching our
// initial driver info.
FXL_DCHECK(tgt_string != nullptr);
tgt_string->assign(reinterpret_cast<char*>(resp.str), resp.strlen);
return OnDriverInfoFetched(info_bit);
}
zx_status_t AudioDriver::ProcessGetGainResponse(
audio_stream_cmd_get_gain_resp_t& resp) {
hw_gain_state_.cur_mute = resp.cur_mute;
hw_gain_state_.cur_agc = resp.cur_agc;
hw_gain_state_.cur_gain = resp.cur_gain;
hw_gain_state_.can_mute = resp.can_mute;
hw_gain_state_.can_agc = resp.can_agc;
hw_gain_state_.min_gain = resp.min_gain;
hw_gain_state_.max_gain = resp.max_gain;
hw_gain_state_.gain_step = resp.gain_step;
return OnDriverInfoFetched(kDriverInfoHasGainState);
}
zx_status_t AudioDriver::ProcessGetFormatsResponse(
const audio_stream_cmd_get_formats_resp_t& resp) {
if (!fetching_driver_info()) {
FXL_LOG(ERROR) << "Received unsolicited get formats response.";
return ZX_ERR_BAD_STATE;
}
// Is this the first response? If so, resize our format vector
// before proceeding.
if (resp.first_format_range_ndx == 0) {
format_ranges_.reserve(resp.format_range_count);
}
// Sanity checks
if (resp.first_format_range_ndx >= resp.format_range_count) {
FXL_LOG(ERROR) << "Bad format range index in get formats response! ("
<< resp.first_format_range_ndx
<< " >= " << resp.format_range_count;
return ZX_ERR_INVALID_ARGS;
}
if (resp.first_format_range_ndx != format_ranges_.size()) {
FXL_LOG(ERROR) << "Out of order message in get formats response! ("
<< resp.first_format_range_ndx
<< " != " << format_ranges_.size();
return ZX_ERR_INVALID_ARGS;
}
// Add this set of formats to our list.
uint16_t todo =
std::min<uint16_t>(resp.format_range_count - resp.first_format_range_ndx,
AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE);
for (uint16_t i = 0; i < todo; ++i) {
format_ranges_.emplace_back(resp.format_ranges[i]);
}
// Record the fact that we have now fetched our format list. This will handle
// transitioning to the Unconfigured state and letting our owner know if we
// have managed to fetch all of the initial driver info we need to operate.
return OnDriverInfoFetched(kDriverInfoHasFormats);
}
zx_status_t AudioDriver::ProcessSetFormatResponse(
const audio_stream_cmd_set_format_resp_t& resp, zx::channel rb_channel) {
if (state_ != State::Configuring_SettingFormat) {
FXL_LOG(ERROR) << "Received unexpected set format response while in state "
<< static_cast<uint32_t>(state_);
return ZX_ERR_BAD_STATE;
}
if (resp.result != ZX_OK) {
FXL_LOG(WARNING) << "Error attempting to set format: " << frames_per_sec_
<< "Hz " << channel_count_ << "-Ch 0x" << std::hex
<< sample_format_ << "(res " << std::dec << resp.result
<< ")";
return resp.result;
}
// TODO(johngro) : See MTWN-61. Update audio ins and outputs to take external
// delay into account when sampling.
external_delay_nsec_ = resp.external_delay_nsec;
// Activate out ring buffer channel in our execution domain.
::dispatcher::Channel::ProcessHandler process_handler(
[this](::dispatcher::Channel* channel) -> zx_status_t {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
FXL_DCHECK(rb_channel_.get() == channel);
return ProcessRingBufferChannelMessage();
});
::dispatcher::Channel::ChannelClosedHandler channel_closed_handler(
[this](const ::dispatcher::Channel* channel) {
OBTAIN_EXECUTION_DOMAIN_TOKEN(token, owner_->mix_domain_);
FXL_DCHECK(rb_channel_.get() == channel);
ShutdownSelf("Ring buffer channel closed unexpectedly");
});
zx_status_t res;
res = rb_channel_->Activate(std::move(rb_channel), owner_->mix_domain_,
std::move(process_handler),
std::move(channel_closed_handler));
if (res != ZX_OK) {
FXL_LOG(ERROR) << "Failed to activate ring buffer channel (res = " << res
<< ")";
return res;
}
// Fetch the fifo depth of the ring buffer we just got back. This determines
// how far ahead of the current playout position (in bytes) the hardware may
// read. We need to know this number in order to size the ring buffer vmo
// appropriately
audio_rb_cmd_get_fifo_depth_req req;
req.hdr.cmd = AUDIO_RB_CMD_GET_FIFO_DEPTH;
req.hdr.transaction_id = TXID;
res = rb_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
FXL_LOG(ERROR) << "Failed to request ring buffer fifo depth.";
return res;
}
// Things went well, proceed to the next step in the state machine.
state_ = State::Configuring_GettingFifoDepth;
configuration_timeout_ = zx_deadline_after(kDefaultShortCmdTimeout);
SetupCommandTimeout();
return ZX_OK;
}
zx_status_t AudioDriver::ProcessGetFifoDepthResponse(
const audio_rb_cmd_get_fifo_depth_resp_t& resp) {
if (state_ != State::Configuring_GettingFifoDepth) {
FXL_LOG(ERROR) << "Received unexpected fifo depth response while in state "
<< static_cast<uint32_t>(state_);
return ZX_ERR_BAD_STATE;
}
if (resp.result != ZX_OK) {
FXL_LOG(ERROR) << "Error when fetching ring buffer fifo depth (res = "
<< resp.result << ").";
return resp.result;
}
fifo_depth_bytes_ = resp.fifo_depth;
fifo_depth_frames_ =
(fifo_depth_bytes_ + bytes_per_frame_ - 1) / bytes_per_frame_;
// Figure out how many frames we need in our ring buffer.
int64_t min_frames_64 = TimelineRate::Scale(
min_ring_buffer_duration_, bytes_per_frame_ * frames_per_sec_, ZX_SEC(1));
int64_t overhead =
static_cast<int64_t>(fifo_depth_bytes_) + bytes_per_frame_ - 1;
bool overflow =
((min_frames_64 == TimelineRate::kOverflow) ||
(min_frames_64 > (std::numeric_limits<int64_t>::max() - overhead)));
if (!overflow) {
min_frames_64 += overhead;
min_frames_64 /= bytes_per_frame_;
overflow = min_frames_64 > std::numeric_limits<uint32_t>::max();
}
if (overflow) {
FXL_LOG(ERROR)
<< "Overflow while attempting to compute ring buffer size in frames.";
FXL_LOG(ERROR) << "duration : " << min_ring_buffer_duration_;
FXL_LOG(ERROR) << "bytes per frame : " << bytes_per_frame_;
FXL_LOG(ERROR) << "frames per sec : " << frames_per_sec_;
FXL_LOG(ERROR) << "fifo depth : " << fifo_depth_bytes_;
FXL_LOG(ERROR) << "bytes per frame : " << bytes_per_frame_;
return ZX_ERR_INTERNAL;
}
// Request the ring buffer.
audio_rb_cmd_get_buffer_req_t req;
req.hdr.cmd = AUDIO_RB_CMD_GET_BUFFER;
req.hdr.transaction_id = TXID;
req.min_ring_buffer_frames = static_cast<uint32_t>(min_frames_64);
req.notifications_per_ring = 0;
zx_status_t res = rb_channel_->Write(&req, sizeof(req));
if (res != ZX_OK) {
ShutdownSelf("Failed to request ring buffer vmo", res);
return res;
}
state_ = State::Configuring_GettingRingBuffer;
configuration_timeout_ = zx_deadline_after(kDefaultShortCmdTimeout);
SetupCommandTimeout();
return ZX_OK;
}
zx_status_t AudioDriver::ProcessGetBufferResponse(
const audio_rb_cmd_get_buffer_resp_t& resp, zx::vmo rb_vmo) {
if (state_ != State::Configuring_GettingRingBuffer) {
FXL_LOG(ERROR) << "Received unexpected get buffer response while in state "
<< static_cast<uint32_t>(state_);
return ZX_ERR_BAD_STATE;
}
if (resp.result != ZX_OK) {
ShutdownSelf("Error when fetching ring buffer vmo", resp.result);
return resp.result;
}
{
std::lock_guard<std::mutex> lock(ring_buffer_state_lock_);
ring_buffer_ = DriverRingBuffer::Create(std::move(rb_vmo), bytes_per_frame_,
resp.num_ring_buffer_frames,
owner_->is_input());
if (ring_buffer_ == nullptr) {
ShutdownSelf("Failed to allocate and map driver ring buffer");
return ZX_ERR_INTERNAL;
}
FXL_DCHECK(!clock_mono_to_ring_pos_bytes_.invertable());
ring_buffer_state_gen_.Next();
}
// We are now configured. Let our owner know about this important milestone.
state_ = State::Configured;
configuration_timeout_ = ZX_TIME_INFINITE;
SetupCommandTimeout();
owner_->OnDriverConfigComplete();
return ZX_OK;
}
zx_status_t AudioDriver::ProcessStartResponse(
const audio_rb_cmd_start_resp_t& resp) {
if (state_ != State::Starting) {
FXL_LOG(ERROR) << "Received unexpected start response while in state "
<< static_cast<uint32_t>(state_);
return ZX_ERR_BAD_STATE;
}
if (resp.result != ZX_OK) {
ShutdownSelf("Error when starting ring buffer", resp.result);
return resp.result;
}
// Now that we have started up, compute the transformation from clock
// monotonic to the ring buffer position (in bytes) Then update the ring
// buffer state's transformation and bump the generation counter.
TimelineFunction func(0, resp.start_time, frames_per_sec_ * bytes_per_frame_,
ZX_SEC(1));
{
std::lock_guard<std::mutex> lock(ring_buffer_state_lock_);
FXL_DCHECK(!clock_mono_to_ring_pos_bytes_.invertable());
FXL_DCHECK(ring_buffer_ != nullptr);
clock_mono_to_ring_pos_bytes_ = func;
ring_buffer_state_gen_.Next();
}
// We are now configured. Let our owner know about this important milestone.
state_ = State::Started;
configuration_timeout_ = ZX_TIME_INFINITE;
SetupCommandTimeout();
owner_->OnDriverStartComplete();
return ZX_OK;
}
zx_status_t AudioDriver::ProcessStopResponse(
const audio_rb_cmd_stop_resp_t& resp) {
if (state_ != State::Stopping) {
FXL_LOG(ERROR) << "Received unexpected stop response while in state "
<< static_cast<uint32_t>(state_);
return ZX_ERR_BAD_STATE;
}
if (resp.result != ZX_OK) {
ShutdownSelf("Error when stopping ring buffer", resp.result);
return resp.result;
}
// We are now stopped. Let our owner know about this important milestone.
state_ = State::Configured;
configuration_timeout_ = ZX_TIME_INFINITE;
SetupCommandTimeout();
owner_->OnDriverStopComplete();
return ZX_OK;
}
void AudioDriver::ShutdownSelf(const char* debug_reason,
zx_status_t debug_status) {
if (state_ == State::Shutdown) {
return;
}
if (debug_reason != nullptr) {
FXL_LOG(INFO) << "AudioDriver ("
<< (owner_->is_input() ? "input" : "output")
<< ") shutting down: reason = \"" << debug_reason
<< "\" (status = " << debug_status << ")";
}
// Release all of our resources.
Cleanup();
owner_->ShutdownSelf();
state_ = State::Shutdown;
}
void AudioDriver::SetupCommandTimeout() {
zx_time_t timeout;
timeout = fetch_driver_info_timeout_;
timeout = fbl::min(timeout, configuration_timeout_);
timeout = fbl::min(timeout, pd_enable_timeout_);
if (last_set_timeout_ != timeout) {
if (timeout != ZX_TIME_INFINITE) {
cmd_timeout_->Arm(timeout);
} else {
cmd_timeout_->Cancel();
}
last_set_timeout_ = timeout;
}
}
void AudioDriver::ReportPlugStateChange(bool plugged, zx_time_t plug_time) {
{
fbl::AutoLock lock(&plugged_lock_);
plugged_ = plugged;
plug_time_ = plug_time;
}
if (pd_enabled_) {
owner_->OnDriverPlugStateChange(plugged, plug_time);
}
}
zx_status_t AudioDriver::OnDriverInfoFetched(uint32_t info) {
// We should never fetch the same info twice.
if (fetched_driver_info_ & info) {
ShutdownSelf("Duplicate driver info fetch\n");
return ZX_ERR_BAD_STATE;
}
// Record the new piece of info we just fetched.
FXL_DCHECK(state_ == State::MissingDriverInfo);
fetched_driver_info_ |= info;
// Have we finished fetching our initial driver info? If so, cancel the
// timeout, transition to the unconfigured state, and let our owner know that
// we have finished.
if ((fetched_driver_info_ & kDriverInfoHasAll) == kDriverInfoHasAll) {
// We are done. Clear the fetch driver info timeout and let our owner know.
fetch_driver_info_timeout_ = ZX_TIME_INFINITE;
state_ = State::Unconfigured;
SetupCommandTimeout();
owner_->OnDriverInfoFetched();
}
return ZX_OK;
}
zx_status_t AudioDriver::SendSetGain(
const AudioDeviceSettings::GainState& gain_state,
audio_set_gain_flags_t set_flags) {
audio_stream_cmd_set_gain_req_t req;
req.hdr.cmd =
static_cast<audio_cmd_t>(AUDIO_STREAM_CMD_SET_GAIN | AUDIO_FLAG_NO_ACK);
req.hdr.transaction_id = TXID;
// clang-format off
req.flags = static_cast<audio_set_gain_flags_t>(
set_flags |
(gain_state.muted ? AUDIO_SGF_MUTE : 0) |
(gain_state.agc_enabled ? AUDIO_SGF_AGC : 0));
// clang-format on
req.gain = gain_state.gain_db;
return stream_channel_->Write(&req, sizeof(req));
}
} // namespace media::audio