| // 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 <audio-proto-utils/format-utils.h> |
| #include <ddk/debug.h> |
| #include <lib/simple-audio-stream/simple-audio-stream.h> |
| #include <limits> |
| #include <utility> |
| #include <zircon/device/audio.h> |
| |
| namespace audio { |
| |
| void SimpleAudioStream::Shutdown() { |
| if (domain_ != nullptr) { |
| domain_->Deactivate(); |
| } |
| |
| { |
| // Now that we know our domain has been deactivated, it should be safe to |
| // assert that we are holding the domain token (assuming that users of |
| // Shutdown behave in a single threaded fashion) |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, domain_); |
| |
| // Channels-to-notify should already be empty. Explicitly clear it, to be safe. |
| plug_notify_channels_.clear(); |
| |
| ShutdownHook(); |
| } |
| } |
| |
| zx_status_t SimpleAudioStream::CreateInternal() { |
| zx_status_t res; |
| |
| ZX_DEBUG_ASSERT(domain_ == nullptr); |
| { |
| // We have not created the domain yet, it should be safe to pretend that |
| // we have the token (since we know that no dispatches are going to be |
| // invoked from the non-existent domain at this point) |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, domain_); |
| res = Init(); |
| if (res != ZX_OK) { |
| zxlogf(ERROR, "Init failure in %s (res %d)\n", __PRETTY_FUNCTION__, res); |
| return res; |
| } |
| // If no subclass has set this, we need to do so here. |
| if (plug_time_ == 0) { plug_time_ = zx_clock_get(ZX_CLOCK_MONOTONIC); } |
| } |
| |
| domain_ = dispatcher::ExecutionDomain::Create(); |
| if (domain_ == nullptr) { |
| zxlogf(ERROR, "Failed to create execution domain in %s\n", __PRETTY_FUNCTION__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| res = InitPost(); |
| if (res != ZX_OK) { |
| zxlogf(ERROR, "InitPost failure in %s (res %d)\n", __PRETTY_FUNCTION__, res); |
| return res; |
| } |
| |
| res = PublishInternal(); |
| if (res != ZX_OK) { |
| zxlogf(ERROR, "Publish failure in %s (res %d)\n", __PRETTY_FUNCTION__, res); |
| return res; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SimpleAudioStream::PublishInternal() { |
| device_name_[sizeof(device_name_) - 1] = 0; |
| if (!strlen(device_name_)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // If we succeed in adding our device, add an explicit reference to |
| // ourselves to represent the reference now being held by the DDK. We will |
| // get this reference back when the DDK (eventually) calls release. |
| zx_status_t res = DdkAdd(device_name_); |
| if (res == ZX_OK) { |
| AddRef(); |
| } |
| |
| return res; |
| } |
| |
| // Called by a child subclass during Init, to establish the properties of this plug. |
| // Caller must include only flags defined for audio_stream_cmd_plug_detect_resp_t. |
| void SimpleAudioStream::SetInitialPlugState(audio_pd_notify_flags_t initial_state) { |
| audio_pd_notify_flags_t known_flags = AUDIO_PDNF_HARDWIRED | AUDIO_PDNF_CAN_NOTIFY | |
| AUDIO_PDNF_PLUGGED; |
| ZX_DEBUG_ASSERT((initial_state & known_flags) == initial_state); |
| |
| pd_flags_ = initial_state; |
| plug_time_ = zx_clock_get(ZX_CLOCK_MONOTONIC); |
| } |
| |
| // Called by a child subclass when a dynamic plug state change occurs. |
| // Special behavior if this isn't actually a change, or if we should not be able to unplug. |
| zx_status_t SimpleAudioStream::SetPlugState(bool plugged) { |
| if (plugged == ((pd_flags_ & AUDIO_PDNF_PLUGGED) != 0)) { return ZX_OK; } |
| |
| ZX_DEBUG_ASSERT(((pd_flags_ & AUDIO_PDNF_HARDWIRED) == 0) || plugged); |
| |
| if (plugged) { pd_flags_ |= AUDIO_PDNF_PLUGGED; } |
| else { pd_flags_ &= ~AUDIO_PDNF_PLUGGED; } |
| plug_time_ = zx_clock_get(ZX_CLOCK_MONOTONIC); |
| |
| if (pd_flags_ & AUDIO_PDNF_CAN_NOTIFY) { return NotifyPlugDetect(); } |
| |
| return ZX_OK; |
| } |
| |
| // Asynchronously notify of plug state changes. |
| zx_status_t SimpleAudioStream::NotifyPlugDetect() { |
| audio_proto::PlugDetectNotify notif; |
| |
| notif.hdr.transaction_id = AUDIO_INVALID_TRANSACTION_ID; |
| notif.hdr.cmd = AUDIO_STREAM_PLUG_DETECT_NOTIFY; |
| |
| notif.flags = pd_flags_; |
| notif.plug_state_time = plug_time_; |
| |
| for (auto& channel : plug_notify_channels_) { |
| // Any error also triggers ChannelClosedHandler; no need to handle it here. |
| (void) channel.Write(¬if, sizeof(notif)); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t SimpleAudioStream::NotifyPosition(const audio_proto::RingBufPositionNotify& notif) { |
| fbl::AutoLock channel_lock(&channel_lock_); |
| |
| if (!expected_notifications_per_ring_.load() || (rb_channel_ == nullptr)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return rb_channel_->Write(¬if, sizeof(notif)); |
| } |
| |
| void SimpleAudioStream::DdkUnbind() { |
| Shutdown(); |
| |
| // TODO(johngro): We need to signal our SimpleAudioStream owner to let them |
| // know that we have been unbound and are in the process of shutting down. |
| |
| // Unpublish our device node. |
| DdkRemove(); |
| } |
| |
| void SimpleAudioStream::DdkRelease() { |
| // Recover our ref from the DDK, then let it fall out of scope. |
| auto thiz = fbl::internal::MakeRefPtrNoAdopt(this); |
| } |
| |
| zx_status_t SimpleAudioStream::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, size_t* out_actual) { |
| // The only IOCTL we support is get channel. |
| if (op != AUDIO_IOCTL_GET_CHANNEL) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if ((out_buf == nullptr) || |
| (out_actual == nullptr) || |
| (out_len != sizeof(zx_handle_t))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock channel_lock(&channel_lock_); |
| |
| // Attempt to allocate a new driver channel and bind it to us. If we don't |
| // already have an stream_channel_, flag this channel is the privileged |
| // connection (The connection which is allowed to do things like change |
| // formats). |
| bool privileged = (stream_channel_ == nullptr); |
| auto channel = dispatcher::Channel::Create<AudioStreamChannel>(); |
| if (channel == nullptr) |
| return ZX_ERR_NO_MEMORY; |
| |
| dispatcher::Channel::ProcessHandler phandler( |
| [ stream = fbl::WrapRefPtr(this), privileged ](dispatcher::Channel * channel)->zx_status_t { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain_); |
| return stream->ProcessStreamChannel(channel, privileged); |
| }); |
| |
| dispatcher::Channel::ChannelClosedHandler chandler = dispatcher::Channel::ChannelClosedHandler( |
| [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel)->void { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain_); |
| fbl::AutoLock channel_lock(&stream->channel_lock_); |
| stream->DeactivateStreamChannel(channel); |
| }); |
| |
| zx::channel client_endpoint; |
| zx_status_t res = channel->Activate(&client_endpoint, |
| domain_, |
| std::move(phandler), |
| std::move(chandler)); |
| if (res == ZX_OK) { |
| if (privileged) { |
| ZX_DEBUG_ASSERT(stream_channel_ == nullptr); |
| stream_channel_ = channel; |
| } |
| |
| *(reinterpret_cast<zx_handle_t*>(out_buf)) = client_endpoint.release(); |
| *out_actual = sizeof(zx_handle_t); |
| } |
| |
| return res; |
| } |
| |
| #define HREQ(_cmd, _payload, _handler, _allow_noack, ...) \ |
| case _cmd: \ |
| if (req_size != sizeof(req._payload)) { \ |
| zxlogf(ERROR, "Bad " #_cmd \ |
| " 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)) { \ |
| zxlogf(ERROR, "NO_ACK flag not allowed for " #_cmd "\n"); \ |
| return ZX_ERR_INVALID_ARGS; \ |
| } \ |
| return _handler(channel, req._payload, ##__VA_ARGS__); |
| zx_status_t SimpleAudioStream::ProcessStreamChannel(dispatcher::Channel* channel, bool privileged) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| |
| 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; |
| } 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) { |
| HREQ(AUDIO_STREAM_CMD_GET_FORMATS, get_formats, OnGetStreamFormats, false); |
| HREQ(AUDIO_STREAM_CMD_SET_FORMAT, set_format, OnSetStreamFormat, false, privileged); |
| HREQ(AUDIO_STREAM_CMD_GET_GAIN, get_gain, OnGetGain, false); |
| HREQ(AUDIO_STREAM_CMD_SET_GAIN, set_gain, OnSetGain, true); |
| HREQ(AUDIO_STREAM_CMD_PLUG_DETECT, plug_detect, OnPlugDetect, true); |
| HREQ(AUDIO_STREAM_CMD_GET_UNIQUE_ID, get_unique_id, OnGetUniqueId, false); |
| HREQ(AUDIO_STREAM_CMD_GET_STRING, get_string, OnGetString, false); |
| default: |
| zxlogf(ERROR, "Unrecognized stream command 0x%04x\n", req.hdr.cmd); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t SimpleAudioStream::ProcessRingBufferChannel(dispatcher::Channel* channel) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| |
| union { |
| audio_proto::CmdHdr hdr; |
| audio_proto::RingBufGetFifoDepthReq get_fifo_depth; |
| audio_proto::RingBufGetBufferReq get_buffer; |
| audio_proto::RingBufStartReq rb_start; |
| audio_proto::RingBufStopReq rb_stop; |
| } 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) { |
| HREQ(AUDIO_RB_CMD_GET_FIFO_DEPTH, get_fifo_depth, OnGetFifoDepth, false); |
| HREQ(AUDIO_RB_CMD_GET_BUFFER, get_buffer, OnGetBuffer, false); |
| HREQ(AUDIO_RB_CMD_START, rb_start, OnStart, false); |
| HREQ(AUDIO_RB_CMD_STOP, rb_stop, OnStop, false); |
| default: |
| zxlogf(ERROR, "Unrecognized ring buffer command 0x%04x\n", req.hdr.cmd); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| #undef HREQ |
| |
| void SimpleAudioStream::DeactivateStreamChannel(const dispatcher::Channel* channel) { |
| if (stream_channel_.get() == channel) { |
| stream_channel_ = nullptr; |
| } |
| |
| auto audio_stream_channel = |
| static_cast<AudioStreamChannel*>(const_cast<dispatcher::Channel*>(channel)); |
| if (audio_stream_channel->InContainer()) { |
| plug_notify_channels_.erase(*audio_stream_channel); |
| } |
| } |
| |
| void SimpleAudioStream::DeactivateRingBufferChannel(const dispatcher::Channel* channel) { |
| if (channel == rb_channel_.get()) { |
| if (rb_started_) { |
| Stop(); |
| rb_started_ = false; |
| } |
| rb_fetched_ = false; |
| expected_notifications_per_ring_.store(0); |
| rb_channel_.reset(); |
| } |
| } |
| |
| zx_status_t SimpleAudioStream::OnGetStreamFormats(dispatcher::Channel* channel, |
| const audio_proto::StreamGetFmtsReq& req) const { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| uint16_t formats_sent = 0; |
| audio_proto::StreamGetFmtsResp resp = { }; |
| |
| if (supported_formats_.size() > std::numeric_limits<uint16_t>::max()) { |
| zxlogf(ERROR, |
| "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 { |
| uint16_t todo, payload_sz; |
| zx_status_t res; |
| |
| todo = fbl::min<uint16_t>(static_cast<uint16_t>(supported_formats_.size() - formats_sent), |
| AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE); |
| payload_sz = static_cast<uint16_t>(sizeof(resp.format_ranges[0]) * todo); |
| |
| resp.first_format_range_ndx = formats_sent; |
| ::memcpy(resp.format_ranges, supported_formats_.get() + formats_sent, payload_sz); |
| |
| res = channel->Write(&resp, sizeof(resp)); |
| if (res != ZX_OK) { |
| zxlogf(ERROR, "Failed to send get stream formats response (res %d)\n", res); |
| return res; |
| } |
| |
| formats_sent = (uint16_t)(formats_sent + todo); |
| } while (formats_sent < supported_formats_.size()); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SimpleAudioStream::OnSetStreamFormat(dispatcher::Channel* channel, |
| const audio_proto::StreamSetFmtReq& req, |
| bool privileged) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| |
| zx::channel client_rb_channel; |
| audio_proto::StreamSetFmtResp resp = { }; |
| bool found_one = false; |
| resp.hdr = req.hdr; |
| |
| // Only the privileged stream channel is allowed to change the format. |
| if (!privileged) { |
| resp.result = ZX_ERR_ACCESS_DENIED; |
| goto finished; |
| } |
| |
| // Check the format for compatibility |
| for (const auto& fmt : supported_formats_) { |
| if (audio::utils::FormatIsCompatible(req.frames_per_second, |
| req.channels, |
| req.sample_format, |
| fmt)) { |
| found_one = true; |
| break; |
| } |
| } |
| |
| if (!found_one) { |
| resp.result = ZX_ERR_INVALID_ARGS; |
| goto finished; |
| } |
| |
| // Determine the frame size. |
| frame_size_ = audio::utils::ComputeFrameSize(req.channels, req.sample_format); |
| if (!frame_size_) { |
| zxlogf(ERROR, "Failed to compute frame size (ch %hu fmt 0x%08x)\n", |
| req.channels, req.sample_format); |
| resp.result = ZX_ERR_INTERNAL; |
| goto finished; |
| } |
| |
| // Looks like we are going ahead with this format change. Tear down any |
| // exiting ring buffer interface before proceeding. |
| { |
| fbl::AutoLock channel_lock(&channel_lock_); |
| if (rb_channel_ != nullptr) { |
| rb_channel_->Deactivate(); |
| DeactivateRingBufferChannel(rb_channel_.get()); |
| ZX_DEBUG_ASSERT(rb_channel_ == nullptr); |
| } |
| } |
| |
| // Actually attempt to change the format. |
| resp.result = ChangeFormat(req); |
| if (resp.result != ZX_OK) { |
| goto finished; |
| } |
| |
| // Create a new ring buffer channel which can be used to move bulk data and |
| // bind it to us. |
| { |
| fbl::AutoLock channel_lock(&channel_lock_); |
| rb_channel_ = dispatcher::Channel::Create(); |
| if (rb_channel_ == nullptr) { |
| resp.result = ZX_ERR_NO_MEMORY; |
| } else { |
| dispatcher::Channel::ProcessHandler phandler( |
| [stream = fbl::WrapRefPtr(this)](dispatcher::Channel * channel) -> zx_status_t { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain_); |
| return stream->ProcessRingBufferChannel(channel); |
| }); |
| |
| dispatcher::Channel::ChannelClosedHandler chandler( |
| [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->domain_); |
| fbl::AutoLock channel_lock(&stream->channel_lock_); |
| stream->DeactivateRingBufferChannel(channel); |
| }); |
| |
| resp.result = rb_channel_->Activate(&client_rb_channel, |
| domain_, |
| std::move(phandler), |
| std::move(chandler)); |
| if (resp.result != ZX_OK) { |
| rb_channel_.reset(); |
| } |
| } |
| } |
| |
| finished: |
| if (resp.result == ZX_OK) { |
| resp.external_delay_nsec = external_delay_nsec_; |
| return channel->Write(&resp, sizeof(resp), std::move(client_rb_channel)); |
| } else { |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| } |
| |
| zx_status_t SimpleAudioStream::OnGetGain(dispatcher::Channel* channel, |
| const audio_proto::GetGainReq& req) const { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| audio_proto::GetGainResp resp = cur_gain_state_; |
| resp.hdr = req.hdr; |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t SimpleAudioStream::OnSetGain(dispatcher::Channel* channel, |
| const audio_proto::SetGainReq& req) { |
| audio_proto::SetGainResp resp; |
| resp.hdr = req.hdr; |
| |
| // Sanity check the request before passing it along |
| if ((req.flags & AUDIO_SGF_MUTE_VALID) && (req.flags & AUDIO_SGF_MUTE) && |
| !cur_gain_state_.can_mute) { |
| resp.result = ZX_ERR_NOT_SUPPORTED; |
| goto finished; |
| } |
| |
| if ((req.flags & AUDIO_SGF_AGC_VALID) && (req.flags & AUDIO_SGF_AGC) && |
| !cur_gain_state_.can_agc) { |
| resp.result = ZX_ERR_NOT_SUPPORTED; |
| goto finished; |
| } |
| |
| if ((req.flags & AUDIO_SGF_GAIN_VALID) && |
| ((req.gain < cur_gain_state_.min_gain) || (req.gain > cur_gain_state_.max_gain))) { |
| resp.result = ZX_ERR_INVALID_ARGS; |
| goto finished; |
| } |
| |
| resp.result = SetGain(req); |
| |
| finished: |
| resp.cur_mute = cur_gain_state_.cur_mute; |
| resp.cur_agc = cur_gain_state_.cur_agc; |
| resp.cur_gain = cur_gain_state_.cur_gain; |
| return (req.hdr.cmd & AUDIO_FLAG_NO_ACK) ? ZX_OK : channel->Write(&resp, sizeof(resp)); |
| } |
| |
| // Called when receiving a AUDIO_STREAM_CMD_PLUG_DETECT message from a client. |
| zx_status_t SimpleAudioStream::OnPlugDetect(dispatcher::Channel* channel, |
| const audio_proto::PlugDetectReq& req) { |
| // It should never be the case that both bits are set -- but if so, DISABLE notifications. |
| bool disable = ((req.flags & AUDIO_PDF_DISABLE_NOTIFICATIONS) != 0); |
| bool enable = ((req.flags & AUDIO_PDF_ENABLE_NOTIFICATIONS) != 0) && !disable; |
| |
| auto audio_stream_channel = static_cast<AudioStreamChannel*>(channel); |
| { |
| fbl::AutoLock channel_lock(&channel_lock_); |
| if (enable) { |
| if (plug_notify_channels_.is_empty()) { |
| EnableAsyncNotification(true); |
| } |
| if (!audio_stream_channel->InContainer()) { |
| plug_notify_channels_.push_back(fbl::WrapRefPtr(audio_stream_channel)); |
| } |
| } else if (disable) { |
| if (audio_stream_channel->InContainer()) { |
| plug_notify_channels_.erase(*audio_stream_channel); |
| } |
| if (plug_notify_channels_.is_empty()) { |
| EnableAsyncNotification(false); |
| } |
| } |
| } |
| |
| if (req.hdr.cmd & AUDIO_FLAG_NO_ACK) { return ZX_OK; } |
| |
| audio_proto::PlugDetectResp resp = { }; |
| resp.hdr = req.hdr; |
| resp.flags = pd_flags_; |
| resp.plug_state_time = plug_time_; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t SimpleAudioStream::OnGetUniqueId(dispatcher::Channel* channel, |
| const audio_proto::GetUniqueIdReq& req) const { |
| audio_proto::GetUniqueIdResp resp; |
| |
| resp.hdr = req.hdr; |
| resp.unique_id = unique_id_; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t SimpleAudioStream::OnGetString(dispatcher::Channel* channel, |
| const audio_proto::GetStringReq& req) const { |
| audio_proto::GetStringResp resp; |
| |
| resp.hdr = req.hdr; |
| resp.id = req.id; |
| |
| const char* str; |
| switch (req.id) { |
| case AUDIO_STREAM_STR_ID_MANUFACTURER: str = mfr_name_; break; |
| case AUDIO_STREAM_STR_ID_PRODUCT: str = prod_name_; break; |
| default: str = nullptr; break; |
| } |
| |
| if (str == nullptr) { |
| resp.result = ZX_ERR_NOT_FOUND; |
| resp.strlen = 0; |
| } else { |
| int res = snprintf(reinterpret_cast<char*>(resp.str), sizeof(resp.str), "%s", str); |
| ZX_DEBUG_ASSERT(res >= 0); |
| resp.result = ZX_OK; |
| resp.strlen = fbl::min<uint32_t>(res, sizeof(resp.str) - 1); |
| } |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t SimpleAudioStream::OnGetFifoDepth(dispatcher::Channel* channel, |
| const audio_proto::RingBufGetFifoDepthReq& req) { |
| audio_proto::RingBufGetFifoDepthResp resp = {}; |
| |
| resp.hdr = req.hdr; |
| resp.result = ZX_OK; |
| resp.fifo_depth = fifo_depth_; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t SimpleAudioStream::OnGetBuffer(dispatcher::Channel* channel, |
| const audio_proto::RingBufGetBufferReq& req) { |
| audio_proto::RingBufGetBufferResp resp = {}; |
| resp.hdr = req.hdr; |
| |
| if (rb_started_) { |
| resp.result = ZX_ERR_BAD_STATE; |
| } else { |
| zx::vmo buffer; |
| resp.result = GetBuffer(req, &resp.num_ring_buffer_frames, &buffer); |
| if (resp.result == ZX_OK) { |
| zx_status_t res = channel->Write(&resp, sizeof(resp), std::move(buffer)); |
| if (res == ZX_OK) { |
| expected_notifications_per_ring_.store(req.notifications_per_ring); |
| rb_fetched_ = true; |
| } |
| return res; |
| } else { |
| expected_notifications_per_ring_.store(0); |
| } |
| } |
| |
| ZX_DEBUG_ASSERT(resp.result != ZX_OK); |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t SimpleAudioStream::OnStart(dispatcher::Channel* channel, |
| const audio_proto::RingBufStartReq& req) { |
| audio_proto::RingBufStartResp resp = {}; |
| resp.hdr = req.hdr; |
| |
| if (rb_started_ || !rb_fetched_) { |
| resp.result = ZX_ERR_BAD_STATE; |
| } else { |
| resp.result = Start(&resp.start_time); |
| if (resp.result == ZX_OK) { |
| rb_started_ = true; |
| } |
| } |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t SimpleAudioStream::OnStop(dispatcher::Channel* channel, |
| const audio_proto::RingBufStopReq& req) { |
| audio_proto::RingBufStopResp resp = {}; |
| resp.hdr = req.hdr; |
| |
| if (!rb_started_) { |
| resp.result = ZX_ERR_BAD_STATE; |
| } else { |
| resp.result = Stop(); |
| if (resp.result == ZX_OK) { |
| rb_started_ = false; |
| } |
| } |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| } // namespace audio |