| // Copyright 2017 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include "object/bus_transaction_initiator_dispatcher.h" |
| |
| #include <align.h> |
| #include <lib/counters.h> |
| #include <lib/debuglog.h> |
| #include <zircon/errors.h> |
| #include <zircon/rights.h> |
| #include <zircon/types.h> |
| |
| #include <new> |
| |
| #include <dev/iommu.h> |
| #include <object/process_dispatcher.h> |
| #include <object/thread_dispatcher.h> |
| #include <vm/pinned_vm_object.h> |
| #include <vm/vm_object.h> |
| |
| KCOUNTER(dispatcher_bti_create_count, "dispatcher.bti.create") |
| KCOUNTER(dispatcher_bti_destroy_count, "dispatcher.bti.destroy") |
| |
| zx_status_t BusTransactionInitiatorDispatcher::Create( |
| fbl::RefPtr<IommuDispatcher> iommu, uint64_t bti_id, |
| KernelHandle<BusTransactionInitiatorDispatcher>* handle, zx_rights_t* rights) { |
| if (!iommu->iommu().IsValidBusTxnId(bti_id)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AllocChecker ac; |
| KernelHandle new_handle( |
| fbl::AdoptRef(new (&ac) BusTransactionInitiatorDispatcher(ktl::move(iommu), bti_id))); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| *rights = default_rights(); |
| *handle = ktl::move(new_handle); |
| return ZX_OK; |
| } |
| |
| BusTransactionInitiatorDispatcher::BusTransactionInitiatorDispatcher( |
| fbl::RefPtr<IommuDispatcher> iommu, uint64_t bti_id) |
| : iommu_(ktl::move(iommu)), bti_id_(bti_id), zero_handles_(false) { |
| DEBUG_ASSERT(iommu_); |
| kcounter_add(dispatcher_bti_create_count, 1); |
| } |
| |
| BusTransactionInitiatorDispatcher::~BusTransactionInitiatorDispatcher() { |
| DEBUG_ASSERT(pinned_memory_.is_empty()); |
| kcounter_add(dispatcher_bti_destroy_count, 1); |
| } |
| |
| zx_status_t BusTransactionInitiatorDispatcher::Pin( |
| fbl::RefPtr<VmObject> vmo, uint64_t offset, uint64_t size, uint32_t perms, |
| KernelHandle<PinnedMemoryTokenDispatcher>* pmt_handle, zx_rights_t* pmt_rights) { |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(offset)); |
| DEBUG_ASSERT(IS_PAGE_ALIGNED(size)); |
| |
| if (size == 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| PinnedVmObject pinned_vmo; |
| zx_status_t status = PinnedVmObject::Create(vmo, offset, size, &pinned_vmo); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| Guard<Mutex> guard{get_lock()}; |
| // User may not pin new memory if either our BTI has hit zero handles, or if |
| // we currently have quarantined pages. In the case that there are active |
| // quarantined pages, driver code is expected to take the steps to stop their |
| // DMA, and then release the quarantine before proceeding to pin new memory. |
| if (zero_handles_ || !quarantine_.is_empty()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return PinnedMemoryTokenDispatcher::Create(fbl::RefPtr(this), ktl::move(pinned_vmo), perms, |
| pmt_handle, pmt_rights); |
| } |
| |
| void BusTransactionInitiatorDispatcher::ReleaseQuarantine() { |
| QuarantineList tmp; |
| |
| // The PMT dtor will call RemovePmo, which will reacquire this BTI's lock. |
| // To avoid deadlock, drop the lock before letting the quarantined PMTs go. |
| { |
| Guard<Mutex> guard{get_lock()}; |
| quarantine_.swap(tmp); |
| } |
| } |
| |
| void BusTransactionInitiatorDispatcher::on_zero_handles() { |
| Guard<Mutex> guard{get_lock()}; |
| // Prevent new pinning from happening. The Dispatcher will stick around |
| // until all of the PMTs are closed. |
| zero_handles_ = true; |
| |
| // Do not clear out the quarantine list. PMTs hold a reference to the BTI |
| // and the BTI holds a reference to each quarantined PMT. We intentionally |
| // leak the BTI, all quarantined PMTs, and their underlying VMOs. We could |
| // get away with freeing the BTI and the PMTs, but for safety we must leak |
| // at least the pinned parts of the VMOs, since we have no assurance that |
| // hardware is not still reading/writing to it. |
| if (!quarantine_.is_empty()) { |
| PrintQuarantineWarningLocked(BtiPageLeakReason::BtiClose); |
| } |
| } |
| |
| zx_status_t BusTransactionInitiatorDispatcher::set_name(const char* name, size_t len) { |
| // The kernel implementation of fbl::Name is protected using an internal |
| // spinlock. No need for any special locks here. |
| return name_.set(name, len); |
| } |
| |
| void BusTransactionInitiatorDispatcher::get_name(char (&out_name)[ZX_MAX_NAME_LEN]) const { |
| // The kernel implementation of fbl::Name is protected using an internal |
| // spinlock. No need for any special locks here. |
| name_.get(ZX_MAX_NAME_LEN, out_name); |
| } |
| |
| void BusTransactionInitiatorDispatcher::AddPmoLocked(PinnedMemoryTokenDispatcher* pmt) { |
| DEBUG_ASSERT(!fbl::InContainer<PmtListTag>(*pmt)); |
| pinned_memory_.push_back(pmt); |
| } |
| |
| void BusTransactionInitiatorDispatcher::RemovePmo(PinnedMemoryTokenDispatcher* pmt) { |
| Guard<Mutex> guard{get_lock()}; |
| DEBUG_ASSERT(fbl::InContainer<PmtListTag>(*pmt)); |
| pinned_memory_.erase(*pmt); |
| } |
| |
| void BusTransactionInitiatorDispatcher::Quarantine(fbl::RefPtr<PinnedMemoryTokenDispatcher> pmt) { |
| Guard<Mutex> guard{get_lock()}; |
| |
| DEBUG_ASSERT(fbl::InContainer<PmtListTag>(*pmt)); |
| quarantine_.push_back(ktl::move(pmt)); |
| |
| if (zero_handles_) { |
| // If we quarantine when at zero handles, this PMT will be leaked. See |
| // the comment in on_zero_handles(). |
| PrintQuarantineWarningLocked(BtiPageLeakReason::PmtClose); |
| } |
| } |
| |
| // The count of the pinned memory object tokens. |
| uint64_t BusTransactionInitiatorDispatcher::pmo_count() const { |
| Guard<Mutex> guard{get_lock()}; |
| return pinned_memory_.size_slow(); |
| } |
| |
| // The count of the quarantined pinned memory object tokens. |
| uint64_t BusTransactionInitiatorDispatcher::quarantine_count() const { |
| Guard<Mutex> guard{get_lock()}; |
| return quarantine_.size_slow(); |
| } |
| |
| void BusTransactionInitiatorDispatcher::PrintQuarantineWarningLocked(BtiPageLeakReason reason) { |
| uint64_t leaked_pages = 0; |
| size_t num_entries = 0; |
| for (const auto& pmt : quarantine_) { |
| leaked_pages += pmt.size() / PAGE_SIZE; |
| num_entries++; |
| } |
| |
| char proc_name[ZX_MAX_NAME_LEN] = {0}; |
| char thread_name[ZX_MAX_NAME_LEN] = {0}; |
| char bti_name[ZX_MAX_NAME_LEN] = {0}; |
| |
| // If we have no current thread dispatcher, then this is a kernel thread. We |
| // have no process to report, just report that the action was taken by a |
| // kernel thread and leave it at that. |
| ThreadDispatcher* thread_disp = ThreadDispatcher::GetCurrent(); |
| if (thread_disp == nullptr) { |
| snprintf(proc_name, sizeof(proc_name), "<kernel>"); |
| snprintf(thread_name, sizeof(thread_name), "<kernel>"); |
| } else { |
| // Get the name of the user mode process and thread which closed the handle |
| // to the object which eventually resulted in the leak. |
| ProcessDispatcher::GetCurrent()->get_name(proc_name); |
| thread_disp->get_name(thread_name); |
| } |
| |
| // Fetch the BTI name (if any). |
| this->get_name(bti_name); |
| |
| // If any of these strings are empty, replace them with just "<unknown>". |
| if (!proc_name[0]) { |
| snprintf(proc_name, sizeof(proc_name), "<unknown>"); |
| } |
| if (!thread_name[0]) { |
| snprintf(thread_name, sizeof(thread_name), "<unknown>"); |
| } |
| if (!bti_name[0]) { |
| snprintf(bti_name, sizeof(bti_name), "<unknown>"); |
| } |
| |
| // Finally, print the message describing the leak, as best we can. |
| const char* leak_cause; |
| switch (reason) { |
| case BtiPageLeakReason::BtiClose: |
| leak_cause = "a BTI being closed with a non-empty quarantine list"; |
| break; |
| |
| case BtiPageLeakReason::PmtClose: |
| leak_cause = "a pinned PMT being closed, when the BTI used to pin it was already closed"; |
| break; |
| |
| default: |
| leak_cause = "<unknown>"; |
| break; |
| } |
| |
| // TODO(fxbug.dev/56157): Make this an OOPS once the driver bugs are fixed. |
| printf("KERN: Bus Transaction Initiator (ID 0x%lx, name \"%s\") has leaked %" PRIu64 |
| " pages in %zu VMOs. Leak was caused by %s. The last handle was closed by process " |
| "\"%s\", and thread \"%s\"\n", |
| bti_id_, bti_name, leaked_pages, num_entries, leak_cause, proc_name, thread_name); |
| } |