// 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 #include #include #include #include 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_); 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; } } 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; } 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(); 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; if (privileged) { 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_, fbl::move(phandler), fbl::move(chandler)); if (res == ZX_OK) { if (privileged) { ZX_DEBUG_ASSERT(stream_channel_ == nullptr); stream_channel_ = channel; } *(reinterpret_cast(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(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(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; } } 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() > fbl::numeric_limits::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(supported_formats_.size()); do { uint16_t todo, payload_sz; zx_status_t res; todo = fbl::min(static_cast(supported_formats_.size() - formats_sent), AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE); payload_sz = static_cast(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_, fbl::move(phandler), fbl::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), fbl::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)); } zx_status_t SimpleAudioStream::OnPlugDetect(dispatcher::Channel* channel, const audio_proto::PlugDetectReq& req) { if (req.hdr.cmd & AUDIO_FLAG_NO_ACK) return ZX_OK; audio_proto::PlugDetectResp resp = { }; resp.hdr = req.hdr; resp.flags = static_cast(AUDIO_PDNF_HARDWIRED | AUDIO_PDNF_PLUGGED); 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(resp.str), sizeof(resp.str), "%s", str); ZX_DEBUG_ASSERT(res >= 0); resp.result = ZX_OK; resp.strlen = fbl::min(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), fbl::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