| // 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 <string.h> |
| |
| #include <limits> |
| #include <utility> |
| |
| #include <audio-proto-utils/format-utils.h> |
| #include <audio-proto/audio-proto.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/auto_call.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 audio { |
| namespace intel_hda { |
| namespace codecs { |
| |
| // Device FIDL thunks |
| fuchsia_hardware_audio_Device_ops_t IntelHDAStreamBase::AUDIO_FIDL_THUNKS{ |
| .GetChannel = [](void* ctx, fidl_txn_t* txn) -> zx_status_t { |
| return reinterpret_cast<IntelHDAStreamBase*>(ctx)->GetChannel(txn); |
| }, |
| }; |
| |
| zx_protocol_device_t IntelHDAStreamBase::STREAM_DEVICE_THUNKS = []() { |
| zx_protocol_device_t sdt = {}; |
| sdt.version = DEVICE_OPS_VERSION; |
| sdt.message = [](void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) { |
| return fuchsia_hardware_audio_Device_dispatch(ctx, txn, msg, &AUDIO_FIDL_THUNKS); |
| }; |
| return sdt; |
| }(); |
| |
| IntelHDAStreamBase::IntelHDAStreamBase(uint32_t id, bool is_input) : id_(id), is_input_(is_input) { |
| snprintf(dev_name_, sizeof(dev_name_), "%s-stream-%03u", is_input_ ? "input" : "output", id_); |
| default_domain_ = dispatcher::ExecutionDomain::Create(); |
| } |
| |
| 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_); |
| persistent_unique_id_ = id; |
| } |
| |
| zx_status_t IntelHDAStreamBase::Activate(fbl::RefPtr<IntelHDACodecDriverBase>&& parent_codec, |
| const fbl::RefPtr<dispatcher::Channel>& codec_channel) { |
| ZX_DEBUG_ASSERT(codec_channel != nullptr); |
| |
| fbl::AutoLock obj_lock(&obj_lock_); |
| if (is_active() || (codec_channel_ != nullptr) || (default_domain_ == 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 = fbl::MakeAutoCall([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()); |
| } |
| |
| default_domain_->Deactivate(); |
| |
| { |
| 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_remove_deprecated(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, |
| zx::channel&& ring_buffer_channel) { |
| ZX_DEBUG_ASSERT(ring_buffer_channel.is_valid()); |
| |
| fbl::AutoLock obj_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; |
| |
| // 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; |
| |
| // 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; |
| } |
| |
| // 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), std::move(ring_buffer_channel)); |
| |
| 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 |
| set_format_tid_ = AUDIO_INVALID_TRANSACTION_ID; |
| |
| 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; |
| } |
| |
| zx_status_t IntelHDAStreamBase::GetChannel(fidl_txn_t* txn) { |
| fbl::AutoLock obj_lock(&obj_lock_); |
| |
| // Do not allow any new connections if we are in the process of shutting down |
| if (!is_active()) |
| return ZX_ERR_BAD_STATE; |
| |
| // 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 && (set_format_tid_ != AUDIO_INVALID_TRANSACTION_ID)) |
| return ZX_ERR_SHOULD_WAIT; |
| |
| // 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 channel = dispatcher::Channel::Create(); |
| if (channel == nullptr) |
| return ZX_ERR_NO_MEMORY; |
| |
| dispatcher::Channel::ProcessHandler phandler( |
| [stream = fbl::RefPtr(this), privileged](dispatcher::Channel* channel) -> zx_status_t { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_); |
| return stream->ProcessClientRequest(channel, privileged); |
| }); |
| |
| dispatcher::Channel::ChannelClosedHandler chandler( |
| [stream = fbl::RefPtr(this), privileged](const dispatcher::Channel* channel) -> void { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_); |
| stream->ProcessClientDeactivate(channel, privileged); |
| }); |
| |
| zx::channel client_endpoint; |
| zx_status_t res = channel->Activate(&client_endpoint, default_domain_, std::move(phandler), |
| std::move(chandler)); |
| if (res == ZX_OK) { |
| if (privileged) { |
| ZX_DEBUG_ASSERT(stream_channel_ == nullptr); |
| stream_channel_ = channel; |
| } |
| |
| fuchsia_hardware_audio_DeviceGetChannel_reply(txn, client_endpoint.release()); |
| } |
| |
| return res; |
| } |
| |
| zx_status_t IntelHDAStreamBase::DoGetStreamFormatsLocked(dispatcher::Channel* channel, |
| bool privileged, |
| const audio_proto::StreamGetFmtsReq& req) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| size_t formats_sent = 0; |
| audio_proto::StreamGetFmtsResp resp = {}; |
| |
| 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 ZX_ERR_INTERNAL; |
| } |
| |
| resp.hdr = req.hdr; |
| resp.format_range_count = static_cast<uint16_t>(supported_formats_.size()); |
| |
| do { |
| size_t todo, payload_sz, __UNUSED to_send; |
| zx_status_t res; |
| |
| todo = fbl::min<size_t>(supported_formats_.size() - formats_sent, |
| AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE); |
| payload_sz = sizeof(resp.format_ranges[0]) * todo; |
| to_send = offsetof(audio_proto::StreamGetFmtsResp, format_ranges) + payload_sz; |
| |
| resp.first_format_range_ndx = static_cast<uint16_t>(formats_sent); |
| ::memcpy(resp.format_ranges, supported_formats_.data() + formats_sent, payload_sz); |
| |
| res = channel->Write(&resp, sizeof(resp)); |
| if (res != ZX_OK) { |
| DEBUG_LOG("Failed to send get stream formats response (res %d)\n", res); |
| return res; |
| } |
| |
| formats_sent += todo; |
| } while (formats_sent < supported_formats_.size()); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t IntelHDAStreamBase::DoSetStreamFormatLocked(dispatcher::Channel* channel, |
| bool privileged, |
| const audio_proto::StreamSetFmtReq& fmt) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| ihda_proto::SetStreamFmtReq req; |
| uint16_t encoded_fmt; |
| bool found_supported_format = false; |
| zx_status_t res; |
| |
| // Check to make sure that this channel is permitted to change formats. |
| if (!privileged) { |
| res = ZX_ERR_ACCESS_DENIED; |
| goto send_fail_response; |
| } |
| |
| // 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) || |
| (set_format_tid_ != AUDIO_INVALID_TRANSACTION_ID)) { |
| res = ZX_ERR_BAD_STATE; |
| goto send_fail_response; |
| } |
| |
| // Is the requested format compatible with this stream? |
| for (const auto& format_range : supported_formats_) { |
| found_supported_format = utils::FormatIsCompatible(fmt.frames_per_second, fmt.channels, |
| fmt.sample_format, format_range); |
| if (found_supported_format) |
| break; |
| } |
| |
| if (!found_supported_format) { |
| res = ZX_ERR_NOT_SUPPORTED; |
| goto send_fail_response; |
| } |
| |
| // 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); |
| goto send_fail_response; |
| } |
| |
| // 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); |
| goto send_fail_response; |
| } |
| |
| // 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)); |
| 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); |
| return res; |
| } |
| |
| // Success! Record the transaction ID of the request. It indicates that the |
| // format change is in progress, and will be needed to send the final |
| // response back to the caller. |
| set_format_tid_ = fmt.hdr.transaction_id; |
| encoded_fmt_ = encoded_fmt; |
| return ZX_OK; |
| |
| send_fail_response: |
| audio_proto::StreamSetFmtResp resp = {}; |
| resp.hdr = fmt.hdr; |
| resp.result = res; |
| |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| res = channel->Write(&resp, sizeof(resp)); |
| if (res != ZX_OK) |
| DEBUG_LOG("Failing to write %zu bytes in response (res %d)\n", sizeof(resp), res); |
| return res; |
| } |
| |
| zx_status_t IntelHDAStreamBase::DoGetGainLocked(dispatcher::Channel* channel, bool privileged, |
| const audio_proto::GetGainReq& req) { |
| // Fill out the response header, then let the stream implementation fill out |
| // the payload. |
| audio_proto::GetGainResp resp = {}; |
| resp.hdr = req.hdr; |
| OnGetGainLocked(&resp); |
| |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t IntelHDAStreamBase::DoSetGainLocked(dispatcher::Channel* channel, bool privileged, |
| const audio_proto::SetGainReq& req) { |
| if (req.hdr.cmd & AUDIO_FLAG_NO_ACK) { |
| OnSetGainLocked(req, nullptr); |
| return ZX_OK; |
| } |
| |
| // Fill out the response header, then let the stream implementation fill out |
| // the payload. |
| audio_proto::SetGainResp resp = {}; |
| resp.hdr = req.hdr; |
| OnSetGainLocked(req, &resp); |
| |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t IntelHDAStreamBase::DoPlugDetectLocked(dispatcher::Channel* channel, bool privileged, |
| const audio_proto::PlugDetectReq& req) { |
| if (req.hdr.cmd & AUDIO_FLAG_NO_ACK) { |
| OnPlugDetectLocked(channel, req, nullptr); |
| return ZX_OK; |
| } |
| |
| // Fill out the response header, then let the stream implementation fill out |
| // the payload. |
| audio_proto::PlugDetectResp resp = {}; |
| resp.hdr = req.hdr; |
| OnPlugDetectLocked(channel, req, &resp); |
| |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t IntelHDAStreamBase::DoGetUniqueIdLocked(dispatcher::Channel* channel, bool privileged, |
| const audio_proto::GetUniqueIdReq& req) { |
| audio_proto::GetUniqueIdResp resp = {}; |
| |
| resp.hdr = req.hdr; |
| resp.unique_id = persistent_unique_id_; |
| |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t IntelHDAStreamBase::DoGetStringLocked(dispatcher::Channel* channel, bool privileged, |
| const audio_proto::GetStringReq& req) { |
| // Fill out the response header, then let the stream implementation fill out |
| // the payload. |
| audio_proto::GetStringResp resp = {}; |
| resp.hdr = req.hdr; |
| resp.id = req.id; |
| OnGetStringLocked(req, &resp); |
| |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| #define HANDLE_REQ(_ioctl, _payload, _handler, _allow_noack) \ |
| case _ioctl: \ |
| if (req_size != sizeof(req._payload)) { \ |
| DEBUG_LOG("Bad " #_ioctl " response length (%u != %zu)\n", req_size, sizeof(req._payload)); \ |
| return ZX_ERR_INVALID_ARGS; \ |
| } \ |
| if (!_allow_noack && (req.hdr.cmd & AUDIO_FLAG_NO_ACK)) { \ |
| DEBUG_LOG("NO_ACK flag not allowed for " #_ioctl "\n"); \ |
| return ZX_ERR_INVALID_ARGS; \ |
| } \ |
| return _handler(channel, privileged, req._payload); |
| zx_status_t IntelHDAStreamBase::ProcessClientRequest(dispatcher::Channel* channel, |
| bool privileged) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock obj_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() || (codec_channel_ == nullptr)) |
| return ZX_ERR_BAD_STATE; |
| |
| union { |
| audio_proto::CmdHdr hdr; |
| audio_proto::StreamGetFmtsReq get_formats; |
| audio_proto::StreamSetFmtReq set_format; |
| audio_proto::GetGainReq get_gain; |
| audio_proto::SetGainReq set_gain; |
| audio_proto::PlugDetectReq plug_detect; |
| audio_proto::GetUniqueIdReq get_unique_id; |
| audio_proto::GetStringReq get_string; |
| // TODO(johngro) : add more commands here |
| } req; |
| |
| static_assert(sizeof(req) <= 256, |
| "Request buffer is getting to be too large to hold on the stack!"); |
| |
| uint32_t req_size; |
| zx_status_t res = channel->Read(&req, sizeof(req), &req_size); |
| if (res != ZX_OK) |
| return res; |
| |
| if ((req_size < sizeof(req.hdr) || (req.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID))) |
| return ZX_ERR_INVALID_ARGS; |
| |
| // Strip the NO_ACK flag from the request before selecting the dispatch target. |
| auto cmd = static_cast<audio_proto::Cmd>(req.hdr.cmd & ~AUDIO_FLAG_NO_ACK); |
| switch (cmd) { |
| HANDLE_REQ(AUDIO_STREAM_CMD_GET_FORMATS, get_formats, DoGetStreamFormatsLocked, false); |
| HANDLE_REQ(AUDIO_STREAM_CMD_SET_FORMAT, set_format, DoSetStreamFormatLocked, false); |
| HANDLE_REQ(AUDIO_STREAM_CMD_GET_GAIN, get_gain, DoGetGainLocked, false); |
| HANDLE_REQ(AUDIO_STREAM_CMD_SET_GAIN, set_gain, DoSetGainLocked, true); |
| HANDLE_REQ(AUDIO_STREAM_CMD_PLUG_DETECT, plug_detect, DoPlugDetectLocked, true); |
| HANDLE_REQ(AUDIO_STREAM_CMD_GET_UNIQUE_ID, get_unique_id, DoGetUniqueIdLocked, false); |
| HANDLE_REQ(AUDIO_STREAM_CMD_GET_STRING, get_string, DoGetStringLocked, false); |
| default: |
| DEBUG_LOG("Unrecognized stream command 0x%04x\n", req.hdr.cmd); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| #undef HANDLE_REQ |
| |
| void IntelHDAStreamBase::ProcessClientDeactivate(const dispatcher::Channel* channel, |
| bool privileged) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock obj_lock(&obj_lock_); |
| |
| // Let our subclass know that this channel is going away. |
| OnChannelDeactivateLocked(*channel); |
| |
| // Is this the privileged stream channel? |
| if (privileged) { |
| ZX_DEBUG_ASSERT(channel == stream_channel_.get()); |
| stream_channel_.reset(); |
| } |
| } |
| |
| 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 dispatcher::Channel& 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::GetGainResp* 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(dispatcher::Channel* response_channel, |
| const audio_proto::PlugDetectReq& req, |
| audio_proto::PlugDetectResp* 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; |
| } |
| |
| 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 = fbl::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; |
| } |
| } |
| |
| } // namespace codecs |
| } // namespace intel_hda |
| } // namespace audio |