Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iommu/sva: Assign a PASID to mm on PASID allocation and free it on mm exit

PASIDs are process-wide. It was attempted to use refcounted PASIDs to
free them when the last thread drops the refcount. This turned out to
be complex and error prone. Given the fact that the PASID space is 20
bits, which allows up to 1M processes to have a PASID associated
concurrently, PASID resource exhaustion is not a realistic concern.

Therefore, it was decided to simplify the approach and stick with lazy
on demand PASID allocation, but drop the eager free approach and make an
allocated PASID's lifetime bound to the lifetime of the process.

Get rid of the refcounting mechanisms and replace/rename the interfaces
to reflect this new approach.

[ bp: Massage commit message. ]

Suggested-by: Dave Hansen <dave.hansen@linux.intel.com>
Signed-off-by: Fenghua Yu <fenghua.yu@intel.com>
Signed-off-by: Borislav Petkov <bp@suse.de>
Reviewed-by: Tony Luck <tony.luck@intel.com>
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Jacob Pan <jacob.jun.pan@linux.intel.com>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Acked-by: Joerg Roedel <jroedel@suse.de>
Link: https://lore.kernel.org/r/20220207230254.3342514-6-fenghua.yu@intel.com

authored by

Fenghua Yu and committed by
Borislav Petkov
701fac40 a6cbd440

+38 -88
+1 -4
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c
··· 340 340 bond->smmu_mn = arm_smmu_mmu_notifier_get(smmu_domain, mm); 341 341 if (IS_ERR(bond->smmu_mn)) { 342 342 ret = PTR_ERR(bond->smmu_mn); 343 - goto err_free_pasid; 343 + goto err_free_bond; 344 344 } 345 345 346 346 list_add(&bond->list, &master->bonds); 347 347 return &bond->sva; 348 348 349 - err_free_pasid: 350 - iommu_sva_free_pasid(mm); 351 349 err_free_bond: 352 350 kfree(bond); 353 351 return ERR_PTR(ret); ··· 375 377 if (refcount_dec_and_test(&bond->refs)) { 376 378 list_del(&bond->list); 377 379 arm_smmu_mmu_notifier_put(bond->smmu_mn); 378 - iommu_sva_free_pasid(bond->mm); 379 380 kfree(bond); 380 381 } 381 382 mutex_unlock(&sva_lock);
+2 -2
drivers/iommu/intel/iommu.c
··· 4781 4781 link_failed: 4782 4782 spin_unlock_irqrestore(&device_domain_lock, flags); 4783 4783 if (list_empty(&domain->subdevices) && domain->default_pasid > 0) 4784 - ioasid_put(domain->default_pasid); 4784 + ioasid_free(domain->default_pasid); 4785 4785 4786 4786 return ret; 4787 4787 } ··· 4811 4811 spin_unlock_irqrestore(&device_domain_lock, flags); 4812 4812 4813 4813 if (list_empty(&domain->subdevices) && domain->default_pasid > 0) 4814 - ioasid_put(domain->default_pasid); 4814 + ioasid_free(domain->default_pasid); 4815 4815 } 4816 4816 4817 4817 static int prepare_domain_attach_device(struct iommu_domain *domain,
-9
drivers/iommu/intel/svm.c
··· 514 514 return iommu_sva_alloc_pasid(mm, PASID_MIN, max_pasid - 1); 515 515 } 516 516 517 - static void intel_svm_free_pasid(struct mm_struct *mm) 518 - { 519 - iommu_sva_free_pasid(mm); 520 - } 521 - 522 517 static struct iommu_sva *intel_svm_bind_mm(struct intel_iommu *iommu, 523 518 struct device *dev, 524 519 struct mm_struct *mm, ··· 657 662 kfree(svm); 658 663 } 659 664 } 660 - /* Drop a PASID reference and free it if no reference. */ 661 - intel_svm_free_pasid(mm); 662 665 } 663 666 out: 664 667 return ret; ··· 1040 1047 } 1041 1048 1042 1049 sva = intel_svm_bind_mm(iommu, dev, mm, flags); 1043 - if (IS_ERR_OR_NULL(sva)) 1044 - intel_svm_free_pasid(mm); 1045 1050 mutex_unlock(&pasid_mutex); 1046 1051 1047 1052 return sva;
+4 -35
drivers/iommu/ioasid.c
··· 2 2 /* 3 3 * I/O Address Space ID allocator. There is one global IOASID space, split into 4 4 * subsets. Users create a subset with DECLARE_IOASID_SET, then allocate and 5 - * free IOASIDs with ioasid_alloc and ioasid_put. 5 + * free IOASIDs with ioasid_alloc() and ioasid_free(). 6 6 */ 7 7 #include <linux/ioasid.h> 8 8 #include <linux/module.h> ··· 15 15 struct ioasid_set *set; 16 16 void *private; 17 17 struct rcu_head rcu; 18 - refcount_t refs; 19 18 }; 20 19 21 20 /* ··· 314 315 315 316 data->set = set; 316 317 data->private = private; 317 - refcount_set(&data->refs, 1); 318 318 319 319 /* 320 320 * Custom allocator needs allocator data to perform platform specific ··· 346 348 EXPORT_SYMBOL_GPL(ioasid_alloc); 347 349 348 350 /** 349 - * ioasid_get - obtain a reference to the IOASID 350 - * @ioasid: the ID to get 351 - */ 352 - void ioasid_get(ioasid_t ioasid) 353 - { 354 - struct ioasid_data *ioasid_data; 355 - 356 - spin_lock(&ioasid_allocator_lock); 357 - ioasid_data = xa_load(&active_allocator->xa, ioasid); 358 - if (ioasid_data) 359 - refcount_inc(&ioasid_data->refs); 360 - else 361 - WARN_ON(1); 362 - spin_unlock(&ioasid_allocator_lock); 363 - } 364 - EXPORT_SYMBOL_GPL(ioasid_get); 365 - 366 - /** 367 - * ioasid_put - Release a reference to an ioasid 351 + * ioasid_free - Free an ioasid 368 352 * @ioasid: the ID to remove 369 - * 370 - * Put a reference to the IOASID, free it when the number of references drops to 371 - * zero. 372 - * 373 - * Return: %true if the IOASID was freed, %false otherwise. 374 353 */ 375 - bool ioasid_put(ioasid_t ioasid) 354 + void ioasid_free(ioasid_t ioasid) 376 355 { 377 - bool free = false; 378 356 struct ioasid_data *ioasid_data; 379 357 380 358 spin_lock(&ioasid_allocator_lock); ··· 359 385 pr_err("Trying to free unknown IOASID %u\n", ioasid); 360 386 goto exit_unlock; 361 387 } 362 - 363 - free = refcount_dec_and_test(&ioasid_data->refs); 364 - if (!free) 365 - goto exit_unlock; 366 388 367 389 active_allocator->ops->free(ioasid, active_allocator->ops->pdata); 368 390 /* Custom allocator needs additional steps to free the xa element */ ··· 369 399 370 400 exit_unlock: 371 401 spin_unlock(&ioasid_allocator_lock); 372 - return free; 373 402 } 374 - EXPORT_SYMBOL_GPL(ioasid_put); 403 + EXPORT_SYMBOL_GPL(ioasid_free); 375 404 376 405 /** 377 406 * ioasid_find - Find IOASID data
+12 -27
drivers/iommu/iommu-sva-lib.c
··· 18 18 * 19 19 * Try to allocate a PASID for this mm, or take a reference to the existing one 20 20 * provided it fits within the [@min, @max] range. On success the PASID is 21 - * available in mm->pasid, and must be released with iommu_sva_free_pasid(). 22 - * @min must be greater than 0, because 0 indicates an unused mm->pasid. 21 + * available in mm->pasid and will be available for the lifetime of the mm. 23 22 * 24 23 * Returns 0 on success and < 0 on error. 25 24 */ ··· 32 33 return -EINVAL; 33 34 34 35 mutex_lock(&iommu_sva_lock); 35 - if (mm->pasid) { 36 - if (mm->pasid >= min && mm->pasid <= max) 37 - ioasid_get(mm->pasid); 38 - else 36 + /* Is a PASID already associated with this mm? */ 37 + if (pasid_valid(mm->pasid)) { 38 + if (mm->pasid < min || mm->pasid >= max) 39 39 ret = -EOVERFLOW; 40 - } else { 41 - pasid = ioasid_alloc(&iommu_sva_pasid, min, max, mm); 42 - if (pasid == INVALID_IOASID) 43 - ret = -ENOMEM; 44 - else 45 - mm->pasid = pasid; 40 + goto out; 46 41 } 42 + 43 + pasid = ioasid_alloc(&iommu_sva_pasid, min, max, mm); 44 + if (!pasid_valid(pasid)) 45 + ret = -ENOMEM; 46 + else 47 + mm_pasid_set(mm, pasid); 48 + out: 47 49 mutex_unlock(&iommu_sva_lock); 48 50 return ret; 49 51 } 50 52 EXPORT_SYMBOL_GPL(iommu_sva_alloc_pasid); 51 - 52 - /** 53 - * iommu_sva_free_pasid - Release the mm's PASID 54 - * @mm: the mm 55 - * 56 - * Drop one reference to a PASID allocated with iommu_sva_alloc_pasid() 57 - */ 58 - void iommu_sva_free_pasid(struct mm_struct *mm) 59 - { 60 - mutex_lock(&iommu_sva_lock); 61 - if (ioasid_put(mm->pasid)) 62 - mm->pasid = 0; 63 - mutex_unlock(&iommu_sva_lock); 64 - } 65 - EXPORT_SYMBOL_GPL(iommu_sva_free_pasid); 66 53 67 54 /* ioasid_find getter() requires a void * argument */ 68 55 static bool __mmget_not_zero(void *mm)
-1
drivers/iommu/iommu-sva-lib.h
··· 9 9 #include <linux/mm_types.h> 10 10 11 11 int iommu_sva_alloc_pasid(struct mm_struct *mm, ioasid_t min, ioasid_t max); 12 - void iommu_sva_free_pasid(struct mm_struct *mm); 13 12 struct mm_struct *iommu_sva_find(ioasid_t pasid); 14 13 15 14 /* I/O Page fault */
+2 -10
include/linux/ioasid.h
··· 34 34 #if IS_ENABLED(CONFIG_IOASID) 35 35 ioasid_t ioasid_alloc(struct ioasid_set *set, ioasid_t min, ioasid_t max, 36 36 void *private); 37 - void ioasid_get(ioasid_t ioasid); 38 - bool ioasid_put(ioasid_t ioasid); 37 + void ioasid_free(ioasid_t ioasid); 39 38 void *ioasid_find(struct ioasid_set *set, ioasid_t ioasid, 40 39 bool (*getter)(void *)); 41 40 int ioasid_register_allocator(struct ioasid_allocator_ops *allocator); ··· 52 53 return INVALID_IOASID; 53 54 } 54 55 55 - static inline void ioasid_get(ioasid_t ioasid) 56 - { 57 - } 58 - 59 - static inline bool ioasid_put(ioasid_t ioasid) 60 - { 61 - return false; 62 - } 56 + static inline void ioasid_free(ioasid_t ioasid) { } 63 57 64 58 static inline void *ioasid_find(struct ioasid_set *set, ioasid_t ioasid, 65 59 bool (*getter)(void *))
+16
include/linux/sched/mm.h
··· 439 439 { 440 440 mm->pasid = INVALID_IOASID; 441 441 } 442 + 443 + /* Associate a PASID with an mm_struct: */ 444 + static inline void mm_pasid_set(struct mm_struct *mm, u32 pasid) 445 + { 446 + mm->pasid = pasid; 447 + } 448 + 449 + static inline void mm_pasid_drop(struct mm_struct *mm) 450 + { 451 + if (pasid_valid(mm->pasid)) { 452 + ioasid_free(mm->pasid); 453 + mm->pasid = INVALID_IOASID; 454 + } 455 + } 442 456 #else 443 457 static inline void mm_pasid_init(struct mm_struct *mm) {} 458 + static inline void mm_pasid_set(struct mm_struct *mm, u32 pasid) {} 459 + static inline void mm_pasid_drop(struct mm_struct *mm) {} 444 460 #endif 445 461 446 462 #endif /* _LINUX_SCHED_MM_H */
+1
kernel/fork.c
··· 1115 1115 } 1116 1116 if (mm->binfmt) 1117 1117 module_put(mm->binfmt->module); 1118 + mm_pasid_drop(mm); 1118 1119 mmdrop(mm); 1119 1120 } 1120 1121