// 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 #include #include #include #include #include #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 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(®s()->rirbsts); unsigned int rirb_wr_ptr = REG_RD(®s()->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(®s()->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 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, fbl::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&& 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(fbl::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(fbl::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(®s()->corbsts); REG_WR(®s()->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(fbl::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(®s()->corbwp)); unsigned int corb_rd_ptr = REG_RD(®s()->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(®s()->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(®s()->statests) & HDA_REG_STATESTS_MASK; if (statests) { REG_WR(®s()->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(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(®s()->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