blob: 29f77a01ebe2085b76dc1a62e7637ee0b33b454c [file] [log] [blame]
// 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 <dev/iommu.h>
#include <err.h>
#include <lib/counters.h>
#include <new>
#include <vm/pinned_vm_object.h>
#include <vm/vm_object.h>
#include <zircon/rights.h>
KCOUNTER(dispatcher_bti_create_count, "dispatcher.bti.create")
KCOUNTER(dispatcher_bti_destroy_count, "dispatcher.bti.destroy")
zx_status_t BusTransactionInitiatorDispatcher::Create(
fbl::RefPtr<Iommu> iommu, uint64_t bti_id,
KernelHandle<BusTransactionInitiatorDispatcher>* handle, zx_rights_t* rights) {
if (!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<Iommu> iommu,
uint64_t bti_id)
: iommu_(ktl::move(iommu)), bti_id_(bti_id), zero_handles_(false) {
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<fbl::Mutex> guard{get_lock()};
if (zero_handles_) {
return ZX_ERR_BAD_STATE;
}
return PinnedMemoryTokenDispatcher::Create(fbl::WrapRefPtr(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<fbl::Mutex> guard{get_lock()};
quarantine_.swap(tmp);
}
}
void BusTransactionInitiatorDispatcher::on_zero_handles() {
Guard<fbl::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();
}
}
void BusTransactionInitiatorDispatcher::AddPmoLocked(PinnedMemoryTokenDispatcher* pmt) {
DEBUG_ASSERT(!pmt->dll_pmt_.InContainer());
pinned_memory_.push_back(pmt);
}
void BusTransactionInitiatorDispatcher::RemovePmo(PinnedMemoryTokenDispatcher* pmt) {
Guard<fbl::Mutex> guard{get_lock()};
DEBUG_ASSERT(pmt->dll_pmt_.InContainer());
pinned_memory_.erase(*pmt);
}
void BusTransactionInitiatorDispatcher::Quarantine(fbl::RefPtr<PinnedMemoryTokenDispatcher> pmt) {
Guard<fbl::Mutex> guard{get_lock()};
DEBUG_ASSERT(pmt->dll_pmt_.InContainer());
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();
}
}
void BusTransactionInitiatorDispatcher::PrintQuarantineWarningLocked() {
uint64_t leaked_pages = 0;
size_t num_entries = 0;
for (const auto& pmt : quarantine_) {
leaked_pages += pmt.size() / PAGE_SIZE;
num_entries++;
}
printf("Bus Transaction Initiator 0x%lx has leaked %" PRIu64 " pages in %zu VMOs\n",
bti_id_, leaked_pages, num_entries);
}