| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fbl/auto_call.h> |
| #include <fbl/limits.h> |
| |
| #include <zircon/device/audio.h> |
| |
| #include <audio-proto-utils/format-utils.h> |
| |
| #include "intel-audio-dsp.h" |
| #include "intel-dsp-stream.h" |
| |
| namespace audio { |
| namespace intel_hda { |
| |
| IntelDspStream::IntelDspStream(uint32_t id, bool is_input, const DspPipeline& pipeline) |
| : IntelHDAStreamBase(id, is_input), pipeline_(pipeline) { |
| snprintf(log_prefix_, sizeof(log_prefix_), "IHDA DSP %cStream #%u", is_input ? 'I' : 'O', id); |
| } |
| |
| zx_status_t IntelDspStream::ProcessSetStreamFmt(const ihda_proto::SetStreamFmtResp& codec_resp, |
| zx::channel&& ring_buffer_channel) { |
| ZX_DEBUG_ASSERT(ring_buffer_channel.is_valid()); |
| |
| fbl::AutoLock lock(obj_lock()); |
| audio_proto::StreamSetFmtResp resp = { }; |
| zx_status_t res = ZX_OK; |
| |
| // Are we shutting down? |
| if (!is_active()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // The DSP needs to coordinate with ring buffer commands. Set up an additional |
| // channel to intercept messages on the ring buffer channel. |
| zx::channel client_endpoint; |
| res = CreateClientRingBufferChannelLocked(fbl::move(ring_buffer_channel), &client_endpoint); |
| if (res != ZX_OK) { |
| LOG(ERROR, "Failed to set up client ring buffer channel (res %d)\n", res); |
| goto finished; |
| } |
| |
| // Let the implementation send the commands required to finish changing the |
| // stream format. |
| res = FinishChangeStreamFormatLocked(encoded_fmt()); |
| if (res != ZX_OK) { |
| LOG(ERROR, "Failed to finish set format (enc fmt 0x%04hx res %d)\n", encoded_fmt(), res); |
| goto finished; |
| } |
| |
| ZX_DEBUG_ASSERT(client_endpoint.is_valid()); |
| |
| // Respond to the caller, transferring the DMA handle back in the process. |
| resp.hdr.cmd = AUDIO_STREAM_CMD_SET_FORMAT; |
| resp.hdr.transaction_id = set_format_tid(); |
| resp.result = ZX_OK; |
| resp.external_delay_nsec = 0; // report his properly based on the codec path delay. |
| res = stream_channel()->Write(&resp, sizeof(resp), fbl::move(client_endpoint)); |
| |
| // 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 ((set_format_tid() == AUDIO_INVALID_TRANSACTION_ID) || |
| (stream_channel() == nullptr)) { |
| 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()->Deactivate(); |
| stream_channel() = nullptr; |
| } |
| |
| // One way or the other, this set format operation is finished. Clear out |
| // the in-flight transaction ID |
| SetFormatTidLocked(AUDIO_INVALID_TRANSACTION_ID); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t IntelDspStream::CreateClientRingBufferChannelLocked( |
| zx::channel&& ring_buffer_channel, |
| zx::channel* out_client_channel) { |
| // Attempt to allocate a new ring buffer channel and bind it to us. |
| // This channel is connected to the upstream device. |
| auto channel = dispatcher::Channel::Create(); |
| if (channel == nullptr) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| dispatcher::Channel::ProcessHandler phandler( |
| [stream = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain()); |
| return stream->ProcessRbRequest(channel); |
| }); |
| |
| dispatcher::Channel::ChannelClosedHandler chandler( |
| [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain()); |
| stream->ProcessRbDeactivate(channel); |
| }); |
| |
| zx_status_t res = channel->Activate(fbl::move(ring_buffer_channel), |
| domain(), |
| fbl::move(phandler), |
| fbl::move(chandler)); |
| if (res != ZX_OK) { |
| return res; |
| } |
| ZX_DEBUG_ASSERT(rb_channel_ == nullptr); |
| rb_channel_ = channel; |
| |
| // Attempt to allocate a new ring buffer channel and bind it to us. |
| // This channel is connected to the client. |
| auto client_channel = dispatcher::Channel::Create(); |
| if (client_channel == nullptr) { |
| rb_channel_->Deactivate(); |
| rb_channel_ = nullptr; |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| dispatcher::Channel::ProcessHandler client_phandler( |
| [stream = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain()); |
| return stream->ProcessClientRbRequest(channel); |
| }); |
| |
| dispatcher::Channel::ChannelClosedHandler client_chandler( |
| [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain()); |
| stream->ProcessClientRbDeactivate(channel); |
| }); |
| |
| res = client_channel->Activate(out_client_channel, |
| domain(), |
| fbl::move(client_phandler), |
| fbl::move(client_chandler)); |
| if (res == ZX_OK) { |
| ZX_DEBUG_ASSERT(client_rb_channel_ == nullptr); |
| client_rb_channel_ = client_channel; |
| } else { |
| rb_channel_->Deactivate(); |
| rb_channel_ = nullptr; |
| } |
| |
| return res; |
| } |
| |
| zx_status_t IntelDspStream::ProcessRbRequest(dispatcher::Channel* channel) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock lock(obj_lock()); |
| |
| // If we have lost our connection to the codec device, or are in the process |
| // of shutting down, there is nothing further we can do. Fail the request |
| // and close the connection to the caller. |
| if (!is_active() || (rb_channel_ == nullptr) || (client_rb_channel_ == nullptr)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| zx::handle rxed_handle; |
| uint32_t req_size; |
| union { |
| audio_proto::CmdHdr hdr; |
| audio_proto::RingBufGetFifoDepthResp get_fifo_depth; |
| audio_proto::RingBufGetBufferResp get_buffer; |
| audio_proto::RingBufStartResp start; |
| audio_proto::RingBufStopResp stop; |
| } req; |
| // TODO(johngro) : How large is too large? |
| static_assert(sizeof(req) <= 256, "Request buffer is too large to hold on the stack!"); |
| |
| zx_status_t res = channel->Read(&req, sizeof(req), &req_size, &rxed_handle); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| switch (req.hdr.cmd) { |
| case AUDIO_RB_CMD_START: |
| { |
| auto dsp = fbl::RefPtr<IntelAudioDsp>::Downcast(parent_codec()); |
| zx_status_t st = dsp->StartPipeline(pipeline_); |
| if (st != ZX_OK) { |
| audio_proto::RingBufStartResp resp = { }; |
| resp.hdr = req.hdr; |
| resp.result = st; |
| return client_rb_channel_->Write(&resp, sizeof(resp)); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return client_rb_channel_->Write(&req, req_size, fbl::move(rxed_handle)); |
| } |
| |
| void IntelDspStream::ProcessRbDeactivate(const dispatcher::Channel* channel) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock lock(obj_lock()); |
| |
| LOG(TRACE, "ProcessClientRbDeactivate\n"); |
| |
| ZX_DEBUG_ASSERT(channel == rb_channel_.get()); |
| rb_channel_->Deactivate(); |
| rb_channel_ = nullptr; |
| |
| // Deactivate the client channel. |
| if (client_rb_channel_ != nullptr) { |
| client_rb_channel_->Deactivate(); |
| client_rb_channel_ = nullptr; |
| } |
| } |
| |
| zx_status_t IntelDspStream::ProcessClientRbRequest(dispatcher::Channel* channel) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock lock(obj_lock()); |
| |
| // If we have lost our connection to the codec device, or are in the process |
| // of shutting down, there is nothing further we can do. Fail the request |
| // and close the connection to the caller. |
| if (!is_active() || (rb_channel_ == nullptr) || (client_rb_channel_ == nullptr)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| uint32_t req_size; |
| union { |
| audio_proto::CmdHdr hdr; |
| audio_proto::RingBufGetFifoDepthReq get_fifo_depth; |
| audio_proto::RingBufGetBufferReq get_buffer; |
| audio_proto::RingBufStartReq start; |
| audio_proto::RingBufStopReq stop; |
| } req; |
| // TODO(johngro) : How large is too large? |
| static_assert(sizeof(req) <= 256, "Request buffer is too large to hold on the stack!"); |
| |
| zx_status_t res = channel->Read(&req, sizeof(req), &req_size); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| switch (req.hdr.cmd) { |
| case AUDIO_RB_CMD_STOP: |
| { |
| auto dsp = fbl::RefPtr<IntelAudioDsp>::Downcast(parent_codec()); |
| zx_status_t st = dsp->PausePipeline(pipeline_); |
| if (st != ZX_OK) { |
| audio_proto::RingBufStopResp resp = { }; |
| resp.hdr = req.hdr; |
| resp.result = st; |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return rb_channel_->Write(&req, req_size); |
| } |
| |
| void IntelDspStream::ProcessClientRbDeactivate(const dispatcher::Channel* channel) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock lock(obj_lock()); |
| |
| LOG(TRACE, "ProcessClientRbDeactivate\n"); |
| |
| ZX_DEBUG_ASSERT(channel == client_rb_channel_.get()); |
| client_rb_channel_->Deactivate(); |
| client_rb_channel_ = nullptr; |
| |
| // Deactivate the upstream channel. |
| if (rb_channel_ != nullptr) { |
| rb_channel_->Deactivate(); |
| rb_channel_ = nullptr; |
| } |
| } |
| |
| zx_status_t IntelDspStream::OnActivateLocked() { |
| // FIXME(yky) Hardcode supported formats. |
| fbl::Vector<audio_proto::FormatRange> supported_formats; |
| zx_status_t res = MakeFormatRangeList(SampleCaps(IHDA_PCM_SIZE_16BITS | IHDA_PCM_RATE_48000, |
| IHDA_PCM_FORMAT_PCM), |
| 2, |
| &supported_formats); |
| if (res != ZX_OK) { |
| return res; |
| } |
| SetSupportedFormatsLocked(fbl::move(supported_formats)); |
| return ZX_OK; |
| } |
| |
| void IntelDspStream::OnDeactivateLocked() { |
| LOG(TRACE, "OnDeactivateLocked\n"); |
| } |
| |
| void IntelDspStream::OnChannelDeactivateLocked(const dispatcher::Channel& channel) { |
| LOG(TRACE, "OnChannelDeactivateLocked\n"); |
| } |
| |
| zx_status_t IntelDspStream::OnDMAAssignedLocked() { |
| LOG(TRACE, "OnDMAAssignedLocked\n"); |
| return PublishDeviceLocked(); |
| } |
| |
| zx_status_t IntelDspStream::OnSolicitedResponseLocked(const CodecResponse& resp) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t IntelDspStream::OnUnsolicitedResponseLocked(const CodecResponse& resp) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t IntelDspStream::BeginChangeStreamFormatLocked( |
| const audio_proto::StreamSetFmtReq& req) { |
| LOG(TRACE, "BeginChangeStreamFormatLocked\n"); |
| return ZX_OK; |
| } |
| |
| zx_status_t IntelDspStream::FinishChangeStreamFormatLocked(uint16_t encoded_fmt) { |
| LOG(TRACE, "FinishChangeStreamFormatLocked\n"); |
| return ZX_OK; |
| } |
| |
| void IntelDspStream::OnGetGainLocked(audio_proto::GetGainResp* out_resp) { |
| LOG(TRACE, "OnGetGainLocked\n"); |
| } |
| |
| void IntelDspStream::OnSetGainLocked(const audio_proto::SetGainReq& req, |
| audio_proto::SetGainResp* out_resp) { |
| LOG(TRACE, "OnSetGainLocked\n"); |
| } |
| |
| void IntelDspStream::OnPlugDetectLocked(dispatcher::Channel* response_channel, |
| const audio_proto::PlugDetectReq& req, |
| audio_proto::PlugDetectResp* out_resp) { |
| LOG(TRACE, "OnPlugDetectLocked\n"); |
| } |
| |
| } // namespace intel_hda |
| } // namespace audio |