1// Copyright 2017 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include <object/bus_transaction_initiator_dispatcher.h>
8
9#include <dev/iommu.h>
10#include <err.h>
11#include <vm/vm_object.h>
12#include <zircon/rights.h>
13#include <zxcpp/new.h>
14
15zx_status_t BusTransactionInitiatorDispatcher::Create(fbl::RefPtr<Iommu> iommu, uint64_t bti_id,
16                                                      fbl::RefPtr<Dispatcher>* dispatcher,
17                                                      zx_rights_t* rights) {
18
19    if (!iommu->IsValidBusTxnId(bti_id)) {
20        return ZX_ERR_INVALID_ARGS;
21    }
22
23    fbl::AllocChecker ac;
24    auto disp = new (&ac) BusTransactionInitiatorDispatcher(fbl::move(iommu), bti_id);
25    if (!ac.check()) {
26        return ZX_ERR_NO_MEMORY;
27    }
28
29    *rights = ZX_DEFAULT_BTI_RIGHTS;
30    *dispatcher = fbl::AdoptRef<Dispatcher>(disp);
31    return ZX_OK;
32}
33
34BusTransactionInitiatorDispatcher::BusTransactionInitiatorDispatcher(fbl::RefPtr<Iommu> iommu,
35                                                                     uint64_t bti_id)
36        : iommu_(fbl::move(iommu)), bti_id_(bti_id), zero_handles_(false) {}
37
38BusTransactionInitiatorDispatcher::~BusTransactionInitiatorDispatcher() {
39    DEBUG_ASSERT(pinned_memory_.is_empty());
40}
41
42zx_status_t BusTransactionInitiatorDispatcher::Pin(fbl::RefPtr<VmObject> vmo, uint64_t offset,
43                                                   uint64_t size, uint32_t perms,
44                                                   fbl::RefPtr<Dispatcher>* pmt,
45                                                   zx_rights_t* pmt_rights) {
46
47    DEBUG_ASSERT(IS_PAGE_ALIGNED(offset));
48    DEBUG_ASSERT(IS_PAGE_ALIGNED(size));
49
50    if (size == 0) {
51        return ZX_ERR_INVALID_ARGS;
52    }
53
54    Guard<fbl::Mutex> guard{get_lock()};
55
56    if (zero_handles_) {
57        return ZX_ERR_BAD_STATE;
58    }
59
60    return PinnedMemoryTokenDispatcher::Create(fbl::WrapRefPtr(this), fbl::move(vmo),
61                                               offset, size, perms, pmt, pmt_rights);
62}
63
64void BusTransactionInitiatorDispatcher::ReleaseQuarantine() {
65    QuarantineList tmp;
66
67    // The PMT dtor will call RemovePmo, which will reacquire this BTI's lock.
68    // To avoid deadlock, drop the lock before letting the quarantined PMTs go.
69    {
70        Guard<fbl::Mutex> guard{get_lock()};
71        quarantine_.swap(tmp);
72    }
73}
74
75void BusTransactionInitiatorDispatcher::on_zero_handles() {
76    Guard<fbl::Mutex> guard{get_lock()};
77    // Prevent new pinning from happening.  The Dispatcher will stick around
78    // until all of the PMTs are closed.
79    zero_handles_ = true;
80
81    // Do not clear out the quarantine list.  PMTs hold a reference to the BTI
82    // and the BTI holds a reference to each quarantined PMT.  We intentionally
83    // leak the BTI, all quarantined PMTs, and their underlying VMOs.  We could
84    // get away with freeing the BTI and the PMTs, but for safety we must leak
85    // at least the pinned parts of the VMOs, since we have no assurance that
86    // hardware is not still reading/writing to it.
87    if (!quarantine_.is_empty()) {
88        PrintQuarantineWarningLocked();
89    }
90}
91
92void BusTransactionInitiatorDispatcher::AddPmoLocked(PinnedMemoryTokenDispatcher* pmt) {
93    DEBUG_ASSERT(!pmt->dll_pmt_.InContainer());
94    pinned_memory_.push_back(pmt);
95}
96
97void BusTransactionInitiatorDispatcher::RemovePmo(PinnedMemoryTokenDispatcher* pmt) {
98    Guard<fbl::Mutex> guard{get_lock()};
99    DEBUG_ASSERT(pmt->dll_pmt_.InContainer());
100    pinned_memory_.erase(*pmt);
101}
102
103void BusTransactionInitiatorDispatcher::Quarantine(fbl::RefPtr<PinnedMemoryTokenDispatcher> pmt) {
104    Guard<fbl::Mutex> guard{get_lock()};
105
106    DEBUG_ASSERT(pmt->dll_pmt_.InContainer());
107    quarantine_.push_back(fbl::move(pmt));
108
109    if (zero_handles_) {
110        // If we quarantine when at zero handles, this PMT will be leaked.  See
111        // the comment in on_zero_handles().
112        PrintQuarantineWarningLocked();
113    }
114}
115
116void BusTransactionInitiatorDispatcher::PrintQuarantineWarningLocked() {
117    uint64_t leaked_pages = 0;
118    size_t num_entries = 0;
119    for (const auto& pmt : quarantine_) {
120        leaked_pages += pmt.size() / PAGE_SIZE;
121        num_entries++;
122    }
123    printf("Bus Transaction Initiator 0x%lx has leaked %" PRIu64 " pages in %zu VMOs\n",
124           bti_id_, leaked_pages, num_entries);
125}
126