blob: cf75e5fd2bdd72d80760e1205bac1bec6d440eb1 [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 <endian.h>
#include <zircon/assert.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <string.h>
#include <intel-hda/utils/intel-hda-registers.h>
#include <utility>
#include "debug-logging.h"
#include "intel-hda-codec.h"
#include "intel-hda-controller.h"
namespace audio {
namespace intel_hda {
void IntelHDAController::WakeupIrqHandler() {
LOG(SPEW, "Waking up IRQ handler\n");
ZX_DEBUG_ASSERT(irq_wakeup_event_ != nullptr);
irq_wakeup_event_->Signal();
}
fbl::RefPtr<IntelHDACodec> IntelHDAController::GetCodec(uint id) {
ZX_DEBUG_ASSERT(id < countof(codecs_));
fbl::AutoLock codec_lock(&codec_lock_);
return codecs_[id];
}
void IntelHDAController::SnapshotRIRB() {
fbl::AutoLock rirb_lock(&rirb_lock_);
ZX_DEBUG_ASSERT(rirb_ && rirb_entry_count_ && rirb_mask_);
uint8_t rirbsts = REG_RD(&regs()->rirbsts);
unsigned int rirb_wr_ptr = REG_RD(&regs()->rirbwp) & rirb_mask_;
unsigned int pending = (rirb_entry_count_ + rirb_wr_ptr - rirb_rd_ptr_) & rirb_mask_;
// Copy the current state of the RIRB into our snapshot memory. Note: we
// loop at most up to 2 times in order to deal with the case where the
// active region of the ring buffer wraps around the end.
//
// TODO(johngro) : Make sure to invalidate cache for the memory region
// occupied by the RIRB before we copy into our snapshot if we are running
// on an architecure where cache coherency is not automatically managed for
// us via. something like snooping, or by a un-cached policy set on our
// mapped pages in the MMU. */
rirb_snapshot_cnt_ = 0;
while (pending) {
/* Intel HDA ring buffers are strange, see comments in
* intel_hda_codec_send_cmd. */
unsigned int tmp_rd = (rirb_rd_ptr_ + 1) & rirb_mask_;
unsigned int todo = fbl::min(pending, (rirb_entry_count_ - tmp_rd));
memcpy(rirb_snapshot_ + rirb_snapshot_cnt_,
rirb_ + tmp_rd,
sizeof(rirb_snapshot_[0]) * todo);
rirb_rd_ptr_ = (rirb_rd_ptr_ + todo) & rirb_mask_;
rirb_snapshot_cnt_ += todo;
pending -= todo;
}
REG_WR(&regs()->rirbsts, rirbsts);
ZX_DEBUG_ASSERT(!pending);
LOG(SPEW, "RIRB has %u pending responses; WP is @%u\n", rirb_snapshot_cnt_, rirb_wr_ptr);
if (rirbsts & HDA_REG_RIRBSTS_OIS) {
// TODO(johngro) : Implement retry behavior for codec command and
// control.
//
// The OIS bit in the RIRBSTS register indicates that hardware has
// encountered a overrun while attempting to write to the Response Input
// Ring Buffer. IOW - responses were received, but the controller was
// unable to write to system memory in time, and some of the responses
// were lost. This should *really* never happen. If it does, all bets
// are pretty much off. Every command verb sent is supposed to receive
// a response from the codecs; if a response is dropped it can easily
// wedge a codec's command and control state machine.
//
// This problem is not limited to HW being unable to write to system
// memory in time. There is no HW read pointer for the RIRB. The
// implication of this is that HW has no way to know that it has overrun
// SW if SW is not keeping up. If this was to happen, there would be no
// way for the system to know, it would just look like a large number of
// responses were lost.
//
// In either case, the only mitigation we could possibly implement would
// be a reasonable retry system at the codec driver level.
//
// Right now, we just log the error, ack the IRQ and move on.
LOG(ERROR, "CRITICAL ERROR: controller overrun detected while "
"attempting to write to response input ring buffer.\n");
}
}
void IntelHDAController::ProcessRIRB() {
fbl::AutoLock rirb_lock(&rirb_lock_);
ZX_DEBUG_ASSERT(rirb_snapshot_cnt_ < HDA_RIRB_MAX_ENTRIES);
ZX_DEBUG_ASSERT(rirb_snapshot_cnt_ < rirb_entry_count_);
for (unsigned int i = 0; i < rirb_snapshot_cnt_; ++i) {
auto& resp = rirb_snapshot_[i];
resp.OnReceived(); // Fixup endianness
/* Figure out the codec this came from */
uint32_t caddr = resp.caddr();
/* Sanity checks */
if (caddr >= countof(codecs_)) {
LOG(ERROR, "Received %ssolicited response with illegal codec address (%u) "
"[0x%08x, 0x%08x]\n",
resp.unsolicited() ? "un" : "", caddr, resp.data, resp.data_ex);
continue;
}
auto codec = GetCodec(caddr);
if (!codec) {
LOG(ERROR, "Received %ssolicited response for non-existent codec address (%u) "
"[0x%08x, 0x%08x]\n",
resp.unsolicited() ? "un" : "", caddr, resp.data, resp.data_ex);
continue;
}
LOG(TRACE, "RX[%2u]: 0x%08x%s\n",
caddr, resp.data, resp.unsolicited() ? " (unsolicited)" : "");
if (!resp.unsolicited()) {
fbl::unique_ptr<CodecCmdJob> job;
{
fbl::AutoLock corb_lock(&corb_lock_);
// If this was a solicited response, there needs to be an in-flight
// job waiting at the head of the in-flight queue which triggered
// it.
if (in_flight_corb_jobs_.is_empty()) {
LOG(ERROR,
"Received solicited response for codec address (%u) [0x%08x, 0x%08x] "
"but no in-flight job is waiting for it\n",
caddr, resp.data, resp.data_ex);
continue;
}
// Grab the front of the in-flight queue.
job = in_flight_corb_jobs_.pop_front();
}
// Sanity checks complete. Pass the response and the job which
// triggered it on to the codec.
codec->ProcessSolicitedResponse(resp, std::move(job));
} else {
auto codec = GetCodec(caddr);
if (!codec) {
LOG(ERROR,
"Received unsolicited response for non-existent codec address (%u) "
"[0x%08x, 0x%08x]\n", caddr, resp.data, resp.data_ex);
continue;
}
codec->ProcessUnsolicitedResponse(resp);
}
}
rirb_snapshot_cnt_ = 0;
}
void IntelHDAController::SendCodecCmdLocked(CodecCommand cmd) {
ZX_DEBUG_ASSERT(corb_space_ > 0);
// Write the command into the ring buffer and update the SW shadow of the
// write pointer. We will update the HW write pointer later on when we
// commit the new CORB commands.
//
// Note: Intel's ring buffers are a bit wonky. See Section 4.4.1.4, but the
// general idea is that to send a command, you do *not* write the command at
// WP and then bump the WP. Instead you write the command to (WP + 1) %
// RING_SIZE, then update WP to be (WP + 1) % RING_SIZE. IOW - The write
// pointer always points to the last command written, not the place where
// the next command will go. This behavior holds in the RIRB direction as
// well.
corb_wr_ptr_ = (corb_wr_ptr_ + 1) & corb_mask_;
corb_[corb_wr_ptr_].data = htole32(cmd.data);
corb_space_--;
}
zx_status_t IntelHDAController::QueueCodecCmd(fbl::unique_ptr<CodecCmdJob>&& job) {
ZX_DEBUG_ASSERT(job != nullptr);
LOG(TRACE, "TX: Codec ID %u Node ID %hu Verb 0x%05x\n",
job->codec_id(), job->nid(), job->verb().val);
// Enter the lock, then check out the state of the ring buffer. If the
// buffer is full, or if there are already commands backed up into the
// pending queue, just add the job to the end of the pending queue.
// Otherwise, actually write the command into the CORB, add the job to the
// end of the in-flight queue, and wakeup the IRQ thread.
//
fbl::AutoLock corb_lock(&corb_lock_);
ZX_DEBUG_ASSERT(corb_wr_ptr_ < corb_entry_count_);
ZX_DEBUG_ASSERT(corb_);
if (!corb_space_) {
// If we have no space in the CORB, there must be some jobs which are
// currently in-flight.
ZX_DEBUG_ASSERT(!in_flight_corb_jobs_.is_empty());
pending_corb_jobs_.push_back(std::move(job));
} else {
// Alternatively, if there is space in the CORB, the pending job queue
// had better be empty.
ZX_DEBUG_ASSERT(pending_corb_jobs_.is_empty());
SendCodecCmdLocked(job->command());
in_flight_corb_jobs_.push_back(std::move(job));
}
CommitCORBLocked();
return ZX_OK;
}
void IntelHDAController::ProcessCORB() {
fbl::AutoLock corb_lock(&corb_lock_);
// Check IRQ status for the CORB
uint8_t corbsts = REG_RD(&regs()->corbsts);
REG_WR(&regs()->corbsts, corbsts);
if (corbsts & HDA_REG_CORBSTS_MEI) {
// TODO(johngro) : Implement proper controller reset behavior.
//
// The MEI bit in CORBSTS indicates some form memory error detected by
// the controller while attempting to read from system memory. This is
// Extremely Bad and should never happen. If it does, the TRM suggests
// that all bets are off, and the only reasonable action is to
// completely shutdown and reset the controller.
//
// Right now, we do not implement this behavior. Instead we log, then
// assert in debug builds. In release builds, we simply ack the
// interrupt and move on.
//
LOG(ERROR, "CRITICAL ERROR: controller encountered an unrecoverable "
"error attempting to read from system memory!\n");
ZX_DEBUG_ASSERT(false);
}
// Figure out how much space we have in the CORB
ComputeCORBSpaceLocked();
// While we have room in the CORB, and still have commands which are waiting
// to be sent out, move commands from the pending queue into the in-flight
// queue.
LOG(SPEW, "CORB has space for %u commands; WP is @%u\n", corb_space_, corb_wr_ptr_);
while (corb_space_ && !pending_corb_jobs_.is_empty()) {
auto job = pending_corb_jobs_.pop_front();
SendCodecCmdLocked(job->command());
in_flight_corb_jobs_.push_back(std::move(job));
}
LOG(SPEW, "Update CORB WP; WP is @%u\n", corb_wr_ptr_);
// Update the CORB write pointer.
CommitCORBLocked();
}
void IntelHDAController::ComputeCORBSpaceLocked() {
ZX_DEBUG_ASSERT(corb_entry_count_ && corb_mask_);
ZX_DEBUG_ASSERT(corb_wr_ptr_ == REG_RD(&regs()->corbwp));
unsigned int corb_rd_ptr = REG_RD(&regs()->corbrp) & corb_mask_;
unsigned int corb_used = (corb_entry_count_ + corb_wr_ptr_ - corb_rd_ptr) & corb_mask_;
/* The way the Intel HDA command ring buffers work, it is impossible to ever
* be using more than N - 1 of the ring buffer entries. Our available
* space should be the ring buffer size, minus the amt currently used, minus 1 */
ZX_DEBUG_ASSERT(corb_entry_count_ > corb_used);
ZX_DEBUG_ASSERT(corb_max_in_flight_ >= corb_used);
corb_space_ = corb_max_in_flight_ - corb_used;
}
void IntelHDAController::CommitCORBLocked() {
// TODO(johngro) : Make sure to force a write back of the cache for the
// dirty portions of the CORB before we update the write pointer if we are
// running on an architecure where cache coherency is not automatically
// managed for us via. snooping or by an explicit uncached or write-thru
// policy set on our mapped pages in the MMU.
ZX_DEBUG_ASSERT(regs());
ZX_DEBUG_ASSERT(corb_entry_count_ && corb_mask_);
ZX_DEBUG_ASSERT(corb_wr_ptr_ < corb_entry_count_);
REG_WR(&regs()->corbwp, corb_wr_ptr_);
}
void IntelHDAController::ProcessStreamIRQ(uint32_t intsts) {
for (uint32_t i = 0; intsts; i++, intsts >>= 1) {
if (intsts & 0x1) {
ZX_DEBUG_ASSERT(i < countof(all_streams_));
ZX_DEBUG_ASSERT(all_streams_[i] != nullptr);
all_streams_[i]->ProcessStreamIRQ();
}
}
}
void IntelHDAController::ProcessControllerIRQ() {
// Start by checking for codec wake events.
uint16_t statests = REG_RD(&regs()->statests) & HDA_REG_STATESTS_MASK;
if (statests) {
REG_WR(&regs()->statests, statests);
uint32_t tmp = statests;
for (uint8_t i = 0u; statests && (i < countof(codecs_)); ++i, tmp >>= 1) {
if (!(tmp & 1u))
continue;
// TODO(johngro) : How is a codec supposed to signal a hot unplug
// event? Docs clearly indicate that they can be hot plugged, and
// that you detect hot plug events by enabling wake events and
// checking the STATESTS register when you receive one, but they
// don't seem to give any indication of how to detect that a codec
// has been unplugged.
if (codecs_[i] == nullptr) {
codecs_[i] = IntelHDACodec::Create(*this, i);
// If we successfully created our codec, attempt to start it up.
// If it fails to start, release our reference to the codec.
if ((codecs_[i] != nullptr) && (codecs_[i]->Startup() != ZX_OK)) {
codecs_[i] = nullptr;
}
} else {
codecs_[i]->ProcessWakeupEvt();
}
}
}
}
zx_status_t IntelHDAController::HandleIrq() {
if (GetState() != State::OPERATING) {
LOG(WARN, "IRQ Handler shutting down due to invalid state (%u)!\n",
static_cast<uint32_t>(GetState()));
return ZX_ERR_BAD_STATE;
}
// Take a snapshot of any pending responses ASAP in order to minimize
// the chance of an RIRB overflow. We will process the responses which
// we snapshot-ed in a short while after we are done handling other
// important IRQ tasks.
SnapshotRIRB();
// Fetch the interrupt status word and dispatch as appropriate
uint32_t intsts = REG_RD(&regs()->intsts);
if (intsts & HDA_REG_INTCTL_SIE_MASK)
ProcessStreamIRQ(intsts & HDA_REG_INTCTL_SIE_MASK);
if (intsts & HDA_REG_INTCTL_CIE)
ProcessControllerIRQ();
if (dsp_ != nullptr) {
dsp_->ProcessIRQ();
}
ProcessRIRB();
ProcessCORB();
return ZX_OK;
}
} // namespace intel_hda
} // namespace audio