blob: 8ef17301fba3ce4ea5d7bf5ddd66a477fcbd0c8b [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 <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <lib/zx/channel.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <dispatcher-pool/dispatcher-thread-pool.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 <utility>
#include "debug-logging.h"
namespace audio {
namespace intel_hda {
namespace codecs {
void IntelHDACodecDriverBase::PrintDebugPrefix() const {
printf("HDACodec : ");
}
IntelHDACodecDriverBase::IntelHDACodecDriverBase() {
default_domain_ = dispatcher::ExecutionDomain::Create();
ZX_ASSERT(default_domain_ != nullptr);
}
#define DEV(_ctx) static_cast<IntelHDACodecDriverBase*>(_ctx)
zx_protocol_device_t IntelHDACodecDriverBase::CODEC_DEVICE_THUNKS = {
.version = DEVICE_OPS_VERSION,
.get_protocol = nullptr,
.open = nullptr,
.open_at = nullptr,
.close = nullptr,
.unbind = nullptr,
.release = [](void* ctx) { DEV(ctx)->DeviceRelease(); },
.read = nullptr,
.write = nullptr,
.get_size = nullptr,
.ioctl = nullptr,
.suspend = [](void* ctx, uint32_t flags) -> zx_status_t {
return DEV(ctx)->Suspend(flags);
},
.resume = nullptr,
.rxrpc = nullptr,
.message = nullptr,
};
#undef DEV
zx_status_t IntelHDACodecDriverBase::Bind(zx_device_t* codec_dev, const char* name) {
zx_status_t res;
if (codec_dev == nullptr)
return ZX_ERR_INVALID_ARGS;
if (codec_device_ != nullptr)
return ZX_ERR_BAD_STATE;
ihda_codec_protocol_t proto;
res = device_get_protocol(codec_dev, ZX_PROTOCOL_IHDA_CODEC, &proto);
if (res != ZX_OK)
return res;
if ((proto.ops == nullptr) ||
(proto.ops->get_driver_channel == nullptr))
return ZX_ERR_NOT_SUPPORTED;
// Allocate a dispatcher::Channel object which we will use to talk to the codec device
fbl::RefPtr<dispatcher::Channel> device_channel = dispatcher::Channel::Create();
if (device_channel == nullptr)
return ZX_ERR_NO_MEMORY;
// Obtain a channel handle from the device
zx::channel channel;
res = proto.ops->get_driver_channel(proto.ctx, channel.reset_and_get_address());
if (res != ZX_OK)
return res;
// 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;
}
// Activate our device channel. If something goes wrong, clear out the
// internal device_channel_ reference.
dispatcher::Channel::ProcessHandler phandler(
[codec = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t {
OBTAIN_EXECUTION_DOMAIN_TOKEN(t, codec->default_domain_);
return codec->ProcessClientRequest(channel);
});
dispatcher::Channel::ChannelClosedHandler chandler(
[codec = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void {
OBTAIN_EXECUTION_DOMAIN_TOKEN(t, codec->default_domain_);
codec->ProcessClientDeactivate(channel);
});
res = device_channel->Activate(std::move(channel),
default_domain_,
std::move(phandler),
std::move(chandler));
if (res != ZX_OK) {
fbl::AutoLock device_channel_lock(&device_channel_lock_);
device_channel_.reset();
return res;
}
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 = ddk_ref.leak_ref();
}
args.ops = &CODEC_DEVICE_THUNKS;
args.flags = DEVICE_ADD_NON_BINDABLE;
// Publish the device.
res = device_add(codec_dev, &args, nullptr);
if (res != ZX_OK) {
LOG("Failed to add codec device for \"%s\" (res %d)\n", name, res);
fbl::AutoLock device_channel_lock(&device_channel_lock_);
device_channel_.reset();
codec->Shutdown();
codec.reset();
return res;
}
// Success! Now that we are started, stash a pointer to the codec device
// that we are the driver for.
codec_device_ = codec_dev;
return ZX_OK;
}
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();
DEBUG_LOG("Shutdown complete\n");
}
zx_status_t IntelHDACodecDriverBase::Suspend(uint32_t flags) {
return ZX_ERR_NOT_SUPPORTED;
}
void IntelHDACodecDriverBase::DeviceRelease() {
auto thiz = fbl::internal::MakeRefPtrNoAdopt(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(dispatcher::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_status_t res;
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);
zx::channel channel;
res = ConvertHandle(&rxed_handle, &channel);
if (res != ZX_OK) {
DEBUG_LOG("Invalid or non-Channel handle in IHDA_CODEC_SET_STREAM_FORMAT "
"response (res %d)\n", res);
return res;
}
return stream->ProcessSetStreamFmt(resp.set_stream_fmt, std::move(channel));
}
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(const dispatcher::Channel* channel) {
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.
if (channel == device_channel_.get()) {
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_->Deactivate();
device_channel_ = nullptr;
}
}
zx_status_t IntelHDACodecDriverBase::SendCodecCommand(uint16_t nid,
CodecVerb verb,
bool no_ack) {
fbl::RefPtr<dispatcher::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<dispatcher::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::WrapRefPtr(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 < fbl::count_of(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 < fbl::count_of(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 < fbl::count_of(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