| // 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. |
| |
| #pragma once |
| |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/pci.h> |
| #include <zircon/types.h> |
| #include <fbl/atomic.h> |
| #include <fbl/intrusive_single_list.h> |
| #include <fbl/recycler.h> |
| #include <fbl/unique_ptr.h> |
| #include <fbl/vmo_mapper.h> |
| #include <threads.h> |
| #include <zx/interrupt.h> |
| |
| #include <dispatcher-pool/dispatcher-execution-domain.h> |
| #include <intel-hda/utils/codec-commands.h> |
| #include <intel-hda/utils/intel-hda-registers.h> |
| #include <intel-hda/utils/intel-hda-proto.h> |
| |
| #include "codec-cmd-job.h" |
| #include "debug-logging.h" |
| #include "intel-hda-codec.h" |
| #include "pinned-vmo.h" |
| #include "thread-annotations.h" |
| #include "utils.h" |
| |
| namespace audio { |
| namespace intel_hda { |
| |
| class IntelHDAController : public fbl::RefCounted<IntelHDAController> { |
| public: |
| IntelHDAController(); |
| ~IntelHDAController(); |
| |
| zx_status_t Init(zx_device_t* pci_dev); |
| |
| // one-liner accessors. |
| const char* dev_name() const { return device_get_name(dev_node_); } |
| zx_device_t* dev_node() { return dev_node_; } |
| const zx_pcie_device_info_t& dev_info() const { return pci_dev_info_; } |
| unsigned int id() const { return id_; } |
| const char* log_prefix() const { return log_prefix_; } |
| |
| // CORB/RIRB |
| zx_status_t QueueCodecCmd(fbl::unique_ptr<CodecCmdJob>&& job) TA_EXCL(corb_lock_); |
| |
| // DMA Streams |
| fbl::RefPtr<IntelHDAStream> AllocateStream(IntelHDAStream::Type type) |
| TA_EXCL(stream_pool_lock_); |
| void ReturnStream(fbl::RefPtr<IntelHDAStream>&& stream) |
| TA_EXCL(stream_pool_lock_); |
| |
| static zx_status_t DriverInit(void** out_ctx); |
| static zx_status_t DriverBind(void* ctx, zx_device_t* device); |
| static void DriverRelease(void* ctx); |
| |
| private: |
| using StateStorage = uint32_t; |
| enum class State : StateStorage { |
| STARTING, |
| OPERATING, |
| SHUTTING_DOWN, |
| SHUT_DOWN, |
| }; |
| |
| static constexpr uint RIRB_RESERVED_RESPONSE_SLOTS = 8u; |
| |
| // Accessor for our mapped registers |
| hda_registers_t* regs() const { |
| return &reinterpret_cast<hda_all_registers_t*>(mapped_regs_.start())->regs; |
| } |
| |
| int IRQThread(); |
| void WakeupIRQThread(); |
| void ShutdownIRQThread(); |
| |
| // Internal stream bookkeeping. |
| void ReturnStreamLocked(fbl::RefPtr<IntelHDAStream>&& stream) TA_REQ (stream_pool_lock_); |
| uint8_t AllocateStreamTagLocked(bool input) TA_REQ (stream_pool_lock_); |
| void ReleaseStreamTagLocked (bool input, uint8_t tag_num) TA_REQ (stream_pool_lock_); |
| |
| // Device interface implementation |
| void DeviceShutdown(); |
| void DeviceRelease(); |
| zx_status_t DeviceIoctl(uint32_t op, void* out_buf, size_t out_len, size_t* out_actual); |
| |
| // Root device interface implementation |
| void RootDeviceRelease(); |
| |
| // State control |
| // TODO(johngro) : extend fbl::atomic to support enum classes as well. |
| void SetState(State state) { state_.store(static_cast<StateStorage>(state)); } |
| State GetState() { return static_cast<State>(state_.load()); } |
| |
| // Codec lifetime maanagement |
| fbl::RefPtr<IntelHDACodec> GetCodec(uint id); |
| |
| // Methods used during initialization |
| zx_status_t InitInternal(zx_device_t* pci_dev); |
| zx_status_t ResetControllerHW(); |
| zx_status_t SetupPCIDevice(zx_device_t* pci_dev); |
| zx_status_t SetupPCIInterrupts(); |
| zx_status_t SetupStreamDescriptors() TA_EXCL(stream_pool_lock_); |
| zx_status_t SetupCommandBufferSize(uint8_t* size_reg, unsigned int* entry_count); |
| zx_status_t SetupCommandBuffer() TA_EXCL(corb_lock_, rirb_lock_); |
| |
| void WaitForIrqOrWakeup(); |
| |
| zx_status_t ResetCORBRdPtrLocked() TA_REQ(corb_lock_); |
| |
| void SnapshotRIRB() TA_EXCL(corb_lock_, rirb_lock_); |
| void ProcessRIRB() TA_EXCL(corb_lock_, rirb_lock_); |
| |
| void ProcessCORB() TA_EXCL(corb_lock_, rirb_lock_); |
| void ComputeCORBSpaceLocked() TA_REQ(corb_lock_); |
| void CommitCORBLocked() TA_REQ(corb_lock_); |
| void SendCodecCmdLocked(CodecCommand cmd) TA_REQ(corb_lock_); |
| |
| void ProcessStreamIRQ(uint32_t intsts); |
| void ProcessControllerIRQ(); |
| |
| // Thunk for interacting with client channels |
| zx_status_t ProcessClientRequest(dispatcher::Channel* channel); |
| zx_status_t SnapshotRegs(dispatcher::Channel* channel, |
| const ihda_controller_snapshot_regs_req_t& req); |
| |
| // Dispatcher framework state |
| fbl::RefPtr<dispatcher::ExecutionDomain> default_domain_; |
| |
| // IRQ thread and state machine. |
| fbl::atomic<StateStorage> state_; |
| thrd_t irq_thread_; |
| bool irq_thread_started_ = false; |
| |
| // Log prefix storage |
| char log_prefix_[LOG_PREFIX_STORAGE] = { 0 }; |
| |
| // Upstream PCI device, protocol interface, and device info. |
| zx_device_t* pci_dev_ = nullptr; |
| pci_protocol_t pci_ = { nullptr, nullptr }; |
| zx_pcie_device_info_t pci_dev_info_; |
| static zx_protocol_device_t ROOT_DEVICE_THUNKS; |
| |
| // Unique ID and published HDA device node. |
| const uint32_t id_; |
| zx_device_t* dev_node_ = nullptr; |
| |
| // PCI Registers and IRQ |
| zx::interrupt irq_; |
| fbl::VmoMapper mapped_regs_; |
| |
| // A handle to the Bus Transaction Initiator for this PCI device. Used to |
| // grant access to specific regions of physical mememory to the controller |
| // hardware so that it may DMA. |
| fbl::RefPtr<RefCountedBti> pci_bti_; |
| |
| // Physical memory allocated for the command buffer (CORB/RIRB) |
| fbl::VmoMapper cmd_buf_cpu_mem_ TA_GUARDED(corb_lock_); |
| PinnedVmo cmd_buf_hda_mem_ TA_GUARDED(corb_lock_); |
| |
| // Stream state |
| fbl::Mutex stream_pool_lock_; |
| IntelHDAStream::Tree free_input_streams_ TA_GUARDED(stream_pool_lock_); |
| IntelHDAStream::Tree free_output_streams_ TA_GUARDED(stream_pool_lock_); |
| IntelHDAStream::Tree free_bidir_streams_ TA_GUARDED(stream_pool_lock_); |
| uint16_t free_input_tags_ TA_GUARDED(stream_pool_lock_) = 0xFFFEu; |
| uint16_t free_output_tags_ TA_GUARDED(stream_pool_lock_) = 0xFFFEu; |
| |
| // Array of pointers to all possible streams (used for O(1) lookup during IRQ dispatch) |
| fbl::RefPtr<IntelHDAStream> all_streams_[MAX_STREAMS_PER_CONTROLLER]; |
| |
| // Codec bus command ring-buffer state (CORB/RIRB) |
| fbl::Mutex corb_lock_; |
| CodecCommand* corb_ TA_GUARDED(corb_lock_) = nullptr; |
| unsigned int corb_entry_count_ TA_GUARDED(corb_lock_) = 0; |
| unsigned int corb_mask_ TA_GUARDED(corb_lock_) = 0; |
| unsigned int corb_wr_ptr_ TA_GUARDED(corb_lock_) = 0; |
| unsigned int corb_space_ TA_GUARDED(corb_lock_) = 0; |
| unsigned int corb_max_in_flight_ TA_GUARDED(corb_lock_) = 0; |
| |
| fbl::Mutex rirb_lock_ TA_ACQ_BEFORE(corb_lock_); |
| CodecResponse* rirb_ TA_GUARDED(rirb_lock_) = nullptr; |
| unsigned int rirb_entry_count_ TA_GUARDED(rirb_lock_) = 0; |
| unsigned int rirb_mask_ TA_GUARDED(rirb_lock_) = 0; |
| unsigned int rirb_rd_ptr_ TA_GUARDED(rirb_lock_) = 0; |
| unsigned int rirb_snapshot_cnt_ TA_GUARDED(rirb_lock_) = 0; |
| CodecResponse rirb_snapshot_[HDA_RIRB_MAX_ENTRIES] TA_GUARDED(rirb_lock_); |
| |
| fbl::DoublyLinkedList<fbl::unique_ptr<CodecCmdJob>> in_flight_corb_jobs_ |
| TA_GUARDED(corb_lock_); |
| fbl::DoublyLinkedList<fbl::unique_ptr<CodecCmdJob>> pending_corb_jobs_ |
| TA_GUARDED(corb_lock_); |
| |
| fbl::Mutex codec_lock_; |
| fbl::RefPtr<IntelHDACodec> codecs_[HDA_MAX_CODECS]; |
| |
| static fbl::atomic_uint32_t device_id_gen_; |
| static zx_protocol_device_t CONTROLLER_DEVICE_THUNKS; |
| }; |
| |
| } // namespace intel_hda |
| } // namespace audio |