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