From owner-svn-src-all@FreeBSD.ORG Tue Mar 18 16:41:33 2014 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 8776DCDD; Tue, 18 Mar 2014 16:41:33 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.freebsd.org (Postfix) with ESMTPS id 66E89C27; Tue, 18 Mar 2014 16:41:33 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.8/8.14.8) with ESMTP id s2IGfXE5038756; Tue, 18 Mar 2014 16:41:33 GMT (envelope-from kib@svn.freebsd.org) Received: (from kib@localhost) by svn.freebsd.org (8.14.8/8.14.8/Submit) id s2IGfW6a038753; Tue, 18 Mar 2014 16:41:32 GMT (envelope-from kib@svn.freebsd.org) Message-Id: <201403181641.s2IGfW6a038753@svn.freebsd.org> From: Konstantin Belousov Date: Tue, 18 Mar 2014 16:41:32 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r263306 - head/sys/x86/iommu X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.17 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 18 Mar 2014 16:41:33 -0000 Author: kib Date: Tue Mar 18 16:41:32 2014 New Revision: 263306 URL: http://svnweb.freebsd.org/changeset/base/263306 Log: 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 MFC after: 1 week Modified: head/sys/x86/iommu/busdma_dmar.c head/sys/x86/iommu/intel_ctx.c head/sys/x86/iommu/intel_dmar.h Modified: head/sys/x86/iommu/busdma_dmar.c ============================================================================== --- head/sys/x86/iommu/busdma_dmar.c Tue Mar 18 16:20:33 2014 (r263305) +++ head/sys/x86/iommu/busdma_dmar.c Tue Mar 18 16:41:32 2014 (r263306) @@ -47,6 +47,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -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 de 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 *d * 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 Modified: head/sys/x86/iommu/intel_ctx.c ============================================================================== --- head/sys/x86/iommu/intel_ctx.c Tue Mar 18 16:20:33 2014 (r263305) +++ head/sys/x86/iommu/intel_ctx.c Tue Mar 18 16:41:32 2014 (r263306) @@ -262,17 +262,15 @@ dmar_ctx_dtr(struct dmar_ctx *ctx, bool } 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, dev 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, dev 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); } Modified: head/sys/x86/iommu/intel_dmar.h ============================================================================== --- head/sys/x86/iommu/intel_dmar.h Tue Mar 18 16:20:33 2014 (r263305) +++ head/sys/x86/iommu/intel_dmar.h Tue Mar 18 16:41:32 2014 (r263306) @@ -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,