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