Timeout DMAR commands.

Implement timeouts for register-based DMAR commands.  Tunable/sysctl
hw.dmar.timeout specifies the timeout in nanoseconds, set it to zero
to allow infinite wait.  Default is 1ms.

Runtime modification of the sysctl is not safe, it is allowed for
debugging.

Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
This commit is contained in:
kib 2017-03-27 07:06:45 +00:00
parent 742f9efd6b
commit f3a84dabad
4 changed files with 124 additions and 46 deletions

View File

@ -290,6 +290,8 @@ int dmar_enable_ir(struct dmar_unit *unit);
int dmar_disable_ir(struct dmar_unit *unit);
bool dmar_barrier_enter(struct dmar_unit *dmar, u_int barrier_id);
void dmar_barrier_exit(struct dmar_unit *dmar, u_int barrier_id);
uint64_t dmar_get_timeout(void);
void dmar_update_timeout(uint64_t newval);
int dmar_fault_intr(void *arg);
void dmar_enable_fault_intr(struct dmar_unit *unit);
@ -507,6 +509,36 @@ dmar_test_boundary(dmar_gaddr_t start, dmar_gaddr_t size,
return (start + size <= ((start + boundary) & ~(boundary - 1)));
}
extern struct timespec dmar_hw_timeout;
#define DMAR_WAIT_UNTIL(cond) \
{ \
struct timespec last, curr; \
bool forever; \
\
if (dmar_hw_timeout.tv_sec == 0 && \
dmar_hw_timeout.tv_nsec == 0) { \
forever = true; \
} else { \
forever = false; \
nanouptime(&curr); \
last = curr; \
timespecadd(&last, &dmar_hw_timeout); \
} \
for (;;) { \
if (cond) { \
error = 0; \
break; \
} \
nanouptime(&curr); \
if (!forever && timespeccmp(&last, &curr, <)) { \
error = ETIMEDOUT; \
break; \
} \
cpu_spinwait(); \
} \
}
#ifdef INVARIANTS
#define TD_PREP_PINNED_ASSERT \
int old_td_pinned; \

View File

@ -402,6 +402,7 @@ dmar_attach(device_t dev)
{
struct dmar_unit *unit;
ACPI_DMAR_HARDWARE_UNIT *dmaru;
uint64_t timeout;
int i, error;
unit = device_get_softc(dev);
@ -426,6 +427,10 @@ dmar_attach(device_t dev)
dmar_print_caps(dev, unit, dmaru);
dmar_quirks_post_ident(unit);
timeout = dmar_get_timeout();
TUNABLE_UINT64_FETCH("hw.dmar.timeout", &timeout);
dmar_update_timeout(timeout);
for (i = 0; i < DMAR_INTR_TOTAL; i++)
unit->intrs[i].irq = -1;

View File

@ -40,6 +40,7 @@ __FBSDID("$FreeBSD$");
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/taskqueue.h>
#include <sys/time.h>
#include <sys/tree.h>
#include <sys/vmem.h>
#include <machine/bus.h>
@ -70,27 +71,27 @@ dmar_qi_seq_processed(const struct dmar_unit *unit,
static int
dmar_enable_qi(struct dmar_unit *unit)
{
int error;
DMAR_ASSERT_LOCKED(unit);
unit->hw_gcmd |= DMAR_GCMD_QIE;
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_QIES) == 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_QIES)
!= 0));
return (error);
}
static int
dmar_disable_qi(struct dmar_unit *unit)
{
int error;
DMAR_ASSERT_LOCKED(unit);
unit->hw_gcmd &= ~DMAR_GCMD_QIE;
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_QIES) != 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_QIES)
== 0));
return (error);
}
static void

View File

@ -46,6 +46,7 @@ __FBSDID("$FreeBSD$");
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/taskqueue.h>
#include <sys/time.h>
#include <sys/tree.h>
#include <sys/vmem.h>
#include <dev/pci/pcivar.h>
@ -401,6 +402,7 @@ int
dmar_load_root_entry_ptr(struct dmar_unit *unit)
{
vm_page_t root_entry;
int error;
/*
* Access to the GCMD register must be serialized while the
@ -413,10 +415,9 @@ dmar_load_root_entry_ptr(struct dmar_unit *unit)
VM_OBJECT_RUNLOCK(unit->ctx_obj);
dmar_write8(unit, DMAR_RTADDR_REG, VM_PAGE_TO_PHYS(root_entry));
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd | DMAR_GCMD_SRTP);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_RTPS) == 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_RTPS)
!= 0));
return (error);
}
/*
@ -426,6 +427,7 @@ dmar_load_root_entry_ptr(struct dmar_unit *unit)
int
dmar_inv_ctx_glob(struct dmar_unit *unit)
{
int error;
/*
* Access to the CCMD register must be serialized while the
@ -441,10 +443,9 @@ dmar_inv_ctx_glob(struct dmar_unit *unit)
* writes the upper dword last.
*/
dmar_write8(unit, DMAR_CCMD_REG, DMAR_CCMD_ICC | DMAR_CCMD_CIRG_GLOB);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_CCMD_REG + 4) & DMAR_CCMD_ICC32) != 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_CCMD_REG + 4) & DMAR_CCMD_ICC32)
== 0));
return (error);
}
/*
@ -453,7 +454,7 @@ dmar_inv_ctx_glob(struct dmar_unit *unit)
int
dmar_inv_iotlb_glob(struct dmar_unit *unit)
{
int reg;
int error, reg;
DMAR_ASSERT_LOCKED(unit);
KASSERT(!unit->qi_enabled, ("QI enabled"));
@ -462,11 +463,9 @@ dmar_inv_iotlb_glob(struct dmar_unit *unit)
/* See a comment about DMAR_CCMD_ICC in dmar_inv_ctx_glob. */
dmar_write8(unit, reg + DMAR_IOTLB_REG_OFF, DMAR_IOTLB_IVT |
DMAR_IOTLB_IIRG_GLB | DMAR_IOTLB_DR | DMAR_IOTLB_DW);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, reg + DMAR_IOTLB_REG_OFF + 4) &
DMAR_IOTLB_IVT32) != 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, reg + DMAR_IOTLB_REG_OFF + 4) &
DMAR_IOTLB_IVT32) == 0));
return (error);
}
/*
@ -476,6 +475,7 @@ dmar_inv_iotlb_glob(struct dmar_unit *unit)
int
dmar_flush_write_bufs(struct dmar_unit *unit)
{
int error;
DMAR_ASSERT_LOCKED(unit);
@ -486,42 +486,42 @@ dmar_flush_write_bufs(struct dmar_unit *unit)
("dmar%d: no RWBF", unit->unit));
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd | DMAR_GCMD_WBF);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_WBFS) == 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_WBFS)
!= 0));
return (error);
}
int
dmar_enable_translation(struct dmar_unit *unit)
{
int error;
DMAR_ASSERT_LOCKED(unit);
unit->hw_gcmd |= DMAR_GCMD_TE;
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_TES) == 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_TES)
!= 0));
return (error);
}
int
dmar_disable_translation(struct dmar_unit *unit)
{
int error;
DMAR_ASSERT_LOCKED(unit);
unit->hw_gcmd &= ~DMAR_GCMD_TE;
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_TES) != 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_TES)
== 0));
return (error);
}
int
dmar_load_irt_ptr(struct dmar_unit *unit)
{
uint64_t irta, s;
int error;
DMAR_ASSERT_LOCKED(unit);
irta = unit->irt_phys;
@ -534,37 +534,36 @@ dmar_load_irt_ptr(struct dmar_unit *unit)
irta |= s;
dmar_write8(unit, DMAR_IRTA_REG, irta);
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd | DMAR_GCMD_SIRTP);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_IRTPS) == 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_IRTPS)
!= 0));
return (error);
}
int
dmar_enable_ir(struct dmar_unit *unit)
{
int error;
DMAR_ASSERT_LOCKED(unit);
unit->hw_gcmd |= DMAR_GCMD_IRE;
unit->hw_gcmd &= ~DMAR_GCMD_CFI;
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_IRES) == 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_IRES)
!= 0));
return (error);
}
int
dmar_disable_ir(struct dmar_unit *unit)
{
int error;
DMAR_ASSERT_LOCKED(unit);
unit->hw_gcmd &= ~DMAR_GCMD_IRE;
dmar_write4(unit, DMAR_GCMD_REG, unit->hw_gcmd);
/* XXXKIB should have a timeout */
while ((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_IRES) != 0)
cpu_spinwait();
return (0);
DMAR_WAIT_UNTIL(((dmar_read4(unit, DMAR_GSTS_REG) & DMAR_GSTS_IRES)
== 0));
return (error);
}
#define BARRIER_F \
@ -619,6 +618,43 @@ dmar_barrier_exit(struct dmar_unit *dmar, u_int barrier_id)
int dmar_match_verbose;
int dmar_batch_coalesce = 100;
struct timespec dmar_hw_timeout = {
.tv_sec = 0,
.tv_nsec = 1000000
};
static const uint64_t d = 1000000000;
void
dmar_update_timeout(uint64_t newval)
{
/* XXXKIB not atomic */
dmar_hw_timeout.tv_sec = newval / d;
dmar_hw_timeout.tv_nsec = newval % d;
}
uint64_t
dmar_get_timeout(void)
{
return ((uint64_t)dmar_hw_timeout.tv_sec * d +
dmar_hw_timeout.tv_nsec);
}
static int
dmar_timeout_sysctl(SYSCTL_HANDLER_ARGS)
{
uint64_t val;
int error;
val = dmar_get_timeout();
error = sysctl_handle_long(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
dmar_update_timeout(val);
return (error);
}
static SYSCTL_NODE(_hw, OID_AUTO, dmar, CTLFLAG_RD, NULL, "");
SYSCTL_INT(_hw_dmar, OID_AUTO, tbl_pagecnt, CTLFLAG_RD,
@ -630,6 +666,10 @@ SYSCTL_INT(_hw_dmar, OID_AUTO, match_verbose, CTLFLAG_RWTUN,
SYSCTL_INT(_hw_dmar, OID_AUTO, batch_coalesce, CTLFLAG_RWTUN,
&dmar_batch_coalesce, 0,
"Number of qi batches between interrupt");
SYSCTL_PROC(_hw_dmar, OID_AUTO, timeout,
CTLTYPE_U64 | CTLFLAG_RW | CTLFLAG_MPSAFE, 0, 0,
dmar_timeout_sysctl, "QU",
"Timeout for command wait, in nanoseconds");
#ifdef INVARIANTS
int dmar_check_free;
SYSCTL_INT(_hw_dmar, OID_AUTO, check_free, CTLFLAG_RWTUN,