x86/iommu: Reduce the number of queued invalidation interrupts

Restructure dmar_qi_task() so as to reduce the number of invalidation
completion interrupts.  Specifically, because processing completed
invalidations in dmar_qi_task() can take quite some time, don't reenable
completion interrupts until processing has completed a first time. Then,
check a second time after reenabling completion interrupts, so that
any invalidations that complete just before interrupts are reenabled
do not linger until a future invalidation might raise an interrupt.
(Recent changes have made checking for completed invalidations cheap; no
locking is required.)

Reviewed by:	kib
MFC after:	1 week
Differential Revision:	https://reviews.freebsd.org/D36054
This commit is contained in:
Alan Cox 2022-07-31 14:28:30 -05:00
parent 35ce7d40f9
commit 7f46deccbe

View file

@ -411,14 +411,34 @@ dmar_qi_intr(void *arg)
return (FILTER_HANDLED);
}
static void
dmar_qi_drain_tlb_flush(struct dmar_unit *unit)
{
struct iommu_map_entry *entry, *head;
for (head = unit->tlb_flush_head;; head = entry) {
entry = (struct iommu_map_entry *)
atomic_load_acq_ptr((uintptr_t *)&head->tlb_flush_next);
if (entry == NULL ||
!dmar_qi_seq_processed(unit, &entry->gseq))
break;
unit->tlb_flush_head = entry;
iommu_gas_free_entry(head);
if ((entry->flags & IOMMU_MAP_ENTRY_RMRR) != 0)
iommu_gas_free_region(entry);
else
iommu_gas_free_space(entry);
}
}
static void
dmar_qi_task(void *arg, int pending __unused)
{
struct dmar_unit *unit;
struct iommu_map_entry *entry, *head;
uint32_t ics;
unit = arg;
dmar_qi_drain_tlb_flush(unit);
/*
* Request an interrupt on the completion of the next invalidation
@ -428,23 +448,16 @@ dmar_qi_task(void *arg, int pending __unused)
if ((ics & DMAR_ICS_IWC) != 0) {
ics = DMAR_ICS_IWC;
dmar_write4(unit, DMAR_ICS_REG, ics);
/*
* Drain a second time in case the DMAR processes an entry
* after the first call and before clearing DMAR_ICS_IWC.
* Otherwise, such entries will linger until a later entry
* that requests an interrupt is processed.
*/
dmar_qi_drain_tlb_flush(unit);
}
for (;;) {
head = unit->tlb_flush_head;
entry = (struct iommu_map_entry *)
atomic_load_acq_ptr((uintptr_t *)&head->tlb_flush_next);
if (entry == NULL)
break;
if (!dmar_qi_seq_processed(unit, &entry->gseq))
break;
unit->tlb_flush_head = entry;
iommu_gas_free_entry(head);
if ((entry->flags & IOMMU_MAP_ENTRY_RMRR) != 0)
iommu_gas_free_region(entry);
else
iommu_gas_free_space(entry);
}
if (unit->inv_seq_waiters > 0) {
/*
* Acquire the DMAR lock so that wakeup() is called only after