0a110d5b17
translation. In particular, despite IO-APICs only take 8bit apic id, IR translation structures accept 32bit APIC Id, which allows x2APIC mode to function properly. Extend msi_cpu of struct msi_intrsrc and io_cpu of ioapic_intsrc to full int from one byte. KPI of IR is isolated into the x86/iommu/iommu_intrmap.h, to avoid bringing all dmar headers into interrupt code. The non-PCI(e) devices which generate message interrupts on FSB require special handling. The HPET FSB interrupts are remapped, while DMAR interrupts are not. For each msi and ioapic interrupt source, the iommu cookie is added, which is in fact index of the IRE (interrupt remap entry) in the IR table. Cookie is made at the source allocation time, and then used at the map time to fill both IRE and device registers. The MSI address/data registers and IO-APIC redirection registers are programmed with the special values which are recognized by IR and used to restore the IRE index, to find proper delivery mode and target. Map all MSI interrupts in the block when msi_map() is called. Since an interrupt source setup and dismantle code are done in the non-sleepable context, flushing interrupt entries cache in the IR hardware, which is done async and ideally waits for the interrupt, requires busy-wait for queue to drain. The dmar_qi_wait_for_seq() is modified to take a boolean argument requesting busy-wait for the written sequence number instead of waiting for interrupt. Some interrupts are configured before IR is initialized, e.g. ACPI SCI. Add intr_reprogram() function to reprogram all already configured interrupts, and call it immediately before an IR unit is enabled. There is still a small window after the IO-APIC redirection entry is reprogrammed with cookie but before the unit is enabled, but to fix this properly, IR must be started much earlier. Add workarounds for 5500 and X58 northbridges, some revisions of which have severe flaws in handling IR. Use the same identification methods as employed by Linux. Review: https://reviews.freebsd.org/D1892 Reviewed by: neel Discussed with: jhb Tested by: glebius, pho (previous versions) Sponsored by: The FreeBSD Foundation MFC after: 3 weeks
1266 lines
32 KiB
C
1266 lines
32 KiB
C
/*-
|
|
* Copyright (c) 2013-2015 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by Konstantin Belousov <kib@FreeBSD.org>
|
|
* under sponsorship from the FreeBSD Foundation.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "opt_acpi.h"
|
|
#if defined(__amd64__)
|
|
#define DEV_APIC
|
|
#else
|
|
#include "opt_apic.h"
|
|
#endif
|
|
#include "opt_ddb.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/memdesc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/rwlock.h>
|
|
#include <sys/smp.h>
|
|
#include <sys/taskqueue.h>
|
|
#include <sys/tree.h>
|
|
#include <sys/vmem.h>
|
|
#include <machine/bus.h>
|
|
#include <contrib/dev/acpica/include/acpi.h>
|
|
#include <contrib/dev/acpica/include/accommon.h>
|
|
#include <dev/acpica/acpivar.h>
|
|
#include <vm/vm.h>
|
|
#include <vm/vm_extern.h>
|
|
#include <vm/vm_kern.h>
|
|
#include <vm/vm_object.h>
|
|
#include <vm/vm_page.h>
|
|
#include <vm/vm_pager.h>
|
|
#include <vm/vm_map.h>
|
|
#include <x86/include/busdma_impl.h>
|
|
#include <x86/iommu/intel_reg.h>
|
|
#include <x86/iommu/busdma_dmar.h>
|
|
#include <x86/iommu/intel_dmar.h>
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
#ifdef DEV_APIC
|
|
#include "pcib_if.h"
|
|
#endif
|
|
|
|
#define DMAR_FAULT_IRQ_RID 0
|
|
#define DMAR_QI_IRQ_RID 1
|
|
#define DMAR_REG_RID 2
|
|
|
|
static devclass_t dmar_devclass;
|
|
static device_t *dmar_devs;
|
|
static int dmar_devcnt;
|
|
|
|
typedef int (*dmar_iter_t)(ACPI_DMAR_HEADER *, void *);
|
|
|
|
static void
|
|
dmar_iterate_tbl(dmar_iter_t iter, void *arg)
|
|
{
|
|
ACPI_TABLE_DMAR *dmartbl;
|
|
ACPI_DMAR_HEADER *dmarh;
|
|
char *ptr, *ptrend;
|
|
ACPI_STATUS status;
|
|
|
|
status = AcpiGetTable(ACPI_SIG_DMAR, 1, (ACPI_TABLE_HEADER **)&dmartbl);
|
|
if (ACPI_FAILURE(status))
|
|
return;
|
|
ptr = (char *)dmartbl + sizeof(*dmartbl);
|
|
ptrend = (char *)dmartbl + dmartbl->Header.Length;
|
|
for (;;) {
|
|
if (ptr >= ptrend)
|
|
break;
|
|
dmarh = (ACPI_DMAR_HEADER *)ptr;
|
|
if (dmarh->Length <= 0) {
|
|
printf("dmar_identify: corrupted DMAR table, l %d\n",
|
|
dmarh->Length);
|
|
break;
|
|
}
|
|
ptr += dmarh->Length;
|
|
if (!iter(dmarh, arg))
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct find_iter_args {
|
|
int i;
|
|
ACPI_DMAR_HARDWARE_UNIT *res;
|
|
};
|
|
|
|
static int
|
|
dmar_find_iter(ACPI_DMAR_HEADER *dmarh, void *arg)
|
|
{
|
|
struct find_iter_args *fia;
|
|
|
|
if (dmarh->Type != ACPI_DMAR_TYPE_HARDWARE_UNIT)
|
|
return (1);
|
|
|
|
fia = arg;
|
|
if (fia->i == 0) {
|
|
fia->res = (ACPI_DMAR_HARDWARE_UNIT *)dmarh;
|
|
return (0);
|
|
}
|
|
fia->i--;
|
|
return (1);
|
|
}
|
|
|
|
static ACPI_DMAR_HARDWARE_UNIT *
|
|
dmar_find_by_index(int idx)
|
|
{
|
|
struct find_iter_args fia;
|
|
|
|
fia.i = idx;
|
|
fia.res = NULL;
|
|
dmar_iterate_tbl(dmar_find_iter, &fia);
|
|
return (fia.res);
|
|
}
|
|
|
|
static int
|
|
dmar_count_iter(ACPI_DMAR_HEADER *dmarh, void *arg)
|
|
{
|
|
|
|
if (dmarh->Type == ACPI_DMAR_TYPE_HARDWARE_UNIT)
|
|
dmar_devcnt++;
|
|
return (1);
|
|
}
|
|
|
|
static int dmar_enable = 0;
|
|
static void
|
|
dmar_identify(driver_t *driver, device_t parent)
|
|
{
|
|
ACPI_TABLE_DMAR *dmartbl;
|
|
ACPI_DMAR_HARDWARE_UNIT *dmarh;
|
|
ACPI_STATUS status;
|
|
int i, error;
|
|
|
|
if (acpi_disabled("dmar"))
|
|
return;
|
|
TUNABLE_INT_FETCH("hw.dmar.enable", &dmar_enable);
|
|
if (!dmar_enable)
|
|
return;
|
|
#ifdef INVARIANTS
|
|
TUNABLE_INT_FETCH("hw.dmar.check_free", &dmar_check_free);
|
|
#endif
|
|
TUNABLE_INT_FETCH("hw.dmar.match_verbose", &dmar_match_verbose);
|
|
status = AcpiGetTable(ACPI_SIG_DMAR, 1, (ACPI_TABLE_HEADER **)&dmartbl);
|
|
if (ACPI_FAILURE(status))
|
|
return;
|
|
haw = dmartbl->Width + 1;
|
|
if ((1ULL << (haw + 1)) > BUS_SPACE_MAXADDR)
|
|
dmar_high = BUS_SPACE_MAXADDR;
|
|
else
|
|
dmar_high = 1ULL << (haw + 1);
|
|
if (bootverbose) {
|
|
printf("DMAR HAW=%d flags=<%b>\n", dmartbl->Width,
|
|
(unsigned)dmartbl->Flags,
|
|
"\020\001INTR_REMAP\002X2APIC_OPT_OUT");
|
|
}
|
|
|
|
dmar_iterate_tbl(dmar_count_iter, NULL);
|
|
if (dmar_devcnt == 0)
|
|
return;
|
|
dmar_devs = malloc(sizeof(device_t) * dmar_devcnt, M_DEVBUF,
|
|
M_WAITOK | M_ZERO);
|
|
for (i = 0; i < dmar_devcnt; i++) {
|
|
dmarh = dmar_find_by_index(i);
|
|
if (dmarh == NULL) {
|
|
printf("dmar_identify: cannot find HWUNIT %d\n", i);
|
|
continue;
|
|
}
|
|
dmar_devs[i] = BUS_ADD_CHILD(parent, 1, "dmar", i);
|
|
if (dmar_devs[i] == NULL) {
|
|
printf("dmar_identify: cannot create instance %d\n", i);
|
|
continue;
|
|
}
|
|
error = bus_set_resource(dmar_devs[i], SYS_RES_MEMORY,
|
|
DMAR_REG_RID, dmarh->Address, PAGE_SIZE);
|
|
if (error != 0) {
|
|
printf(
|
|
"dmar%d: unable to alloc register window at 0x%08jx: error %d\n",
|
|
i, (uintmax_t)dmarh->Address, error);
|
|
device_delete_child(parent, dmar_devs[i]);
|
|
dmar_devs[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
dmar_probe(device_t dev)
|
|
{
|
|
|
|
if (acpi_get_handle(dev) != NULL)
|
|
return (ENXIO);
|
|
device_set_desc(dev, "DMA remap");
|
|
return (BUS_PROBE_NOWILDCARD);
|
|
}
|
|
|
|
static void
|
|
dmar_release_intr(device_t dev, struct dmar_unit *unit, int idx)
|
|
{
|
|
struct dmar_msi_data *dmd;
|
|
|
|
dmd = &unit->intrs[idx];
|
|
if (dmd->irq == -1)
|
|
return;
|
|
bus_teardown_intr(dev, dmd->irq_res, dmd->intr_handle);
|
|
bus_release_resource(dev, SYS_RES_IRQ, dmd->irq_rid, dmd->irq_res);
|
|
bus_delete_resource(dev, SYS_RES_IRQ, dmd->irq_rid);
|
|
PCIB_RELEASE_MSIX(device_get_parent(device_get_parent(dev)),
|
|
dev, dmd->irq);
|
|
dmd->irq = -1;
|
|
}
|
|
|
|
static void
|
|
dmar_release_resources(device_t dev, struct dmar_unit *unit)
|
|
{
|
|
int i;
|
|
|
|
dmar_fini_busdma(unit);
|
|
dmar_fini_irt(unit);
|
|
dmar_fini_qi(unit);
|
|
dmar_fini_fault_log(unit);
|
|
for (i = 0; i < DMAR_INTR_TOTAL; i++)
|
|
dmar_release_intr(dev, unit, i);
|
|
if (unit->regs != NULL) {
|
|
bus_deactivate_resource(dev, SYS_RES_MEMORY, unit->reg_rid,
|
|
unit->regs);
|
|
bus_release_resource(dev, SYS_RES_MEMORY, unit->reg_rid,
|
|
unit->regs);
|
|
unit->regs = NULL;
|
|
}
|
|
if (unit->domids != NULL) {
|
|
delete_unrhdr(unit->domids);
|
|
unit->domids = NULL;
|
|
}
|
|
if (unit->ctx_obj != NULL) {
|
|
vm_object_deallocate(unit->ctx_obj);
|
|
unit->ctx_obj = NULL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
dmar_alloc_irq(device_t dev, struct dmar_unit *unit, int idx)
|
|
{
|
|
device_t pcib;
|
|
struct dmar_msi_data *dmd;
|
|
uint64_t msi_addr;
|
|
uint32_t msi_data;
|
|
int error;
|
|
|
|
dmd = &unit->intrs[idx];
|
|
pcib = device_get_parent(device_get_parent(dev)); /* Really not pcib */
|
|
error = PCIB_ALLOC_MSIX(pcib, dev, &dmd->irq);
|
|
if (error != 0) {
|
|
device_printf(dev, "cannot allocate %s interrupt, %d\n",
|
|
dmd->name, error);
|
|
goto err1;
|
|
}
|
|
error = bus_set_resource(dev, SYS_RES_IRQ, dmd->irq_rid,
|
|
dmd->irq, 1);
|
|
if (error != 0) {
|
|
device_printf(dev, "cannot set %s interrupt resource, %d\n",
|
|
dmd->name, error);
|
|
goto err2;
|
|
}
|
|
dmd->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
|
|
&dmd->irq_rid, RF_ACTIVE);
|
|
if (dmd->irq_res == NULL) {
|
|
device_printf(dev,
|
|
"cannot allocate resource for %s interrupt\n", dmd->name);
|
|
error = ENXIO;
|
|
goto err3;
|
|
}
|
|
error = bus_setup_intr(dev, dmd->irq_res, INTR_TYPE_MISC,
|
|
dmd->handler, NULL, unit, &dmd->intr_handle);
|
|
if (error != 0) {
|
|
device_printf(dev, "cannot setup %s interrupt, %d\n",
|
|
dmd->name, error);
|
|
goto err4;
|
|
}
|
|
bus_describe_intr(dev, dmd->irq_res, dmd->intr_handle, dmd->name);
|
|
error = PCIB_MAP_MSI(pcib, dev, dmd->irq, &msi_addr, &msi_data);
|
|
if (error != 0) {
|
|
device_printf(dev, "cannot map %s interrupt, %d\n",
|
|
dmd->name, error);
|
|
goto err5;
|
|
}
|
|
dmar_write4(unit, dmd->msi_data_reg, msi_data);
|
|
dmar_write4(unit, dmd->msi_addr_reg, msi_addr);
|
|
/* Only for xAPIC mode */
|
|
dmar_write4(unit, dmd->msi_uaddr_reg, msi_addr >> 32);
|
|
return (0);
|
|
|
|
err5:
|
|
bus_teardown_intr(dev, dmd->irq_res, dmd->intr_handle);
|
|
err4:
|
|
bus_release_resource(dev, SYS_RES_IRQ, dmd->irq_rid, dmd->irq_res);
|
|
err3:
|
|
bus_delete_resource(dev, SYS_RES_IRQ, dmd->irq_rid);
|
|
err2:
|
|
PCIB_RELEASE_MSIX(pcib, dev, dmd->irq);
|
|
dmd->irq = -1;
|
|
err1:
|
|
return (error);
|
|
}
|
|
|
|
#ifdef DEV_APIC
|
|
static int
|
|
dmar_remap_intr(device_t dev, device_t child, u_int irq)
|
|
{
|
|
struct dmar_unit *unit;
|
|
struct dmar_msi_data *dmd;
|
|
uint64_t msi_addr;
|
|
uint32_t msi_data;
|
|
int i, error;
|
|
|
|
unit = device_get_softc(dev);
|
|
for (i = 0; i < DMAR_INTR_TOTAL; i++) {
|
|
dmd = &unit->intrs[i];
|
|
if (irq == dmd->irq) {
|
|
error = PCIB_MAP_MSI(device_get_parent(
|
|
device_get_parent(dev)),
|
|
dev, irq, &msi_addr, &msi_data);
|
|
if (error != 0)
|
|
return (error);
|
|
DMAR_LOCK(unit);
|
|
(dmd->disable_intr)(unit);
|
|
dmar_write4(unit, dmd->msi_data_reg, msi_data);
|
|
dmar_write4(unit, dmd->msi_addr_reg, msi_addr);
|
|
dmar_write4(unit, dmd->msi_uaddr_reg, msi_addr >> 32);
|
|
(dmd->enable_intr)(unit);
|
|
DMAR_UNLOCK(unit);
|
|
return (0);
|
|
}
|
|
}
|
|
return (ENOENT);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
dmar_print_caps(device_t dev, struct dmar_unit *unit,
|
|
ACPI_DMAR_HARDWARE_UNIT *dmaru)
|
|
{
|
|
uint32_t caphi, ecaphi;
|
|
|
|
device_printf(dev, "regs@0x%08jx, ver=%d.%d, seg=%d, flags=<%b>\n",
|
|
(uintmax_t)dmaru->Address, DMAR_MAJOR_VER(unit->hw_ver),
|
|
DMAR_MINOR_VER(unit->hw_ver), dmaru->Segment,
|
|
dmaru->Flags, "\020\001INCLUDE_ALL_PCI");
|
|
caphi = unit->hw_cap >> 32;
|
|
device_printf(dev, "cap=%b,", (u_int)unit->hw_cap,
|
|
"\020\004AFL\005WBF\006PLMR\007PHMR\010CM\027ZLR\030ISOCH");
|
|
printf("%b, ", caphi, "\020\010PSI\027DWD\030DRD\031FL1GP\034PSI");
|
|
printf("ndoms=%d, sagaw=%d, mgaw=%d, fro=%d, nfr=%d, superp=%d",
|
|
DMAR_CAP_ND(unit->hw_cap), DMAR_CAP_SAGAW(unit->hw_cap),
|
|
DMAR_CAP_MGAW(unit->hw_cap), DMAR_CAP_FRO(unit->hw_cap),
|
|
DMAR_CAP_NFR(unit->hw_cap), DMAR_CAP_SPS(unit->hw_cap));
|
|
if ((unit->hw_cap & DMAR_CAP_PSI) != 0)
|
|
printf(", mamv=%d", DMAR_CAP_MAMV(unit->hw_cap));
|
|
printf("\n");
|
|
ecaphi = unit->hw_ecap >> 32;
|
|
device_printf(dev, "ecap=%b,", (u_int)unit->hw_ecap,
|
|
"\020\001C\002QI\003DI\004IR\005EIM\007PT\010SC\031ECS\032MTS"
|
|
"\033NEST\034DIS\035PASID\036PRS\037ERS\040SRS");
|
|
printf("%b, ", ecaphi, "\020\002NWFS\003EAFS");
|
|
printf("mhmw=%d, iro=%d\n", DMAR_ECAP_MHMV(unit->hw_ecap),
|
|
DMAR_ECAP_IRO(unit->hw_ecap));
|
|
}
|
|
|
|
static int
|
|
dmar_attach(device_t dev)
|
|
{
|
|
struct dmar_unit *unit;
|
|
ACPI_DMAR_HARDWARE_UNIT *dmaru;
|
|
int i, error;
|
|
|
|
unit = device_get_softc(dev);
|
|
unit->dev = dev;
|
|
unit->unit = device_get_unit(dev);
|
|
dmaru = dmar_find_by_index(unit->unit);
|
|
if (dmaru == NULL)
|
|
return (EINVAL);
|
|
unit->segment = dmaru->Segment;
|
|
unit->base = dmaru->Address;
|
|
unit->reg_rid = DMAR_REG_RID;
|
|
unit->regs = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
|
|
&unit->reg_rid, RF_ACTIVE);
|
|
if (unit->regs == NULL) {
|
|
device_printf(dev, "cannot allocate register window\n");
|
|
return (ENOMEM);
|
|
}
|
|
unit->hw_ver = dmar_read4(unit, DMAR_VER_REG);
|
|
unit->hw_cap = dmar_read8(unit, DMAR_CAP_REG);
|
|
unit->hw_ecap = dmar_read8(unit, DMAR_ECAP_REG);
|
|
if (bootverbose)
|
|
dmar_print_caps(dev, unit, dmaru);
|
|
dmar_quirks_post_ident(unit);
|
|
|
|
for (i = 0; i < DMAR_INTR_TOTAL; i++)
|
|
unit->intrs[i].irq = -1;
|
|
|
|
unit->intrs[DMAR_INTR_FAULT].name = "fault";
|
|
unit->intrs[DMAR_INTR_FAULT].irq_rid = DMAR_FAULT_IRQ_RID;
|
|
unit->intrs[DMAR_INTR_FAULT].handler = dmar_fault_intr;
|
|
unit->intrs[DMAR_INTR_FAULT].msi_data_reg = DMAR_FEDATA_REG;
|
|
unit->intrs[DMAR_INTR_FAULT].msi_addr_reg = DMAR_FEADDR_REG;
|
|
unit->intrs[DMAR_INTR_FAULT].msi_uaddr_reg = DMAR_FEUADDR_REG;
|
|
unit->intrs[DMAR_INTR_FAULT].enable_intr = dmar_enable_fault_intr;
|
|
unit->intrs[DMAR_INTR_FAULT].disable_intr = dmar_disable_fault_intr;
|
|
error = dmar_alloc_irq(dev, unit, DMAR_INTR_FAULT);
|
|
if (error != 0) {
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
if (DMAR_HAS_QI(unit)) {
|
|
unit->intrs[DMAR_INTR_QI].name = "qi";
|
|
unit->intrs[DMAR_INTR_QI].irq_rid = DMAR_QI_IRQ_RID;
|
|
unit->intrs[DMAR_INTR_QI].handler = dmar_qi_intr;
|
|
unit->intrs[DMAR_INTR_QI].msi_data_reg = DMAR_IEDATA_REG;
|
|
unit->intrs[DMAR_INTR_QI].msi_addr_reg = DMAR_IEADDR_REG;
|
|
unit->intrs[DMAR_INTR_QI].msi_uaddr_reg = DMAR_IEUADDR_REG;
|
|
unit->intrs[DMAR_INTR_QI].enable_intr = dmar_enable_qi_intr;
|
|
unit->intrs[DMAR_INTR_QI].disable_intr = dmar_disable_qi_intr;
|
|
error = dmar_alloc_irq(dev, unit, DMAR_INTR_QI);
|
|
if (error != 0) {
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
}
|
|
|
|
mtx_init(&unit->lock, "dmarhw", NULL, MTX_DEF);
|
|
unit->domids = new_unrhdr(0, dmar_nd2mask(DMAR_CAP_ND(unit->hw_cap)),
|
|
&unit->lock);
|
|
|
|
/*
|
|
* 9.2 "Context Entry":
|
|
* When Caching Mode (CM) field is reported as Set, the
|
|
* domain-id value of zero is architecturally reserved.
|
|
* Software must not use domain-id value of zero
|
|
* when CM is Set.
|
|
*/
|
|
if ((unit->hw_cap & DMAR_CAP_CM) != 0)
|
|
alloc_unr_specific(unit->domids, 0);
|
|
|
|
unit->ctx_obj = vm_pager_allocate(OBJT_PHYS, NULL, IDX_TO_OFF(1 +
|
|
DMAR_CTX_CNT), 0, 0, NULL);
|
|
|
|
/*
|
|
* Allocate and load the root entry table pointer. Enable the
|
|
* address translation after the required invalidations are
|
|
* done.
|
|
*/
|
|
dmar_pgalloc(unit->ctx_obj, 0, DMAR_PGF_WAITOK | DMAR_PGF_ZERO);
|
|
DMAR_LOCK(unit);
|
|
error = dmar_load_root_entry_ptr(unit);
|
|
if (error != 0) {
|
|
DMAR_UNLOCK(unit);
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
error = dmar_inv_ctx_glob(unit);
|
|
if (error != 0) {
|
|
DMAR_UNLOCK(unit);
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
if ((unit->hw_ecap & DMAR_ECAP_DI) != 0) {
|
|
error = dmar_inv_iotlb_glob(unit);
|
|
if (error != 0) {
|
|
DMAR_UNLOCK(unit);
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
}
|
|
|
|
DMAR_UNLOCK(unit);
|
|
error = dmar_init_fault_log(unit);
|
|
if (error != 0) {
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
error = dmar_init_qi(unit);
|
|
if (error != 0) {
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
error = dmar_init_irt(unit);
|
|
if (error != 0) {
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
error = dmar_init_busdma(unit);
|
|
if (error != 0) {
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
|
|
#ifdef NOTYET
|
|
DMAR_LOCK(unit);
|
|
error = dmar_enable_translation(unit);
|
|
if (error != 0) {
|
|
DMAR_UNLOCK(unit);
|
|
dmar_release_resources(dev, unit);
|
|
return (error);
|
|
}
|
|
DMAR_UNLOCK(unit);
|
|
#endif
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
dmar_detach(device_t dev)
|
|
{
|
|
|
|
return (EBUSY);
|
|
}
|
|
|
|
static int
|
|
dmar_suspend(device_t dev)
|
|
{
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
dmar_resume(device_t dev)
|
|
{
|
|
|
|
/* XXXKIB */
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t dmar_methods[] = {
|
|
DEVMETHOD(device_identify, dmar_identify),
|
|
DEVMETHOD(device_probe, dmar_probe),
|
|
DEVMETHOD(device_attach, dmar_attach),
|
|
DEVMETHOD(device_detach, dmar_detach),
|
|
DEVMETHOD(device_suspend, dmar_suspend),
|
|
DEVMETHOD(device_resume, dmar_resume),
|
|
#ifdef DEV_APIC
|
|
DEVMETHOD(bus_remap_intr, dmar_remap_intr),
|
|
#endif
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t dmar_driver = {
|
|
"dmar",
|
|
dmar_methods,
|
|
sizeof(struct dmar_unit),
|
|
};
|
|
|
|
DRIVER_MODULE(dmar, acpi, dmar_driver, dmar_devclass, 0, 0);
|
|
MODULE_DEPEND(dmar, acpi, 1, 1, 1);
|
|
|
|
static void
|
|
dmar_print_path(device_t dev, const char *banner, int busno, int depth,
|
|
const ACPI_DMAR_PCI_PATH *path)
|
|
{
|
|
int i;
|
|
|
|
device_printf(dev, "%s [%d, ", banner, busno);
|
|
for (i = 0; i < depth; i++) {
|
|
if (i != 0)
|
|
printf(", ");
|
|
printf("(%d, %d)", path[i].Device, path[i].Function);
|
|
}
|
|
printf("]\n");
|
|
}
|
|
|
|
static int
|
|
dmar_dev_depth(device_t child)
|
|
{
|
|
devclass_t pci_class;
|
|
device_t bus, pcib;
|
|
int depth;
|
|
|
|
pci_class = devclass_find("pci");
|
|
for (depth = 1; ; depth++) {
|
|
bus = device_get_parent(child);
|
|
pcib = device_get_parent(bus);
|
|
if (device_get_devclass(device_get_parent(pcib)) !=
|
|
pci_class)
|
|
return (depth);
|
|
child = pcib;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dmar_dev_path(device_t child, int *busno, ACPI_DMAR_PCI_PATH *path, int depth)
|
|
{
|
|
devclass_t pci_class;
|
|
device_t bus, pcib;
|
|
|
|
pci_class = devclass_find("pci");
|
|
for (depth--; depth != -1; depth--) {
|
|
path[depth].Device = pci_get_slot(child);
|
|
path[depth].Function = pci_get_function(child);
|
|
bus = device_get_parent(child);
|
|
pcib = device_get_parent(bus);
|
|
if (device_get_devclass(device_get_parent(pcib)) !=
|
|
pci_class) {
|
|
/* reached a host bridge */
|
|
*busno = pcib_get_bus(bus);
|
|
return;
|
|
}
|
|
child = pcib;
|
|
}
|
|
panic("wrong depth");
|
|
}
|
|
|
|
static int
|
|
dmar_match_pathes(int busno1, const ACPI_DMAR_PCI_PATH *path1, int depth1,
|
|
int busno2, const ACPI_DMAR_PCI_PATH *path2, int depth2,
|
|
enum AcpiDmarScopeType scope_type)
|
|
{
|
|
int i, depth;
|
|
|
|
if (busno1 != busno2)
|
|
return (0);
|
|
if (scope_type == ACPI_DMAR_SCOPE_TYPE_ENDPOINT && depth1 != depth2)
|
|
return (0);
|
|
depth = depth1;
|
|
if (depth2 < depth)
|
|
depth = depth2;
|
|
for (i = 0; i < depth; i++) {
|
|
if (path1[i].Device != path2[i].Device ||
|
|
path1[i].Function != path2[i].Function)
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
dmar_match_devscope(ACPI_DMAR_DEVICE_SCOPE *devscope, device_t dev,
|
|
int dev_busno, const ACPI_DMAR_PCI_PATH *dev_path, int dev_path_len)
|
|
{
|
|
ACPI_DMAR_PCI_PATH *path;
|
|
int path_len;
|
|
|
|
if (devscope->Length < sizeof(*devscope)) {
|
|
printf("dmar_find: corrupted DMAR table, dl %d\n",
|
|
devscope->Length);
|
|
return (-1);
|
|
}
|
|
if (devscope->EntryType != ACPI_DMAR_SCOPE_TYPE_ENDPOINT &&
|
|
devscope->EntryType != ACPI_DMAR_SCOPE_TYPE_BRIDGE)
|
|
return (0);
|
|
path_len = devscope->Length - sizeof(*devscope);
|
|
if (path_len % 2 != 0) {
|
|
printf("dmar_find_bsf: corrupted DMAR table, dl %d\n",
|
|
devscope->Length);
|
|
return (-1);
|
|
}
|
|
path_len /= 2;
|
|
path = (ACPI_DMAR_PCI_PATH *)(devscope + 1);
|
|
if (path_len == 0) {
|
|
printf("dmar_find: corrupted DMAR table, dl %d\n",
|
|
devscope->Length);
|
|
return (-1);
|
|
}
|
|
if (dmar_match_verbose)
|
|
dmar_print_path(dev, "DMAR", devscope->Bus, path_len, path);
|
|
|
|
return (dmar_match_pathes(devscope->Bus, path, path_len, dev_busno,
|
|
dev_path, dev_path_len, devscope->EntryType));
|
|
}
|
|
|
|
struct dmar_unit *
|
|
dmar_find(device_t dev)
|
|
{
|
|
device_t dmar_dev;
|
|
ACPI_DMAR_HARDWARE_UNIT *dmarh;
|
|
ACPI_DMAR_DEVICE_SCOPE *devscope;
|
|
char *ptr, *ptrend;
|
|
int i, match, dev_domain, dev_busno, dev_path_len;
|
|
|
|
dmar_dev = NULL;
|
|
dev_domain = pci_get_domain(dev);
|
|
dev_path_len = dmar_dev_depth(dev);
|
|
ACPI_DMAR_PCI_PATH dev_path[dev_path_len];
|
|
dmar_dev_path(dev, &dev_busno, dev_path, dev_path_len);
|
|
if (dmar_match_verbose)
|
|
dmar_print_path(dev, "PCI", dev_busno, dev_path_len, dev_path);
|
|
|
|
for (i = 0; i < dmar_devcnt; i++) {
|
|
if (dmar_devs[i] == NULL)
|
|
continue;
|
|
dmarh = dmar_find_by_index(i);
|
|
if (dmarh == NULL)
|
|
continue;
|
|
if (dmarh->Segment != dev_domain)
|
|
continue;
|
|
if ((dmarh->Flags & ACPI_DMAR_INCLUDE_ALL) != 0) {
|
|
dmar_dev = dmar_devs[i];
|
|
if (dmar_match_verbose) {
|
|
device_printf(dev,
|
|
"pci%d:%d:%d:%d matched dmar%d INCLUDE_ALL\n",
|
|
dev_domain, pci_get_bus(dev),
|
|
pci_get_slot(dev),
|
|
pci_get_function(dev),
|
|
((struct dmar_unit *)device_get_softc(
|
|
dmar_dev))->unit);
|
|
}
|
|
goto found;
|
|
}
|
|
ptr = (char *)dmarh + sizeof(*dmarh);
|
|
ptrend = (char *)dmarh + dmarh->Header.Length;
|
|
for (;;) {
|
|
if (ptr >= ptrend)
|
|
break;
|
|
devscope = (ACPI_DMAR_DEVICE_SCOPE *)ptr;
|
|
ptr += devscope->Length;
|
|
if (dmar_match_verbose) {
|
|
device_printf(dev,
|
|
"pci%d:%d:%d:%d matching dmar%d\n",
|
|
dev_domain, pci_get_bus(dev),
|
|
pci_get_slot(dev),
|
|
pci_get_function(dev),
|
|
((struct dmar_unit *)device_get_softc(
|
|
dmar_devs[i]))->unit);
|
|
}
|
|
match = dmar_match_devscope(devscope, dev, dev_busno,
|
|
dev_path, dev_path_len);
|
|
if (dmar_match_verbose) {
|
|
if (match == -1)
|
|
printf("table error\n");
|
|
else if (match == 0)
|
|
printf("not matched\n");
|
|
else
|
|
printf("matched\n");
|
|
}
|
|
if (match == -1)
|
|
return (NULL);
|
|
else if (match == 1) {
|
|
dmar_dev = dmar_devs[i];
|
|
goto found;
|
|
}
|
|
}
|
|
}
|
|
return (NULL);
|
|
found:
|
|
return (device_get_softc(dmar_dev));
|
|
}
|
|
|
|
static struct dmar_unit *
|
|
dmar_find_nonpci(u_int id, u_int entry_type, uint16_t *rid)
|
|
{
|
|
device_t dmar_dev;
|
|
struct dmar_unit *unit;
|
|
ACPI_DMAR_HARDWARE_UNIT *dmarh;
|
|
ACPI_DMAR_DEVICE_SCOPE *devscope;
|
|
ACPI_DMAR_PCI_PATH *path;
|
|
char *ptr, *ptrend;
|
|
int i;
|
|
|
|
for (i = 0; i < dmar_devcnt; i++) {
|
|
dmar_dev = dmar_devs[i];
|
|
if (dmar_dev == NULL)
|
|
continue;
|
|
unit = (struct dmar_unit *)device_get_softc(dmar_dev);
|
|
dmarh = dmar_find_by_index(i);
|
|
if (dmarh == NULL)
|
|
continue;
|
|
ptr = (char *)dmarh + sizeof(*dmarh);
|
|
ptrend = (char *)dmarh + dmarh->Header.Length;
|
|
for (;;) {
|
|
if (ptr >= ptrend)
|
|
break;
|
|
devscope = (ACPI_DMAR_DEVICE_SCOPE *)ptr;
|
|
ptr += devscope->Length;
|
|
if (devscope->EntryType != entry_type)
|
|
continue;
|
|
if (devscope->EnumerationId != id)
|
|
continue;
|
|
if (devscope->Length - sizeof(ACPI_DMAR_DEVICE_SCOPE)
|
|
== 2) {
|
|
if (rid != NULL) {
|
|
path = (ACPI_DMAR_PCI_PATH *)
|
|
(devscope + 1);
|
|
*rid = PCI_RID(devscope->Bus,
|
|
path->Device, path->Function);
|
|
}
|
|
return (unit);
|
|
} else {
|
|
/* XXXKIB */
|
|
printf(
|
|
"dmar_find_nonpci: id %d type %d path length != 2\n",
|
|
id, entry_type);
|
|
}
|
|
}
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
|
|
struct dmar_unit *
|
|
dmar_find_hpet(device_t dev, uint16_t *rid)
|
|
{
|
|
ACPI_HANDLE handle;
|
|
uint32_t hpet_id;
|
|
|
|
handle = acpi_get_handle(dev);
|
|
if (ACPI_FAILURE(acpi_GetInteger(handle, "_UID", &hpet_id)))
|
|
return (NULL);
|
|
return (dmar_find_nonpci(hpet_id, ACPI_DMAR_SCOPE_TYPE_HPET, rid));
|
|
}
|
|
|
|
struct dmar_unit *
|
|
dmar_find_ioapic(u_int apic_id, uint16_t *rid)
|
|
{
|
|
|
|
return (dmar_find_nonpci(apic_id, ACPI_DMAR_SCOPE_TYPE_IOAPIC, rid));
|
|
}
|
|
|
|
struct rmrr_iter_args {
|
|
struct dmar_ctx *ctx;
|
|
device_t dev;
|
|
int dev_domain;
|
|
int dev_busno;
|
|
ACPI_DMAR_PCI_PATH *dev_path;
|
|
int dev_path_len;
|
|
struct dmar_map_entries_tailq *rmrr_entries;
|
|
};
|
|
|
|
static int
|
|
dmar_rmrr_iter(ACPI_DMAR_HEADER *dmarh, void *arg)
|
|
{
|
|
struct rmrr_iter_args *ria;
|
|
ACPI_DMAR_RESERVED_MEMORY *resmem;
|
|
ACPI_DMAR_DEVICE_SCOPE *devscope;
|
|
struct dmar_map_entry *entry;
|
|
char *ptr, *ptrend;
|
|
int match;
|
|
|
|
if (dmarh->Type != ACPI_DMAR_TYPE_RESERVED_MEMORY)
|
|
return (1);
|
|
|
|
ria = arg;
|
|
resmem = (ACPI_DMAR_RESERVED_MEMORY *)dmarh;
|
|
if (dmar_match_verbose) {
|
|
printf("RMRR [%jx,%jx] segment %d\n",
|
|
(uintmax_t)resmem->BaseAddress,
|
|
(uintmax_t)resmem->EndAddress,
|
|
resmem->Segment);
|
|
}
|
|
if (resmem->Segment != ria->dev_domain)
|
|
return (1);
|
|
|
|
ptr = (char *)resmem + sizeof(*resmem);
|
|
ptrend = (char *)resmem + resmem->Header.Length;
|
|
for (;;) {
|
|
if (ptr >= ptrend)
|
|
break;
|
|
devscope = (ACPI_DMAR_DEVICE_SCOPE *)ptr;
|
|
ptr += devscope->Length;
|
|
match = dmar_match_devscope(devscope, ria->dev, ria->dev_busno,
|
|
ria->dev_path, ria->dev_path_len);
|
|
if (match == 1) {
|
|
if (dmar_match_verbose)
|
|
printf("matched\n");
|
|
entry = dmar_gas_alloc_entry(ria->ctx, DMAR_PGF_WAITOK);
|
|
entry->start = resmem->BaseAddress;
|
|
/* The RMRR entry end address is inclusive. */
|
|
entry->end = resmem->EndAddress;
|
|
TAILQ_INSERT_TAIL(ria->rmrr_entries, entry,
|
|
unroll_link);
|
|
} else if (dmar_match_verbose) {
|
|
printf("not matched, err %d\n", match);
|
|
}
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
dmar_ctx_parse_rmrr(struct dmar_ctx *ctx, device_t dev,
|
|
struct dmar_map_entries_tailq *rmrr_entries)
|
|
{
|
|
struct rmrr_iter_args ria;
|
|
|
|
ria.dev_domain = pci_get_domain(dev);
|
|
ria.dev_path_len = dmar_dev_depth(dev);
|
|
ACPI_DMAR_PCI_PATH dev_path[ria.dev_path_len];
|
|
dmar_dev_path(dev, &ria.dev_busno, dev_path, ria.dev_path_len);
|
|
|
|
if (dmar_match_verbose) {
|
|
device_printf(dev, "parsing RMRR entries for ");
|
|
dmar_print_path(dev, "PCI", ria.dev_busno, ria.dev_path_len,
|
|
dev_path);
|
|
}
|
|
|
|
ria.ctx = ctx;
|
|
ria.dev = dev;
|
|
ria.dev_path = dev_path;
|
|
ria.rmrr_entries = rmrr_entries;
|
|
dmar_iterate_tbl(dmar_rmrr_iter, &ria);
|
|
}
|
|
|
|
struct inst_rmrr_iter_args {
|
|
struct dmar_unit *dmar;
|
|
};
|
|
|
|
static device_t
|
|
dmar_path_dev(int segment, int path_len, int busno,
|
|
const ACPI_DMAR_PCI_PATH *path)
|
|
{
|
|
devclass_t pci_class;
|
|
device_t bus, pcib, dev;
|
|
int i;
|
|
|
|
pci_class = devclass_find("pci");
|
|
dev = NULL;
|
|
for (i = 0; i < path_len; i++, path++) {
|
|
dev = pci_find_dbsf(segment, busno, path->Device,
|
|
path->Function);
|
|
if (dev == NULL)
|
|
break;
|
|
if (i != path_len - 1) {
|
|
bus = device_get_parent(dev);
|
|
pcib = device_get_parent(bus);
|
|
if (device_get_devclass(device_get_parent(pcib)) !=
|
|
pci_class)
|
|
return (NULL);
|
|
}
|
|
busno = pcib_get_bus(dev);
|
|
}
|
|
return (dev);
|
|
}
|
|
|
|
static int
|
|
dmar_inst_rmrr_iter(ACPI_DMAR_HEADER *dmarh, void *arg)
|
|
{
|
|
const ACPI_DMAR_RESERVED_MEMORY *resmem;
|
|
const ACPI_DMAR_DEVICE_SCOPE *devscope;
|
|
struct inst_rmrr_iter_args *iria;
|
|
const char *ptr, *ptrend;
|
|
struct dmar_unit *dev_dmar;
|
|
device_t dev;
|
|
|
|
if (dmarh->Type != ACPI_DMAR_TYPE_RESERVED_MEMORY)
|
|
return (1);
|
|
|
|
iria = arg;
|
|
resmem = (ACPI_DMAR_RESERVED_MEMORY *)dmarh;
|
|
if (resmem->Segment != iria->dmar->segment)
|
|
return (1);
|
|
if (dmar_match_verbose) {
|
|
printf("dmar%d: RMRR [%jx,%jx]\n", iria->dmar->unit,
|
|
(uintmax_t)resmem->BaseAddress,
|
|
(uintmax_t)resmem->EndAddress);
|
|
}
|
|
|
|
ptr = (const char *)resmem + sizeof(*resmem);
|
|
ptrend = (const char *)resmem + resmem->Header.Length;
|
|
for (;;) {
|
|
if (ptr >= ptrend)
|
|
break;
|
|
devscope = (const ACPI_DMAR_DEVICE_SCOPE *)ptr;
|
|
ptr += devscope->Length;
|
|
/* XXXKIB bridge */
|
|
if (devscope->EntryType != ACPI_DMAR_SCOPE_TYPE_ENDPOINT)
|
|
continue;
|
|
if (dmar_match_verbose) {
|
|
dmar_print_path(iria->dmar->dev, "RMRR scope",
|
|
devscope->Bus, (devscope->Length -
|
|
sizeof(ACPI_DMAR_DEVICE_SCOPE)) / 2,
|
|
(const ACPI_DMAR_PCI_PATH *)(devscope + 1));
|
|
}
|
|
dev = dmar_path_dev(resmem->Segment, (devscope->Length -
|
|
sizeof(ACPI_DMAR_DEVICE_SCOPE)) / 2, devscope->Bus,
|
|
(const ACPI_DMAR_PCI_PATH *)(devscope + 1));
|
|
if (dev == NULL) {
|
|
if (dmar_match_verbose)
|
|
printf("null dev\n");
|
|
continue;
|
|
}
|
|
dev_dmar = dmar_find(dev);
|
|
if (dev_dmar != iria->dmar) {
|
|
if (dmar_match_verbose) {
|
|
printf("dmar%d matched, skipping\n",
|
|
dev_dmar->unit);
|
|
}
|
|
continue;
|
|
}
|
|
if (dmar_match_verbose)
|
|
printf("matched, instantiating RMRR context\n");
|
|
dmar_instantiate_ctx(iria->dmar, dev, true);
|
|
}
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
/*
|
|
* Pre-create all contexts for the DMAR which have RMRR entries.
|
|
*/
|
|
int
|
|
dmar_instantiate_rmrr_ctxs(struct dmar_unit *dmar)
|
|
{
|
|
struct inst_rmrr_iter_args iria;
|
|
int error;
|
|
|
|
if (!dmar_barrier_enter(dmar, DMAR_BARRIER_RMRR))
|
|
return (0);
|
|
|
|
error = 0;
|
|
iria.dmar = dmar;
|
|
if (dmar_match_verbose)
|
|
printf("dmar%d: instantiating RMRR contexts\n", dmar->unit);
|
|
dmar_iterate_tbl(dmar_inst_rmrr_iter, &iria);
|
|
DMAR_LOCK(dmar);
|
|
if (!LIST_EMPTY(&dmar->contexts)) {
|
|
KASSERT((dmar->hw_gcmd & DMAR_GCMD_TE) == 0,
|
|
("dmar%d: RMRR not handled but translation is already enabled",
|
|
dmar->unit));
|
|
error = dmar_enable_translation(dmar);
|
|
}
|
|
dmar_barrier_exit(dmar, DMAR_BARRIER_RMRR);
|
|
return (error);
|
|
}
|
|
|
|
#ifdef DDB
|
|
#include <ddb/ddb.h>
|
|
#include <ddb/db_lex.h>
|
|
|
|
static void
|
|
dmar_print_ctx_entry(const struct dmar_map_entry *entry)
|
|
{
|
|
struct dmar_map_entry *l, *r;
|
|
|
|
db_printf(
|
|
" start %jx end %jx free_after %jx free_down %jx flags %x ",
|
|
entry->start, entry->end, entry->free_after, entry->free_down,
|
|
entry->flags);
|
|
db_printf("left ");
|
|
l = RB_LEFT(entry, rb_entry);
|
|
if (l == NULL)
|
|
db_printf("NULL ");
|
|
else
|
|
db_printf("%jx ", l->start);
|
|
db_printf("right ");
|
|
r = RB_RIGHT(entry, rb_entry);
|
|
if (r == NULL)
|
|
db_printf("NULL");
|
|
else
|
|
db_printf("%jx", r->start);
|
|
db_printf("\n");
|
|
}
|
|
|
|
static void
|
|
dmar_print_ctx(struct dmar_ctx *ctx, bool show_mappings)
|
|
{
|
|
struct dmar_map_entry *entry;
|
|
|
|
db_printf(
|
|
" @%p pci%d:%d:%d dom %d mgaw %d agaw %d pglvl %d end %jx\n"
|
|
" refs %d flags %x pgobj %p map_ents %u loads %lu unloads %lu\n",
|
|
ctx, pci_get_bus(ctx->ctx_tag.owner),
|
|
pci_get_slot(ctx->ctx_tag.owner),
|
|
pci_get_function(ctx->ctx_tag.owner), ctx->domain, ctx->mgaw,
|
|
ctx->agaw, ctx->pglvl, (uintmax_t)ctx->end, ctx->refs,
|
|
ctx->flags, ctx->pgtbl_obj, ctx->entries_cnt, ctx->loads,
|
|
ctx->unloads);
|
|
if (!show_mappings)
|
|
return;
|
|
db_printf(" mapped:\n");
|
|
RB_FOREACH(entry, dmar_gas_entries_tree, &ctx->rb_root) {
|
|
dmar_print_ctx_entry(entry);
|
|
if (db_pager_quit)
|
|
break;
|
|
}
|
|
if (db_pager_quit)
|
|
return;
|
|
db_printf(" unloading:\n");
|
|
TAILQ_FOREACH(entry, &ctx->unload_entries, dmamap_link) {
|
|
dmar_print_ctx_entry(entry);
|
|
if (db_pager_quit)
|
|
break;
|
|
}
|
|
}
|
|
|
|
DB_FUNC(dmar_ctx, db_dmar_print_ctx, db_show_table, CS_OWN, NULL)
|
|
{
|
|
struct dmar_unit *unit;
|
|
struct dmar_ctx *ctx;
|
|
bool show_mappings, valid;
|
|
int domain, bus, device, function, i, t;
|
|
db_expr_t radix;
|
|
|
|
valid = false;
|
|
radix = db_radix;
|
|
db_radix = 10;
|
|
t = db_read_token();
|
|
if (t == tSLASH) {
|
|
t = db_read_token();
|
|
if (t != tIDENT) {
|
|
db_printf("Bad modifier\n");
|
|
db_radix = radix;
|
|
db_skip_to_eol();
|
|
return;
|
|
}
|
|
show_mappings = strchr(db_tok_string, 'm') != NULL;
|
|
t = db_read_token();
|
|
} else {
|
|
show_mappings = false;
|
|
}
|
|
if (t == tNUMBER) {
|
|
domain = db_tok_number;
|
|
t = db_read_token();
|
|
if (t == tNUMBER) {
|
|
bus = db_tok_number;
|
|
t = db_read_token();
|
|
if (t == tNUMBER) {
|
|
device = db_tok_number;
|
|
t = db_read_token();
|
|
if (t == tNUMBER) {
|
|
function = db_tok_number;
|
|
valid = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
db_radix = radix;
|
|
db_skip_to_eol();
|
|
if (!valid) {
|
|
db_printf("usage: show dmar_ctx [/m] "
|
|
"<domain> <bus> <device> <func>\n");
|
|
return;
|
|
}
|
|
for (i = 0; i < dmar_devcnt; i++) {
|
|
unit = device_get_softc(dmar_devs[i]);
|
|
LIST_FOREACH(ctx, &unit->contexts, link) {
|
|
if (domain == unit->segment &&
|
|
bus == pci_get_bus(ctx->ctx_tag.owner) &&
|
|
device == pci_get_slot(ctx->ctx_tag.owner) &&
|
|
function == pci_get_function(ctx->ctx_tag.owner)) {
|
|
dmar_print_ctx(ctx, show_mappings);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
out:;
|
|
}
|
|
|
|
static void
|
|
dmar_print_one(int idx, bool show_ctxs, bool show_mappings)
|
|
{
|
|
struct dmar_unit *unit;
|
|
struct dmar_ctx *ctx;
|
|
int i, frir;
|
|
|
|
unit = device_get_softc(dmar_devs[idx]);
|
|
db_printf("dmar%d at %p, root at 0x%jx, ver 0x%x\n", unit->unit, unit,
|
|
dmar_read8(unit, DMAR_RTADDR_REG), dmar_read4(unit, DMAR_VER_REG));
|
|
db_printf("cap 0x%jx ecap 0x%jx gsts 0x%x fsts 0x%x fectl 0x%x\n",
|
|
(uintmax_t)dmar_read8(unit, DMAR_CAP_REG),
|
|
(uintmax_t)dmar_read8(unit, DMAR_ECAP_REG),
|
|
dmar_read4(unit, DMAR_GSTS_REG),
|
|
dmar_read4(unit, DMAR_FSTS_REG),
|
|
dmar_read4(unit, DMAR_FECTL_REG));
|
|
db_printf("fed 0x%x fea 0x%x feua 0x%x\n",
|
|
dmar_read4(unit, DMAR_FEDATA_REG),
|
|
dmar_read4(unit, DMAR_FEADDR_REG),
|
|
dmar_read4(unit, DMAR_FEUADDR_REG));
|
|
db_printf("primary fault log:\n");
|
|
for (i = 0; i < DMAR_CAP_NFR(unit->hw_cap); i++) {
|
|
frir = (DMAR_CAP_FRO(unit->hw_cap) + i) * 16;
|
|
db_printf(" %d at 0x%x: %jx %jx\n", i, frir,
|
|
(uintmax_t)dmar_read8(unit, frir),
|
|
(uintmax_t)dmar_read8(unit, frir + 8));
|
|
}
|
|
if (DMAR_HAS_QI(unit)) {
|
|
db_printf("ied 0x%x iea 0x%x ieua 0x%x\n",
|
|
dmar_read4(unit, DMAR_IEDATA_REG),
|
|
dmar_read4(unit, DMAR_IEADDR_REG),
|
|
dmar_read4(unit, DMAR_IEUADDR_REG));
|
|
if (unit->qi_enabled) {
|
|
db_printf("qi is enabled: queue @0x%jx (IQA 0x%jx) "
|
|
"size 0x%jx\n"
|
|
" head 0x%x tail 0x%x avail 0x%x status 0x%x ctrl 0x%x\n"
|
|
" hw compl 0x%x@%p/phys@%jx next seq 0x%x gen 0x%x\n",
|
|
(uintmax_t)unit->inv_queue,
|
|
(uintmax_t)dmar_read8(unit, DMAR_IQA_REG),
|
|
(uintmax_t)unit->inv_queue_size,
|
|
dmar_read4(unit, DMAR_IQH_REG),
|
|
dmar_read4(unit, DMAR_IQT_REG),
|
|
unit->inv_queue_avail,
|
|
dmar_read4(unit, DMAR_ICS_REG),
|
|
dmar_read4(unit, DMAR_IECTL_REG),
|
|
unit->inv_waitd_seq_hw,
|
|
&unit->inv_waitd_seq_hw,
|
|
(uintmax_t)unit->inv_waitd_seq_hw_phys,
|
|
unit->inv_waitd_seq,
|
|
unit->inv_waitd_gen);
|
|
} else {
|
|
db_printf("qi is disabled\n");
|
|
}
|
|
}
|
|
if (show_ctxs) {
|
|
db_printf("contexts:\n");
|
|
LIST_FOREACH(ctx, &unit->contexts, link) {
|
|
dmar_print_ctx(ctx, show_mappings);
|
|
if (db_pager_quit)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DB_SHOW_COMMAND(dmar, db_dmar_print)
|
|
{
|
|
bool show_ctxs, show_mappings;
|
|
|
|
show_ctxs = strchr(modif, 'c') != NULL;
|
|
show_mappings = strchr(modif, 'm') != NULL;
|
|
if (!have_addr) {
|
|
db_printf("usage: show dmar [/c] [/m] index\n");
|
|
return;
|
|
}
|
|
dmar_print_one((int)addr, show_ctxs, show_mappings);
|
|
}
|
|
|
|
DB_SHOW_ALL_COMMAND(dmars, db_show_all_dmars)
|
|
{
|
|
int i;
|
|
bool show_ctxs, show_mappings;
|
|
|
|
show_ctxs = strchr(modif, 'c') != NULL;
|
|
show_mappings = strchr(modif, 'm') != NULL;
|
|
|
|
for (i = 0; i < dmar_devcnt; i++) {
|
|
dmar_print_one(i, show_ctxs, show_mappings);
|
|
if (db_pager_quit)
|
|
break;
|
|
}
|
|
}
|
|
#endif
|