blob: 791c3721072fd740bc07d7559ae64d07feb3c812 [file] [log] [blame]
// 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 <fuchsia/hardware/intelhda/codec/cpp/banjo.h>
#include <lib/zx/channel.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <iterator>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/string_printf.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 <intel-hda/utils/utils.h>
#include "debug-logging.h"
namespace audio {
namespace intel_hda {
namespace codecs {
void IntelHDACodecDriverBase::PrintDebugPrefix() const { printf("HDACodec : "); }
IntelHDACodecDriverBase::IntelHDACodecDriverBase() : loop_(&kAsyncLoopConfigNeverAttachToThread) {
loop_.StartThread("intel-hda-codec-driver-loop");
}
#define DEV(_ctx) static_cast<IntelHDACodecDriverBase*>(_ctx)
zx_protocol_device_t IntelHDACodecDriverBase::CODEC_DEVICE_THUNKS = []() {
zx_protocol_device_t ops = {};
ops.version = DEVICE_OPS_VERSION;
ops.release = [](void* ctx) { DEV(ctx)->DeviceRelease(); };
ops.suspend = [](void* ctx, uint8_t requested_state, bool enable_wake, uint8_t suspend_reason) {
uint8_t out_state;
zx_status_t status =
DEV(ctx)->Suspend(requested_state, enable_wake, suspend_reason, &out_state);
device_suspend_reply(DEV(ctx)->zxdev(), status, out_state);
};
return ops;
}();
#undef DEV
Status IntelHDACodecDriverBase::Bind(zx_device_t* codec_dev, const char* name) {
ZX_ASSERT(codec_dev != nullptr);
if (codec_device_ != nullptr) {
return Status(ZX_ERR_BAD_STATE, "Codec already bound.");
}
ddk::IhdaCodecProtocolClient client;
zx_status_t result = ddk::IhdaCodecProtocolClient::CreateFromDevice(codec_dev, &client);
if (result != ZX_OK) {
return Status(result, "Failure while attempting to fetch DDK protocol.");
}
zx::channel channel;
// Obtain a channel handle from the device
result = client.GetDriverChannel(&channel);
if (result != ZX_OK) {
return Status(result, "Error fetching driver channel.");
}
auto device_channel = Channel::Create(std::move(channel));
if (device_channel == nullptr) {
return Status(ZX_ERR_NO_MEMORY, "Error creating device channel.");
}
// Stash our reference to our device channel. If activate succeeds, we
// could start to receive messages from the codec device immediately.
{
fbl::AutoLock device_channel_lock(&device_channel_lock_);
device_channel_ = device_channel;
device_channel_->SetHandler(
[codec = fbl::RefPtr(this)](async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
codec->ChannelSignalled(dispatcher, wait, status, signal);
});
result = device_channel_->BeginWait(loop_.dispatcher());
if (result != ZX_OK) {
device_channel_.reset();
return Status(result, "Error on begin wait.");
}
}
auto codec = fbl::RefPtr<IntelHDACodecDriverBase>(this);
// Initialize our device and fill out the protocol hooks
device_add_args_t args;
memset(&args, 0, sizeof(args));
args.version = DEVICE_ADD_ARGS_VERSION;
args.name = name;
{
// use a different refptr to avoid problems in error path
auto ddk_ref = codec;
args.ctx = fbl::ExportToRawPtr(&ddk_ref);
}
args.ops = &CODEC_DEVICE_THUNKS;
args.flags = DEVICE_ADD_NON_BINDABLE;
// Publish the device.
result = device_add(codec_dev, &args, &zxdev_);
if (result != ZX_OK) {
LOG("Failed to add codec device for \"%s\" (result %d)\n", name, result);
fbl::AutoLock device_channel_lock(&device_channel_lock_);
device_channel_.reset();
codec->Shutdown();
codec.reset();
return Status(result, fbl::StringPrintf("Failed to add codec device for \"%s\".", name));
}
// Success! Now that we are started, stash a pointer to the codec device
// that we are the driver for.
codec_device_ = codec_dev;
return OkStatus();
}
void IntelHDACodecDriverBase::ChannelSignalled(async_dispatcher_t* dispatcher,
async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
if (status != ZX_ERR_CANCELED) { // Cancel is expected.
return;
}
}
bool readable_asserted = signal->observed & ZX_CHANNEL_READABLE;
bool peer_closed_asserted = signal->observed & ZX_CHANNEL_PEER_CLOSED;
if (readable_asserted) {
// Grab a reference to the device channel, the processing may grab the lock.
fbl::RefPtr<Channel> device_channel;
{
fbl::AutoLock device_channel_lock(&device_channel_lock_);
device_channel = device_channel_;
}
zx_status_t status = ProcessClientRequest(device_channel.get());
if (status != ZX_OK) {
peer_closed_asserted = true;
}
}
if (peer_closed_asserted) {
ProcessClientDeactivate();
} else if (readable_asserted) {
wait->Begin(dispatcher);
}
}
void IntelHDACodecDriverBase::Shutdown() {
// Flag the fact that we are shutting down. This will prevent any new
// streams from becoming activated.
{
fbl::AutoLock shutdown_lock(&shutdown_lock_);
shutting_down_ = true;
}
DEBUG_LOG("Shutting down codec\n");
active_streams_lock_.Acquire();
while (!active_streams_.is_empty()) {
auto delete_me = active_streams_.pop_front();
active_streams_lock_.Release();
delete_me->Deactivate();
delete_me = nullptr;
active_streams_lock_.Acquire();
}
active_streams_lock_.Release();
// Close the connection to our codec.
DEBUG_LOG("Unlinking from controller\n");
UnlinkFromController();
loop_.Shutdown();
DEBUG_LOG("Shutdown complete\n");
}
zx_status_t IntelHDACodecDriverBase::Suspend(uint8_t requested_state, bool enable_wake,
uint8_t suspend_reason, uint8_t* out_state) {
*out_state = DEV_POWER_STATE_D0;
return ZX_ERR_NOT_SUPPORTED;
}
void IntelHDACodecDriverBase::DeviceRelease() {
auto thiz = fbl::ImportFromRawPtr(this);
// Shut the codec down.
thiz->Shutdown();
// Let go of the reference.
thiz.reset();
}
#define CHECK_RESP_ALLOW_HANDLE(_ioctl, _payload) \
do { \
if (resp_size != sizeof(resp._payload)) { \
DEBUG_LOG("Bad " #_ioctl " response length (%u != %zu)\n", resp_size, \
sizeof(resp._payload)); \
return ZX_ERR_INVALID_ARGS; \
} \
} while (0)
#define CHECK_RESP(_ioctl, _payload) \
do { \
if (rxed_handle.is_valid()) { \
DEBUG_LOG("Unexpected handle in " #_ioctl " response\n"); \
return ZX_ERR_INVALID_ARGS; \
} \
CHECK_RESP_ALLOW_HANDLE(_ioctl, _payload); \
} while (0)
zx_status_t IntelHDACodecDriverBase::ProcessClientRequest(Channel* channel) {
ZX_DEBUG_ASSERT(channel != nullptr);
uint32_t resp_size;
CodecChannelResponses resp;
zx::handle rxed_handle;
zx_status_t res = channel->Read(&resp, sizeof(resp), &resp_size, rxed_handle);
if (res != ZX_OK) {
DEBUG_LOG("Error reading from device channel (res %d)!\n", res);
return res;
}
if (resp_size < sizeof(resp.hdr)) {
DEBUG_LOG("Bad length (%u) reading from device channel (expected at least %zu)!\n", resp_size,
sizeof(resp.hdr));
return ZX_ERR_INVALID_ARGS;
}
// Does this response belong to one of our streams?
if ((resp.hdr.transaction_id != IHDA_INVALID_TRANSACTION_ID) &&
(resp.hdr.transaction_id != CODEC_TID)) {
auto stream = GetActiveStream(resp.hdr.transaction_id);
if (stream == nullptr) {
DEBUG_LOG("Received codec device response for inactive stream (id %u)\n",
resp.hdr.transaction_id);
return ZX_ERR_BAD_STATE;
} else {
return ProcessStreamResponse(stream, resp, resp_size, std::move(rxed_handle));
}
} else {
switch (resp.hdr.cmd) {
case IHDA_CODEC_SEND_CORB_CMD: {
CHECK_RESP(IHDA_CODEC_SEND_CORB_CMD, send_corb);
CodecResponse payload(resp.send_corb.data, resp.send_corb.data_ex);
if (!payload.unsolicited())
return ProcessSolicitedResponse(payload);
// If this is an unsolicited response, check to see if the tag is
// owned by a stream or not. If it is, dispatch the payload to the
// stream, otherwise give it to the codec.
uint32_t stream_id;
zx_status_t res = MapUnsolTagToStreamId(payload.unsol_tag(), &stream_id);
if (res != ZX_OK) {
DEBUG_LOG("Received unexpected unsolicited response (tag %u)\n", payload.unsol_tag());
return ZX_OK;
}
if (stream_id == CODEC_TID)
return ProcessUnsolicitedResponse(payload);
auto stream = GetActiveStream(stream_id);
if (stream == nullptr) {
DEBUG_LOG("Received unsolicited response (tag %u) for inactive stream (id %u)\n",
payload.unsol_tag(), stream_id);
return ZX_OK;
} else {
return stream->ProcessResponse(payload);
}
}
default:
DEBUG_LOG("Received unexpected response type (%u) for codec device!\n", resp.hdr.cmd);
return ZX_ERR_INVALID_ARGS;
}
}
}
zx_status_t IntelHDACodecDriverBase::ProcessStreamResponse(
const fbl::RefPtr<IntelHDAStreamBase>& stream, const CodecChannelResponses& resp,
uint32_t resp_size, zx::handle&& rxed_handle) {
ZX_DEBUG_ASSERT(stream != nullptr);
switch (resp.hdr.cmd) {
case IHDA_CODEC_SEND_CORB_CMD: {
CHECK_RESP(IHDA_CODEC_SEND_CORB_CMD, send_corb);
CodecResponse payload(resp.send_corb.data, resp.send_corb.data_ex);
if (payload.unsolicited()) {
DEBUG_LOG("Unsolicited response sent directly to stream ID %u! (0x%08x, 0x%08x)\n",
stream->id(), payload.data, payload.data_ex);
return ZX_ERR_INVALID_ARGS;
}
return stream->ProcessResponse(payload);
}
case IHDA_CODEC_REQUEST_STREAM:
CHECK_RESP(IHDA_CODEC_REQUEST_STREAM, request_stream);
return stream->ProcessRequestStream(resp.request_stream);
case IHDA_CODEC_SET_STREAM_FORMAT: {
CHECK_RESP_ALLOW_HANDLE(IHDA_CODEC_SET_STREAM_FORMAT, set_stream_fmt);
return stream->ProcessSetStreamFmt(resp.set_stream_fmt);
}
default:
DEBUG_LOG("Received unexpected response type (%u) for codec stream device!\n", resp.hdr.cmd);
return ZX_ERR_INVALID_ARGS;
}
}
#undef CHECK_RESP
#undef CHECK_RESP_ALLOW_HANDLE
void IntelHDACodecDriverBase::ProcessClientDeactivate() {
bool do_shutdown = false;
{
fbl::AutoLock device_channel_lock(&device_channel_lock_);
// If the channel we use to talk to our device is closing, clear out our
// internal bookkeeping.
//
// TODO(johngro) : We should probably tell our implementation about this.
do_shutdown = true;
device_channel_.reset();
}
if (do_shutdown)
Shutdown();
}
void IntelHDACodecDriverBase::UnlinkFromController() {
fbl::AutoLock device_channel_lock(&device_channel_lock_);
if (device_channel_ != nullptr) {
device_channel_ = nullptr;
}
}
zx_status_t IntelHDACodecDriverBase::SendCodecCommand(uint16_t nid, CodecVerb verb, bool no_ack) {
fbl::RefPtr<Channel> device_channel;
{
fbl::AutoLock device_channel_lock(&device_channel_lock_);
device_channel = device_channel_;
}
if (device_channel == nullptr)
return ZX_ERR_BAD_STATE;
ihda_codec_send_corb_cmd_req_t cmd;
cmd.hdr.cmd = no_ack ? IHDA_CODEC_SEND_CORB_CMD_NOACK : IHDA_CODEC_SEND_CORB_CMD;
cmd.hdr.transaction_id = CODEC_TID;
cmd.nid = nid;
cmd.verb = verb.val;
return device_channel->Write(&cmd, sizeof(cmd));
}
fbl::RefPtr<IntelHDAStreamBase> IntelHDACodecDriverBase::GetActiveStream(uint32_t stream_id) {
fbl::AutoLock active_streams_lock(&active_streams_lock_);
auto iter = active_streams_.find(stream_id);
return iter.IsValid() ? iter.CopyPointer() : nullptr;
}
zx_status_t IntelHDACodecDriverBase::ActivateStream(const fbl::RefPtr<IntelHDAStreamBase>& stream) {
if ((stream == nullptr) || (stream->id() == IHDA_INVALID_TRANSACTION_ID) ||
(stream->id() == CODEC_TID))
return ZX_ERR_INVALID_ARGS;
fbl::AutoLock shutdown_lock(&shutdown_lock_);
if (shutting_down_)
return ZX_ERR_BAD_STATE;
// Grab a reference to the channel we use to talk to the codec device. If
// the channel has already been closed, we cannot activate this stream.
fbl::RefPtr<Channel> device_channel;
{
fbl::AutoLock device_channel_lock(&device_channel_lock_);
if (device_channel_ == nullptr)
return ZX_ERR_BAD_STATE;
device_channel = device_channel_;
}
// Add this channel to the set of active channels. If we encounter a key
// collision, then something is wrong with our codec driver implementation.
// Fail the activation.
{
fbl::AutoLock active_streams_lock(&active_streams_lock_);
if (!active_streams_.insert_or_find(stream))
return ZX_ERR_BAD_STATE;
}
// Go ahead and activate the stream.
return stream->Activate(fbl::RefPtr(this), device_channel);
}
zx_status_t IntelHDACodecDriverBase::AllocateUnsolTag(const IntelHDAStreamBase& stream,
uint8_t* out_tag) {
return AllocateUnsolTag(stream.id(), out_tag);
}
void IntelHDACodecDriverBase::ReleaseUnsolTag(const IntelHDAStreamBase& stream, uint8_t tag) {
return ReleaseUnsolTag(stream.id(), tag);
}
void IntelHDACodecDriverBase::ReleaseAllUnsolTags(const IntelHDAStreamBase& stream) {
return ReleaseAllUnsolTags(stream.id());
}
zx_status_t IntelHDACodecDriverBase::DeactivateStream(uint32_t stream_id) {
fbl::RefPtr<IntelHDAStreamBase> stream;
{
fbl::AutoLock active_streams_lock(&active_streams_lock_);
stream = active_streams_.erase(stream_id);
}
if (stream == nullptr)
return ZX_ERR_NOT_FOUND;
stream->Deactivate();
return ZX_OK;
}
zx_status_t IntelHDACodecDriverBase::AllocateUnsolTag(uint32_t stream_id, uint8_t* out_tag) {
ZX_DEBUG_ASSERT(out_tag != nullptr);
fbl::AutoLock unsol_tag_lock(&unsol_tag_lock_);
static_assert(sizeof(free_unsol_tags_) == sizeof(long long int),
"free_unsol_tags_ is not the same size as a long long int. "
"Cannot use ffsll to find the first set bit!");
uint32_t first_set = ffsll(free_unsol_tags_);
if (!first_set)
return ZX_ERR_NO_MEMORY;
--first_set;
*out_tag = static_cast<uint8_t>(first_set);
free_unsol_tags_ &= ~(1ull << first_set);
unsol_tag_to_stream_id_map_[first_set] = stream_id;
return ZX_OK;
}
void IntelHDACodecDriverBase::ReleaseUnsolTag(uint32_t stream_id, uint8_t tag) {
fbl::AutoLock unsol_tag_lock(&unsol_tag_lock_);
uint64_t mask = uint64_t(1u) << tag;
ZX_DEBUG_ASSERT(mask != 0);
ZX_DEBUG_ASSERT(!(free_unsol_tags_ & mask));
ZX_DEBUG_ASSERT(tag < std::size(unsol_tag_to_stream_id_map_));
ZX_DEBUG_ASSERT(unsol_tag_to_stream_id_map_[tag] == stream_id);
free_unsol_tags_ |= mask;
}
void IntelHDACodecDriverBase::ReleaseAllUnsolTags(uint32_t stream_id) {
fbl::AutoLock unsol_tag_lock(&unsol_tag_lock_);
for (uint32_t tmp = 0u; tmp < std::size(unsol_tag_to_stream_id_map_); ++tmp) {
uint64_t mask = uint64_t(1u) << tmp;
if (!(free_unsol_tags_ & mask) && (unsol_tag_to_stream_id_map_[tmp] == stream_id)) {
free_unsol_tags_ |= mask;
}
}
}
zx_status_t IntelHDACodecDriverBase::MapUnsolTagToStreamId(uint8_t tag, uint32_t* out_stream_id) {
ZX_DEBUG_ASSERT(out_stream_id != nullptr);
fbl::AutoLock unsol_tag_lock(&unsol_tag_lock_);
uint64_t mask = uint64_t(1u) << tag;
if ((!mask) || (free_unsol_tags_ & mask))
return ZX_ERR_NOT_FOUND;
ZX_DEBUG_ASSERT(tag < std::size(unsol_tag_to_stream_id_map_));
*out_stream_id = unsol_tag_to_stream_id_map_[tag];
return ZX_OK;
}
} // namespace codecs
} // namespace intel_hda
} // namespace audio