1 // Copyright 2018 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 "device_context.h"
8 
9 #include <fbl/auto_call.h>
10 #include <ktl/unique_ptr.h>
11 #include <kernel/range_check.h>
12 #include <ktl/move.h>
13 #include <new>
14 #include <trace.h>
15 #include <vm/vm.h>
16 #include <vm/vm_object_paged.h>
17 
18 #include "hw.h"
19 #include "iommu_impl.h"
20 
21 #define LOCAL_TRACE 0
22 
23 namespace intel_iommu {
24 
DeviceContext(ds::Bdf bdf,uint32_t domain_id,IommuImpl * parent,volatile ds::ExtendedContextEntry * context_entry)25 DeviceContext::DeviceContext(ds::Bdf bdf, uint32_t domain_id, IommuImpl* parent,
26                              volatile ds::ExtendedContextEntry* context_entry)
27         : parent_(parent), extended_context_entry_(context_entry), second_level_pt_(parent, this),
28           region_alloc_(), bdf_(bdf), extended_(true), domain_id_(domain_id) {
29 }
30 
DeviceContext(ds::Bdf bdf,uint32_t domain_id,IommuImpl * parent,volatile ds::ContextEntry * context_entry)31 DeviceContext::DeviceContext(ds::Bdf bdf, uint32_t domain_id, IommuImpl* parent,
32                              volatile ds::ContextEntry* context_entry)
33         : parent_(parent), context_entry_(context_entry), second_level_pt_(parent, this),
34           region_alloc_(), bdf_(bdf), extended_(false),
35           domain_id_(domain_id) {
36 }
37 
~DeviceContext()38 DeviceContext::~DeviceContext() {
39     bool was_present;
40     if (extended_) {
41         ds::ExtendedContextEntry entry;
42         entry.ReadFrom(extended_context_entry_);
43         was_present = entry.present();
44         entry.set_present(0);
45         entry.WriteTo(extended_context_entry_);
46     } else {
47         ds::ContextEntry entry;
48         entry.ReadFrom(context_entry_);
49         was_present = entry.present();
50         entry.set_present(0);
51         entry.WriteTo(context_entry_);
52     }
53 
54     if (was_present) {
55         // When modifying a present (extended) context entry, we must serially
56         // invalidate the context-cache, the PASID-cache, then the IOTLB (see
57         // 6.2.2.1 "Context-Entry Programming Considerations" in the VT-d spec,
58         // Oct 2014 rev).
59         parent_->InvalidateContextCacheDomain(domain_id_);
60         // TODO(teisenbe): Invalidate the PASID cache once we support those
61         parent_->InvalidateIotlbDomainAll(domain_id_);
62     }
63 
64     second_level_pt_.Destroy();
65 }
66 
InitCommon()67 zx_status_t DeviceContext::InitCommon() {
68     // TODO(teisenbe): don't hardcode PML4_L
69     DEBUG_ASSERT(parent_->caps()->supports_48_bit_agaw());
70     zx_status_t status = second_level_pt_.Init(PML4_L);
71     if (status != ZX_OK) {
72         return status;
73     }
74 
75     constexpr size_t kMaxAllocatorMemoryUsage = 16 * PAGE_SIZE;
76     fbl::RefPtr<RegionAllocator::RegionPool> region_pool =
77             RegionAllocator::RegionPool::Create(kMaxAllocatorMemoryUsage);
78     if (region_pool == nullptr) {
79         return ZX_ERR_NO_MEMORY;
80     }
81     status = region_alloc_.SetRegionPool(ktl::move(region_pool));
82     if (status != ZX_OK) {
83         return status;
84     }
85 
86     // Start the allocations at 1MB to handle the equivalent of nullptr
87     // dereferences.
88     uint64_t base = 1ull << 20;
89     uint64_t size = aspace_size() - base;
90     region_alloc_.AddRegion({ .base = 1ull << 20, .size = size });
91     return ZX_OK;
92 }
93 
Create(ds::Bdf bdf,uint32_t domain_id,IommuImpl * parent,volatile ds::ContextEntry * context_entry,ktl::unique_ptr<DeviceContext> * device)94 zx_status_t DeviceContext::Create(ds::Bdf bdf, uint32_t domain_id, IommuImpl* parent,
95                                   volatile ds::ContextEntry* context_entry,
96                                   ktl::unique_ptr<DeviceContext>* device) {
97     ds::ContextEntry entry;
98     entry.ReadFrom(context_entry);
99 
100     // It's a bug if we're trying to re-initialize an existing entry
101     ASSERT(!entry.present());
102 
103     fbl::AllocChecker ac;
104     ktl::unique_ptr<DeviceContext> dev(new (&ac) DeviceContext(bdf, domain_id, parent,
105                                                                context_entry));
106     if (!ac.check()) {
107         return ZX_ERR_NO_MEMORY;
108     }
109 
110     zx_status_t status = dev->InitCommon();
111     if (status != ZX_OK) {
112         return status;
113     }
114 
115     entry.set_present(1);
116     entry.set_fault_processing_disable(0);
117     entry.set_translation_type(ds::ContextEntry::kDeviceTlbDisabled);
118     // TODO(teisenbe): don't hardcode this
119     entry.set_address_width(ds::ContextEntry::k48Bit);
120     entry.set_domain_id(domain_id);
121     entry.set_second_level_pt_ptr(dev->second_level_pt_.phys() >> 12);
122 
123     entry.WriteTo(context_entry);
124 
125     *device = ktl::move(dev);
126     return ZX_OK;
127 }
128 
Create(ds::Bdf bdf,uint32_t domain_id,IommuImpl * parent,volatile ds::ExtendedContextEntry * context_entry,ktl::unique_ptr<DeviceContext> * device)129 zx_status_t DeviceContext::Create(ds::Bdf bdf, uint32_t domain_id, IommuImpl* parent,
130                                   volatile ds::ExtendedContextEntry* context_entry,
131                                   ktl::unique_ptr<DeviceContext>* device) {
132 
133     ds::ExtendedContextEntry entry;
134     entry.ReadFrom(context_entry);
135 
136     // It's a bug if we're trying to re-initialize an existing entry
137     ASSERT(!entry.present());
138 
139     fbl::AllocChecker ac;
140     ktl::unique_ptr<DeviceContext> dev(new (&ac) DeviceContext(bdf, domain_id,
141                                                                parent, context_entry));
142     if (!ac.check()) {
143         return ZX_ERR_NO_MEMORY;
144     }
145 
146     zx_status_t status = dev->InitCommon();
147     if (status != ZX_OK) {
148         return status;
149     }
150 
151     entry.set_present(1);
152     entry.set_fault_processing_disable(0);
153     entry.set_translation_type(ds::ExtendedContextEntry::kHostModeWithDeviceTlbDisabled);
154     entry.set_deferred_invld_enable(0);
155     entry.set_page_request_enable(0);
156     entry.set_nested_translation_enable(0);
157     entry.set_pasid_enable(0);
158     entry.set_global_page_enable(0);
159     // TODO(teisenbe): don't hardcode this
160     entry.set_address_width(ds::ExtendedContextEntry::k48Bit);
161     entry.set_no_exec_enable(1);
162     entry.set_write_protect_enable(1);
163     entry.set_cache_disable(0);
164     entry.set_extended_mem_type_enable(0);
165     entry.set_domain_id(domain_id);
166     entry.set_smep_enable(1);
167     entry.set_extended_accessed_flag_enable(0);
168     entry.set_execute_requests_enable(0);
169     entry.set_second_level_execute_bit_enable(0);
170     entry.set_second_level_pt_ptr(dev->second_level_pt_.phys() >> 12);
171 
172     entry.WriteTo(context_entry);
173 
174     *device = ktl::move(dev);
175     return ZX_OK;
176 }
177 
178 namespace {
179 
perms_to_arch_mmu_flags(uint32_t perms)180 uint perms_to_arch_mmu_flags(uint32_t perms) {
181     uint flags = 0;
182     if (perms & IOMMU_FLAG_PERM_READ) {
183         flags |= ARCH_MMU_FLAG_PERM_READ;
184     }
185     if (perms & IOMMU_FLAG_PERM_WRITE) {
186         flags |= ARCH_MMU_FLAG_PERM_WRITE;
187     }
188     if (perms & IOMMU_FLAG_PERM_EXECUTE) {
189         flags |= ARCH_MMU_FLAG_PERM_EXECUTE;
190     }
191     return flags;
192 }
193 
194 } // namespace
195 
SecondLevelMap(const fbl::RefPtr<VmObject> & vmo,uint64_t offset,size_t size,uint32_t perms,bool map_contiguous,paddr_t * virt_paddr,size_t * mapped_len)196 zx_status_t DeviceContext::SecondLevelMap(const fbl::RefPtr<VmObject>& vmo, uint64_t offset,
197                                           size_t size, uint32_t perms, bool map_contiguous,
198                                           paddr_t* virt_paddr, size_t* mapped_len) {
199     DEBUG_ASSERT(IS_PAGE_ALIGNED(offset));
200 
201     uint flags = perms_to_arch_mmu_flags(perms);
202 
203     if (vmo->is_paged() && !static_cast<VmObjectPaged*>(vmo.get())->is_contiguous()) {
204         return SecondLevelMapDiscontiguous(vmo, offset, size, flags, map_contiguous,
205                                            virt_paddr, mapped_len);
206     }
207     return SecondLevelMapContiguous(vmo, offset, size, flags, virt_paddr, mapped_len);
208 }
209 
SecondLevelMapDiscontiguous(const fbl::RefPtr<VmObject> & vmo,uint64_t offset,size_t size,uint flags,bool map_contiguous,paddr_t * virt_paddr,size_t * mapped_len)210 zx_status_t DeviceContext::SecondLevelMapDiscontiguous(const fbl::RefPtr<VmObject>& vmo,
211                                                        uint64_t offset, size_t size, uint flags,
212                                                        bool map_contiguous, paddr_t* virt_paddr,
213                                                        size_t* mapped_len) {
214     // If we don't need to map everything, don't try to map more than
215     // the min contiguity at a time.
216     const uint64_t min_contig = minimum_contiguity();
217     if (!map_contiguous && size > min_contig) {
218         size = min_contig;
219     }
220 
221     auto lookup_fn = [](void* ctx, size_t offset, size_t index, paddr_t pa) {
222         paddr_t* paddr = static_cast<paddr_t*>(ctx);
223         paddr[index] = pa;
224         return ZX_OK;
225     };
226 
227     RegionAllocator::Region::UPtr region;
228     zx_status_t status = region_alloc_.GetRegion(size, min_contig, region);
229     if (status != ZX_OK) {
230         return status;
231     }
232 
233     // Reserve a spot in the allocated regions list, so the extension can't fail
234     // after we do the map.
235     fbl::AllocChecker ac;
236     allocated_regions_.reserve(allocated_regions_.size() + 1, &ac);
237     if (!ac.check()) {
238         return ZX_ERR_NO_MEMORY;
239     }
240 
241     paddr_t base = region->base;
242     size_t remaining = size;
243 
244     auto cleanup_partial = fbl::MakeAutoCall([&]() {
245         size_t allocated = base - region->base;
246         size_t unmapped;
247         second_level_pt_.UnmapPages(base, allocated / PAGE_SIZE, &unmapped);
248         DEBUG_ASSERT(unmapped == allocated / PAGE_SIZE);
249     });
250 
251     while (remaining > 0) {
252         const size_t kNumEntriesPerLookup = 32;
253         size_t chunk_size = fbl::min(remaining, kNumEntriesPerLookup * PAGE_SIZE);
254         paddr_t paddrs[kNumEntriesPerLookup] = {};
255         status = vmo->Lookup(offset, chunk_size, lookup_fn, &paddrs);
256         if (status != ZX_OK) {
257             return status;
258         }
259 
260         size_t map_len = chunk_size / PAGE_SIZE;
261         size_t mapped;
262         status = second_level_pt_.MapPages(base, paddrs, map_len, flags, &mapped);
263         if (status != ZX_OK) {
264             return status;
265         }
266         ASSERT(mapped == map_len);
267 
268         base += chunk_size;
269         offset += chunk_size;
270         remaining -= chunk_size;
271     }
272 
273     cleanup_partial.cancel();
274 
275     *virt_paddr = region->base;
276     *mapped_len = size;
277 
278     allocated_regions_.push_back(ktl::move(region), &ac);
279     // Check shouldn't be able to fail, since we reserved the capacity already
280     ASSERT(ac.check());
281 
282     LTRACEF("Map(%02x:%02x.%1x): -> [%p, %p) %#x\n", bdf_.bus(), bdf_.dev(), bdf_.func(),
283             (void*)*virt_paddr, (void*)(*virt_paddr + *mapped_len), flags);
284     return ZX_OK;
285 }
286 
SecondLevelMapContiguous(const fbl::RefPtr<VmObject> & vmo,uint64_t offset,size_t size,uint flags,paddr_t * virt_paddr,size_t * mapped_len)287 zx_status_t DeviceContext::SecondLevelMapContiguous(const fbl::RefPtr<VmObject>& vmo,
288                                                     uint64_t offset, size_t size, uint flags,
289                                                     paddr_t* virt_paddr, size_t* mapped_len) {
290 
291     DEBUG_ASSERT(!vmo->is_paged() || static_cast<VmObjectPaged*>(vmo.get())->is_contiguous());
292 
293     auto lookup_fn = [](void* ctx, size_t offset, size_t index, paddr_t pa) {
294         paddr_t* paddr = static_cast<paddr_t*>(ctx);
295         *paddr = pa;
296         return ZX_OK;
297     };
298 
299     // Lookup the page in the VMO at the given offset. Since we know the VMO is
300     // contiguous, we can just extrapolate the rest of the addresses from the
301     // first.
302     paddr_t paddr = UINT64_MAX;
303     zx_status_t status = vmo->Lookup(offset, PAGE_SIZE, lookup_fn, &paddr);
304     if (status != ZX_OK) {
305         return status;
306     }
307     DEBUG_ASSERT(paddr != UINT64_MAX);
308 
309     RegionAllocator::Region::UPtr region;
310     uint64_t min_contig = minimum_contiguity();
311     status = region_alloc_.GetRegion(size, min_contig, region);
312     if (status != ZX_OK) {
313         return status;
314     }
315 
316     // Reserve a spot in the allocated regions list, so the extension can't fail
317     // after we do the map.
318     fbl::AllocChecker ac;
319     allocated_regions_.reserve(allocated_regions_.size() + 1, &ac);
320     if (!ac.check()) {
321         return ZX_ERR_NO_MEMORY;
322     }
323 
324     size_t map_len = size / PAGE_SIZE;
325     size_t mapped;
326     status = second_level_pt_.MapPagesContiguous(region->base, paddr, map_len, flags, &mapped);
327     if (status != ZX_OK) {
328         return status;
329     }
330     ASSERT(mapped == map_len);
331 
332     *virt_paddr = region->base;
333     *mapped_len = map_len * PAGE_SIZE;
334 
335     allocated_regions_.push_back(ktl::move(region), &ac);
336     // Check shouldn't be able to fail, since we reserved the capacity already
337     ASSERT(ac.check());
338 
339     LTRACEF("Map(%02x:%02x.%1x): [%p, %p) -> %p %#x\n", bdf_.bus(), bdf_.dev(), bdf_.func(),
340             (void*)paddr, (void*)(paddr + size), (void*)paddr, flags);
341     return ZX_OK;
342 }
343 
SecondLevelMapIdentity(paddr_t base,size_t size,uint32_t perms)344 zx_status_t DeviceContext::SecondLevelMapIdentity(paddr_t base, size_t size, uint32_t perms) {
345     DEBUG_ASSERT(IS_PAGE_ALIGNED(base));
346     DEBUG_ASSERT(IS_PAGE_ALIGNED(size));
347 
348     uint flags = perms_to_arch_mmu_flags(perms);
349 
350     RegionAllocator::Region::UPtr region;
351     zx_status_t status = region_alloc_.GetRegion({ base, size }, region);
352     if (status != ZX_OK) {
353         return status;
354     }
355 
356     // Reserve a spot in the allocated regions list, so the extension can't fail
357     // after we do the map.
358     fbl::AllocChecker ac;
359     allocated_regions_.reserve(allocated_regions_.size() + 1, &ac);
360     if (!ac.check()) {
361         return ZX_ERR_NO_MEMORY;
362     }
363 
364     size_t map_len = size / PAGE_SIZE;
365     size_t mapped;
366     status = second_level_pt_.MapPagesContiguous(base, base, map_len, flags, &mapped);
367     if (status != ZX_OK) {
368         return status;
369     }
370     ASSERT(mapped == map_len);
371 
372     allocated_regions_.push_back(ktl::move(region), &ac);
373     ASSERT(ac.check());
374     return ZX_OK;
375 }
376 
SecondLevelUnmap(paddr_t virt_paddr,size_t size)377 zx_status_t DeviceContext::SecondLevelUnmap(paddr_t virt_paddr, size_t size) {
378     DEBUG_ASSERT(IS_PAGE_ALIGNED(virt_paddr));
379     DEBUG_ASSERT(IS_PAGE_ALIGNED(size));
380 
381     // Check if we're trying to partially unmap a region, and if so fail.
382     for (size_t i = 0; i < allocated_regions_.size(); ++i) {
383         const auto& region = allocated_regions_[i];
384 
385         paddr_t intersect_base;
386         size_t intersect_size;
387         if (!GetIntersect(virt_paddr, size, region->base, region->size,
388                           &intersect_base, &intersect_size)) {
389             continue;
390         }
391 
392         if (intersect_base != region->base || intersect_size != region->size) {
393             return ZX_ERR_NOT_SUPPORTED;
394         }
395     }
396 
397     for (size_t i = 0; i < allocated_regions_.size(); ++i) {
398         const auto& region = allocated_regions_[i];
399         if (region->base < virt_paddr || region->base + region->size > virt_paddr + size) {
400             continue;
401         }
402 
403         size_t unmapped;
404         LTRACEF("Unmap(%02x:%02x.%1x): [%p, %p)\n", bdf_.bus(), bdf_.dev(), bdf_.func(),
405                 (void*)region->base, (void*)(region->base + region->size));
406         zx_status_t status = second_level_pt_.UnmapPages(region->base, region->size / PAGE_SIZE,
407                                                          &unmapped);
408         // Unmap should only be able to fail if an input was invalid
409         ASSERT(status == ZX_OK);
410         allocated_regions_.erase(i);
411         i--;
412     }
413 
414     return ZX_OK;
415 }
416 
minimum_contiguity() const417 uint64_t DeviceContext::minimum_contiguity() const {
418     // TODO(teisenbe): Do not hardcode this.
419     return 1ull << 20;
420 }
421 
aspace_size() const422 uint64_t DeviceContext::aspace_size() const {
423     // TODO(teisenbe): Do not hardcode this
424     // 2^48 is the size of an address space using 4-levevel translation.
425     return 1ull << 48;
426 }
427 
428 } // namespace intel_iommu
429