The pasid-num-bits property shouldn't need a dedicated fwspec field,
it's a job for device properties. Add properties for IORT, and access
the number of PASID bits using device_property_read_u32().
Suggested-by: Robin Murphy <robin.murphy@arm.com>
Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
include/linux/iommu.h | 2 --
drivers/acpi/arm64/iort.c | 13 +++++++------
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 3 ++-
drivers/iommu/of_iommu.c | 5 -----
4 files changed, 9 insertions(+), 14 deletions(-)
Some devices manage I/O Page Faults (IOPF) themselves instead of relying
on PCIe PRI or Arm SMMU stall. Allow their drivers to enable SVA without
mandating IOMMU-managed IOPF. The other device drivers now need to first
enable IOMMU_DEV_FEAT_IOPF before enabling IOMMU_DEV_FEAT_SVA. Enabling
IOMMU_DEV_FEAT_IOPF on its own doesn't have any effect visible to the
device driver, it is used in combination with other features.
Signed-off-by: Jean-Philippe Brucker <redacted>
---
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Joerg Roedel <joro@8bytes.org>
Cc: Lu Baolu <baolu.lu@linux.intel.com>
Cc: Will Deacon <will@kernel.org>
Cc: Zhangfei Gao <zhangfei.gao@linaro.org>
Cc: Zhou Wang <wangzhou1@hisilicon.com>
---
include/linux/iommu.h | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
Some systems allow devices to handle I/O Page Faults in the core mm. For
example systems implementing the PCIe PRI extension or Arm SMMU stall
model. Infrastructure for reporting these recoverable page faults was
added to the IOMMU core by commit 0c830e6b3282 ("iommu: Introduce device
fault report API"). Add a page fault handler for host SVA.
IOMMU driver can now instantiate several fault workqueues and link them
to IOPF-capable devices. Drivers can choose between a single global
workqueue, one per IOMMU device, one per low-level fault queue, one per
domain, etc.
When it receives a fault event, most commonly in an IRQ handler, the
IOMMU driver reports the fault using iommu_report_device_fault(), which
calls the registered handler. The page fault handler then calls the mm
fault handler, and reports either success or failure with
iommu_page_response(). After the handler succeeds, the hardware retries
the access.
The iopf_param pointer could be embedded into iommu_fault_param. But
putting iopf_param into the iommu_param structure allows us not to care
about ordering between calls to iopf_queue_add_device() and
iommu_register_device_fault_handler().
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
drivers/iommu/Makefile | 1 +
drivers/iommu/iommu-sva-lib.h | 53 ++++
include/linux/iommu.h | 2 +
drivers/iommu/io-pgfault.c | 461 ++++++++++++++++++++++++++++++++++
4 files changed, 517 insertions(+)
create mode 100644 drivers/iommu/io-pgfault.c
@@ -0,0 +1,461 @@+// SPDX-License-Identifier: GPL-2.0+/*+*Handledevicepagefaults+*+*Copyright(C)2020ARMLtd.+*/++#include<linux/iommu.h>+#include<linux/list.h>+#include<linux/sched/mm.h>+#include<linux/slab.h>+#include<linux/workqueue.h>++#include"iommu-sva-lib.h"++/**+*structiopf_queue-IOPageFaultqueue+*@wq:thefaultworkqueue+*@devices:devicesattachedtothisqueue+*@lock:protectsthedevicelist+*/+structiopf_queue{+structworkqueue_struct*wq;+structlist_headdevices;+structmutexlock;+};++/**+*structiopf_device_param-IOPageFaultdataattachedtoadevice+*@dev:thedevicethatownsthisparam+*@queue:IOPFqueue+*@queue_list:indexintoqueue->devices+*@partial:faultsthatarepartofaPageRequestGroupforwhichthelast+*requesthasn'tbeensubmittedyet.+*/+structiopf_device_param{+structdevice*dev;+structiopf_queue*queue;+structlist_headqueue_list;+structlist_headpartial;+};++structiopf_fault{+structiommu_faultfault;+structlist_headlist;+};++structiopf_group{+structiopf_faultlast_fault;+structlist_headfaults;+structwork_structwork;+structdevice*dev;+};++staticintiopf_complete_group(structdevice*dev,structiopf_fault*iopf,+enumiommu_page_response_codestatus)+{+structiommu_page_responseresp={+.version=IOMMU_PAGE_RESP_VERSION_1,+.pasid=iopf->fault.prm.pasid,+.grpid=iopf->fault.prm.grpid,+.code=status,+};++if((iopf->fault.prm.flags&IOMMU_FAULT_PAGE_REQUEST_PASID_VALID)&&+(iopf->fault.prm.flags&IOMMU_FAULT_PAGE_RESPONSE_NEEDS_PASID))+resp.flags=IOMMU_PAGE_RESP_PASID_VALID;++returniommu_page_response(dev,&resp);+}++staticenumiommu_page_response_code+iopf_handle_single(structiopf_fault*iopf)+{+vm_fault_tret;+structmm_struct*mm;+structvm_area_struct*vma;+unsignedintaccess_flags=0;+unsignedintfault_flags=FAULT_FLAG_REMOTE;+structiommu_fault_page_request*prm=&iopf->fault.prm;+enumiommu_page_response_codestatus=IOMMU_PAGE_RESP_INVALID;++if(!(prm->flags&IOMMU_FAULT_PAGE_REQUEST_PASID_VALID))+returnstatus;++mm=iommu_sva_find(prm->pasid);+if(IS_ERR_OR_NULL(mm))+returnstatus;++mmap_read_lock(mm);++vma=find_extend_vma(mm,prm->addr);+if(!vma)+/* Unmapped area */+gotoout_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 */+gotoout_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);++returnstatus;+}++staticvoidiopf_handle_group(structwork_struct*work)+{+structiopf_group*group;+structiopf_fault*iopf,*next;+enumiommu_page_response_codestatus=IOMMU_PAGE_RESP_SUCCESS;++group=container_of(work,structiopf_group,work);++list_for_each_entry_safe(iopf,next,&group->faults,list){+/*+*Forthemoment,errorsaresticky:don'thandlesubsequent+*faultsinthegroupifthereisanerror.+*/+if(status==IOMMU_PAGE_RESP_SUCCESS)+status=iopf_handle_single(iopf);++if(!(iopf->fault.prm.flags&+IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE))+kfree(iopf);+}++iopf_complete_group(group->dev,&group->last_fault,status);+kfree(group);+}++/**+*iommu_queue_iopf-IOPageFaulthandler+*@fault:faultevent+*@cookie:structdevice,passedtoiommu_register_device_fault_handler.+*+*Addafaulttothedeviceworkqueue,tobehandledbymm.+*+*Thismoduledoesn'thandlePCIPASIDStopMarker;IOMMUdriversmustdiscard+*thembeforereportingfaults.APASIDStopMarker(LRW=0b100)doesn't+*expectaresponse.ItmaybegeneratedwhendisablingaPASID(issuinga+*PASIDstoprequest)bysomePCIdevices.+*+*ThePASIDstoprequestisissuedbythedevicedriverbeforeunbind().Once+*itcompletes,nopagerequestisgeneratedforthisPASIDanymoreand+*outstandingoneshavebeenpushedtotheIOMMU(asperPCIe4.0r1.0-6.20.1+*and10.4.1.2-ManagingPASIDTLPPrefixUsage).SomePCIdeviceswillwait+*foralloutstandingpagerequeststocomebackwitharesponsebefore+*completingthePASIDstoprequest.Othersdonotwaitforpageresponses,and+*insteadissuethisStopMarkerthattellsuswhenthePASIDcanbe+*reallocated.+*+*ItissafetodiscardtheStopMarkerbecauseitisanoptimization.+*a.Pagerequests,whicharepostedrequests,havebeenflushedtotheIOMMU+*whenthestoprequestcompletes.+*b.TheIOMMUdriverflushesallfaultqueuesonunbind()beforefreeingthe+*PASID.+*+*SoeventhoughtheStopMarkermightbeissuedbythedevice*after*thestop+*requestcompletes,outstandingfaultswillhavebeendealtwithbythetime+*thePASIDisfreed.+*+*Return:0onsuccessand<0onerror.+*/+intiommu_queue_iopf(structiommu_fault*fault,void*cookie)+{+intret;+structiopf_group*group;+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;++structdevice*dev=cookie;+structdev_iommu*param=dev->iommu;++lockdep_assert_held(¶m->lock);++if(fault->type!=IOMMU_FAULT_PAGE_REQ)+/* Not a recoverable page fault */+return-EOPNOTSUPP;++/*+*Aslongaswe'reholdingparam->lock,thequeuecan'tbeunlinked+*fromthedeviceandthereforecannotdisappear.+*/+iopf_param=param->iopf_param;+if(!iopf_param)+return-ENODEV;++if(!(fault->prm.flags&IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE)){+iopf=kzalloc(sizeof(*iopf),GFP_KERNEL);+if(!iopf)+return-ENOMEM;++iopf->fault=*fault;++/* Non-last request of a group. Postpone until the last one */+list_add(&iopf->list,&iopf_param->partial);++return0;+}++group=kzalloc(sizeof(*group),GFP_KERNEL);+if(!group){+/*+*Thecallerwillsendaresponsetothehardware.Butwedo+*needtocleanupbeforeleaving,otherwisepartialfaults+*willbestuck.+*/+ret=-ENOMEM;+gotocleanup_partial;+}++group->dev=dev;+group->last_fault.fault=*fault;+INIT_LIST_HEAD(&group->faults);+list_add(&group->last_fault.list,&group->faults);+INIT_WORK(&group->work,iopf_handle_group);++/* See if we have partial faults for this group */+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list){+if(iopf->fault.prm.grpid==fault->prm.grpid)+/* Insert *before* the last fault */+list_move(&iopf->list,&group->faults);+}++queue_work(iopf_param->queue->wq,&group->work);+return0;++cleanup_partial:+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list){+if(iopf->fault.prm.grpid==fault->prm.grpid){+list_del(&iopf->list);+kfree(iopf);+}+}+returnret;+}+EXPORT_SYMBOL_GPL(iommu_queue_iopf);++/**+*iopf_queue_flush_dev-Ensurethatallqueuedfaultshavebeenprocessed+*@dev:theendpointwhosefaultsneedtobeflushed.+*+*TheIOMMUdrivercallsthisbeforereleasingaPASID,toensurethatall+*pendingfaultsforthisPASIDhavebeenhandled,andwon'thittheaddress+*spaceofthenextprocessthatusesthisPASID.Thedrivermustmakesure+*thatnonewfaultisaddedtothequeue.Inparticularitmustflushits+*low-levelqueuebeforecallingthisfunction.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_flush_dev(structdevice*dev)+{+intret=0;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param)+return-ENODEV;++mutex_lock(¶m->lock);+iopf_param=param->iopf_param;+if(iopf_param)+flush_workqueue(iopf_param->queue->wq);+else+ret=-ENODEV;+mutex_unlock(¶m->lock);++returnret;+}+EXPORT_SYMBOL_GPL(iopf_queue_flush_dev);++/**+*iopf_queue_discard_partial-Removeallpendingpartialfault+*@queue:thequeuewhosepartialfaultsneedtobediscarded+*+*Whenthehardwarequeueoverflows,lastpagefaultsinagroupmayhavebeen+*lostandtheIOMMUdrivercallsthistodiscardallpartialfaults.The+*drivershouldn'tbeaddingnewfaultstothisqueueconcurrently.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_discard_partial(structiopf_queue*queue)+{+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;++if(!queue)+return-EINVAL;++mutex_lock(&queue->lock);+list_for_each_entry(iopf_param,&queue->devices,queue_list){+list_for_each_entry_safe(iopf,next,&iopf_param->partial,+list){+list_del(&iopf->list);+kfree(iopf);+}+}+mutex_unlock(&queue->lock);+return0;+}+EXPORT_SYMBOL_GPL(iopf_queue_discard_partial);++/**+*iopf_queue_add_device-Addproducertothefaultqueue+*@queue:IOPFqueue+*@dev:devicetoadd+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_add_device(structiopf_queue*queue,structdevice*dev)+{+intret=-EBUSY;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param)+return-ENODEV;++iopf_param=kzalloc(sizeof(*iopf_param),GFP_KERNEL);+if(!iopf_param)+return-ENOMEM;++INIT_LIST_HEAD(&iopf_param->partial);+iopf_param->queue=queue;+iopf_param->dev=dev;++mutex_lock(&queue->lock);+mutex_lock(¶m->lock);+if(!param->iopf_param){+list_add(&iopf_param->queue_list,&queue->devices);+param->iopf_param=iopf_param;+ret=0;+}+mutex_unlock(¶m->lock);+mutex_unlock(&queue->lock);++if(ret)+kfree(iopf_param);++returnret;+}+EXPORT_SYMBOL_GPL(iopf_queue_add_device);++/**+*iopf_queue_remove_device-Removeproducerfromfaultqueue+*@queue:IOPFqueue+*@dev:devicetoremove+*+*Callermakessurethatnomorefaultsarereportedforthisdevice.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_remove_device(structiopf_queue*queue,structdevice*dev)+{+intret=-EINVAL;+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param||!queue)+return-EINVAL;++mutex_lock(&queue->lock);+mutex_lock(¶m->lock);+iopf_param=param->iopf_param;+if(iopf_param&&iopf_param->queue==queue){+list_del(&iopf_param->queue_list);+param->iopf_param=NULL;+ret=0;+}+mutex_unlock(¶m->lock);+mutex_unlock(&queue->lock);+if(ret)+returnret;++/* Just in case some faults are still stuck */+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list)+kfree(iopf);++kfree(iopf_param);++return0;+}+EXPORT_SYMBOL_GPL(iopf_queue_remove_device);++/**+*iopf_queue_alloc-Allocateandinitializeafaultqueue+*@name:auniquestringidentifyingthequeue(forworkqueue)+*+*Return:thequeueonsuccessandNULLonerror.+*/+structiopf_queue*iopf_queue_alloc(constchar*name)+{+structiopf_queue*queue;++queue=kzalloc(sizeof(*queue),GFP_KERNEL);+if(!queue)+returnNULL;++/*+*TheWQisunorderedbecausethelow-levelhandlerenqueuesfaultsby+*group.PRIrequestswithinagrouphavetobeordered,butonce+*that'sdealtwith,thehigh-levelfunctioncanhandlegroupsoutof+*order.+*/+queue->wq=alloc_workqueue("iopf_queue/%s",WQ_UNBOUND,0,name);+if(!queue->wq){+kfree(queue);+returnNULL;+}++INIT_LIST_HEAD(&queue->devices);+mutex_init(&queue->lock);++returnqueue;+}+EXPORT_SYMBOL_GPL(iopf_queue_alloc);++/**+*iopf_queue_free-FreeIOPFqueue+*@queue:queuetofree+*+*Counterparttoiopf_queue_alloc().Thedrivermustnotbequeuingfaultsor+*adding/removingdevicesonthisqueueanymore.+*/+voidiopf_queue_free(structiopf_queue*queue)+{+structiopf_device_param*iopf_param,*next;++if(!queue)+return;++list_for_each_entry_safe(iopf_param,next,&queue->devices,queue_list)+iopf_queue_remove_device(queue,iopf_param->dev);++destroy_workqueue(queue->wq);+kfree(queue);+}+EXPORT_SYMBOL_GPL(iopf_queue_free);
--
2.30.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Allow drivers to query and enable IOMMU_DEV_FEAT_IOPF, which amounts to
checking whether PRI is enabled.
Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/iommu.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
When handling faults from the event or PRI queue, we need to find the
struct device associated with a SID. Add a rb_tree to keep track of
SIDs.
Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 13 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 161 ++++++++++++++++----
2 files changed, 144 insertions(+), 30 deletions(-)
@@ -1355,6 +1355,32 @@ static int arm_smmu_init_l2_strtab(struct arm_smmu_device *smmu, u32 sid)return0;}+__maybe_unused+staticstructarm_smmu_master*+arm_smmu_find_master(structarm_smmu_device*smmu,u32sid)+{+structrb_node*node;+structarm_smmu_stream*stream;+structarm_smmu_master*master=NULL;++mutex_lock(&smmu->streams_mutex);+node=smmu->streams.rb_node;+while(node){+stream=rb_entry(node,structarm_smmu_stream,node);+if(stream->id<sid){+node=node->rb_right;+}elseif(stream->id>sid){+node=node->rb_left;+}else{+master=stream->master;+break;+}+}+mutex_unlock(&smmu->streams_mutex);++returnmaster;+}+/* IRQ and event handlers */staticirqreturn_tarm_smmu_evtq_thread(intirq,void*dev){
@@ -1588,8 +1614,8 @@ static int arm_smmu_atc_inv_master(struct arm_smmu_master *master)arm_smmu_atc_inv_to_cmd(0,0,0,&cmd);-for(i=0;i<master->num_sids;i++){-cmd.atc.sid=master->sids[i];+for(i=0;i<master->num_streams;i++){+cmd.atc.sid=master->streams[i].id;arm_smmu_cmdq_issue_cmd(master->smmu,&cmd);}
@@ -1632,8 +1658,8 @@ int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain, int ssid,if(!master->ats_enabled)continue;-for(i=0;i<master->num_sids;i++){-cmd.atc.sid=master->sids[i];+for(i=0;i<master->num_streams;i++){+cmd.atc.sid=master->streams[i].id;arm_smmu_cmdq_batch_add(smmu_domain->smmu,&cmds,&cmd);}}
@@ -2065,13 +2091,13 @@ static void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master)inti,j;structarm_smmu_device*smmu=master->smmu;-for(i=0;i<master->num_sids;++i){-u32sid=master->sids[i];+for(i=0;i<master->num_streams;++i){+u32sid=master->streams[i].id;__le64*step=arm_smmu_get_step_for_sid(smmu,sid);/* Bridged PCI devices may end up with duplicated IDs */for(j=0;j<i;j++)-if(master->sids[j]==sid)+if(master->streams[j].id==sid)break;if(j<i)continue;
@@ -2345,11 +2371,101 @@ static bool arm_smmu_sid_in_range(struct arm_smmu_device *smmu, u32 sid)returnsid<limit;}+staticintarm_smmu_insert_master(structarm_smmu_device*smmu,+structarm_smmu_master*master)+{+inti;+intret=0;+structarm_smmu_stream*new_stream,*cur_stream;+structrb_node**new_node,*parent_node=NULL;+structiommu_fwspec*fwspec=dev_iommu_fwspec_get(master->dev);++master->streams=kcalloc(fwspec->num_ids,+sizeof(*master->streams),GFP_KERNEL);+if(!master->streams)+return-ENOMEM;+master->num_streams=fwspec->num_ids;++mutex_lock(&smmu->streams_mutex);+for(i=0;i<fwspec->num_ids&&!ret;i++){+u32sid=fwspec->ids[i];++new_stream=&master->streams[i];+new_stream->id=sid;+new_stream->master=master;++/*+*ChecktheSIDsareinrangeoftheSMMUandourstreamtable+*/+if(!arm_smmu_sid_in_range(smmu,sid)){+ret=-ERANGE;+break;+}++/* Ensure l2 strtab is initialised */+if(smmu->features&ARM_SMMU_FEAT_2_LVL_STRTAB){+ret=arm_smmu_init_l2_strtab(smmu,sid);+if(ret)+break;+}++/* Insert into SID tree */+new_node=&(smmu->streams.rb_node);+while(*new_node){+cur_stream=rb_entry(*new_node,structarm_smmu_stream,+node);+parent_node=*new_node;+if(cur_stream->id>new_stream->id){+new_node=&((*new_node)->rb_left);+}elseif(cur_stream->id<new_stream->id){+new_node=&((*new_node)->rb_right);+}else{+dev_warn(master->dev,+"stream %u already in tree\n",+cur_stream->id);+ret=-EINVAL;+break;+}+}++if(!ret){+rb_link_node(&new_stream->node,parent_node,new_node);+rb_insert_color(&new_stream->node,&smmu->streams);+}+}++if(ret){+for(;i>0;i--)+rb_erase(&master->streams[i].node,&smmu->streams);+kfree(master->streams);+}+mutex_unlock(&smmu->streams_mutex);++returnret;+}++staticvoidarm_smmu_remove_master(structarm_smmu_master*master)+{+inti;+structarm_smmu_device*smmu=master->smmu;+structiommu_fwspec*fwspec=dev_iommu_fwspec_get(master->dev);++if(!smmu||!master->streams)+return;++mutex_lock(&smmu->streams_mutex);+for(i=0;i<fwspec->num_ids;i++)+rb_erase(&master->streams[i].node,&smmu->streams);+mutex_unlock(&smmu->streams_mutex);++kfree(master->streams);+}+staticstructiommu_opsarm_smmu_ops;staticstructiommu_device*arm_smmu_probe_device(structdevice*dev){-inti,ret;+intret;structarm_smmu_device*smmu;structarm_smmu_master*master;structiommu_fwspec*fwspec=dev_iommu_fwspec_get(dev);
@@ -2370,27 +2486,12 @@ static struct iommu_device *arm_smmu_probe_device(struct device *dev)master->dev=dev;master->smmu=smmu;-master->sids=fwspec->ids;-master->num_sids=fwspec->num_ids;INIT_LIST_HEAD(&master->bonds);dev_iommu_priv_set(dev,master);-/* Check the SIDs are in range of the SMMU and our stream table */-for(i=0;i<master->num_sids;i++){-u32sid=master->sids[i];--if(!arm_smmu_sid_in_range(smmu,sid)){-ret=-ERANGE;-gotoerr_free_master;-}--/* Ensure l2 strtab is initialised */-if(smmu->features&ARM_SMMU_FEAT_2_LVL_STRTAB){-ret=arm_smmu_init_l2_strtab(smmu,sid);-if(ret)-gotoerr_free_master;-}-}+ret=arm_smmu_insert_master(smmu,master);+if(ret)+gotoerr_free_master;device_property_read_u32(dev,"pasid-num-bits",&master->ssid_bits);master->ssid_bits=min(smmu->ssid_bits,master->ssid_bits);
On ARM systems, some platform devices behind an IOMMU may support stall,
which is the ability to recover from page faults. Let the firmware tell us
when a device supports stall.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
.../devicetree/bindings/iommu/iommu.txt | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
@@ -92,6 +92,24 @@ Optional properties: tagging DMA transactions with an address space identifier. By default, this is 0, which means that the device only has one address space.+- dma-can-stall: When present, the master can wait for a transaction to+ complete for an indefinite amount of time. Upon translation fault some+ IOMMUs, instead of aborting the translation immediately, may first+ notify the driver and keep the transaction in flight. This allows the OS+ to inspect the fault and, for example, make physical pages resident+ before updating the mappings and completing the transaction. Such IOMMU+ accepts a limited number of simultaneous stalled transactions before+ having to either put back-pressure on the master, or abort new faulting+ transactions.++ Firmware has to opt-in stalling, because most buses and masters don't+ support it. In particular it isn't compatible with PCI, where+ transactions have to complete before a time limit. More generally it+ won't work in systems and masters that haven't been designed for+ stalling. For example the OS, in order to handle a stalled transaction,+ may attempt to retrieve pages from secondary storage in a stalled+ domain, leading to a deadlock.+ Notes: ======
--
2.30.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
The SMMU provides a Stall model for handling page faults in platform
devices. It is similar to PCIe PRI, but doesn't require devices to have
their own translation cache. Instead, faulting transactions are parked
and the OS is given a chance to fix the page tables and retry the
transaction.
Enable stall for devices that support it (opt-in by firmware). When an
event corresponds to a translation error, call the IOMMU fault handler.
If the fault is recoverable, it will call us back to terminate or
continue the stall.
To use stall device drivers need to enable IOMMU_DEV_FEAT_IOPF, which
initializes the fault queue for the device.
Tested-by: Zhangfei Gao <zhangfei.gao@linaro.org>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 43 ++++
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 59 +++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 189 +++++++++++++++++-
3 files changed, 276 insertions(+), 15 deletions(-)
@@ -435,9 +435,13 @@ bool arm_smmu_sva_supported(struct arm_smmu_device *smmu)returntrue;}-staticboolarm_smmu_iopf_supported(structarm_smmu_master*master)+boolarm_smmu_master_iopf_supported(structarm_smmu_master*master){-returnfalse;+/* We're not keeping track of SIDs in fault events */+if(master->num_streams!=1)+returnfalse;++returnmaster->stall_enabled;}boolarm_smmu_master_sva_supported(structarm_smmu_master*master)
@@ -445,8 +449,8 @@ bool arm_smmu_master_sva_supported(struct arm_smmu_master *master)if(!(master->smmu->features&ARM_SMMU_FEAT_SVA))returnfalse;-/* SSID and IOPF support are mandatory for the moment */-returnmaster->ssid_bits&&arm_smmu_iopf_supported(master);+/* SSID support is mandatory for the moment */+returnmaster->ssid_bits;}boolarm_smmu_master_sva_enabled(structarm_smmu_master*master)
@@ -988,7 +1032,6 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid,u64val;boolcd_live;__le64*cdptr;-structarm_smmu_device*smmu=smmu_domain->smmu;if(WARN_ON(ssid>=(1<<smmu_domain->s1_cfg.s1cdmax)))return-E2BIG;
@@ -1033,8 +1076,7 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid,FIELD_PREP(CTXDESC_CD_0_ASID,cd->asid)|CTXDESC_CD_0_V;-/* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */-if(smmu->features&ARM_SMMU_FEAT_STALL_FORCE)+if(smmu_domain->stall_enabled)val|=CTXDESC_CD_0_S;}
@@ -1382,9 +1423,96 @@ arm_smmu_find_master(struct arm_smmu_device *smmu, u32 sid)}/* IRQ and event handlers */+staticintarm_smmu_handle_evt(structarm_smmu_device*smmu,u64*evt)+{+intret;+u32reason;+u32perm=0;+structarm_smmu_master*master;+boolssid_valid=evt[0]&EVTQ_0_SSV;+u32sid=FIELD_GET(EVTQ_0_SID,evt[0]);+structiommu_fault_eventfault_evt={};+structiommu_fault*flt=&fault_evt.fault;++/* Stage-2 is always pinned at the moment */+if(evt[1]&EVTQ_1_S2)+return-EFAULT;++master=arm_smmu_find_master(smmu,sid);+if(!master)+return-EINVAL;++if(evt[1]&EVTQ_1_RnW)+perm|=IOMMU_FAULT_PERM_READ;+else+perm|=IOMMU_FAULT_PERM_WRITE;++if(evt[1]&EVTQ_1_InD)+perm|=IOMMU_FAULT_PERM_EXEC;++if(evt[1]&EVTQ_1_PnU)+perm|=IOMMU_FAULT_PERM_PRIV;++switch(FIELD_GET(EVTQ_0_ID,evt[0])){+caseEVT_ID_TRANSLATION_FAULT:+caseEVT_ID_ADDR_SIZE_FAULT:+caseEVT_ID_ACCESS_FAULT:+reason=IOMMU_FAULT_REASON_PTE_FETCH;+break;+caseEVT_ID_PERMISSION_FAULT:+reason=IOMMU_FAULT_REASON_PERMISSION;+break;+default:+return-EOPNOTSUPP;+}++if(evt[1]&EVTQ_1_STALL){+flt->type=IOMMU_FAULT_PAGE_REQ;+flt->prm=(structiommu_fault_page_request){+.flags=IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE,+.grpid=FIELD_GET(EVTQ_1_STAG,evt[1]),+.perm=perm,+.addr=FIELD_GET(EVTQ_2_ADDR,evt[2]),+};++if(ssid_valid){+flt->prm.flags|=IOMMU_FAULT_PAGE_REQUEST_PASID_VALID;+flt->prm.pasid=FIELD_GET(EVTQ_0_SSID,evt[0]);+}+}else{+flt->type=IOMMU_FAULT_DMA_UNRECOV;+flt->event=(structiommu_fault_unrecoverable){+.reason=reason,+.flags=IOMMU_FAULT_UNRECOV_ADDR_VALID|+IOMMU_FAULT_UNRECOV_FETCH_ADDR_VALID,+.perm=perm,+.addr=FIELD_GET(EVTQ_2_ADDR,evt[2]),+.fetch_addr=FIELD_GET(EVTQ_3_IPA,evt[3]),+};++if(ssid_valid){+flt->event.flags|=IOMMU_FAULT_UNRECOV_PASID_VALID;+flt->event.pasid=FIELD_GET(EVTQ_0_SSID,evt[0]);+}+}++ret=iommu_report_device_fault(master->dev,&fault_evt);+if(ret&&flt->type==IOMMU_FAULT_PAGE_REQ){+/* Nobody cared, abort the access */+structiommu_page_responseresp={+.pasid=flt->prm.pasid,+.grpid=flt->prm.grpid,+.code=IOMMU_PAGE_RESP_FAILURE,+};+arm_smmu_page_response(master->dev,&fault_evt,&resp);+}++returnret;+}+staticirqreturn_tarm_smmu_evtq_thread(intirq,void*dev){-inti;+inti,ret;structarm_smmu_device*smmu=dev;structarm_smmu_queue*q=&smmu->evtq.q;structarm_smmu_ll_queue*llq=&q->llq;
The IOPF (I/O Page Fault) feature is now enabled independently from the
SVA feature, because some IOPF implementations are device-specific and
do not require IOMMU support for PCIe PRI or Arm SMMU stall.
Enable IOPF unconditionally when enabling SVA for now. In the future, if
a device driver implementing a uacce interface doesn't need IOPF
support, it will need to tell the uacce module, for example with a new
flag.
Acked-by: Zhangfei Gao <zhangfei.gao@linaro.org>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Zhangfei Gao <zhangfei.gao@linaro.org>
Cc: Zhou Wang <wangzhou1@hisilicon.com>
---
drivers/misc/uacce/uacce.c | 39 +++++++++++++++++++++++++++++---------
1 file changed, 30 insertions(+), 9 deletions(-)
@@ -487,8 +509,7 @@ void uacce_remove(struct uacce_device *uacce)mutex_unlock(&uacce->queues_lock);/* disable sva now since no opened queues */-if(uacce->flags&UACCE_DEV_SVA)-iommu_dev_disable_feature(uacce->parent,IOMMU_DEV_FEAT_SVA);+uacce_disable_sva(uacce);if(uacce->cdev)cdev_device_del(uacce->cdev,&uacce->dev);
--
2.30.0
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Auger Eric <eric.auger@redhat.com> Date: 2021-01-31 19:35:53
Hi Jean,
On 1/27/21 4:43 PM, Jean-Philippe Brucker wrote:
Some systems allow devices to handle I/O Page Faults in the core mm. For
example systems implementing the PCIe PRI extension or Arm SMMU stall
model. Infrastructure for reporting these recoverable page faults was
added to the IOMMU core by commit 0c830e6b3282 ("iommu: Introduce device
fault report API"). Add a page fault handler for host SVA.
IOMMU driver can now instantiate several fault workqueues and link them
to IOPF-capable devices. Drivers can choose between a single global
workqueue, one per IOMMU device, one per low-level fault queue, one per
domain, etc.
When it receives a fault event, most commonly in an IRQ handler, the
IOMMU driver reports the fault using iommu_report_device_fault(), which
calls the registered handler. The page fault handler then calls the mm
fault handler, and reports either success or failure with
iommu_page_response(). After the handler succeeds, the hardware retries
the access.
The iopf_param pointer could be embedded into iommu_fault_param. But
putting iopf_param into the iommu_param structure allows us not to care
about ordering between calls to iopf_queue_add_device() and
iommu_register_device_fault_handler().
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Thanks
Eric
@@ -0,0 +1,461 @@+// SPDX-License-Identifier: GPL-2.0+/*+*Handledevicepagefaults+*+*Copyright(C)2020ARMLtd.+*/++#include<linux/iommu.h>+#include<linux/list.h>+#include<linux/sched/mm.h>+#include<linux/slab.h>+#include<linux/workqueue.h>++#include"iommu-sva-lib.h"++/**+*structiopf_queue-IOPageFaultqueue+*@wq:thefaultworkqueue+*@devices:devicesattachedtothisqueue+*@lock:protectsthedevicelist+*/+structiopf_queue{+structworkqueue_struct*wq;+structlist_headdevices;+structmutexlock;+};++/**+*structiopf_device_param-IOPageFaultdataattachedtoadevice+*@dev:thedevicethatownsthisparam+*@queue:IOPFqueue+*@queue_list:indexintoqueue->devices+*@partial:faultsthatarepartofaPageRequestGroupforwhichthelast+*requesthasn'tbeensubmittedyet.+*/+structiopf_device_param{+structdevice*dev;+structiopf_queue*queue;+structlist_headqueue_list;+structlist_headpartial;+};++structiopf_fault{+structiommu_faultfault;+structlist_headlist;+};++structiopf_group{+structiopf_faultlast_fault;+structlist_headfaults;+structwork_structwork;+structdevice*dev;+};++staticintiopf_complete_group(structdevice*dev,structiopf_fault*iopf,+enumiommu_page_response_codestatus)+{+structiommu_page_responseresp={+.version=IOMMU_PAGE_RESP_VERSION_1,+.pasid=iopf->fault.prm.pasid,+.grpid=iopf->fault.prm.grpid,+.code=status,+};++if((iopf->fault.prm.flags&IOMMU_FAULT_PAGE_REQUEST_PASID_VALID)&&+(iopf->fault.prm.flags&IOMMU_FAULT_PAGE_RESPONSE_NEEDS_PASID))+resp.flags=IOMMU_PAGE_RESP_PASID_VALID>++returniommu_page_response(dev,&resp);+}++staticenumiommu_page_response_code+iopf_handle_single(structiopf_fault*iopf)+{+vm_fault_tret;+structmm_struct*mm;+structvm_area_struct*vma;+unsignedintaccess_flags=0;+unsignedintfault_flags=FAULT_FLAG_REMOTE;+structiommu_fault_page_request*prm=&iopf->fault.prm;+enumiommu_page_response_codestatus=IOMMU_PAGE_RESP_INVALID;++if(!(prm->flags&IOMMU_FAULT_PAGE_REQUEST_PASID_VALID))+returnstatus;++mm=iommu_sva_find(prm->pasid);+if(IS_ERR_OR_NULL(mm))+returnstatus;++mmap_read_lock(mm);++vma=find_extend_vma(mm,prm->addr);+if(!vma)+/* Unmapped area */+gotoout_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 */+gotoout_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);++returnstatus;+}++staticvoidiopf_handle_group(structwork_struct*work)+{+structiopf_group*group;+structiopf_fault*iopf,*next;+enumiommu_page_response_codestatus=IOMMU_PAGE_RESP_SUCCESS;++group=container_of(work,structiopf_group,work);++list_for_each_entry_safe(iopf,next,&group->faults,list){+/*+*Forthemoment,errorsaresticky:don'thandlesubsequent+*faultsinthegroupifthereisanerror.+*/+if(status==IOMMU_PAGE_RESP_SUCCESS)+status=iopf_handle_single(iopf);++if(!(iopf->fault.prm.flags&+IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE))+kfree(iopf);+}++iopf_complete_group(group->dev,&group->last_fault,status);+kfree(group);+}++/**+*iommu_queue_iopf-IOPageFaulthandler+*@fault:faultevent+*@cookie:structdevice,passedtoiommu_register_device_fault_handler.+*+*Addafaulttothedeviceworkqueue,tobehandledbymm.+*+*Thismoduledoesn'thandlePCIPASIDStopMarker;IOMMUdriversmustdiscard+*thembeforereportingfaults.APASIDStopMarker(LRW=0b100)doesn't+*expectaresponse.ItmaybegeneratedwhendisablingaPASID(issuinga+*PASIDstoprequest)bysomePCIdevices.+*+*ThePASIDstoprequestisissuedbythedevicedriverbeforeunbind().Once+*itcompletes,nopagerequestisgeneratedforthisPASIDanymoreand+*outstandingoneshavebeenpushedtotheIOMMU(asperPCIe4.0r1.0-6.20.1+*and10.4.1.2-ManagingPASIDTLPPrefixUsage).SomePCIdeviceswillwait+*foralloutstandingpagerequeststocomebackwitharesponsebefore+*completingthePASIDstoprequest.Othersdonotwaitforpageresponses,and+*insteadissuethisStopMarkerthattellsuswhenthePASIDcanbe+*reallocated.+*+*ItissafetodiscardtheStopMarkerbecauseitisanoptimization.+*a.Pagerequests,whicharepostedrequests,havebeenflushedtotheIOMMU+*whenthestoprequestcompletes.+*b.TheIOMMUdriverflushesallfaultqueuesonunbind()beforefreeingthe+*PASID.+*+*SoeventhoughtheStopMarkermightbeissuedbythedevice*after*thestop+*requestcompletes,outstandingfaultswillhavebeendealtwithbythetime+*thePASIDisfreed.+*+*Return:0onsuccessand<0onerror.+*/+intiommu_queue_iopf(structiommu_fault*fault,void*cookie)+{+intret;+structiopf_group*group;+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;++structdevice*dev=cookie;+structdev_iommu*param=dev->iommu;++lockdep_assert_held(¶m->lock);++if(fault->type!=IOMMU_FAULT_PAGE_REQ)+/* Not a recoverable page fault */+return-EOPNOTSUPP;++/*+*Aslongaswe'reholdingparam->lock,thequeuecan'tbeunlinked+*fromthedeviceandthereforecannotdisappear.+*/+iopf_param=param->iopf_param;+if(!iopf_param)+return-ENODEV;++if(!(fault->prm.flags&IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE)){+iopf=kzalloc(sizeof(*iopf),GFP_KERNEL);+if(!iopf)+return-ENOMEM;++iopf->fault=*fault;++/* Non-last request of a group. Postpone until the last one */+list_add(&iopf->list,&iopf_param->partial);++return0;+}++group=kzalloc(sizeof(*group),GFP_KERNEL);+if(!group){+/*+*Thecallerwillsendaresponsetothehardware.Butwedo+*needtocleanupbeforeleaving,otherwisepartialfaults+*willbestuck.+*/+ret=-ENOMEM;+gotocleanup_partial;+}++group->dev=dev;+group->last_fault.fault=*fault;+INIT_LIST_HEAD(&group->faults);+list_add(&group->last_fault.list,&group->faults);+INIT_WORK(&group->work,iopf_handle_group);>++/* See if we have partial faults for this group */+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list){+if(iopf->fault.prm.grpid==fault->prm.grpid)+/* Insert *before* the last fault */+list_move(&iopf->list,&group->faults);+}++queue_work(iopf_param->queue->wq,&group->work);+return0;++cleanup_partial:+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list){+if(iopf->fault.prm.grpid==fault->prm.grpid){+list_del(&iopf->list);+kfree(iopf);+}+}+returnret;+}+EXPORT_SYMBOL_GPL(iommu_queue_iopf);++/**+*iopf_queue_flush_dev-Ensurethatallqueuedfaultshavebeenprocessed+*@dev:theendpointwhosefaultsneedtobeflushed.+*+*TheIOMMUdrivercallsthisbeforereleasingaPASID,toensurethatall+*pendingfaultsforthisPASIDhavebeenhandled,andwon'thittheaddress+*spaceofthenextprocessthatusesthisPASID.Thedrivermustmakesure+*thatnonewfaultisaddedtothequeue.Inparticularitmustflushits+*low-levelqueuebeforecallingthisfunction.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_flush_dev(structdevice*dev)+{+intret=0;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param)+return-ENODEV;++mutex_lock(¶m->lock);+iopf_param=param->iopf_param;+if(iopf_param)+flush_workqueue(iopf_param->queue->wq);+else+ret=-ENODEV;+mutex_unlock(¶m->lock);++returnret;+}+EXPORT_SYMBOL_GPL(iopf_queue_flush_dev);++/**+*iopf_queue_discard_partial-Removeallpendingpartialfault+*@queue:thequeuewhosepartialfaultsneedtobediscarded+*+*Whenthehardwarequeueoverflows,lastpagefaultsinagroupmayhavebeen+*lostandtheIOMMUdrivercallsthistodiscardallpartialfaults.The+*drivershouldn'tbeaddingnewfaultstothisqueueconcurrently.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_discard_partial(structiopf_queue*queue)+{+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;++if(!queue)+return-EINVAL;++mutex_lock(&queue->lock);+list_for_each_entry(iopf_param,&queue->devices,queue_list){+list_for_each_entry_safe(iopf,next,&iopf_param->partial,+list){+list_del(&iopf->list);+kfree(iopf);+}+}+mutex_unlock(&queue->lock);+return0;+}+EXPORT_SYMBOL_GPL(iopf_queue_discard_partial);++/**+*iopf_queue_add_device-Addproducertothefaultqueue+*@queue:IOPFqueue+*@dev:devicetoadd+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_add_device(structiopf_queue*queue,structdevice*dev)+{+intret=-EBUSY;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param)+return-ENODEV;++iopf_param=kzalloc(sizeof(*iopf_param),GFP_KERNEL);+if(!iopf_param)+return-ENOMEM;++INIT_LIST_HEAD(&iopf_param->partial);+iopf_param->queue=queue;+iopf_param->dev=dev;++mutex_lock(&queue->lock);+mutex_lock(¶m->lock);+if(!param->iopf_param){+list_add(&iopf_param->queue_list,&queue->devices);+param->iopf_param=iopf_param;+ret=0;+}+mutex_unlock(¶m->lock);+mutex_unlock(&queue->lock);++if(ret)+kfree(iopf_param);++returnret;+}+EXPORT_SYMBOL_GPL(iopf_queue_add_device);++/**+*iopf_queue_remove_device-Removeproducerfromfaultqueue+*@queue:IOPFqueue+*@dev:devicetoremove+*+*Callermakessurethatnomorefaultsarereportedforthisdevice.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_remove_device(structiopf_queue*queue,structdevice*dev)+{+intret=-EINVAL;+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param||!queue)+return-EINVAL;++mutex_lock(&queue->lock);+mutex_lock(¶m->lock);+iopf_param=param->iopf_param;+if(iopf_param&&iopf_param->queue==queue){+list_del(&iopf_param->queue_list);+param->iopf_param=NULL;+ret=0;+}+mutex_unlock(¶m->lock);+mutex_unlock(&queue->lock);+if(ret)+returnret;++/* Just in case some faults are still stuck */+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list)+kfree(iopf);++kfree(iopf_param);++return0;+}+EXPORT_SYMBOL_GPL(iopf_queue_remove_device);++/**+*iopf_queue_alloc-Allocateandinitializeafaultqueue+*@name:auniquestringidentifyingthequeue(forworkqueue)+*+*Return:thequeueonsuccessandNULLonerror.+*/+structiopf_queue*iopf_queue_alloc(constchar*name)+{+structiopf_queue*queue;++queue=kzalloc(sizeof(*queue),GFP_KERNEL);+if(!queue)+returnNULL;++/*+*TheWQisunorderedbecausethelow-levelhandlerenqueuesfaultsby+*group.PRIrequestswithinagrouphavetobeordered,butonce+*that'sdealtwith,thehigh-levelfunctioncanhandlegroupsoutof+*order.+*/+queue->wq=alloc_workqueue("iopf_queue/%s",WQ_UNBOUND,0,name);+if(!queue->wq){+kfree(queue);+returnNULL;+}++INIT_LIST_HEAD(&queue->devices);+mutex_init(&queue->lock);++returnqueue;+}+EXPORT_SYMBOL_GPL(iopf_queue_alloc);++/**+*iopf_queue_free-FreeIOPFqueue+*@queue:queuetofree+*+*Counterparttoiopf_queue_alloc().Thedrivermustnotbequeuingfaultsor+*adding/removingdevicesonthisqueueanymore.+*/+voidiopf_queue_free(structiopf_queue*queue)+{+structiopf_device_param*iopf_param,*next;++if(!queue)+return;++list_for_each_entry_safe(iopf_param,next,&queue->devices,queue_list)+iopf_queue_remove_device(queue,iopf_param->dev);++destroy_workqueue(queue->wq);+kfree(queue);+}+EXPORT_SYMBOL_GPL(iopf_queue_free);
From: Auger Eric <eric.auger@redhat.com> Date: 2021-01-31 20:32:50
Hi Jean,
Some rather minor comments§questions below that may not justify a respin.
On 1/27/21 4:43 PM, Jean-Philippe Brucker wrote:
quoted hunk
The SMMU provides a Stall model for handling page faults in platform
devices. It is similar to PCIe PRI, but doesn't require devices to have
their own translation cache. Instead, faulting transactions are parked
and the OS is given a chance to fix the page tables and retry the
transaction.
Enable stall for devices that support it (opt-in by firmware). When an
event corresponds to a translation error, call the IOMMU fault handler.
If the fault is recoverable, it will call us back to terminate or
continue the stall.
To use stall device drivers need to enable IOMMU_DEV_FEAT_IOPF, which
initializes the fault queue for the device.
Tested-by: Zhangfei Gao <zhangfei.gao@linaro.org>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 43 ++++
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 59 +++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 189 +++++++++++++++++-
3 files changed, 276 insertions(+), 15 deletions(-)
@@ -445,8 +449,8 @@ bool arm_smmu_master_sva_supported(struct arm_smmu_master *master) if (!(master->smmu->features & ARM_SMMU_FEAT_SVA)) return false;- /* SSID and IOPF support are mandatory for the moment */- return master->ssid_bits && arm_smmu_iopf_supported(master);+ /* SSID support is mandatory for the moment */+ return master->ssid_bits; } bool arm_smmu_master_sva_enabled(struct arm_smmu_master *master)
@@ -459,13 +463,55 @@ bool arm_smmu_master_sva_enabled(struct arm_smmu_master *master) return enabled; }+static int arm_smmu_master_sva_enable_iopf(struct arm_smmu_master *master)+{+ int ret;+ struct device *dev = master->dev;++ /*+ * Drivers for devices supporting PRI or stall should enable IOPF first.+ * Others have device-specific fault handlers and don't need IOPF.+ */+ if (!arm_smmu_master_iopf_supported(master))+ return 0;++ if (!master->iopf_enabled)+ return -EINVAL;++ ret = iopf_queue_add_device(master->smmu->evtq.iopf, dev);+ if (ret)+ return ret;++ ret = iommu_register_device_fault_handler(dev, iommu_queue_iopf, dev);+ if (ret) {+ iopf_queue_remove_device(master->smmu->evtq.iopf, dev);+ return ret;+ }+ return 0;+}++static void arm_smmu_master_sva_disable_iopf(struct arm_smmu_master *master)+{+ struct device *dev = master->dev;++ if (!master->iopf_enabled)+ return;++ iommu_unregister_device_fault_handler(dev);+ iopf_queue_remove_device(master->smmu->evtq.iopf, dev);+}+ int arm_smmu_master_enable_sva(struct arm_smmu_master *master) {+ int ret;+ mutex_lock(&sva_lock);- master->sva_enabled = true;+ ret = arm_smmu_master_sva_enable_iopf(master);+ if (!ret)+ master->sva_enabled = true; mutex_unlock(&sva_lock);- return 0;+ return ret; } int arm_smmu_master_disable_sva(struct arm_smmu_master *master)
nit: shall IOMMU_FAULT_UNRECOV_FETCH_ADDR_VALID be set here? Supported
unrecoverable faults feature the IPA field which is UNKNOWN for S1
translations. fetch_addr rather was
corresponding to WALK_EABT.Fetch_addr to me.
From: Zhou Wang <wangzhou1@hisilicon.com> Date: 2021-02-01 01:21:50
On 2021/1/27 23:43, Jean-Philippe Brucker wrote:
The SMMU provides a Stall model for handling page faults in platform
devices. It is similar to PCIe PRI, but doesn't require devices to have
their own translation cache. Instead, faulting transactions are parked
and the OS is given a chance to fix the page tables and retry the
transaction.
Enable stall for devices that support it (opt-in by firmware). When an
event corresponds to a translation error, call the IOMMU fault handler.
If the fault is recoverable, it will call us back to terminate or
continue the stall.
To use stall device drivers need to enable IOMMU_DEV_FEAT_IOPF, which
initializes the fault queue for the device.
Tested-by: Zhangfei Gao <zhangfei.gao@linaro.org>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 43 ++++
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-sva.c | 59 +++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 189 +++++++++++++++++-
3 files changed, 276 insertions(+), 15 deletions(-)
[...]
quoted hunk
@@ -1033,8 +1076,7 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid, FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) | CTXDESC_CD_0_V;- /* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */- if (smmu->features & ARM_SMMU_FEAT_STALL_FORCE)+ if (smmu_domain->stall_enabled)
Could we add ssid checking here? like: if (smmu_domain->stall_enabled && ssid).
The reason is if not CD.S will also be set when ssid is 0, which is not needed.
Best,
Zhou
From: Auger Eric <eric.auger@redhat.com> Date: 2021-02-01 07:29:52
Hi Jean-Philippe,
On 1/27/21 4:43 PM, Jean-Philippe Brucker wrote:
On ARM systems, some platform devices behind an IOMMU may support stall,
which is the ability to recover from page faults. Let the firmware tell us
when a device supports stall.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Jean-Philippe Brucker <redacted>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Eric
@@ -92,6 +92,24 @@ Optional properties: tagging DMA transactions with an address space identifier. By default, this is 0, which means that the device only has one address space.+- dma-can-stall: When present, the master can wait for a transaction to+ complete for an indefinite amount of time. Upon translation fault some+ IOMMUs, instead of aborting the translation immediately, may first+ notify the driver and keep the transaction in flight. This allows the OS+ to inspect the fault and, for example, make physical pages resident+ before updating the mappings and completing the transaction. Such IOMMU+ accepts a limited number of simultaneous stalled transactions before+ having to either put back-pressure on the master, or abort new faulting+ transactions.++ Firmware has to opt-in stalling, because most buses and masters don't+ support it. In particular it isn't compatible with PCI, where+ transactions have to complete before a time limit. More generally it+ won't work in systems and masters that haven't been designed for+ stalling. For example the OS, in order to handle a stalled transaction,+ may attempt to retrieve pages from secondary storage in a stalled+ domain, leading to a deadlock.+ Notes: ======
From: Auger Eric <eric.auger@redhat.com> Date: 2021-02-01 07:32:21
Hi,
On 1/27/21 4:43 PM, Jean-Philippe Brucker wrote:
The pasid-num-bits property shouldn't need a dedicated fwspec field,
it's a job for device properties. Add properties for IORT, and access
the number of PASID bits using device_property_read_u32().
Suggested-by: Robin Murphy <robin.murphy@arm.com>
Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Eric
From: Auger Eric <eric.auger@redhat.com> Date: 2021-02-01 07:37:03
Hi Jean,
On 1/27/21 4:43 PM, Jean-Philippe Brucker wrote:
Some devices manage I/O Page Faults (IOPF) themselves instead of relying
on PCIe PRI or Arm SMMU stall. Allow their drivers to enable SVA without
mandating IOMMU-managed IOPF. The other device drivers now need to first
enable IOMMU_DEV_FEAT_IOPF before enabling IOMMU_DEV_FEAT_SVA. Enabling
IOMMU_DEV_FEAT_IOPF on its own doesn't have any effect visible to the
device driver, it is used in combination with other features.
Signed-off-by: Jean-Philippe Brucker <redacted>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Eric
On Sun, Jan 31, 2021 at 07:29:09PM +0100, Auger Eric wrote:
Hi Jean,
Some rather minor comments§questions below that may not justify a respin.
On 1/27/21 4:43 PM, Jean-Philippe Brucker wrote:
quoted
-static bool arm_smmu_iopf_supported(struct arm_smmu_master *master)
+bool arm_smmu_master_iopf_supported(struct arm_smmu_master *master)
{
- return false;
+ /* We're not keeping track of SIDs in fault events */
shall we? [*] below
That would require storing the incoming SID into the iommu_fault_event
struct, and retrieve it in arm_smmu_page_response(). Easy enough, but I
don't think it's needed for existing devices.
I think fallthrough is mainly useful to tell reader and compiler that a
break was omitted on purpose. When two cases are stuck together the intent
to merge the flow is clear enough in my opinion. GCC's
-Wimplicit-fallthrough doesn't warn in this case.
quoted
+ case IOMMU_PAGE_RESP_FAILURE:
+ cmd.resume.resp = CMDQ_RESUME_0_RESP_ABORT;
+ break;
[...]
quoted
+static int arm_smmu_handle_evt(struct arm_smmu_device *smmu, u64 *evt)
+{
+ int ret;
+ u32 reason;
+ u32 perm = 0;
+ struct arm_smmu_master *master;
+ bool ssid_valid = evt[0] & EVTQ_0_SSV;
+ u32 sid = FIELD_GET(EVTQ_0_SID, evt[0]);
+ struct iommu_fault_event fault_evt = { };
+ struct iommu_fault *flt = &fault_evt.fault;
+
+ /* Stage-2 is always pinned at the moment */
+ if (evt[1] & EVTQ_1_S2)
+ return -EFAULT;
+
+ master = arm_smmu_find_master(smmu, sid);
+ if (!master)
+ return -EINVAL;
+
+ if (evt[1] & EVTQ_1_RnW)
+ perm |= IOMMU_FAULT_PERM_READ;
+ else
+ perm |= IOMMU_FAULT_PERM_WRITE;
+
+ if (evt[1] & EVTQ_1_InD)
+ perm |= IOMMU_FAULT_PERM_EXEC;
+
+ if (evt[1] & EVTQ_1_PnU)
+ perm |= IOMMU_FAULT_PERM_PRIV;
+
+ switch (FIELD_GET(EVTQ_0_ID, evt[0])) {
+ case EVT_ID_TRANSLATION_FAULT:
+ case EVT_ID_ADDR_SIZE_FAULT:
+ case EVT_ID_ACCESS_FAULT:
+ reason = IOMMU_FAULT_REASON_PTE_FETCH;
Doesn't it rather map to IOMMU_FAULT_REASON_ACCESS?
/* access flag check failed */"
Good point, I guess it didn't exist when I wrote this. And ADDR_SIZE_FAULT
corresponds to IOMMU_FAULT_REASON_OOR_ADDRESS now, right?
By the way the wording on those two fault reasons, "access flag" and
"stage", seems arch-specific - x86 names are "accessed flag" and "level".
nit: shall IOMMU_FAULT_UNRECOV_FETCH_ADDR_VALID be set here? Supported
unrecoverable faults feature the IPA field which is UNKNOWN for S1
translations. fetch_addr rather was
corresponding to WALK_EABT.Fetch_addr to me.
Right I should drop the IPA part entirely, since we don't report S2 faults
in this patch.
Thanks,
Jean
Hi Zhou,
On Mon, Feb 01, 2021 at 09:18:42AM +0800, Zhou Wang wrote:
quoted
@@ -1033,8 +1076,7 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid, FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) | CTXDESC_CD_0_V;- /* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */- if (smmu->features & ARM_SMMU_FEAT_STALL_FORCE)+ if (smmu_domain->stall_enabled)
Could we add ssid checking here? like: if (smmu_domain->stall_enabled && ssid).
The reason is if not CD.S will also be set when ssid is 0, which is not needed.
Some drivers may want to get stall events on SSID 0:
https://lore.kernel.org/kvm/20210125090402.1429-1-lushenming@huawei.com/#t
Are you seeing an issue with stall events on ssid 0? Normally there
shouldn't be any fault on this context, but if they happen and no handler
is registered, the SMMU driver will just abort them and report them like a
non-stall event.
Thanks,
Jean
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Zhou Wang <wangzhou1@hisilicon.com> Date: 2021-02-01 12:54:24
On 2021/2/1 19:14, Jean-Philippe Brucker wrote:
Hi Zhou,
On Mon, Feb 01, 2021 at 09:18:42AM +0800, Zhou Wang wrote:
quoted
quoted
@@ -1033,8 +1076,7 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid, FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) | CTXDESC_CD_0_V;- /* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */- if (smmu->features & ARM_SMMU_FEAT_STALL_FORCE)+ if (smmu_domain->stall_enabled)
Could we add ssid checking here? like: if (smmu_domain->stall_enabled && ssid).
The reason is if not CD.S will also be set when ssid is 0, which is not needed.
From: Auger Eric <eric.auger@redhat.com> Date: 2021-02-01 13:19:10
Hi Jean,
On 2/1/21 12:12 PM, Jean-Philippe Brucker wrote:
On Sun, Jan 31, 2021 at 07:29:09PM +0100, Auger Eric wrote:
quoted
Hi Jean,
Some rather minor comments§questions below that may not justify a respin.
On 1/27/21 4:43 PM, Jean-Philippe Brucker wrote:
quoted
-static bool arm_smmu_iopf_supported(struct arm_smmu_master *master)
+bool arm_smmu_master_iopf_supported(struct arm_smmu_master *master)
{
- return false;
+ /* We're not keeping track of SIDs in fault events */
shall we? [*] below
That would require storing the incoming SID into the iommu_fault_event
struct, and retrieve it in arm_smmu_page_response(). Easy enough, but I
don't think it's needed for existing devices.
I think fallthrough is mainly useful to tell reader and compiler that a
break was omitted on purpose. When two cases are stuck together the intent
to merge the flow is clear enough in my opinion. GCC's
-Wimplicit-fallthrough doesn't warn in this case.
OK
quoted
quoted
+ case IOMMU_PAGE_RESP_FAILURE:
+ cmd.resume.resp = CMDQ_RESUME_0_RESP_ABORT;
+ break;
[...]
quoted
quoted
+static int arm_smmu_handle_evt(struct arm_smmu_device *smmu, u64 *evt)
+{
+ int ret;
+ u32 reason;
+ u32 perm = 0;
+ struct arm_smmu_master *master;
+ bool ssid_valid = evt[0] & EVTQ_0_SSV;
+ u32 sid = FIELD_GET(EVTQ_0_SID, evt[0]);
+ struct iommu_fault_event fault_evt = { };
+ struct iommu_fault *flt = &fault_evt.fault;
+
+ /* Stage-2 is always pinned at the moment */
+ if (evt[1] & EVTQ_1_S2)
+ return -EFAULT;
+
+ master = arm_smmu_find_master(smmu, sid);
+ if (!master)
+ return -EINVAL;
+
+ if (evt[1] & EVTQ_1_RnW)
+ perm |= IOMMU_FAULT_PERM_READ;
+ else
+ perm |= IOMMU_FAULT_PERM_WRITE;
+
+ if (evt[1] & EVTQ_1_InD)
+ perm |= IOMMU_FAULT_PERM_EXEC;
+
+ if (evt[1] & EVTQ_1_PnU)
+ perm |= IOMMU_FAULT_PERM_PRIV;
+
+ switch (FIELD_GET(EVTQ_0_ID, evt[0])) {
+ case EVT_ID_TRANSLATION_FAULT:
+ case EVT_ID_ADDR_SIZE_FAULT:
+ case EVT_ID_ACCESS_FAULT:
+ reason = IOMMU_FAULT_REASON_PTE_FETCH;
Doesn't it rather map to IOMMU_FAULT_REASON_ACCESS?
/* access flag check failed */"
Good point, I guess it didn't exist when I wrote this. And ADDR_SIZE_FAULT
corresponds to IOMMU_FAULT_REASON_OOR_ADDRESS now, right?
yes it dies
By the way the wording on those two fault reasons, "access flag" and
"stage", seems arch-specific - x86 names are "accessed flag" and "level".
nit: shall IOMMU_FAULT_UNRECOV_FETCH_ADDR_VALID be set here? Supported
unrecoverable faults feature the IPA field which is UNKNOWN for S1
translations. fetch_addr rather was
corresponding to WALK_EABT.Fetch_addr to me.
Right I should drop the IPA part entirely, since we don't report S2 faults
in this patch.
OK
But as I mentioned this can be fixed separately if you don't have other
comments on this version.
Thanks
Eric
nit: shall IOMMU_FAULT_UNRECOV_FETCH_ADDR_VALID be set here? Supported
unrecoverable faults feature the IPA field which is UNKNOWN for S1
translations. fetch_addr rather was
corresponding to WALK_EABT.Fetch_addr to me.
Right I should drop the IPA part entirely, since we don't report S2 faults
in this patch.
OK
But as I mentioned this can be fixed separately if you don't have other
comments on this version.
From: Shenming Lu <hidden> Date: 2021-02-02 05:52:44
Hi Jean,
It seems that the preprocessing of the page faults(groups) here is relatively
generic, and if a device driver wants to reuse it while having its own iopf_handle_single(),
is there any chance for this? :-)
Thanks,
Shenming
On 2021/1/27 23:43, Jean-Philippe Brucker wrote:
quoted hunk
Some systems allow devices to handle I/O Page Faults in the core mm. For
example systems implementing the PCIe PRI extension or Arm SMMU stall
model. Infrastructure for reporting these recoverable page faults was
added to the IOMMU core by commit 0c830e6b3282 ("iommu: Introduce device
fault report API"). Add a page fault handler for host SVA.
IOMMU driver can now instantiate several fault workqueues and link them
to IOPF-capable devices. Drivers can choose between a single global
workqueue, one per IOMMU device, one per low-level fault queue, one per
domain, etc.
When it receives a fault event, most commonly in an IRQ handler, the
IOMMU driver reports the fault using iommu_report_device_fault(), which
calls the registered handler. The page fault handler then calls the mm
fault handler, and reports either success or failure with
iommu_page_response(). After the handler succeeds, the hardware retries
the access.
The iopf_param pointer could be embedded into iommu_fault_param. But
putting iopf_param into the iommu_param structure allows us not to care
about ordering between calls to iopf_queue_add_device() and
iommu_register_device_fault_handler().
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Jean-Philippe Brucker <redacted>
---
drivers/iommu/Makefile | 1 +
drivers/iommu/iommu-sva-lib.h | 53 ++++
include/linux/iommu.h | 2 +
drivers/iommu/io-pgfault.c | 461 ++++++++++++++++++++++++++++++++++
4 files changed, 517 insertions(+)
create mode 100644 drivers/iommu/io-pgfault.c
@@ -0,0 +1,461 @@+// SPDX-License-Identifier: GPL-2.0+/*+*Handledevicepagefaults+*+*Copyright(C)2020ARMLtd.+*/++#include<linux/iommu.h>+#include<linux/list.h>+#include<linux/sched/mm.h>+#include<linux/slab.h>+#include<linux/workqueue.h>++#include"iommu-sva-lib.h"++/**+*structiopf_queue-IOPageFaultqueue+*@wq:thefaultworkqueue+*@devices:devicesattachedtothisqueue+*@lock:protectsthedevicelist+*/+structiopf_queue{+structworkqueue_struct*wq;+structlist_headdevices;+structmutexlock;+};++/**+*structiopf_device_param-IOPageFaultdataattachedtoadevice+*@dev:thedevicethatownsthisparam+*@queue:IOPFqueue+*@queue_list:indexintoqueue->devices+*@partial:faultsthatarepartofaPageRequestGroupforwhichthelast+*requesthasn'tbeensubmittedyet.+*/+structiopf_device_param{+structdevice*dev;+structiopf_queue*queue;+structlist_headqueue_list;+structlist_headpartial;+};++structiopf_fault{+structiommu_faultfault;+structlist_headlist;+};++structiopf_group{+structiopf_faultlast_fault;+structlist_headfaults;+structwork_structwork;+structdevice*dev;+};++staticintiopf_complete_group(structdevice*dev,structiopf_fault*iopf,+enumiommu_page_response_codestatus)+{+structiommu_page_responseresp={+.version=IOMMU_PAGE_RESP_VERSION_1,+.pasid=iopf->fault.prm.pasid,+.grpid=iopf->fault.prm.grpid,+.code=status,+};++if((iopf->fault.prm.flags&IOMMU_FAULT_PAGE_REQUEST_PASID_VALID)&&+(iopf->fault.prm.flags&IOMMU_FAULT_PAGE_RESPONSE_NEEDS_PASID))+resp.flags=IOMMU_PAGE_RESP_PASID_VALID;++returniommu_page_response(dev,&resp);+}++staticenumiommu_page_response_code+iopf_handle_single(structiopf_fault*iopf)+{+vm_fault_tret;+structmm_struct*mm;+structvm_area_struct*vma;+unsignedintaccess_flags=0;+unsignedintfault_flags=FAULT_FLAG_REMOTE;+structiommu_fault_page_request*prm=&iopf->fault.prm;+enumiommu_page_response_codestatus=IOMMU_PAGE_RESP_INVALID;++if(!(prm->flags&IOMMU_FAULT_PAGE_REQUEST_PASID_VALID))+returnstatus;++mm=iommu_sva_find(prm->pasid);+if(IS_ERR_OR_NULL(mm))+returnstatus;++mmap_read_lock(mm);++vma=find_extend_vma(mm,prm->addr);+if(!vma)+/* Unmapped area */+gotoout_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 */+gotoout_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);++returnstatus;+}++staticvoidiopf_handle_group(structwork_struct*work)+{+structiopf_group*group;+structiopf_fault*iopf,*next;+enumiommu_page_response_codestatus=IOMMU_PAGE_RESP_SUCCESS;++group=container_of(work,structiopf_group,work);++list_for_each_entry_safe(iopf,next,&group->faults,list){+/*+*Forthemoment,errorsaresticky:don'thandlesubsequent+*faultsinthegroupifthereisanerror.+*/+if(status==IOMMU_PAGE_RESP_SUCCESS)+status=iopf_handle_single(iopf);++if(!(iopf->fault.prm.flags&+IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE))+kfree(iopf);+}++iopf_complete_group(group->dev,&group->last_fault,status);+kfree(group);+}++/**+*iommu_queue_iopf-IOPageFaulthandler+*@fault:faultevent+*@cookie:structdevice,passedtoiommu_register_device_fault_handler.+*+*Addafaulttothedeviceworkqueue,tobehandledbymm.+*+*Thismoduledoesn'thandlePCIPASIDStopMarker;IOMMUdriversmustdiscard+*thembeforereportingfaults.APASIDStopMarker(LRW=0b100)doesn't+*expectaresponse.ItmaybegeneratedwhendisablingaPASID(issuinga+*PASIDstoprequest)bysomePCIdevices.+*+*ThePASIDstoprequestisissuedbythedevicedriverbeforeunbind().Once+*itcompletes,nopagerequestisgeneratedforthisPASIDanymoreand+*outstandingoneshavebeenpushedtotheIOMMU(asperPCIe4.0r1.0-6.20.1+*and10.4.1.2-ManagingPASIDTLPPrefixUsage).SomePCIdeviceswillwait+*foralloutstandingpagerequeststocomebackwitharesponsebefore+*completingthePASIDstoprequest.Othersdonotwaitforpageresponses,and+*insteadissuethisStopMarkerthattellsuswhenthePASIDcanbe+*reallocated.+*+*ItissafetodiscardtheStopMarkerbecauseitisanoptimization.+*a.Pagerequests,whicharepostedrequests,havebeenflushedtotheIOMMU+*whenthestoprequestcompletes.+*b.TheIOMMUdriverflushesallfaultqueuesonunbind()beforefreeingthe+*PASID.+*+*SoeventhoughtheStopMarkermightbeissuedbythedevice*after*thestop+*requestcompletes,outstandingfaultswillhavebeendealtwithbythetime+*thePASIDisfreed.+*+*Return:0onsuccessand<0onerror.+*/+intiommu_queue_iopf(structiommu_fault*fault,void*cookie)+{+intret;+structiopf_group*group;+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;++structdevice*dev=cookie;+structdev_iommu*param=dev->iommu;++lockdep_assert_held(¶m->lock);++if(fault->type!=IOMMU_FAULT_PAGE_REQ)+/* Not a recoverable page fault */+return-EOPNOTSUPP;++/*+*Aslongaswe'reholdingparam->lock,thequeuecan'tbeunlinked+*fromthedeviceandthereforecannotdisappear.+*/+iopf_param=param->iopf_param;+if(!iopf_param)+return-ENODEV;++if(!(fault->prm.flags&IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE)){+iopf=kzalloc(sizeof(*iopf),GFP_KERNEL);+if(!iopf)+return-ENOMEM;++iopf->fault=*fault;++/* Non-last request of a group. Postpone until the last one */+list_add(&iopf->list,&iopf_param->partial);++return0;+}++group=kzalloc(sizeof(*group),GFP_KERNEL);+if(!group){+/*+*Thecallerwillsendaresponsetothehardware.Butwedo+*needtocleanupbeforeleaving,otherwisepartialfaults+*willbestuck.+*/+ret=-ENOMEM;+gotocleanup_partial;+}++group->dev=dev;+group->last_fault.fault=*fault;+INIT_LIST_HEAD(&group->faults);+list_add(&group->last_fault.list,&group->faults);+INIT_WORK(&group->work,iopf_handle_group);++/* See if we have partial faults for this group */+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list){+if(iopf->fault.prm.grpid==fault->prm.grpid)+/* Insert *before* the last fault */+list_move(&iopf->list,&group->faults);+}++queue_work(iopf_param->queue->wq,&group->work);+return0;++cleanup_partial:+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list){+if(iopf->fault.prm.grpid==fault->prm.grpid){+list_del(&iopf->list);+kfree(iopf);+}+}+returnret;+}+EXPORT_SYMBOL_GPL(iommu_queue_iopf);++/**+*iopf_queue_flush_dev-Ensurethatallqueuedfaultshavebeenprocessed+*@dev:theendpointwhosefaultsneedtobeflushed.+*+*TheIOMMUdrivercallsthisbeforereleasingaPASID,toensurethatall+*pendingfaultsforthisPASIDhavebeenhandled,andwon'thittheaddress+*spaceofthenextprocessthatusesthisPASID.Thedrivermustmakesure+*thatnonewfaultisaddedtothequeue.Inparticularitmustflushits+*low-levelqueuebeforecallingthisfunction.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_flush_dev(structdevice*dev)+{+intret=0;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param)+return-ENODEV;++mutex_lock(¶m->lock);+iopf_param=param->iopf_param;+if(iopf_param)+flush_workqueue(iopf_param->queue->wq);+else+ret=-ENODEV;+mutex_unlock(¶m->lock);++returnret;+}+EXPORT_SYMBOL_GPL(iopf_queue_flush_dev);++/**+*iopf_queue_discard_partial-Removeallpendingpartialfault+*@queue:thequeuewhosepartialfaultsneedtobediscarded+*+*Whenthehardwarequeueoverflows,lastpagefaultsinagroupmayhavebeen+*lostandtheIOMMUdrivercallsthistodiscardallpartialfaults.The+*drivershouldn'tbeaddingnewfaultstothisqueueconcurrently.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_discard_partial(structiopf_queue*queue)+{+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;++if(!queue)+return-EINVAL;++mutex_lock(&queue->lock);+list_for_each_entry(iopf_param,&queue->devices,queue_list){+list_for_each_entry_safe(iopf,next,&iopf_param->partial,+list){+list_del(&iopf->list);+kfree(iopf);+}+}+mutex_unlock(&queue->lock);+return0;+}+EXPORT_SYMBOL_GPL(iopf_queue_discard_partial);++/**+*iopf_queue_add_device-Addproducertothefaultqueue+*@queue:IOPFqueue+*@dev:devicetoadd+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_add_device(structiopf_queue*queue,structdevice*dev)+{+intret=-EBUSY;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param)+return-ENODEV;++iopf_param=kzalloc(sizeof(*iopf_param),GFP_KERNEL);+if(!iopf_param)+return-ENOMEM;++INIT_LIST_HEAD(&iopf_param->partial);+iopf_param->queue=queue;+iopf_param->dev=dev;++mutex_lock(&queue->lock);+mutex_lock(¶m->lock);+if(!param->iopf_param){+list_add(&iopf_param->queue_list,&queue->devices);+param->iopf_param=iopf_param;+ret=0;+}+mutex_unlock(¶m->lock);+mutex_unlock(&queue->lock);++if(ret)+kfree(iopf_param);++returnret;+}+EXPORT_SYMBOL_GPL(iopf_queue_add_device);++/**+*iopf_queue_remove_device-Removeproducerfromfaultqueue+*@queue:IOPFqueue+*@dev:devicetoremove+*+*Callermakessurethatnomorefaultsarereportedforthisdevice.+*+*Return:0onsuccessand<0onerror.+*/+intiopf_queue_remove_device(structiopf_queue*queue,structdevice*dev)+{+intret=-EINVAL;+structiopf_fault*iopf,*next;+structiopf_device_param*iopf_param;+structdev_iommu*param=dev->iommu;++if(!param||!queue)+return-EINVAL;++mutex_lock(&queue->lock);+mutex_lock(¶m->lock);+iopf_param=param->iopf_param;+if(iopf_param&&iopf_param->queue==queue){+list_del(&iopf_param->queue_list);+param->iopf_param=NULL;+ret=0;+}+mutex_unlock(¶m->lock);+mutex_unlock(&queue->lock);+if(ret)+returnret;++/* Just in case some faults are still stuck */+list_for_each_entry_safe(iopf,next,&iopf_param->partial,list)+kfree(iopf);++kfree(iopf_param);++return0;+}+EXPORT_SYMBOL_GPL(iopf_queue_remove_device);++/**+*iopf_queue_alloc-Allocateandinitializeafaultqueue+*@name:auniquestringidentifyingthequeue(forworkqueue)+*+*Return:thequeueonsuccessandNULLonerror.+*/+structiopf_queue*iopf_queue_alloc(constchar*name)+{+structiopf_queue*queue;++queue=kzalloc(sizeof(*queue),GFP_KERNEL);+if(!queue)+returnNULL;++/*+*TheWQisunorderedbecausethelow-levelhandlerenqueuesfaultsby+*group.PRIrequestswithinagrouphavetobeordered,butonce+*that'sdealtwith,thehigh-levelfunctioncanhandlegroupsoutof+*order.+*/+queue->wq=alloc_workqueue("iopf_queue/%s",WQ_UNBOUND,0,name);+if(!queue->wq){+kfree(queue);+returnNULL;+}++INIT_LIST_HEAD(&queue->devices);+mutex_init(&queue->lock);++returnqueue;+}+EXPORT_SYMBOL_GPL(iopf_queue_alloc);++/**+*iopf_queue_free-FreeIOPFqueue+*@queue:queuetofree+*+*Counterparttoiopf_queue_alloc().Thedrivermustnotbequeuingfaultsor+*adding/removingdevicesonthisqueueanymore.+*/+voidiopf_queue_free(structiopf_queue*queue)+{+structiopf_device_param*iopf_param,*next;++if(!queue)+return;++list_for_each_entry_safe(iopf_param,next,&queue->devices,queue_list)+iopf_queue_remove_device(queue,iopf_param->dev);++destroy_workqueue(queue->wq);+kfree(queue);+}+EXPORT_SYMBOL_GPL(iopf_queue_free);
From: Zhou Wang <wangzhou1@hisilicon.com> Date: 2021-02-26 09:44:52
On 2021/2/1 19:14, Jean-Philippe Brucker wrote:
Hi Zhou,
On Mon, Feb 01, 2021 at 09:18:42AM +0800, Zhou Wang wrote:
quoted
quoted
@@ -1033,8 +1076,7 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid, FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) | CTXDESC_CD_0_V;- /* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */- if (smmu->features & ARM_SMMU_FEAT_STALL_FORCE)+ if (smmu_domain->stall_enabled)
Could we add ssid checking here? like: if (smmu_domain->stall_enabled && ssid).
The reason is if not CD.S will also be set when ssid is 0, which is not needed.
Some drivers may want to get stall events on SSID 0:
https://lore.kernel.org/kvm/20210125090402.1429-1-lushenming@huawei.com/#t
Are you seeing an issue with stall events on ssid 0? Normally there
shouldn't be any fault on this context, but if they happen and no handler
is registered, the SMMU driver will just abort them and report them like a
non-stall event.
Hi Jean,
I notice that there is problem. In my case, I expect that CD0 is for kernel
and other CDs are for user space. Normally there shouldn't be any fault in
kernel, however, we have RAS case which is for some reason there may has
invalid address access from hardware device.
So at least there are two different address access failures: 1. hardware RAS problem;
2. software fault fail(e.g. kill process when doing DMA). Handlings for these
two are different: for 1, we should reset hardware device; for 2, stop related
DMA is enough.
Currently if SMMU returns the same signal(by SMMU resume abort), master device
driver can not tell these two kinds of cases.
From the basic concept, if a CD is used for kernel, its S bit should not be set.
How about we add iommu domain check here too, if DMA domain we do not set S bit for
CD0, if unmanaged domain we set S bit for all CDs?
Thanks,
Zhou
Hi Zhou,
On Fri, Feb 26, 2021 at 05:43:27PM +0800, Zhou Wang wrote:
On 2021/2/1 19:14, Jean-Philippe Brucker wrote:
quoted
Hi Zhou,
On Mon, Feb 01, 2021 at 09:18:42AM +0800, Zhou Wang wrote:
quoted
quoted
@@ -1033,8 +1076,7 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid, FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) | CTXDESC_CD_0_V;- /* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */- if (smmu->features & ARM_SMMU_FEAT_STALL_FORCE)+ if (smmu_domain->stall_enabled)
Could we add ssid checking here? like: if (smmu_domain->stall_enabled && ssid).
The reason is if not CD.S will also be set when ssid is 0, which is not needed.
Some drivers may want to get stall events on SSID 0:
https://lore.kernel.org/kvm/20210125090402.1429-1-lushenming@huawei.com/#t
Are you seeing an issue with stall events on ssid 0? Normally there
shouldn't be any fault on this context, but if they happen and no handler
is registered, the SMMU driver will just abort them and report them like a
non-stall event.
Hi Jean,
I notice that there is problem. In my case, I expect that CD0 is for kernel
and other CDs are for user space. Normally there shouldn't be any fault in
kernel, however, we have RAS case which is for some reason there may has
invalid address access from hardware device.
So at least there are two different address access failures: 1. hardware RAS problem;
2. software fault fail(e.g. kill process when doing DMA). Handlings for these
two are different: for 1, we should reset hardware device; for 2, stop related
DMA is enough.
Right, and in case 2 there should be no report printed since it can be
triggered by user, while you probably want to be loud in case 1.
Currently if SMMU returns the same signal(by SMMU resume abort), master device
driver can not tell these two kinds of cases.
This part I don't understand. So the SMMU sends a RESUME(abort) command,
and then the master reports the DMA error to the device driver, which
cannot differentiate 1 from 2? (I guess there is no SSID in this report?)
But how does disabling stall change this? The invalid DMA access will
still be aborted by the SMMU.
Hypothetically, would it work if all stall events that could not be
handled went to the device driver? Those reports would contain the SSID
(or lack thereof), so you could reset the device in case 1 and ignore case
2. Though resetting the device in the middle of a stalled transaction
probably comes with its own set of problems.
From the basic concept, if a CD is used for kernel, its S bit should not be set.
How about we add iommu domain check here too, if DMA domain we do not set S bit for
CD0, if unmanaged domain we set S bit for all CDs?
I think disabling stall for CD0 of a DMA domain makes sense in general,
even though I don't really understand how that fixes your issue. But
someone might come up with a good use-case for receiving stall events on
DMA mappings, so I'm wondering whether the alternative solution where we
report unhandled stall events to the device driver would also work for
you.
Thanks,
Jean
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Zhou Wang <wangzhou1@hisilicon.com> Date: 2021-02-27 03:40:53
On 2021/2/27 0:29, Jean-Philippe Brucker wrote:
Hi Zhou,
On Fri, Feb 26, 2021 at 05:43:27PM +0800, Zhou Wang wrote:
quoted
On 2021/2/1 19:14, Jean-Philippe Brucker wrote:
quoted
Hi Zhou,
On Mon, Feb 01, 2021 at 09:18:42AM +0800, Zhou Wang wrote:
quoted
quoted
@@ -1033,8 +1076,7 @@ int arm_smmu_write_ctx_desc(struct arm_smmu_domain *smmu_domain, int ssid, FIELD_PREP(CTXDESC_CD_0_ASID, cd->asid) | CTXDESC_CD_0_V;- /* STALL_MODEL==0b10 && CD.S==0 is ILLEGAL */- if (smmu->features & ARM_SMMU_FEAT_STALL_FORCE)+ if (smmu_domain->stall_enabled)
Could we add ssid checking here? like: if (smmu_domain->stall_enabled && ssid).
The reason is if not CD.S will also be set when ssid is 0, which is not needed.
Some drivers may want to get stall events on SSID 0:
https://lore.kernel.org/kvm/20210125090402.1429-1-lushenming@huawei.com/#t
Are you seeing an issue with stall events on ssid 0? Normally there
shouldn't be any fault on this context, but if they happen and no handler
is registered, the SMMU driver will just abort them and report them like a
non-stall event.
Hi Jean,
I notice that there is problem. In my case, I expect that CD0 is for kernel
and other CDs are for user space. Normally there shouldn't be any fault in
kernel, however, we have RAS case which is for some reason there may has
invalid address access from hardware device.
So at least there are two different address access failures: 1. hardware RAS problem;
2. software fault fail(e.g. kill process when doing DMA). Handlings for these
two are different: for 1, we should reset hardware device; for 2, stop related
DMA is enough.
Right, and in case 2 there should be no report printed since it can be
triggered by user, while you probably want to be loud in case 1.
quoted
Currently if SMMU returns the same signal(by SMMU resume abort), master device
driver can not tell these two kinds of cases.
This part I don't understand. So the SMMU sends a RESUME(abort) command,
and then the master reports the DMA error to the device driver, which
cannot differentiate 1 from 2? (I guess there is no SSID in this report?)
But how does disabling stall change this? The invalid DMA access will
still be aborted by the SMMU.
This is about the hardware design. In D06 board, an invalid DMA access from
accelerator devices will be aborted, and an hardware error signal will be
returned to accelerator devices, which reports it as a RAS error irq.
while for the stall case, error signal triggered by SMMU resume abort is
also reported as same RAS error irq. This is problem in D60 board.
In next generation of hardware, a new irq will be added to report SMMU resume
abort information, it works with related registers in accelerator devices to
get related hardware queue, which need to be stopped.
So if CD0.S is 1, invalid DMA access in kernel will be reported into above
new added irq, which has not enough information to tell RAS errors(there are 10+
hardware RAS errors) from SMMU resume abort.
Hypothetically, would it work if all stall events that could not be
handled went to the device driver? Those reports would contain the SSID
(or lack thereof), so you could reset the device in case 1 and ignore case
2. Though resetting the device in the middle of a stalled transaction
As above, it is hard to tell RAS errors and SMMU resume abort in SMMU resume abort
now :(
probably comes with its own set of problems.
quoted
From the basic concept, if a CD is used for kernel, its S bit should not be set.
How about we add iommu domain check here too, if DMA domain we do not set S bit for
CD0, if unmanaged domain we set S bit for all CDs?
I think disabling stall for CD0 of a DMA domain makes sense in general,
even though I don't really understand how that fixes your issue. But
As above, if disabling stall for CD0, an invalid DMA access will be handled
by RAS error irq.
someone might come up with a good use-case for receiving stall events on
If A DMA access in kernel fails, I think there should be a RAS issue :)
So better to disable CD0 stall for DMA domain.
Best,
Zhou
DMA mappings, so I'm wondering whether the alternative solution where we
report unhandled stall events to the device driver would also work for
you.
Thanks,
Jean
.