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