2020-11-06 16:50:48 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* Helpers for IOMMU drivers implementing SVA
*/
2023-03-12 14:26:06 +03:00
# include <linux/mmu_context.h>
2020-11-06 16:50:48 +01:00
# include <linux/mutex.h>
# include <linux/sched/mm.h>
2022-10-31 08:59:13 +08:00
# include <linux/iommu.h>
2020-11-06 16:50:48 +01:00
2022-10-31 08:59:17 +08:00
# include "iommu-sva.h"
2020-11-06 16:50:48 +01:00
static DEFINE_MUTEX ( iommu_sva_lock ) ;
2023-03-22 13:08:00 -07:00
/* Allocate a PASID for the mm within range (inclusive) */
2023-08-09 20:47:55 +08:00
static int iommu_sva_alloc_pasid ( struct mm_struct * mm , struct device * dev )
2020-11-06 16:50:48 +01:00
{
2023-08-09 20:47:55 +08:00
ioasid_t pasid ;
2020-11-06 16:50:48 +01:00
int ret = 0 ;
2023-03-12 14:26:06 +03:00
if ( ! arch_pgtable_dma_compat ( mm ) )
return - EBUSY ;
2020-11-06 16:50:48 +01:00
mutex_lock ( & iommu_sva_lock ) ;
2022-02-07 15:02:48 -08:00
/* Is a PASID already associated with this mm? */
2023-03-12 14:26:05 +03:00
if ( mm_valid_pasid ( mm ) ) {
2023-08-09 20:47:55 +08:00
if ( mm - > pasid > = dev - > iommu - > max_pasids )
2020-11-06 16:50:48 +01:00
ret = - EOVERFLOW ;
2022-02-07 15:02:48 -08:00
goto out ;
2020-11-06 16:50:48 +01:00
}
2022-02-07 15:02:48 -08:00
2023-08-09 20:47:55 +08:00
pasid = iommu_alloc_global_pasid ( dev ) ;
if ( pasid = = IOMMU_PASID_INVALID ) {
ret = - ENOSPC ;
2023-03-22 13:08:00 -07:00
goto out ;
2023-08-09 20:47:55 +08:00
}
mm - > pasid = pasid ;
2023-03-22 13:08:00 -07:00
ret = 0 ;
2022-02-07 15:02:48 -08:00
out :
2020-11-06 16:50:48 +01:00
mutex_unlock ( & iommu_sva_lock ) ;
return ret ;
}
2022-10-31 08:59:13 +08:00
/**
* iommu_sva_bind_device ( ) - Bind a process address space to a device
* @ dev : the device
* @ mm : the mm to bind , caller must hold a reference to mm_users
*
* Create a bond between device and address space , allowing the device to
* access the mm using the PASID returned by iommu_sva_get_pasid ( ) . If a
* bond already exists between @ device and @ mm , an additional internal
* reference is taken . Caller must call iommu_sva_unbind_device ( )
* to release each reference .
*
* iommu_dev_enable_feature ( dev , IOMMU_DEV_FEAT_SVA ) must be called first , to
* initialize the required SVA features .
*
* On error , returns an ERR_PTR value .
*/
struct iommu_sva * iommu_sva_bind_device ( struct device * dev , struct mm_struct * mm )
{
struct iommu_domain * domain ;
struct iommu_sva * handle ;
int ret ;
/* Allocate mm->pasid if necessary. */
2023-08-09 20:47:55 +08:00
ret = iommu_sva_alloc_pasid ( mm , dev ) ;
2022-10-31 08:59:13 +08:00
if ( ret )
return ERR_PTR ( ret ) ;
handle = kzalloc ( sizeof ( * handle ) , GFP_KERNEL ) ;
if ( ! handle )
return ERR_PTR ( - ENOMEM ) ;
mutex_lock ( & iommu_sva_lock ) ;
/* Search for an existing domain. */
domain = iommu_get_domain_for_dev_pasid ( dev , mm - > pasid ,
IOMMU_DOMAIN_SVA ) ;
if ( IS_ERR ( domain ) ) {
ret = PTR_ERR ( domain ) ;
goto out_unlock ;
}
if ( domain ) {
domain - > users + + ;
goto out ;
}
/* Allocate a new domain and set it on device pasid. */
domain = iommu_sva_domain_alloc ( dev , mm ) ;
if ( ! domain ) {
ret = - ENOMEM ;
goto out_unlock ;
}
ret = iommu_attach_device_pasid ( domain , dev , mm - > pasid ) ;
if ( ret )
goto out_free_domain ;
domain - > users = 1 ;
out :
mutex_unlock ( & iommu_sva_lock ) ;
handle - > dev = dev ;
handle - > domain = domain ;
return handle ;
out_free_domain :
iommu_domain_free ( domain ) ;
out_unlock :
mutex_unlock ( & iommu_sva_lock ) ;
kfree ( handle ) ;
return ERR_PTR ( ret ) ;
}
EXPORT_SYMBOL_GPL ( iommu_sva_bind_device ) ;
/**
* iommu_sva_unbind_device ( ) - Remove a bond created with iommu_sva_bind_device
* @ handle : the handle returned by iommu_sva_bind_device ( )
*
* Put reference to a bond between device and address space . The device should
* not be issuing any more transaction for this PASID . All outstanding page
* requests for this PASID must have been flushed to the IOMMU .
*/
void iommu_sva_unbind_device ( struct iommu_sva * handle )
{
struct iommu_domain * domain = handle - > domain ;
ioasid_t pasid = domain - > mm - > pasid ;
struct device * dev = handle - > dev ;
mutex_lock ( & iommu_sva_lock ) ;
if ( - - domain - > users = = 0 ) {
iommu_detach_device_pasid ( domain , dev , pasid ) ;
iommu_domain_free ( domain ) ;
}
mutex_unlock ( & iommu_sva_lock ) ;
kfree ( handle ) ;
}
EXPORT_SYMBOL_GPL ( iommu_sva_unbind_device ) ;
u32 iommu_sva_get_pasid ( struct iommu_sva * handle )
{
struct iommu_domain * domain = handle - > domain ;
return domain - > mm - > pasid ;
}
EXPORT_SYMBOL_GPL ( iommu_sva_get_pasid ) ;
2022-10-31 08:59:15 +08:00
/*
* I / O page fault handler for SVA
*/
enum iommu_page_response_code
iommu_sva_handle_iopf ( struct iommu_fault * fault , void * data )
{
vm_fault_t ret ;
struct vm_area_struct * vma ;
struct mm_struct * mm = data ;
unsigned int access_flags = 0 ;
unsigned int fault_flags = FAULT_FLAG_REMOTE ;
struct iommu_fault_page_request * prm = & fault - > prm ;
enum iommu_page_response_code status = IOMMU_PAGE_RESP_INVALID ;
if ( ! ( prm - > flags & IOMMU_FAULT_PAGE_REQUEST_PASID_VALID ) )
return status ;
if ( ! mmget_not_zero ( mm ) )
return status ;
mmap_read_lock ( mm ) ;
2023-06-24 13:45:51 -07:00
vma = vma_lookup ( mm , prm - > addr ) ;
2022-10-31 08:59:15 +08:00
if ( ! vma )
/* Unmapped area */
goto out_put_mm ;
if ( prm - > perm & IOMMU_FAULT_PERM_READ )
access_flags | = VM_READ ;
if ( prm - > perm & IOMMU_FAULT_PERM_WRITE ) {
access_flags | = VM_WRITE ;
fault_flags | = FAULT_FLAG_WRITE ;
}
if ( prm - > perm & IOMMU_FAULT_PERM_EXEC ) {
access_flags | = VM_EXEC ;
fault_flags | = FAULT_FLAG_INSTRUCTION ;
}
if ( ! ( prm - > perm & IOMMU_FAULT_PERM_PRIV ) )
fault_flags | = FAULT_FLAG_USER ;
if ( access_flags & ~ vma - > vm_flags )
/* Access fault */
goto out_put_mm ;
ret = handle_mm_fault ( vma , prm - > addr , fault_flags , NULL ) ;
status = ret & VM_FAULT_ERROR ? IOMMU_PAGE_RESP_INVALID :
IOMMU_PAGE_RESP_SUCCESS ;
out_put_mm :
mmap_read_unlock ( mm ) ;
mmput ( mm ) ;
return status ;
}
2023-03-22 13:07:58 -07:00
void mm_pasid_drop ( struct mm_struct * mm )
{
2023-04-30 13:00:38 -07:00
if ( likely ( ! mm_valid_pasid ( mm ) ) )
2023-03-22 13:08:00 -07:00
return ;
2023-08-09 20:47:55 +08:00
iommu_free_global_pasid ( mm - > pasid ) ;
2023-03-22 13:07:58 -07:00
}