summaryrefslogtreecommitdiffstats
path: root/sys/x86/iommu
diff options
context:
space:
mode:
authorkib <kib@FreeBSD.org>2014-03-18 16:41:32 +0000
committerkib <kib@FreeBSD.org>2014-03-18 16:41:32 +0000
commit97d75577383259467c777e29235ab2de161ad571 (patch)
treea764bf4b33b07bc9622cd09e06e00fcadb324783 /sys/x86/iommu
parentf8f145010fff45759a61dab0a08b7b030271baa2 (diff)
downloadFreeBSD-src-97d75577383259467c777e29235ab2de161ad571.zip
FreeBSD-src-97d75577383259467c777e29235ab2de161ad571.tar.gz
Add support for the PCI(e)-PCI bridges to the Intel VT-d driver. The
bridge takes ownership of the transaction, so bsf of the requester is the bridge and not a device behind it. As result, code needs to walk the hierarchy up to use correct context. Note that PCIe->PCI-X bridges are not handled quite correctly since such bridges are allowed to only take ownership of some transactions. Also, weird but unrealistic cases of PCIe behind PCI bus are also not handled. Still, the patch provides significant step forward for the bridge handling. Submitted by: Jason Harmening <jason.harmening@gmail.com> MFC after: 1 week
Diffstat (limited to 'sys/x86/iommu')
-rw-r--r--sys/x86/iommu/busdma_dmar.c122
-rw-r--r--sys/x86/iommu/intel_ctx.c15
-rw-r--r--sys/x86/iommu/intel_dmar.h2
3 files changed, 122 insertions, 17 deletions
diff --git a/sys/x86/iommu/busdma_dmar.c b/sys/x86/iommu/busdma_dmar.c
index 9c57519..488c7bb 100644
--- a/sys/x86/iommu/busdma_dmar.c
+++ b/sys/x86/iommu/busdma_dmar.c
@@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$");
#include <sys/taskqueue.h>
#include <sys/tree.h>
#include <sys/uio.h>
+#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
@@ -69,15 +70,10 @@ __FBSDID("$FreeBSD$");
*/
static bool
-dmar_bus_dma_is_dev_disabled(device_t dev)
+dmar_bus_dma_is_dev_disabled(int domain, int bus, int slot, int func)
{
char str[128], *env;
- int domain, bus, slot, func;
- domain = pci_get_domain(dev);
- bus = pci_get_bus(dev);
- slot = pci_get_slot(dev);
- func = pci_get_function(dev);
snprintf(str, sizeof(str), "hw.busdma.pci%d.%d.%d.%d.bounce",
domain, bus, slot, func);
env = getenv(str);
@@ -87,11 +83,119 @@ dmar_bus_dma_is_dev_disabled(device_t dev)
return (true);
}
+/*
+ * Given original device, find the requester ID that will be seen by
+ * the DMAR unit and used for page table lookup. PCI bridges may take
+ * ownership of transactions from downstream devices, so it may not be
+ * the same as the BSF of the target device. In those cases, all
+ * devices downstream of the bridge must share a single mapping
+ * domain, and must collectively be assigned to use either DMAR or
+ * bounce mapping.
+ */
+static device_t
+dmar_get_requester(device_t dev, int *bus, int *slot, int *func)
+{
+ devclass_t pci_class;
+ device_t pci, pcib, requester;
+ int cap_offset;
+
+ pci_class = devclass_find("pci");
+ requester = dev;
+
+ *bus = pci_get_bus(dev);
+ *slot = pci_get_slot(dev);
+ *func = pci_get_function(dev);
+
+ /*
+ * Walk the bridge hierarchy from the target device to the
+ * host port to find the translating bridge nearest the DMAR
+ * unit.
+ */
+ for (;;) {
+ pci = device_get_parent(dev);
+ KASSERT(pci != NULL, ("NULL parent for pci%d:%d:%d:%d",
+ pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev),
+ pci_get_function(dev)));
+ KASSERT(device_get_devclass(pci) == pci_class,
+ ("Non-pci parent for pci%d:%d:%d:%d",
+ pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev),
+ pci_get_function(dev)));
+
+ pcib = device_get_parent(pci);
+ KASSERT(pcib != NULL, ("NULL bridge for pci%d:%d:%d:%d",
+ pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev),
+ pci_get_function(dev)));
+
+ /*
+ * The parent of our "bridge" isn't another PCI bus,
+ * so pcib isn't a PCI->PCI bridge but rather a host
+ * port, and the requester ID won't be translated
+ * further.
+ */
+ if (device_get_devclass(device_get_parent(pcib)) != pci_class)
+ break;
+
+ if (pci_find_cap(dev, PCIY_EXPRESS, &cap_offset) != 0) {
+ /*
+ * Device is not PCIe, it cannot be seen as a
+ * requester by DMAR unit.
+ */
+ requester = pcib;
+
+ /* Check whether the bus above is PCIe. */
+ if (pci_find_cap(pcib, PCIY_EXPRESS,
+ &cap_offset) == 0) {
+ /*
+ * The current device is not PCIe, but
+ * the bridge above it is. This is a
+ * PCIe->PCI bridge. Assume that the
+ * requester ID will be the secondary
+ * bus number with slot and function
+ * set to zero.
+ *
+ * XXX: Doesn't handle the case where
+ * the bridge is PCIe->PCI-X, and the
+ * bridge will only take ownership of
+ * requests in some cases. We should
+ * provide context entries with the
+ * same page tables for taken and
+ * non-taken transactions.
+ */
+ *bus = pci_get_bus(dev);
+ *slot = *func = 0;
+ } else {
+ /*
+ * Neither the device nor the bridge
+ * above it are PCIe. This is a
+ * conventional PCI->PCI bridge, which
+ * will use the bridge's BSF as the
+ * requester ID.
+ */
+ *bus = pci_get_bus(pcib);
+ *slot = pci_get_slot(pcib);
+ *func = pci_get_function(pcib);
+ }
+ }
+ /*
+ * Do not stop the loop even if the target device is
+ * PCIe, because it is possible (but unlikely) to have
+ * a PCI->PCIe bridge somewhere in the hierarchy.
+ */
+
+ dev = pcib;
+ }
+ return (requester);
+}
+
struct dmar_ctx *
dmar_instantiate_ctx(struct dmar_unit *dmar, device_t dev, bool rmrr)
{
+ device_t requester;
struct dmar_ctx *ctx;
bool disabled;
+ int bus, slot, func;
+
+ requester = dmar_get_requester(dev, &bus, &slot, &func);
/*
* If the user requested the IOMMU disabled for the device, we
@@ -100,11 +204,11 @@ dmar_instantiate_ctx(struct dmar_unit *dmar, device_t dev, bool rmrr)
* Instead provide the identity mapping for the device
* context.
*/
- disabled = dmar_bus_dma_is_dev_disabled(dev);
- ctx = dmar_get_ctx(dmar, dev, disabled, rmrr);
+ disabled = dmar_bus_dma_is_dev_disabled(pci_get_domain(dev), bus,
+ slot, func);
+ ctx = dmar_get_ctx(dmar, requester, bus, slot, func, disabled, rmrr);
if (ctx == NULL)
return (NULL);
- ctx->ctx_tag.owner = dev;
if (disabled) {
/*
* Keep the first reference on context, release the
diff --git a/sys/x86/iommu/intel_ctx.c b/sys/x86/iommu/intel_ctx.c
index c5a77b9..0b3adeb 100644
--- a/sys/x86/iommu/intel_ctx.c
+++ b/sys/x86/iommu/intel_ctx.c
@@ -262,17 +262,15 @@ dmar_ctx_dtr(struct dmar_ctx *ctx, bool gas_inited, bool pgtbl_inited)
}
struct dmar_ctx *
-dmar_get_ctx(struct dmar_unit *dmar, device_t dev, bool id_mapped, bool rmrr_init)
+dmar_get_ctx(struct dmar_unit *dmar, device_t dev, int bus, int slot, int func,
+ bool id_mapped, bool rmrr_init)
{
struct dmar_ctx *ctx, *ctx1;
dmar_ctx_entry_t *ctxp;
struct sf_buf *sf;
- int bus, slot, func, error, mgaw;
+ int error, mgaw;
bool enable;
- bus = pci_get_bus(dev);
- slot = pci_get_slot(dev);
- func = pci_get_function(dev);
enable = false;
TD_PREP_PINNED_ASSERT;
DMAR_LOCK(dmar);
@@ -356,6 +354,7 @@ dmar_get_ctx(struct dmar_unit *dmar, device_t dev, bool id_mapped, bool rmrr_ini
ctx = dmar_find_ctx_locked(dmar, bus, slot, func);
if (ctx == NULL) {
ctx = ctx1;
+ ctx->ctx_tag.owner = dev;
ctx->domain = alloc_unrl(dmar->domids);
if (ctx->domain == -1) {
DMAR_UNLOCK(dmar);
@@ -376,9 +375,11 @@ dmar_get_ctx(struct dmar_unit *dmar, device_t dev, bool id_mapped, bool rmrr_ini
LIST_INSERT_HEAD(&dmar->contexts, ctx, link);
ctx_id_entry_init(ctx, ctxp);
device_printf(dev,
- "dmar%d pci%d:%d:%d:%d domain %d mgaw %d agaw %d\n",
+ "dmar%d pci%d:%d:%d:%d domain %d mgaw %d "
+ "agaw %d %s-mapped\n",
dmar->unit, dmar->segment, bus, slot,
- func, ctx->domain, ctx->mgaw, ctx->agaw);
+ func, ctx->domain, ctx->mgaw, ctx->agaw,
+ id_mapped ? "id" : "re");
} else {
dmar_ctx_dtr(ctx1, true, true);
}
diff --git a/sys/x86/iommu/intel_dmar.h b/sys/x86/iommu/intel_dmar.h
index 994e5e1..0b68024 100644
--- a/sys/x86/iommu/intel_dmar.h
+++ b/sys/x86/iommu/intel_dmar.h
@@ -270,7 +270,7 @@ void ctx_free_pgtbl(struct dmar_ctx *ctx);
struct dmar_ctx *dmar_instantiate_ctx(struct dmar_unit *dmar, device_t dev,
bool rmrr);
struct dmar_ctx *dmar_get_ctx(struct dmar_unit *dmar, device_t dev,
- bool id_mapped, bool rmrr_init);
+ int bus, int slot, int func, bool id_mapped, bool rmrr_init);
void dmar_free_ctx_locked(struct dmar_unit *dmar, struct dmar_ctx *ctx);
void dmar_free_ctx(struct dmar_ctx *ctx);
struct dmar_ctx *dmar_find_ctx_locked(struct dmar_unit *dmar, int bus,
OpenPOWER on IntegriCloud