| // 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 "src/media/audio/audio_core/audio_driver.h" |
| |
| #include <audio-proto-utils/format-utils.h> |
| |
| #include <cstdio> |
| |
| #include "lib/fidl/cpp/clone.h" |
| #include "src/lib/fxl/logging.h" |
| #include "src/media/audio/audio_core/driver_utils.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 = stream_channel.get_info(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", ZX_ERR_PEER_CLOSED); |
| }); |
| |
| 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", ZX_ERR_TIMED_OUT); |
| 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 string.", 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 list of supported formats. |
| { |
| 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(::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("Channel response too small", ZX_ERR_INVALID_ARGS); |
| 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_.get(), &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_NOTIFY, pd_notify, false, true); |
| plug_state = ((msg.pd_notify.flags & AUDIO_PDNF_PLUGGED) != 0); |
| ReportPlugStateChange(plug_state, msg.pd_notify.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; |
| audio_rb_position_notify_t pos_notify; |
| } msg; |
| static_assert(sizeof(msg) <= 256, |
| "Message buffer is becoming too large to hold on the stack!"); |
| |
| res = ReadMessage(rb_channel_.get(), &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; |
| |
| // Currently we ignore driver-reported position, instead using the system- |
| // internal clock. This message is benign and can be safely ignored, but |
| // because we did not request it, this may indicate some other problem in |
| // the driver state machine. We issue a warning, eat the msg and continue. |
| case AUDIO_RB_POSITION_NOTIFY: |
| CHECK_RESP(AUDIO_RB_POSITION_NOTIFY, pos_notify, false, true); |
| res = ProcessPositionNotify(msg.pos_notify); |
| 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.format_range_count == 0) { |
| FXL_LOG(ERROR) << "Driver reported that it supports no format ranges!"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (resp.first_format_range_ndx >= resp.format_range_count) { |
| FXL_LOG(ERROR) << "Bad format range index in get formats response! (index " |
| << resp.first_format_range_ndx << " should be < total " |
| << 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! (index " |
| << resp.first_format_range_ndx << " != the expected " |
| << 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 AudioCapturers and outputs to take |
| // external delay into account when sampling. |
| external_delay_nsec_ = resp.external_delay_nsec; |
| |
| // Activate our 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"); |
| }); |
| |
| 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", |
| ZX_ERR_NO_MEMORY); |
| return ZX_ERR_NO_MEMORY; |
| } |
| FXL_DCHECK(!clock_mono_to_ring_pos_bytes_.invertible()); |
| |
| 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_.invertible()); |
| FXL_DCHECK(ring_buffer_ != nullptr); |
| clock_mono_to_ring_pos_bytes_ = func; |
| ring_buffer_state_gen_.Next(); |
| } |
| |
| // We are now started. 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; |
| } |
| |
| zx_status_t AudioDriver::ProcessPositionNotify( |
| const audio_rb_position_notify_t& notify) { |
| FXL_DLOG(INFO) << "Unsolicited ring buffer position notification! Time:" |
| << zx_clock_get_monotonic() |
| << " Pos:" << notify.ring_buffer_pos; |
| |
| // Although this notification was unsolicited and unexpected, we only complain |
| // (and only on debug builds), rather than abort-closing the channel. |
| return ZX_OK; |
| } |
| |
| void AudioDriver::ShutdownSelf(const char* reason, zx_status_t status) { |
| if (state_ == State::Shutdown) { |
| return; |
| } |
| |
| if (reason != nullptr) { |
| FXL_LOG(INFO) << (owner_->is_input() ? " Input" : "Output") |
| << " shutting down '" << reason << "', status:" << status; |
| } |
| |
| // Our owner will call our Cleanup function within this call. |
| 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", ZX_ERR_BAD_STATE); |
| 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 |