diff --git a/sys/amd64/pci/pci_cfgreg.c b/sys/amd64/pci/pci_cfgreg.c index a9ee4bccb3a4..5218ff3a40f7 100644 --- a/sys/amd64/pci/pci_cfgreg.c +++ b/sys/amd64/pci/pci_cfgreg.c @@ -1,327 +1,335 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, Stefan Esser * Copyright (c) 2000, Michael Smith * Copyright (c) 2000, BSDi * All rights reserved. * * 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 unmodified, 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 ``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 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 #include #include #include #include #include #include #include #include #include #include #include #include -static uint32_t pci_docfgregread(int bus, int slot, int func, int reg, - int bytes); -static int pciereg_cfgread(int bus, unsigned slot, unsigned func, - unsigned reg, unsigned bytes); -static void pciereg_cfgwrite(int bus, unsigned slot, unsigned func, - unsigned reg, int data, unsigned bytes); +static uint32_t pci_docfgregread(int domain, int bus, int slot, int func, + int reg, int bytes); +static int pciereg_cfgread(int domain, int bus, unsigned slot, + unsigned func, unsigned reg, unsigned bytes); +static void pciereg_cfgwrite(int domain, int bus, unsigned slot, + unsigned func, unsigned reg, int data, unsigned bytes); static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes); static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes); SYSCTL_DECL(_hw_pci); /* * For amd64 we assume that type 1 I/O port-based access always works. * If an ACPI MCFG table exists, pcie_cfgregopen() will be called to * switch to memory-mapped access. */ int cfgmech = CFGMECH_1; static vm_offset_t pcie_base; static int pcie_minbus, pcie_maxbus; static uint32_t pcie_badslots; static struct mtx pcicfg_mtx; MTX_SYSINIT(pcicfg_mtx, &pcicfg_mtx, "pcicfg_mtx", MTX_SPIN); static int mcfg_enable = 1; SYSCTL_INT(_hw_pci, OID_AUTO, mcfg, CTLFLAG_RDTUN, &mcfg_enable, 0, "Enable support for PCI-e memory mapped config access"); int pci_cfgregopen(void) { return (1); } static uint32_t -pci_docfgregread(int bus, int slot, int func, int reg, int bytes) +pci_docfgregread(int domain, int bus, int slot, int func, int reg, int bytes) { + if (domain == 0 && bus == 0 && (1 << slot & pcie_badslots) != 0) + return (pcireg_cfgread(bus, slot, func, reg, bytes)); if (cfgmech == CFGMECH_PCIE && - (bus >= pcie_minbus && bus <= pcie_maxbus) && - (bus != 0 || !(1 << slot & pcie_badslots))) - return (pciereg_cfgread(bus, slot, func, reg, bytes)); - else + (bus >= pcie_minbus && bus <= pcie_maxbus)) + return (pciereg_cfgread(domain, bus, slot, func, reg, bytes)); + else if (domain == 0) return (pcireg_cfgread(bus, slot, func, reg, bytes)); + else + return (-1); } /* * Read configuration space register */ u_int32_t -pci_cfgregread(int bus, int slot, int func, int reg, int bytes) +pci_cfgregread(int domain, int bus, int slot, int func, int reg, int bytes) { uint32_t line; /* * Some BIOS writers seem to want to ignore the spec and put * 0 in the intline rather than 255 to indicate none. Some use * numbers in the range 128-254 to indicate something strange and * apparently undocumented anywhere. Assume these are completely bogus * and map them to 255, which the rest of the PCI code recognizes as * as an invalid IRQ. */ if (reg == PCIR_INTLINE && bytes == 1) { - line = pci_docfgregread(bus, slot, func, PCIR_INTLINE, 1); + line = pci_docfgregread(domain, bus, slot, func, PCIR_INTLINE, + 1); if (line == 0 || line >= 128) line = PCI_INVALID_IRQ; return (line); } - return (pci_docfgregread(bus, slot, func, reg, bytes)); + return (pci_docfgregread(domain, bus, slot, func, reg, bytes)); } /* * Write configuration space register */ void -pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes) +pci_cfgregwrite(int domain, int bus, int slot, int func, int reg, uint32_t data, + int bytes) { + if (domain == 0 && bus == 0 && (1 << slot & pcie_badslots) != 0) { + pcireg_cfgwrite(bus, slot, func, reg, data, bytes); + return; + } if (cfgmech == CFGMECH_PCIE && - (bus >= pcie_minbus && bus <= pcie_maxbus) && - (bus != 0 || !(1 << slot & pcie_badslots))) - pciereg_cfgwrite(bus, slot, func, reg, data, bytes); - else + (bus >= pcie_minbus && bus <= pcie_maxbus)) + pciereg_cfgwrite(domain, bus, slot, func, reg, data, bytes); + else if (domain == 0) pcireg_cfgwrite(bus, slot, func, reg, data, bytes); } /* * Configuration space access using direct register operations */ /* enable configuration space accesses and return data port address */ static int pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes) { int dataport = 0; if (bus <= PCI_BUSMAX && slot <= PCI_SLOTMAX && func <= PCI_FUNCMAX && (unsigned)reg <= PCI_REGMAX && bytes != 3 && (unsigned)bytes <= 4 && (reg & (bytes - 1)) == 0) { outl(CONF1_ADDR_PORT, (1U << 31) | (bus << 16) | (slot << 11) | (func << 8) | (reg & ~0x03)); dataport = CONF1_DATA_PORT + (reg & 0x03); } return (dataport); } /* disable configuration space accesses */ static void pci_cfgdisable(void) { /* * Do nothing. Writing a 0 to the address port can apparently * confuse some bridges and cause spurious access failures. */ } static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes) { int data = -1; int port; mtx_lock_spin(&pcicfg_mtx); port = pci_cfgenable(bus, slot, func, reg, bytes); if (port != 0) { switch (bytes) { case 1: data = inb(port); break; case 2: data = inw(port); break; case 4: data = inl(port); break; } pci_cfgdisable(); } mtx_unlock_spin(&pcicfg_mtx); return (data); } static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes) { int port; mtx_lock_spin(&pcicfg_mtx); port = pci_cfgenable(bus, slot, func, reg, bytes); if (port != 0) { switch (bytes) { case 1: outb(port, data); break; case 2: outw(port, data); break; case 4: outl(port, data); break; } pci_cfgdisable(); } mtx_unlock_spin(&pcicfg_mtx); } int pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus) { uint32_t val1, val2; int slot; if (!mcfg_enable) return (0); if (minbus != 0) return (0); if (bootverbose) printf("PCIe: Memory Mapped configuration base @ 0x%lx\n", base); /* XXX: We should make sure this really fits into the direct map. */ pcie_base = (vm_offset_t)pmap_mapdev_pciecfg(base, (maxbus + 1) << 20); pcie_minbus = minbus; pcie_maxbus = maxbus; cfgmech = CFGMECH_PCIE; /* * On some AMD systems, some of the devices on bus 0 are * inaccessible using memory-mapped PCI config access. Walk * bus 0 looking for such devices. For these devices, we will * fall back to using type 1 config access instead. */ if (pci_cfgregopen() != 0) { for (slot = 0; slot <= PCI_SLOTMAX; slot++) { val1 = pcireg_cfgread(0, slot, 0, 0, 4); if (val1 == 0xffffffff) continue; - val2 = pciereg_cfgread(0, slot, 0, 0, 4); + val2 = pciereg_cfgread(0, 0, slot, 0, 0, 4); if (val2 != val1) pcie_badslots |= (1 << slot); } } return (1); } #define PCIE_VADDR(base, reg, bus, slot, func) \ ((base) + \ ((((bus) & 0xff) << 20) | \ (((slot) & 0x1f) << 15) | \ (((func) & 0x7) << 12) | \ ((reg) & 0xfff))) /* * AMD BIOS And Kernel Developer's Guides for CPU families starting with 10h * have a requirement that all accesses to the memory mapped PCI configuration * space are done using AX class of registers. * Since other vendors do not currently have any contradicting requirements * the AMD access pattern is applied universally. */ static int -pciereg_cfgread(int bus, unsigned slot, unsigned func, unsigned reg, +pciereg_cfgread(int domain, int bus, unsigned slot, unsigned func, unsigned reg, unsigned bytes) { vm_offset_t va; int data = -1; - if (bus < pcie_minbus || bus > pcie_maxbus || slot > PCI_SLOTMAX || - func > PCI_FUNCMAX || reg > PCIE_REGMAX) + if (domain != 0 || bus < pcie_minbus || bus > pcie_maxbus || + slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > PCIE_REGMAX) return (-1); va = PCIE_VADDR(pcie_base, reg, bus, slot, func); switch (bytes) { case 4: __asm("movl %1, %0" : "=a" (data) : "m" (*(volatile uint32_t *)va)); break; case 2: __asm("movzwl %1, %0" : "=a" (data) : "m" (*(volatile uint16_t *)va)); break; case 1: __asm("movzbl %1, %0" : "=a" (data) : "m" (*(volatile uint8_t *)va)); break; } return (data); } static void -pciereg_cfgwrite(int bus, unsigned slot, unsigned func, unsigned reg, int data, - unsigned bytes) +pciereg_cfgwrite(int domain, int bus, unsigned slot, unsigned func, + unsigned reg, int data, unsigned bytes) { vm_offset_t va; - if (bus < pcie_minbus || bus > pcie_maxbus || slot > PCI_SLOTMAX || - func > PCI_FUNCMAX || reg > PCIE_REGMAX) + if (domain != 0 || bus < pcie_minbus || bus > pcie_maxbus || + slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > PCIE_REGMAX) return; va = PCIE_VADDR(pcie_base, reg, bus, slot, func); switch (bytes) { case 4: __asm("movl %1, %0" : "=m" (*(volatile uint32_t *)va) : "a" (data)); break; case 2: __asm("movw %1, %0" : "=m" (*(volatile uint16_t *)va) : "a" ((uint16_t)data)); break; case 1: __asm("movb %1, %0" : "=m" (*(volatile uint8_t *)va) : "a" ((uint8_t)data)); break; } } diff --git a/sys/amd64/vmm/amd/amdvi_hw.c b/sys/amd64/vmm/amd/amdvi_hw.c index bef6b0038812..2707109ce587 100644 --- a/sys/amd64/vmm/amd/amdvi_hw.c +++ b/sys/amd64/vmm/amd/amdvi_hw.c @@ -1,1383 +1,1383 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2016, Anish Gupta (anish@freebsd.org) * All rights reserved. * * 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 unmodified, 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 ``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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ivhd_if.h" #include "pcib_if.h" #include "io/iommu.h" #include "amdvi_priv.h" SYSCTL_DECL(_hw_vmm); SYSCTL_NODE(_hw_vmm, OID_AUTO, amdvi, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, NULL); #define MOD_INC(a, s, m) (((a) + (s)) % ((m) * (s))) #define MOD_DEC(a, s, m) (((a) - (s)) % ((m) * (s))) /* Print RID or device ID in PCI string format. */ #define RID2PCI_STR(d) PCI_RID2BUS(d), PCI_RID2SLOT(d), PCI_RID2FUNC(d) static void amdvi_dump_cmds(struct amdvi_softc *softc, int count); static void amdvi_print_dev_cap(struct amdvi_softc *softc); MALLOC_DEFINE(M_AMDVI, "amdvi", "amdvi"); extern device_t *ivhd_devs; extern int ivhd_count; SYSCTL_INT(_hw_vmm_amdvi, OID_AUTO, count, CTLFLAG_RDTUN, &ivhd_count, 0, NULL); static int amdvi_enable_user = 0; SYSCTL_INT(_hw_vmm_amdvi, OID_AUTO, enable, CTLFLAG_RDTUN, &amdvi_enable_user, 0, NULL); TUNABLE_INT("hw.vmm.amdvi_enable", &amdvi_enable_user); #ifdef AMDVI_ATS_ENABLE /* XXX: ATS is not tested. */ static int amdvi_enable_iotlb = 1; SYSCTL_INT(_hw_vmm_amdvi, OID_AUTO, iotlb_enabled, CTLFLAG_RDTUN, &amdvi_enable_iotlb, 0, NULL); TUNABLE_INT("hw.vmm.enable_iotlb", &amdvi_enable_iotlb); #endif static int amdvi_host_ptp = 1; /* Use page tables for host. */ SYSCTL_INT(_hw_vmm_amdvi, OID_AUTO, host_ptp, CTLFLAG_RDTUN, &amdvi_host_ptp, 0, NULL); TUNABLE_INT("hw.vmm.amdvi.host_ptp", &amdvi_host_ptp); /* Page table level used <= supported by h/w[v1=7]. */ int amdvi_ptp_level = 4; SYSCTL_INT(_hw_vmm_amdvi, OID_AUTO, ptp_level, CTLFLAG_RDTUN, &amdvi_ptp_level, 0, NULL); TUNABLE_INT("hw.vmm.amdvi.ptp_level", &amdvi_ptp_level); /* Disable fault event reporting. */ static int amdvi_disable_io_fault = 0; SYSCTL_INT(_hw_vmm_amdvi, OID_AUTO, disable_io_fault, CTLFLAG_RDTUN, &amdvi_disable_io_fault, 0, NULL); TUNABLE_INT("hw.vmm.amdvi.disable_io_fault", &amdvi_disable_io_fault); static uint32_t amdvi_dom_id = 0; /* 0 is reserved for host. */ SYSCTL_UINT(_hw_vmm_amdvi, OID_AUTO, domain_id, CTLFLAG_RD, &amdvi_dom_id, 0, NULL); /* * Device table entry. * Bus(256) x Dev(32) x Fun(8) x DTE(256 bits or 32 bytes). * = 256 * 2 * PAGE_SIZE. */ static struct amdvi_dte amdvi_dte[PCI_NUM_DEV_MAX] __aligned(PAGE_SIZE); CTASSERT(PCI_NUM_DEV_MAX == 0x10000); CTASSERT(sizeof(amdvi_dte) == 0x200000); static SLIST_HEAD (, amdvi_domain) dom_head; static inline uint32_t amdvi_pci_read(struct amdvi_softc *softc, int off) { - return (pci_cfgregread(PCI_RID2BUS(softc->pci_rid), + return (pci_cfgregread(softc->pci_seg, PCI_RID2BUS(softc->pci_rid), PCI_RID2SLOT(softc->pci_rid), PCI_RID2FUNC(softc->pci_rid), off, 4)); } #ifdef AMDVI_ATS_ENABLE /* XXX: Should be in pci.c */ /* * Check if device has ATS capability and its enabled. * If ATS is absent or disabled, return (-1), otherwise ATS * queue length. */ static int amdvi_find_ats_qlen(uint16_t devid) { device_t dev; uint32_t off, cap; int qlen = -1; dev = pci_find_bsf(PCI_RID2BUS(devid), PCI_RID2SLOT(devid), PCI_RID2FUNC(devid)); if (!dev) { return (-1); } #define PCIM_ATS_EN BIT(31) if (pci_find_extcap(dev, PCIZ_ATS, &off) == 0) { cap = pci_read_config(dev, off + 4, 4); qlen = (cap & 0x1F); qlen = qlen ? qlen : 32; printf("AMD-Vi: PCI device %d.%d.%d ATS %s qlen=%d\n", RID2PCI_STR(devid), (cap & PCIM_ATS_EN) ? "enabled" : "Disabled", qlen); qlen = (cap & PCIM_ATS_EN) ? qlen : -1; } return (qlen); } /* * Check if an endpoint device support device IOTLB or ATS. */ static inline bool amdvi_dev_support_iotlb(struct amdvi_softc *softc, uint16_t devid) { struct ivhd_dev_cfg *cfg; int qlen, i; bool pci_ats, ivhd_ats; qlen = amdvi_find_ats_qlen(devid); if (qlen < 0) return (false); KASSERT(softc, ("softc is NULL")); cfg = softc->dev_cfg; ivhd_ats = false; for (i = 0; i < softc->dev_cfg_cnt; i++) { if ((cfg->start_id <= devid) && (cfg->end_id >= devid)) { ivhd_ats = cfg->enable_ats; break; } cfg++; } pci_ats = (qlen < 0) ? false : true; if (pci_ats != ivhd_ats) device_printf(softc->dev, "BIOS bug: mismatch in ATS setting for %d.%d.%d," "ATS inv qlen = %d\n", RID2PCI_STR(devid), qlen); /* Ignore IVRS setting and respect PCI setting. */ return (pci_ats); } #endif /* Enable IOTLB support for IOMMU if its supported. */ static inline void amdvi_hw_enable_iotlb(struct amdvi_softc *softc) { #ifndef AMDVI_ATS_ENABLE softc->iotlb = false; #else bool supported; supported = (softc->ivhd_flag & IVHD_FLAG_IOTLB) ? true : false; if (softc->pci_cap & AMDVI_PCI_CAP_IOTLB) { if (!supported) device_printf(softc->dev, "IOTLB disabled by BIOS.\n"); if (supported && !amdvi_enable_iotlb) { device_printf(softc->dev, "IOTLB disabled by user.\n"); supported = false; } } else supported = false; softc->iotlb = supported; #endif } static int amdvi_init_cmd(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl = softc->ctrl; ctrl->cmd.len = 8; /* Use 256 command buffer entries. */ softc->cmd_max = 1 << ctrl->cmd.len; softc->cmd = malloc(sizeof(struct amdvi_cmd) * softc->cmd_max, M_AMDVI, M_WAITOK | M_ZERO); if ((uintptr_t)softc->cmd & PAGE_MASK) panic("AMDVi: Command buffer not aligned on page boundary."); ctrl->cmd.base = vtophys(softc->cmd) / PAGE_SIZE; /* * XXX: Reset the h/w pointers in case IOMMU is restarting, * h/w doesn't clear these pointers based on empirical data. */ ctrl->cmd_tail = 0; ctrl->cmd_head = 0; return (0); } /* * Note: Update tail pointer after we have written the command since tail * pointer update cause h/w to execute new commands, see section 3.3 * of AMD IOMMU spec ver 2.0. */ /* Get the command tail pointer w/o updating it. */ static struct amdvi_cmd * amdvi_get_cmd_tail(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl; struct amdvi_cmd *tail; KASSERT(softc, ("softc is NULL")); KASSERT(softc->cmd != NULL, ("cmd is NULL")); ctrl = softc->ctrl; KASSERT(ctrl != NULL, ("ctrl is NULL")); tail = (struct amdvi_cmd *)((uint8_t *)softc->cmd + ctrl->cmd_tail); return (tail); } /* * Update the command tail pointer which will start command execution. */ static void amdvi_update_cmd_tail(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl; int size; size = sizeof(struct amdvi_cmd); KASSERT(softc->cmd != NULL, ("cmd is NULL")); ctrl = softc->ctrl; KASSERT(ctrl != NULL, ("ctrl is NULL")); ctrl->cmd_tail = MOD_INC(ctrl->cmd_tail, size, softc->cmd_max); softc->total_cmd++; #ifdef AMDVI_DEBUG_CMD device_printf(softc->dev, "cmd_tail: %s Tail:0x%x, Head:0x%x.\n", ctrl->cmd_tail, ctrl->cmd_head); #endif } /* * Various commands supported by IOMMU. */ /* Completion wait command. */ static void amdvi_cmd_cmp(struct amdvi_softc *softc, const uint64_t data) { struct amdvi_cmd *cmd; uint64_t pa; cmd = amdvi_get_cmd_tail(softc); KASSERT(cmd != NULL, ("Cmd is NULL")); pa = vtophys(&softc->cmp_data); cmd->opcode = AMDVI_CMP_WAIT_OPCODE; cmd->word0 = (pa & 0xFFFFFFF8) | AMDVI_CMP_WAIT_STORE; cmd->word1 = (pa >> 32) & 0xFFFFF; cmd->addr = data; amdvi_update_cmd_tail(softc); } /* Invalidate device table entry. */ static void amdvi_cmd_inv_dte(struct amdvi_softc *softc, uint16_t devid) { struct amdvi_cmd *cmd; cmd = amdvi_get_cmd_tail(softc); KASSERT(cmd != NULL, ("Cmd is NULL")); cmd->opcode = AMDVI_INVD_DTE_OPCODE; cmd->word0 = devid; amdvi_update_cmd_tail(softc); #ifdef AMDVI_DEBUG_CMD device_printf(softc->dev, "Invalidated DTE:0x%x\n", devid); #endif } /* Invalidate IOMMU page, use for invalidation of domain. */ static void amdvi_cmd_inv_iommu_pages(struct amdvi_softc *softc, uint16_t domain_id, uint64_t addr, bool guest_nested, bool pde, bool page) { struct amdvi_cmd *cmd; cmd = amdvi_get_cmd_tail(softc); KASSERT(cmd != NULL, ("Cmd is NULL")); cmd->opcode = AMDVI_INVD_PAGE_OPCODE; cmd->word1 = domain_id; /* * Invalidate all addresses for this domain. */ cmd->addr = addr; cmd->addr |= pde ? AMDVI_INVD_PAGE_PDE : 0; cmd->addr |= page ? AMDVI_INVD_PAGE_S : 0; amdvi_update_cmd_tail(softc); } #ifdef AMDVI_ATS_ENABLE /* Invalidate device IOTLB. */ static void amdvi_cmd_inv_iotlb(struct amdvi_softc *softc, uint16_t devid) { struct amdvi_cmd *cmd; int qlen; if (!softc->iotlb) return; qlen = amdvi_find_ats_qlen(devid); if (qlen < 0) { panic("AMDVI: Invalid ATS qlen(%d) for device %d.%d.%d\n", qlen, RID2PCI_STR(devid)); } cmd = amdvi_get_cmd_tail(softc); KASSERT(cmd != NULL, ("Cmd is NULL")); #ifdef AMDVI_DEBUG_CMD device_printf(softc->dev, "Invalidate IOTLB devID 0x%x" " Qlen:%d\n", devid, qlen); #endif cmd->opcode = AMDVI_INVD_IOTLB_OPCODE; cmd->word0 = devid; cmd->word1 = qlen; cmd->addr = AMDVI_INVD_IOTLB_ALL_ADDR | AMDVI_INVD_IOTLB_S; amdvi_update_cmd_tail(softc); } #endif #ifdef notyet /* For Interrupt Remap. */ static void amdvi_cmd_inv_intr_map(struct amdvi_softc *softc, uint16_t devid) { struct amdvi_cmd *cmd; cmd = amdvi_get_cmd_tail(softc); KASSERT(cmd != NULL, ("Cmd is NULL")); cmd->opcode = AMDVI_INVD_INTR_OPCODE; cmd->word0 = devid; amdvi_update_cmd_tail(softc); #ifdef AMDVI_DEBUG_CMD device_printf(softc->dev, "Invalidate INTR map of devID 0x%x\n", devid); #endif } #endif /* Invalidate domain using INVALIDATE_IOMMU_PAGES command. */ static void amdvi_inv_domain(struct amdvi_softc *softc, uint16_t domain_id) { struct amdvi_cmd *cmd __diagused; cmd = amdvi_get_cmd_tail(softc); KASSERT(cmd != NULL, ("Cmd is NULL")); /* * See section 3.3.3 of IOMMU spec rev 2.0, software note * for invalidating domain. */ amdvi_cmd_inv_iommu_pages(softc, domain_id, AMDVI_INVD_PAGE_ALL_ADDR, false, true, true); #ifdef AMDVI_DEBUG_CMD device_printf(softc->dev, "Invalidate domain:0x%x\n", domain_id); #endif } static bool amdvi_cmp_wait(struct amdvi_softc *softc) { #ifdef AMDVI_DEBUG_CMD struct amdvi_ctrl *ctrl = softc->ctrl; #endif const uint64_t VERIFY = 0xA5A5; volatile uint64_t *read; int i; bool status; read = &softc->cmp_data; *read = 0; amdvi_cmd_cmp(softc, VERIFY); /* Wait for h/w to update completion data. */ for (i = 0; i < 100 && (*read != VERIFY); i++) { DELAY(1000); /* 1 ms */ } status = (VERIFY == softc->cmp_data) ? true : false; #ifdef AMDVI_DEBUG_CMD if (status) device_printf(softc->dev, "CMD completion DONE Tail:0x%x, " "Head:0x%x, loop:%d.\n", ctrl->cmd_tail, ctrl->cmd_head, loop); #endif return (status); } static void amdvi_wait(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl; int i; KASSERT(softc, ("softc is NULL")); ctrl = softc->ctrl; KASSERT(ctrl != NULL, ("ctrl is NULL")); /* Don't wait if h/w is not enabled. */ if ((ctrl->control & AMDVI_CTRL_EN) == 0) return; for (i = 0; i < 10; i++) { if (amdvi_cmp_wait(softc)) return; } device_printf(softc->dev, "Error: completion failed" " tail:0x%x, head:0x%x.\n", ctrl->cmd_tail, ctrl->cmd_head); /* Dump the last command. */ amdvi_dump_cmds(softc, 1); } static void amdvi_dump_cmds(struct amdvi_softc *softc, int count) { struct amdvi_ctrl *ctrl; struct amdvi_cmd *cmd; int off, i; ctrl = softc->ctrl; device_printf(softc->dev, "Dump last %d command(s):\n", count); /* * If h/w is stuck in completion, it is the previous command, * start dumping from previous command onward. */ off = MOD_DEC(ctrl->cmd_head, sizeof(struct amdvi_cmd), softc->cmd_max); for (i = 0; off != ctrl->cmd_tail && i < count; i++) { cmd = (struct amdvi_cmd *)((uint8_t *)softc->cmd + off); printf(" [CMD%d, off:0x%x] opcode= 0x%x 0x%x" " 0x%x 0x%lx\n", i, off, cmd->opcode, cmd->word0, cmd->word1, cmd->addr); off = MOD_INC(off, sizeof(struct amdvi_cmd), softc->cmd_max); } } static int amdvi_init_event(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl; ctrl = softc->ctrl; ctrl->event.len = 8; softc->event_max = 1 << ctrl->event.len; softc->event = malloc(sizeof(struct amdvi_event) * softc->event_max, M_AMDVI, M_WAITOK | M_ZERO); if ((uintptr_t)softc->event & PAGE_MASK) { device_printf(softc->dev, "Event buffer not aligned on page."); return (false); } ctrl->event.base = vtophys(softc->event) / PAGE_SIZE; /* Reset the pointers. */ ctrl->evt_head = 0; ctrl->evt_tail = 0; return (0); } static inline void amdvi_decode_evt_flag(uint16_t flag) { flag &= AMDVI_EVENT_FLAG_MASK; printf(" 0x%b]\n", flag, "\020" "\001GN" "\002NX" "\003US" "\004I" "\005PR" "\006RW" "\007PE" "\010RZ" "\011TR" ); } /* See section 2.5.4 of AMD IOMMU spec ver 2.62.*/ static inline void amdvi_decode_evt_flag_type(uint8_t type) { switch (AMDVI_EVENT_FLAG_TYPE(type)) { case 0: printf("RSVD\n"); break; case 1: printf("Master Abort\n"); break; case 2: printf("Target Abort\n"); break; case 3: printf("Data Err\n"); break; default: break; } } static void amdvi_decode_inv_dte_evt(uint16_t devid, uint16_t domid, uint64_t addr, uint16_t flag) { printf("\t[IO_PAGE_FAULT EVT: devId:0x%x DomId:0x%x" " Addr:0x%lx", devid, domid, addr); amdvi_decode_evt_flag(flag); } static void amdvi_decode_pf_evt(uint16_t devid, uint16_t domid, uint64_t addr, uint16_t flag) { printf("\t[IO_PAGE_FAULT EVT: devId:0x%x DomId:0x%x" " Addr:0x%lx", devid, domid, addr); amdvi_decode_evt_flag(flag); } static void amdvi_decode_dte_hwerr_evt(uint16_t devid, uint16_t domid, uint64_t addr, uint16_t flag) { printf("\t[DEV_TAB_HW_ERR EVT: devId:0x%x DomId:0x%x" " Addr:0x%lx", devid, domid, addr); amdvi_decode_evt_flag(flag); amdvi_decode_evt_flag_type(flag); } static void amdvi_decode_page_hwerr_evt(uint16_t devid, uint16_t domid, uint64_t addr, uint16_t flag) { printf("\t[PAGE_TAB_HW_ERR EVT: devId:0x%x DomId:0x%x" " Addr:0x%lx", devid, domid, addr); amdvi_decode_evt_flag(flag); amdvi_decode_evt_flag_type(AMDVI_EVENT_FLAG_TYPE(flag)); } static void amdvi_decode_evt(struct amdvi_event *evt) { struct amdvi_cmd *cmd; switch (evt->opcode) { case AMDVI_EVENT_INVALID_DTE: amdvi_decode_inv_dte_evt(evt->devid, evt->pasid_domid, evt->addr, evt->flag); break; case AMDVI_EVENT_PFAULT: amdvi_decode_pf_evt(evt->devid, evt->pasid_domid, evt->addr, evt->flag); break; case AMDVI_EVENT_DTE_HW_ERROR: amdvi_decode_dte_hwerr_evt(evt->devid, evt->pasid_domid, evt->addr, evt->flag); break; case AMDVI_EVENT_PAGE_HW_ERROR: amdvi_decode_page_hwerr_evt(evt->devid, evt->pasid_domid, evt->addr, evt->flag); break; case AMDVI_EVENT_ILLEGAL_CMD: /* FALL THROUGH */ case AMDVI_EVENT_CMD_HW_ERROR: printf("\t[%s EVT]\n", (evt->opcode == AMDVI_EVENT_ILLEGAL_CMD) ? "ILLEGAL CMD" : "CMD HW ERR"); cmd = (struct amdvi_cmd *)PHYS_TO_DMAP(evt->addr); printf("\tCMD opcode= 0x%x 0x%x 0x%x 0x%lx\n", cmd->opcode, cmd->word0, cmd->word1, cmd->addr); break; case AMDVI_EVENT_IOTLB_TIMEOUT: printf("\t[IOTLB_INV_TIMEOUT devid:0x%x addr:0x%lx]\n", evt->devid, evt->addr); break; case AMDVI_EVENT_INVALID_DTE_REQ: printf("\t[INV_DTE devid:0x%x addr:0x%lx type:0x%x tr:%d]\n", evt->devid, evt->addr, evt->flag >> 9, (evt->flag >> 8) & 1); break; case AMDVI_EVENT_INVALID_PPR_REQ: case AMDVI_EVENT_COUNTER_ZERO: printf("AMD-Vi: v2 events.\n"); break; default: printf("Unsupported AMD-Vi event:%d\n", evt->opcode); } } static void amdvi_print_events(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl; struct amdvi_event *event; int i, size; ctrl = softc->ctrl; size = sizeof(struct amdvi_event); for (i = 0; i < softc->event_max; i++) { event = &softc->event[ctrl->evt_head / size]; if (!event->opcode) break; device_printf(softc->dev, "\t[Event%d: Head:0x%x Tail:0x%x]\n", i, ctrl->evt_head, ctrl->evt_tail); amdvi_decode_evt(event); ctrl->evt_head = MOD_INC(ctrl->evt_head, size, softc->event_max); } } static int amdvi_init_dte(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl; ctrl = softc->ctrl; ctrl->dte.base = vtophys(amdvi_dte) / PAGE_SIZE; ctrl->dte.size = 0x1FF; /* 2MB device table. */ return (0); } /* * Not all capabilities of IOMMU are available in ACPI IVHD flag * or EFR entry, read directly from device. */ static int amdvi_print_pci_cap(device_t dev) { struct amdvi_softc *softc; uint32_t off, cap; softc = device_get_softc(dev); off = softc->cap_off; /* * Section 3.7.1 of IOMMU sepc rev 2.0. * Read capability from device. */ cap = amdvi_pci_read(softc, off); /* Make sure capability type[18:16] is 3. */ KASSERT((((cap >> 16) & 0x7) == 0x3), ("Not a IOMMU capability 0x%x@0x%x", cap, off)); softc->pci_cap = cap >> 24; device_printf(softc->dev, "PCI cap 0x%x@0x%x feature:%b\n", cap, off, softc->pci_cap, "\20\1IOTLB\2HT\3NPCache\4EFR\5CapExt"); return (0); } static void amdvi_event_intr(void *arg) { struct amdvi_softc *softc; struct amdvi_ctrl *ctrl; softc = (struct amdvi_softc *)arg; ctrl = softc->ctrl; device_printf(softc->dev, "EVT INTR %ld Status:0x%x" " EVT Head:0x%x Tail:0x%x]\n", softc->event_intr_cnt++, ctrl->status, ctrl->evt_head, ctrl->evt_tail); printf(" [CMD Total 0x%lx] Tail:0x%x, Head:0x%x.\n", softc->total_cmd, ctrl->cmd_tail, ctrl->cmd_head); amdvi_print_events(softc); ctrl->status &= AMDVI_STATUS_EV_OF | AMDVI_STATUS_EV_INTR; } static void amdvi_free_evt_intr_res(device_t dev) { struct amdvi_softc *softc; device_t mmio_dev; softc = device_get_softc(dev); mmio_dev = softc->pci_dev; IVHD_TEARDOWN_INTR(mmio_dev); } static bool amdvi_alloc_intr_resources(struct amdvi_softc *softc) { struct amdvi_ctrl *ctrl; device_t dev, mmio_dev; int err; dev = softc->dev; mmio_dev = softc->pci_dev; /* Clear interrupt status bits. */ ctrl = softc->ctrl; ctrl->status &= AMDVI_STATUS_EV_OF | AMDVI_STATUS_EV_INTR; err = IVHD_SETUP_INTR(mmio_dev, amdvi_event_intr, softc, "fault"); if (err) device_printf(dev, "Interrupt setup failed on %s\n", device_get_nameunit(mmio_dev)); return (err); } static void amdvi_print_dev_cap(struct amdvi_softc *softc) { struct ivhd_dev_cfg *cfg; int i; cfg = softc->dev_cfg; for (i = 0; i < softc->dev_cfg_cnt; i++) { device_printf(softc->dev, "device [0x%x - 0x%x] " "config:%b%s\n", cfg->start_id, cfg->end_id, cfg->data, "\020\001INIT\002ExtInt\003NMI" "\007LINT0\010LINT1", cfg->enable_ats ? "ATS enabled" : ""); cfg++; } } static int amdvi_handle_sysctl(SYSCTL_HANDLER_ARGS) { struct amdvi_softc *softc; int result, type, error = 0; softc = (struct amdvi_softc *)arg1; type = arg2; switch (type) { case 0: result = softc->ctrl->cmd_head; error = sysctl_handle_int(oidp, &result, 0, req); break; case 1: result = softc->ctrl->cmd_tail; error = sysctl_handle_int(oidp, &result, 0, req); break; case 2: result = softc->ctrl->evt_head; error = sysctl_handle_int(oidp, &result, 0, req); break; case 3: result = softc->ctrl->evt_tail; error = sysctl_handle_int(oidp, &result, 0, req); break; default: device_printf(softc->dev, "Unknown sysctl:%d\n", type); } return (error); } static void amdvi_add_sysctl(struct amdvi_softc *softc) { struct sysctl_oid_list *child; struct sysctl_ctx_list *ctx; device_t dev; dev = softc->dev; ctx = device_get_sysctl_ctx(dev); child = SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); SYSCTL_ADD_ULONG(ctx, child, OID_AUTO, "event_intr_count", CTLFLAG_RD, &softc->event_intr_cnt, "Event interrupt count"); SYSCTL_ADD_ULONG(ctx, child, OID_AUTO, "command_count", CTLFLAG_RD, &softc->total_cmd, "Command submitted count"); SYSCTL_ADD_U16(ctx, child, OID_AUTO, "pci_rid", CTLFLAG_RD, &softc->pci_rid, 0, "IOMMU RID"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "command_head", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, softc, 0, amdvi_handle_sysctl, "IU", "Command head"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "command_tail", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, softc, 1, amdvi_handle_sysctl, "IU", "Command tail"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "event_head", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, softc, 2, amdvi_handle_sysctl, "IU", "Command head"); SYSCTL_ADD_PROC(ctx, child, OID_AUTO, "event_tail", CTLTYPE_UINT | CTLFLAG_RD | CTLFLAG_MPSAFE, softc, 3, amdvi_handle_sysctl, "IU", "Command tail"); } int amdvi_setup_hw(struct amdvi_softc *softc) { device_t dev; int status; dev = softc->dev; amdvi_hw_enable_iotlb(softc); amdvi_print_dev_cap(softc); if ((status = amdvi_print_pci_cap(dev)) != 0) { device_printf(dev, "PCI capability.\n"); return (status); } if ((status = amdvi_init_cmd(softc)) != 0) { device_printf(dev, "Couldn't configure command buffer.\n"); return (status); } if ((status = amdvi_init_event(softc)) != 0) { device_printf(dev, "Couldn't configure event buffer.\n"); return (status); } if ((status = amdvi_init_dte(softc)) != 0) { device_printf(dev, "Couldn't configure device table.\n"); return (status); } if ((status = amdvi_alloc_intr_resources(softc)) != 0) { return (status); } amdvi_add_sysctl(softc); return (0); } int amdvi_teardown_hw(struct amdvi_softc *softc) { device_t dev; dev = softc->dev; /* * Called after disable, h/w is stopped by now, free all the resources. */ amdvi_free_evt_intr_res(dev); if (softc->cmd) free(softc->cmd, M_AMDVI); if (softc->event) free(softc->event, M_AMDVI); return (0); } /*********** bhyve interfaces *********************/ static int amdvi_init(void) { if (!ivhd_count) { return (EIO); } if (!amdvi_enable_user && ivhd_count) { printf("bhyve: Found %d AMD-Vi/IOMMU device(s), " "use hw.vmm.amdvi.enable=1 to enable pass-through.\n", ivhd_count); return (EINVAL); } return (0); } static void amdvi_cleanup(void) { /* Nothing. */ } static uint16_t amdvi_domainId(void) { /* * If we hit maximum domain limit, rollover leaving host * domain(0). * XXX: make sure that this domain is not used. */ if (amdvi_dom_id == AMDVI_MAX_DOMAIN) amdvi_dom_id = 1; return ((uint16_t)amdvi_dom_id++); } static void amdvi_do_inv_domain(uint16_t domain_id, bool create) { struct amdvi_softc *softc; int i; for (i = 0; i < ivhd_count; i++) { softc = device_get_softc(ivhd_devs[i]); KASSERT(softc, ("softc is NULL")); /* * If not present pages are cached, invalidate page after * creating domain. */ #if 0 if (create && ((softc->pci_cap & AMDVI_PCI_CAP_NPCACHE) == 0)) continue; #endif amdvi_inv_domain(softc, domain_id); amdvi_wait(softc); } } static void * amdvi_create_domain(vm_paddr_t maxaddr) { struct amdvi_domain *dom; dom = malloc(sizeof(struct amdvi_domain), M_AMDVI, M_ZERO | M_WAITOK); dom->id = amdvi_domainId(); //dom->maxaddr = maxaddr; #ifdef AMDVI_DEBUG_CMD printf("Created domain #%d\n", dom->id); #endif /* * Host domain(#0) don't create translation table. */ if (dom->id || amdvi_host_ptp) dom->ptp = malloc(PAGE_SIZE, M_AMDVI, M_WAITOK | M_ZERO); dom->ptp_level = amdvi_ptp_level; amdvi_do_inv_domain(dom->id, true); SLIST_INSERT_HEAD(&dom_head, dom, next); return (dom); } static void amdvi_free_ptp(uint64_t *ptp, int level) { int i; if (level < 1) return; for (i = 0; i < NPTEPG ; i++) { if ((ptp[i] & AMDVI_PT_PRESENT) == 0) continue; /* XXX: Add super-page or PTE mapping > 4KB. */ #ifdef notyet /* Super-page mapping. */ if (AMDVI_PD_SUPER(ptp[i])) continue; #endif amdvi_free_ptp((uint64_t *)PHYS_TO_DMAP(ptp[i] & AMDVI_PT_MASK), level - 1); } free(ptp, M_AMDVI); } static void amdvi_destroy_domain(void *arg) { struct amdvi_domain *domain; domain = (struct amdvi_domain *)arg; KASSERT(domain, ("domain is NULL")); #ifdef AMDVI_DEBUG_CMD printf("Destroying domain %d\n", domain->id); #endif if (domain->ptp) amdvi_free_ptp(domain->ptp, domain->ptp_level); amdvi_do_inv_domain(domain->id, false); SLIST_REMOVE(&dom_head, domain, amdvi_domain, next); free(domain, M_AMDVI); } static uint64_t amdvi_set_pt(uint64_t *pt, int level, vm_paddr_t gpa, vm_paddr_t hpa, uint64_t pg_size, bool create) { uint64_t *page, pa; int shift, index; const int PT_SHIFT = 9; const int PT_INDEX_MASK = (1 << PT_SHIFT) - 1; /* Based on PT_SHIFT */ if (!pg_size) return (0); if (hpa & (pg_size - 1)) { printf("HPA is not size aligned.\n"); return (0); } if (gpa & (pg_size - 1)) { printf("HPA is not size aligned.\n"); return (0); } shift = PML4SHIFT; while ((shift > PAGE_SHIFT) && (pg_size < (1UL << shift))) { index = (gpa >> shift) & PT_INDEX_MASK; if ((pt[index] == 0) && create) { page = malloc(PAGE_SIZE, M_AMDVI, M_WAITOK | M_ZERO); pa = vtophys(page); pt[index] = pa | AMDVI_PT_PRESENT | AMDVI_PT_RW | ((level - 1) << AMDVI_PD_LEVEL_SHIFT); } #ifdef AMDVI_DEBUG_PTE if ((gpa % 0x1000000) == 0) printf("[level%d, shift = %d]PTE:0x%lx\n", level, shift, pt[index]); #endif #define PTE2PA(x) ((uint64_t)(x) & AMDVI_PT_MASK) pa = PTE2PA(pt[index]); pt = (uint64_t *)PHYS_TO_DMAP(pa); shift -= PT_SHIFT; level--; } /* Leaf entry. */ index = (gpa >> shift) & PT_INDEX_MASK; if (create) { pt[index] = hpa | AMDVI_PT_RW | AMDVI_PT_PRESENT; } else pt[index] = 0; #ifdef AMDVI_DEBUG_PTE if ((gpa % 0x1000000) == 0) printf("[Last level%d, shift = %d]PTE:0x%lx\n", level, shift, pt[index]); #endif return (1ULL << shift); } static uint64_t amdvi_update_mapping(struct amdvi_domain *domain, vm_paddr_t gpa, vm_paddr_t hpa, uint64_t size, bool create) { uint64_t mapped, *ptp, len; int level; KASSERT(domain, ("domain is NULL")); level = domain->ptp_level; KASSERT(level, ("Page table level is 0")); ptp = domain->ptp; KASSERT(ptp, ("PTP is NULL")); mapped = 0; while (mapped < size) { len = amdvi_set_pt(ptp, level, gpa + mapped, hpa + mapped, PAGE_SIZE, create); if (!len) { printf("Error: Couldn't map HPA:0x%lx GPA:0x%lx\n", hpa, gpa); return (0); } mapped += len; } return (mapped); } static uint64_t amdvi_create_mapping(void *arg, vm_paddr_t gpa, vm_paddr_t hpa, uint64_t len) { struct amdvi_domain *domain; domain = (struct amdvi_domain *)arg; if (domain->id && !domain->ptp) { printf("ptp is NULL"); return (-1); } /* * If host domain is created w/o page table, skip IOMMU page * table set-up. */ if (domain->ptp) return (amdvi_update_mapping(domain, gpa, hpa, len, true)); else return (len); } static uint64_t amdvi_remove_mapping(void *arg, vm_paddr_t gpa, uint64_t len) { struct amdvi_domain *domain; domain = (struct amdvi_domain *)arg; /* * If host domain is created w/o page table, skip IOMMU page * table set-up. */ if (domain->ptp) return (amdvi_update_mapping(domain, gpa, 0, len, false)); return (len); } static struct amdvi_softc * amdvi_find_iommu(uint16_t devid) { struct amdvi_softc *softc; int i, j; for (i = 0; i < ivhd_count; i++) { softc = device_get_softc(ivhd_devs[i]); for (j = 0; j < softc->dev_cfg_cnt; j++) if ((devid >= softc->dev_cfg[j].start_id) && (devid <= softc->dev_cfg[j].end_id)) return (softc); } return (NULL); } /* * Set-up device table entry. * IOMMU spec Rev 2.0, section 3.2.2.2, some of the fields must * be set concurrently, e.g. read and write bits. */ static void amdvi_set_dte(struct amdvi_domain *domain, struct amdvi_softc *softc, uint16_t devid, bool enable) { struct amdvi_dte* temp; KASSERT(domain, ("domain is NULL for pci_rid:0x%x\n", devid)); KASSERT(softc, ("softc is NULL for pci_rid:0x%x\n", devid)); temp = &amdvi_dte[devid]; #ifdef AMDVI_ATS_ENABLE /* If IOMMU and device support IOTLB, enable it. */ if (amdvi_dev_support_iotlb(softc, devid) && softc->iotlb) temp->iotlb_enable = 1; #endif /* Avoid duplicate I/O faults. */ temp->sup_second_io_fault = 1; temp->sup_all_io_fault = amdvi_disable_io_fault; temp->dt_valid = 1; temp->domain_id = domain->id; if (enable) { if (domain->ptp) { temp->pt_base = vtophys(domain->ptp) >> 12; temp->pt_level = amdvi_ptp_level; } /* * XXX: Page table valid[TV] bit must be set even if host domain * page tables are not enabled. */ temp->pt_valid = 1; temp->read_allow = 1; temp->write_allow = 1; } } static void amdvi_inv_device(struct amdvi_softc *softc, uint16_t devid) { KASSERT(softc, ("softc is NULL")); amdvi_cmd_inv_dte(softc, devid); #ifdef AMDVI_ATS_ENABLE if (amdvi_dev_support_iotlb(softc, devid)) amdvi_cmd_inv_iotlb(softc, devid); #endif amdvi_wait(softc); } static void amdvi_add_device(void *arg, uint16_t devid) { struct amdvi_domain *domain; struct amdvi_softc *softc; domain = (struct amdvi_domain *)arg; KASSERT(domain != NULL, ("domain is NULL")); #ifdef AMDVI_DEBUG_CMD printf("Assigning device(%d.%d.%d) to domain:%d\n", RID2PCI_STR(devid), domain->id); #endif softc = amdvi_find_iommu(devid); if (softc == NULL) return; amdvi_set_dte(domain, softc, devid, true); amdvi_inv_device(softc, devid); } static void amdvi_remove_device(void *arg, uint16_t devid) { struct amdvi_domain *domain; struct amdvi_softc *softc; domain = (struct amdvi_domain *)arg; #ifdef AMDVI_DEBUG_CMD printf("Remove device(0x%x) from domain:%d\n", devid, domain->id); #endif softc = amdvi_find_iommu(devid); if (softc == NULL) return; amdvi_set_dte(domain, softc, devid, false); amdvi_inv_device(softc, devid); } static void amdvi_enable(void) { struct amdvi_ctrl *ctrl; struct amdvi_softc *softc; uint64_t val; int i; for (i = 0; i < ivhd_count; i++) { softc = device_get_softc(ivhd_devs[i]); KASSERT(softc, ("softc is NULL\n")); ctrl = softc->ctrl; KASSERT(ctrl, ("ctrl is NULL\n")); val = ( AMDVI_CTRL_EN | AMDVI_CTRL_CMD | AMDVI_CTRL_ELOG | AMDVI_CTRL_ELOGINT | AMDVI_CTRL_INV_TO_1S); if (softc->ivhd_flag & IVHD_FLAG_COH) val |= AMDVI_CTRL_COH; if (softc->ivhd_flag & IVHD_FLAG_HTT) val |= AMDVI_CTRL_HTT; if (softc->ivhd_flag & IVHD_FLAG_RPPW) val |= AMDVI_CTRL_RPPW; if (softc->ivhd_flag & IVHD_FLAG_PPW) val |= AMDVI_CTRL_PPW; if (softc->ivhd_flag & IVHD_FLAG_ISOC) val |= AMDVI_CTRL_ISOC; ctrl->control = val; } } static void amdvi_disable(void) { struct amdvi_ctrl *ctrl; struct amdvi_softc *softc; int i; for (i = 0; i < ivhd_count; i++) { softc = device_get_softc(ivhd_devs[i]); KASSERT(softc, ("softc is NULL\n")); ctrl = softc->ctrl; KASSERT(ctrl, ("ctrl is NULL\n")); ctrl->control = 0; } } static void amdvi_invalidate_tlb(void *arg) { struct amdvi_domain *domain; domain = (struct amdvi_domain *)arg; KASSERT(domain, ("domain is NULL")); amdvi_do_inv_domain(domain->id, false); } const struct iommu_ops iommu_ops_amd = { .init = amdvi_init, .cleanup = amdvi_cleanup, .enable = amdvi_enable, .disable = amdvi_disable, .create_domain = amdvi_create_domain, .destroy_domain = amdvi_destroy_domain, .create_mapping = amdvi_create_mapping, .remove_mapping = amdvi_remove_mapping, .add_device = amdvi_add_device, .remove_device = amdvi_remove_device, .invalidate_tlb = amdvi_invalidate_tlb }; diff --git a/sys/amd64/vmm/amd/ivrs_drv.c b/sys/amd64/vmm/amd/ivrs_drv.c index d8ea2442de23..7dd68d8aea95 100644 --- a/sys/amd64/vmm/amd/ivrs_drv.c +++ b/sys/amd64/vmm/amd/ivrs_drv.c @@ -1,760 +1,761 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2016, Anish Gupta (anish@freebsd.org) * Copyright (c) 2021 The FreeBSD Foundation * All rights reserved. * * 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 unmodified, 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 ``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 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 #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "io/iommu.h" #include "amdvi_priv.h" device_t *ivhd_devs; /* IVHD or AMD-Vi device list. */ int ivhd_count; /* Number of IVHD header. */ /* * Cached IVHD header list. * Single entry for each IVHD, filtered the legacy one. */ ACPI_IVRS_HARDWARE1 **ivhd_hdrs; extern int amdvi_ptp_level; /* Page table levels. */ typedef int (*ivhd_iter_t)(ACPI_IVRS_HEADER *ptr, void *arg); /* * Iterate IVRS table for IVHD and IVMD device type. */ static void ivrs_hdr_iterate_tbl(ivhd_iter_t iter, void *arg) { ACPI_TABLE_IVRS *ivrs; ACPI_IVRS_HEADER *ivrs_hdr, *end; ACPI_STATUS status; status = AcpiGetTable(ACPI_SIG_IVRS, 1, (ACPI_TABLE_HEADER **)&ivrs); if (ACPI_FAILURE(status)) return; if (ivrs->Header.Length == 0) { return; } ivrs_hdr = (ACPI_IVRS_HEADER *)(ivrs + 1); end = (ACPI_IVRS_HEADER *)((char *)ivrs + ivrs->Header.Length); while (ivrs_hdr < end) { if ((uint8_t *)ivrs_hdr + ivrs_hdr->Length > (uint8_t *)end) { printf("AMD-Vi:IVHD/IVMD is corrupted, length : %d\n", ivrs_hdr->Length); break; } switch (ivrs_hdr->Type) { case IVRS_TYPE_HARDWARE_LEGACY: /* Legacy */ case IVRS_TYPE_HARDWARE_EFR: case IVRS_TYPE_HARDWARE_MIXED: if (!iter(ivrs_hdr, arg)) return; break; case ACPI_IVRS_TYPE_MEMORY1: case ACPI_IVRS_TYPE_MEMORY2: case ACPI_IVRS_TYPE_MEMORY3: if (!iter(ivrs_hdr, arg)) return; break; default: printf("AMD-Vi:Not IVHD/IVMD type(%d)", ivrs_hdr->Type); } ivrs_hdr = (ACPI_IVRS_HEADER *)((uint8_t *)ivrs_hdr + ivrs_hdr->Length); } } static bool ivrs_is_ivhd(UINT8 type) { switch(type) { case IVRS_TYPE_HARDWARE_LEGACY: case IVRS_TYPE_HARDWARE_EFR: case IVRS_TYPE_HARDWARE_MIXED: return (true); default: return (false); } } /* Count the number of AMD-Vi devices in the system. */ static int ivhd_count_iter(ACPI_IVRS_HEADER * ivrs_he, void *arg) { int *count; count = (int *)arg; if (ivrs_is_ivhd(ivrs_he->Type)) (*count)++; return (1); } struct find_ivrs_hdr_args { int i; ACPI_IVRS_HEADER *ptr; }; static int ivrs_hdr_find_iter(ACPI_IVRS_HEADER * ivrs_hdr, void *args) { struct find_ivrs_hdr_args *fi; fi = (struct find_ivrs_hdr_args *)args; if (ivrs_is_ivhd(ivrs_hdr->Type)) { if (fi->i == 0) { fi->ptr = ivrs_hdr; return (0); } fi->i--; } return (1); } static ACPI_IVRS_HARDWARE1 * ivhd_find_by_index(int idx) { struct find_ivrs_hdr_args fi; fi.i = idx; fi.ptr = NULL; ivrs_hdr_iterate_tbl(ivrs_hdr_find_iter, &fi); return ((ACPI_IVRS_HARDWARE1 *)fi.ptr); } static void ivhd_dev_add_entry(struct amdvi_softc *softc, uint32_t start_id, uint32_t end_id, uint8_t cfg, bool ats) { struct ivhd_dev_cfg *dev_cfg; KASSERT(softc->dev_cfg_cap >= softc->dev_cfg_cnt, ("Impossible case: number of dev_cfg exceeding capacity")); if (softc->dev_cfg_cap == softc->dev_cfg_cnt) { if (softc->dev_cfg_cap == 0) softc->dev_cfg_cap = 1; else softc->dev_cfg_cap <<= 2; softc->dev_cfg = realloc(softc->dev_cfg, sizeof(*softc->dev_cfg) * softc->dev_cfg_cap, M_DEVBUF, M_WAITOK); } dev_cfg = &softc->dev_cfg[softc->dev_cfg_cnt++]; dev_cfg->start_id = start_id; dev_cfg->end_id = end_id; dev_cfg->data = cfg; dev_cfg->enable_ats = ats; } /* * Record device attributes as suggested by BIOS. */ static int ivhd_dev_parse(ACPI_IVRS_HARDWARE1 *ivhd, struct amdvi_softc *softc) { ACPI_IVRS_DE_HEADER *de; uint8_t *p, *end; int range_start_id = -1, range_end_id = -1, i; uint32_t *extended; uint8_t all_data = 0, range_data = 0; bool range_enable_ats = false, enable_ats; switch (ivhd->Header.Type) { case IVRS_TYPE_HARDWARE_LEGACY: p = (uint8_t *)ivhd + sizeof(ACPI_IVRS_HARDWARE1); break; case IVRS_TYPE_HARDWARE_EFR: case IVRS_TYPE_HARDWARE_MIXED: p = (uint8_t *)ivhd + sizeof(ACPI_IVRS_HARDWARE2); break; default: device_printf(softc->dev, "unknown type: 0x%x\n", ivhd->Header.Type); return (-1); } end = (uint8_t *)ivhd + ivhd->Header.Length; while (p < end) { de = (ACPI_IVRS_DE_HEADER *)p; switch (de->Type) { case ACPI_IVRS_TYPE_ALL: all_data = de->DataSetting; for (i = 0; i < softc->dev_cfg_cnt; i++) softc->dev_cfg[i].data |= all_data; break; case ACPI_IVRS_TYPE_SELECT: case ACPI_IVRS_TYPE_ALIAS_SELECT: case ACPI_IVRS_TYPE_EXT_SELECT: enable_ats = false; if (de->Type == ACPI_IVRS_TYPE_EXT_SELECT) { extended = (uint32_t *)(de + 1); enable_ats = (*extended & IVHD_DEV_EXT_ATS_DISABLE) ? false : true; } ivhd_dev_add_entry(softc, de->Id, de->Id, de->DataSetting | all_data, enable_ats); break; case ACPI_IVRS_TYPE_START: case ACPI_IVRS_TYPE_ALIAS_START: case ACPI_IVRS_TYPE_EXT_START: if (range_start_id != -1) { device_printf(softc->dev, "Unexpected start-of-range device entry\n"); return (EINVAL); } range_start_id = de->Id; range_data = de->DataSetting; if (de->Type == ACPI_IVRS_TYPE_EXT_START) { extended = (uint32_t *)(de + 1); range_enable_ats = (*extended & IVHD_DEV_EXT_ATS_DISABLE) ? false : true; } break; case ACPI_IVRS_TYPE_END: if (range_start_id == -1) { device_printf(softc->dev, "Unexpected end-of-range device entry\n"); return (EINVAL); } range_end_id = de->Id; if (range_end_id < range_start_id) { device_printf(softc->dev, "Device entry range going backward\n"); return (EINVAL); } ivhd_dev_add_entry(softc, range_start_id, range_end_id, range_data | all_data, range_enable_ats); range_start_id = range_end_id = -1; range_data = 0; all_data = 0; break; case ACPI_IVRS_TYPE_PAD4: break; case ACPI_IVRS_TYPE_SPECIAL: /* HPET or IOAPIC */ break; default: if ((de->Type < 5) || (de->Type >= ACPI_IVRS_TYPE_PAD8)) device_printf(softc->dev, "Unknown dev entry:0x%x\n", de->Type); } if (de->Type < 0x40) p += sizeof(ACPI_IVRS_DEVICE4); else if (de->Type < 0x80) p += sizeof(ACPI_IVRS_DEVICE8A); else { printf("Variable size IVHD type 0x%x not supported\n", de->Type); break; } } return (0); } static bool ivhd_is_newer(ACPI_IVRS_HEADER *old, ACPI_IVRS_HEADER *new) { if (old->DeviceId == new->DeviceId) { /* * Newer IVRS header type take precedence. */ if (old->Type == IVRS_TYPE_HARDWARE_LEGACY && ((new->Type == IVRS_TYPE_HARDWARE_EFR) || (new->Type == IVRS_TYPE_HARDWARE_MIXED))) return (true); /* * Mixed format IVHD header type take precedence * over fixed format IVHD header types. */ if (old->Type == IVRS_TYPE_HARDWARE_EFR && new->Type == IVRS_TYPE_HARDWARE_MIXED) return (true); } return (false); } static void ivhd_identify(driver_t *driver, device_t parent) { ACPI_TABLE_IVRS *ivrs; ACPI_IVRS_HARDWARE1 *ivhd; ACPI_STATUS status; int i, j, count = 0; uint32_t ivrs_ivinfo; if (acpi_disabled("ivhd")) return; status = AcpiGetTable(ACPI_SIG_IVRS, 1, (ACPI_TABLE_HEADER **)&ivrs); if (ACPI_FAILURE(status)) return; if (ivrs->Header.Length == 0) { return; } ivrs_ivinfo = ivrs->Info; printf("AMD-Vi: IVRS Info VAsize = %d PAsize = %d GVAsize = %d" " flags:%b\n", REG_BITS(ivrs_ivinfo, 21, 15), REG_BITS(ivrs_ivinfo, 14, 8), REG_BITS(ivrs_ivinfo, 7, 5), REG_BITS(ivrs_ivinfo, 22, 22), "\020\001EFRSup"); ivrs_hdr_iterate_tbl(ivhd_count_iter, &count); if (!count) return; ivhd_hdrs = malloc(sizeof(void *) * count, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0; i < count; i++) { ivhd = ivhd_find_by_index(i); KASSERT(ivhd, ("ivhd%d is NULL\n", i)); /* * Scan for presence of legacy and non-legacy device type * for same IOMMU device and override the old one. * * If there is no existing IVHD to the same IOMMU device, * the IVHD header pointer is appended. */ for (j = 0; j < ivhd_count; j++) { if (ivhd_is_newer(&ivhd_hdrs[j]->Header, &ivhd->Header)) break; } ivhd_hdrs[j] = ivhd; if (j == ivhd_count) ivhd_count++; } ivhd_devs = malloc(sizeof(device_t) * ivhd_count, M_DEVBUF, M_WAITOK | M_ZERO); for (i = 0, j = 0; i < ivhd_count; i++) { ivhd = ivhd_hdrs[i]; KASSERT(ivhd, ("ivhd%d is NULL\n", i)); /* * Use a high order to ensure that this driver is probed after * the Host-PCI bridge and the root PCI bus. */ ivhd_devs[i] = BUS_ADD_CHILD(parent, ACPI_DEV_BASE_ORDER + 10 * 10, "ivhd", i); /* * XXX: In case device was not destroyed before, add will fail. * locate the old device instance. */ if (ivhd_devs[i] == NULL) { ivhd_devs[i] = device_find_child(parent, "ivhd", i); if (ivhd_devs[i] == NULL) { printf("AMD-Vi: cant find ivhd%d\n", i); break; } } j++; } /* * Update device count in case failed to attach. */ ivhd_count = j; } static int ivhd_probe(device_t dev) { ACPI_IVRS_HARDWARE1 *ivhd; int unit; if (acpi_get_handle(dev) != NULL) return (ENXIO); unit = device_get_unit(dev); KASSERT((unit < ivhd_count), ("ivhd unit %d > count %d", unit, ivhd_count)); ivhd = ivhd_hdrs[unit]; KASSERT(ivhd, ("ivhd is NULL")); switch (ivhd->Header.Type) { case IVRS_TYPE_HARDWARE_EFR: device_set_desc(dev, "AMD-Vi/IOMMU ivhd with EFR"); break; case IVRS_TYPE_HARDWARE_MIXED: device_set_desc(dev, "AMD-Vi/IOMMU ivhd in mixed format"); break; case IVRS_TYPE_HARDWARE_LEGACY: default: device_set_desc(dev, "AMD-Vi/IOMMU ivhd"); break; } return (BUS_PROBE_NOWILDCARD); } static void ivhd_print_flag(device_t dev, enum IvrsType ivhd_type, uint8_t flag) { /* * IVHD lgeacy type has two extra high bits in flag which has * been moved to EFR for non-legacy device. */ switch (ivhd_type) { case IVRS_TYPE_HARDWARE_LEGACY: device_printf(dev, "Flag:%b\n", flag, "\020" "\001HtTunEn" "\002PassPW" "\003ResPassPW" "\004Isoc" "\005IotlbSup" "\006Coherent" "\007PreFSup" "\010PPRSup"); break; case IVRS_TYPE_HARDWARE_EFR: case IVRS_TYPE_HARDWARE_MIXED: device_printf(dev, "Flag:%b\n", flag, "\020" "\001HtTunEn" "\002PassPW" "\003ResPassPW" "\004Isoc" "\005IotlbSup" "\006Coherent"); break; default: device_printf(dev, "Can't decode flag of ivhd type :0x%x\n", ivhd_type); break; } } /* * Feature in legacy IVHD type(0x10) and attribute in newer type(0x11 and 0x40). */ static void ivhd_print_feature(device_t dev, enum IvrsType ivhd_type, uint32_t feature) { switch (ivhd_type) { case IVRS_TYPE_HARDWARE_LEGACY: device_printf(dev, "Features(type:0x%x) HATS = %d GATS = %d" " MsiNumPPR = %d PNBanks= %d PNCounters= %d\n", ivhd_type, REG_BITS(feature, 31, 30), REG_BITS(feature, 29, 28), REG_BITS(feature, 27, 23), REG_BITS(feature, 22, 17), REG_BITS(feature, 16, 13)); device_printf(dev, "max PASID = %d GLXSup = %d Feature:%b\n", REG_BITS(feature, 12, 8), REG_BITS(feature, 4, 3), feature, "\020" "\002NXSup" "\003GTSup" "\004" "\005IASup" "\006GASup" "\007HESup"); break; /* Fewer features or attributes are reported in non-legacy type. */ case IVRS_TYPE_HARDWARE_EFR: case IVRS_TYPE_HARDWARE_MIXED: device_printf(dev, "Features(type:0x%x) MsiNumPPR = %d" " PNBanks= %d PNCounters= %d\n", ivhd_type, REG_BITS(feature, 27, 23), REG_BITS(feature, 22, 17), REG_BITS(feature, 16, 13)); break; default: /* Other ivhd type features are not decoded. */ device_printf(dev, "Can't decode ivhd type :0x%x\n", ivhd_type); } } /* Print extended features of IOMMU. */ static void ivhd_print_ext_feature(device_t dev, uint64_t ext_feature) { uint32_t ext_low, ext_high; if (!ext_feature) return; ext_low = ext_feature; device_printf(dev, "Extended features[31:0]:%b " "HATS = 0x%x GATS = 0x%x " "GLXSup = 0x%x SmiFSup = 0x%x SmiFRC = 0x%x " "GAMSup = 0x%x DualPortLogSup = 0x%x DualEventLogSup = 0x%x\n", (int)ext_low, "\020" "\001PreFSup" "\002PPRSup" "\003" "\004NXSup" "\005GTSup" "\006" "\007IASup" "\010GASup" "\011HESup" "\012PCSup", REG_BITS(ext_low, 11, 10), REG_BITS(ext_low, 13, 12), REG_BITS(ext_low, 15, 14), REG_BITS(ext_low, 17, 16), REG_BITS(ext_low, 20, 18), REG_BITS(ext_low, 23, 21), REG_BITS(ext_low, 25, 24), REG_BITS(ext_low, 29, 28)); ext_high = ext_feature >> 32; device_printf(dev, "Extended features[62:32]:%b " "Max PASID: 0x%x DevTblSegSup = 0x%x " "MarcSup = 0x%x\n", (int)(ext_high), "\020" "\006USSup" "\011PprOvrflwEarlySup" "\012PPRAutoRspSup" "\015BlKStopMrkSup" "\016PerfOptSup" "\017MsiCapMmioSup" "\021GIOSup" "\022HASup" "\023EPHSup" "\024AttrFWSup" "\025HDSup" "\027InvIotlbSup", REG_BITS(ext_high, 5, 0), REG_BITS(ext_high, 8, 7), REG_BITS(ext_high, 11, 10)); } static int ivhd_print_cap(struct amdvi_softc *softc, ACPI_IVRS_HARDWARE1 * ivhd) { device_t dev; int max_ptp_level; dev = softc->dev; ivhd_print_flag(dev, softc->ivhd_type, softc->ivhd_flag); ivhd_print_feature(dev, softc->ivhd_type, softc->ivhd_feature); ivhd_print_ext_feature(dev, softc->ext_feature); max_ptp_level = 7; /* Make sure device support minimum page level as requested by user. */ if (max_ptp_level < amdvi_ptp_level) { device_printf(dev, "insufficient PTP level:%d\n", max_ptp_level); return (EINVAL); } else { device_printf(softc->dev, "supported paging level:%d, will use only: %d\n", max_ptp_level, amdvi_ptp_level); } return (0); } static int ivhd_attach(device_t dev) { ACPI_IVRS_HARDWARE1 *ivhd; ACPI_IVRS_HARDWARE2 *ivhd_efr; struct amdvi_softc *softc; int status, unit; unit = device_get_unit(dev); KASSERT((unit < ivhd_count), ("ivhd unit %d > count %d", unit, ivhd_count)); /* Make sure its same device for which attach is called. */ KASSERT((ivhd_devs[unit] == dev), ("Not same device old %p new %p", ivhd_devs[unit], dev)); softc = device_get_softc(dev); softc->dev = dev; ivhd = ivhd_hdrs[unit]; KASSERT(ivhd, ("ivhd is NULL")); - softc->pci_dev = pci_find_bsf(PCI_RID2BUS(ivhd->Header.DeviceId), + softc->pci_dev = pci_find_dbsf(ivhd->PciSegmentGroup, + PCI_RID2BUS(ivhd->Header.DeviceId), PCI_RID2SLOT(ivhd->Header.DeviceId), PCI_RID2FUNC(ivhd->Header.DeviceId)); softc->ivhd_type = ivhd->Header.Type; softc->pci_seg = ivhd->PciSegmentGroup; softc->pci_rid = ivhd->Header.DeviceId; softc->ivhd_flag = ivhd->Header.Flags; /* * On lgeacy IVHD type(0x10), it is documented as feature * but in newer type it is attribute. */ softc->ivhd_feature = ivhd->FeatureReporting; /* * PCI capability has more capabilities that are not part of IVRS. */ softc->cap_off = ivhd->CapabilityOffset; #ifdef notyet /* IVHD Info bit[4:0] is event MSI/X number. */ softc->event_msix = ivhd->Info & 0x1F; #endif switch (ivhd->Header.Type) { case IVRS_TYPE_HARDWARE_EFR: case IVRS_TYPE_HARDWARE_MIXED: ivhd_efr = (ACPI_IVRS_HARDWARE2 *)ivhd; softc->ext_feature = ivhd_efr->EfrRegisterImage; break; } softc->ctrl = (struct amdvi_ctrl *) PHYS_TO_DMAP(ivhd->BaseAddress); status = ivhd_dev_parse(ivhd, softc); if (status != 0) { device_printf(dev, "endpoint device parsing error=%d\n", status); goto fail; } status = ivhd_print_cap(softc, ivhd); if (status != 0) goto fail; status = amdvi_setup_hw(softc); if (status != 0) { device_printf(dev, "couldn't be initialised, error=%d\n", status); goto fail; } return (0); fail: free(softc->dev_cfg, M_DEVBUF); return (status); } static int ivhd_detach(device_t dev) { struct amdvi_softc *softc; softc = device_get_softc(dev); amdvi_teardown_hw(softc); free(softc->dev_cfg, M_DEVBUF); /* * XXX: delete the device. * don't allow detach, return EBUSY. */ return (0); } static int ivhd_suspend(device_t dev) { return (0); } static int ivhd_resume(device_t dev) { return (0); } static device_method_t ivhd_methods[] = { DEVMETHOD(device_identify, ivhd_identify), DEVMETHOD(device_probe, ivhd_probe), DEVMETHOD(device_attach, ivhd_attach), DEVMETHOD(device_detach, ivhd_detach), DEVMETHOD(device_suspend, ivhd_suspend), DEVMETHOD(device_resume, ivhd_resume), DEVMETHOD_END }; static driver_t ivhd_driver = { "ivhd", ivhd_methods, sizeof(struct amdvi_softc), }; /* * Load this module at the end after PCI re-probing to configure interrupt. */ DRIVER_MODULE_ORDERED(ivhd, acpi, ivhd_driver, 0, 0, SI_ORDER_ANY); MODULE_DEPEND(ivhd, acpi, 1, 1, 1); MODULE_DEPEND(ivhd, pci, 1, 1, 1); diff --git a/sys/arm64/acpica/pci_cfgreg.c b/sys/arm64/acpica/pci_cfgreg.c index 97c640dfb81c..55627d4abfaf 100644 --- a/sys/arm64/acpica/pci_cfgreg.c +++ b/sys/arm64/acpica/pci_cfgreg.c @@ -1,73 +1,74 @@ /*- * Copyright (c) 2015 The FreeBSD Foundation * * This software was developed by Semihalf under * the sponsorship of 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 #include #include #include #include /* * This file contains stubs for ACPI PCI functions */ /* * Read configuration space register */ uint32_t -pci_cfgregread(int bus, int slot, int func, int reg, int bytes) +pci_cfgregread(int domain, int bus, int slot, int func, int reg, int bytes) { /* ARM64TODO */ panic("pci_cfgregread not implemented"); return (0); } /* * Write configuration space register */ void -pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes) +pci_cfgregwrite(int domain, int bus, int slot, int func, int reg, uint32_t data, + int bytes) { /* ARM64TODO */ panic("pci_cfgregwrite not implemented"); } /* * Initialize access to configuration space */ int pci_cfgregopen(void) { /* ARM64TODO */ panic("pci_cfgregopen not implemented"); return (0); } diff --git a/sys/arm64/include/pci_cfgreg.h b/sys/arm64/include/pci_cfgreg.h index 2c34f62c00c8..579dcd954c9b 100644 --- a/sys/arm64/include/pci_cfgreg.h +++ b/sys/arm64/include/pci_cfgreg.h @@ -1,33 +1,33 @@ /*- * Copyright (c) 2015 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. */ #ifndef _MACHINE_PCI_CFGREG_H #define _MACHINE_PCI_CFGREG_H int pci_cfgregopen(void); -uint32_t pci_cfgregread(int, int, int, int, int); -void pci_cfgregwrite(int, int, int, int, u_int32_t, int); +uint32_t pci_cfgregread(int, int, int, int, int, int); +void pci_cfgregwrite(int, int, int, int, int, uint32_t, int); #endif /* !_MACHINE_PCI_CFGREG_H */ diff --git a/sys/dev/acpica/Osd/OsdHardware.c b/sys/dev/acpica/Osd/OsdHardware.c index b9c11454913b..fbaf76d2a91a 100644 --- a/sys/dev/acpica/Osd/OsdHardware.c +++ b/sys/dev/acpica/Osd/OsdHardware.c @@ -1,140 +1,140 @@ /*- * Copyright (c) 2000, 2001 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. * * 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. */ /* * 6.7 : Hardware Abstraction */ #include #include #include #include extern int acpi_susp_bounce; ACPI_STATUS AcpiOsEnterSleep(UINT8 SleepState, UINT32 RegaValue, UINT32 RegbValue) { /* If testing device suspend only, back out of everything here. */ if (acpi_susp_bounce) return (AE_CTRL_TERMINATE); return (AE_OK); } /* * ACPICA's rather gung-ho approach to hardware resource ownership is a little * troublesome insofar as there is no easy way for us to know in advance * exactly which I/O resources it's going to want to use. * * In order to deal with this, we ignore resource ownership entirely, and simply * use the native I/O space accessor functionality. This is Evil, but it works. */ ACPI_STATUS AcpiOsReadPort(ACPI_IO_ADDRESS InPort, UINT32 *Value, UINT32 Width) { switch (Width) { case 8: *Value = iodev_read_1(InPort); break; case 16: *Value = iodev_read_2(InPort); break; case 32: *Value = iodev_read_4(InPort); break; } return (AE_OK); } ACPI_STATUS AcpiOsWritePort(ACPI_IO_ADDRESS OutPort, UINT32 Value, UINT32 Width) { switch (Width) { case 8: iodev_write_1(OutPort, Value); break; case 16: iodev_write_2(OutPort, Value); break; case 32: iodev_write_4(OutPort, Value); break; } return (AE_OK); } ACPI_STATUS AcpiOsReadPciConfiguration(ACPI_PCI_ID *PciId, UINT32 Register, UINT64 *Value, UINT32 Width) { #ifdef __aarch64__ /* ARM64TODO: Add pci support */ return (AE_SUPPORT); #else if (Width == 64) return (AE_SUPPORT); if (!pci_cfgregopen()) return (AE_NOT_EXIST); - *(UINT64 *)Value = pci_cfgregread(PciId->Bus, PciId->Device, + *(UINT64 *)Value = pci_cfgregread(PciId->Segment, PciId->Bus, PciId->Device, PciId->Function, Register, Width / 8); return (AE_OK); #endif } ACPI_STATUS AcpiOsWritePciConfiguration (ACPI_PCI_ID *PciId, UINT32 Register, UINT64 Value, UINT32 Width) { #ifdef __aarch64__ /* ARM64TODO: Add pci support */ return (AE_SUPPORT); #else if (Width == 64) return (AE_SUPPORT); if (!pci_cfgregopen()) return (AE_NOT_EXIST); - pci_cfgregwrite(PciId->Bus, PciId->Device, PciId->Function, Register, - Value, Width / 8); + pci_cfgregwrite(PciId->Segment, PciId->Bus, PciId->Device, PciId->Function, + Register, Value, Width / 8); return (AE_OK); #endif } diff --git a/sys/dev/acpica/acpi_pci_link.c b/sys/dev/acpica/acpi_pci_link.c index f354441bfd86..dac07a07ae7d 100644 --- a/sys/dev/acpica/acpi_pci_link.c +++ b/sys/dev/acpica/acpi_pci_link.c @@ -1,1125 +1,1132 @@ /*- * Copyright (c) 2002 Mitsuru IWASAKI * All rights reserved. * * 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 #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" /* Hooks for the ACPI CA debugging infrastructure. */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("PCI_LINK") ACPI_SERIAL_DECL(pci_link, "ACPI PCI link"); #define NUM_ISA_INTERRUPTS 16 #define NUM_ACPI_INTERRUPTS 256 /* * An ACPI PCI link device may contain multiple links. Each link has its * own ACPI resource. _PRT entries specify which link is being used via * the Source Index. * * XXX: A note about Source Indices and DPFs: Currently we assume that * the DPF start and end tags are not counted towards the index that * Source Index corresponds to. Also, we assume that when DPFs are in use * they various sets overlap in terms of Indices. Here's an example * resource list indicating these assumptions: * * Resource Index * -------- ----- * I/O Port 0 * Start DPF - * IRQ 1 * MemIO 2 * Start DPF - * IRQ 1 * MemIO 2 * End DPF - * DMA Channel 3 * * The XXX is because I'm not sure if this is a valid assumption to make. */ /* States during DPF processing. */ #define DPF_OUTSIDE 0 #define DPF_FIRST 1 #define DPF_IGNORE 2 struct link; struct acpi_pci_link_softc { int pl_num_links; int pl_crs_bad; struct link *pl_links; device_t pl_dev; }; struct link { struct acpi_pci_link_softc *l_sc; uint8_t l_bios_irq; uint8_t l_irq; uint8_t l_initial_irq; UINT32 l_crs_type; int l_res_index; int l_num_irqs; int *l_irqs; int l_references; bool l_routed:1; bool l_isa_irq:1; ACPI_RESOURCE l_prs_template; }; struct link_count_request { int in_dpf; int count; }; struct link_res_request { struct acpi_pci_link_softc *sc; int in_dpf; int res_index; int link_index; }; static MALLOC_DEFINE(M_PCI_LINK, "pci_link", "ACPI PCI Link structures"); static int pci_link_interrupt_weights[NUM_ACPI_INTERRUPTS]; static int pci_link_bios_isa_irqs; static char *pci_link_ids[] = { "PNP0C0F", NULL }; /* * Fetch the short name associated with an ACPI handle and save it in the * passed in buffer. */ static ACPI_STATUS acpi_short_name(ACPI_HANDLE handle, char *buffer, size_t buflen) { ACPI_BUFFER buf; buf.Length = buflen; buf.Pointer = buffer; return (AcpiGetName(handle, ACPI_SINGLE_NAME, &buf)); } static int acpi_pci_link_probe(device_t dev) { char descr[28], name[12]; int rv; /* * We explicitly do not check _STA since not all systems set it to * sensible values. */ if (acpi_disabled("pci_link")) return (ENXIO); rv = ACPI_ID_PROBE(device_get_parent(dev), dev, pci_link_ids, NULL); if (rv > 0) return (rv); if (ACPI_SUCCESS(acpi_short_name(acpi_get_handle(dev), name, sizeof(name)))) { snprintf(descr, sizeof(descr), "ACPI PCI Link %s", name); device_set_desc_copy(dev, descr); } else device_set_desc(dev, "ACPI PCI Link"); device_quiet(dev); return (rv); } static ACPI_STATUS acpi_count_irq_resources(ACPI_RESOURCE *res, void *context) { struct link_count_request *req; req = (struct link_count_request *)context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (req->in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ req->in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ req->in_dpf = DPF_IGNORE; break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(req->in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); req->in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: /* * Don't count resources if we are in a DPF set that we are * ignoring. */ if (req->in_dpf != DPF_IGNORE) req->count++; } return (AE_OK); } static ACPI_STATUS link_add_crs(ACPI_RESOURCE *res, void *context) { struct link_res_request *req; struct link *link; ACPI_SERIAL_ASSERT(pci_link); req = (struct link_res_request *)context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (req->in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ req->in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ panic( "%s: Multiple dependent functions within a current resource", __func__); break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(req->in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); req->in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: KASSERT(req->link_index < req->sc->pl_num_links, ("%s: array boundary violation", __func__)); link = &req->sc->pl_links[req->link_index]; link->l_res_index = req->res_index; link->l_crs_type = res->Type; req->link_index++; req->res_index++; /* * Only use the current value if there's one IRQ. Some * systems return multiple IRQs (which is nonsense for _CRS) * when the link hasn't been programmed. */ if (res->Type == ACPI_RESOURCE_TYPE_IRQ) { if (res->Data.Irq.InterruptCount == 1) link->l_irq = res->Data.Irq.Interrupts[0]; } else if (res->Data.ExtendedIrq.InterruptCount == 1) link->l_irq = res->Data.ExtendedIrq.Interrupts[0]; /* * An IRQ of zero means that the link isn't routed. */ if (link->l_irq == 0) link->l_irq = PCI_INVALID_IRQ; break; default: req->res_index++; } return (AE_OK); } /* * Populate the set of possible IRQs for each device. */ static ACPI_STATUS link_add_prs(ACPI_RESOURCE *res, void *context) { ACPI_RESOURCE *tmp; struct link_res_request *req; struct link *link; UINT8 *irqs = NULL; UINT32 *ext_irqs = NULL; int i, is_ext_irq = 1; ACPI_SERIAL_ASSERT(pci_link); req = (struct link_res_request *)context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (req->in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ req->in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ req->in_dpf = DPF_IGNORE; break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(req->in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); req->in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: is_ext_irq = 0; /* fall through */ case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: /* * Don't parse resources if we are in a DPF set that we are * ignoring. */ if (req->in_dpf == DPF_IGNORE) break; KASSERT(req->link_index < req->sc->pl_num_links, ("%s: array boundary violation", __func__)); link = &req->sc->pl_links[req->link_index]; if (link->l_res_index == -1) { KASSERT(req->sc->pl_crs_bad, ("res_index should be set")); link->l_res_index = req->res_index; } req->link_index++; req->res_index++; /* * Stash a copy of the resource for later use when doing * _SRS. */ tmp = &link->l_prs_template; if (is_ext_irq) { bcopy(res, tmp, ACPI_RS_SIZE(tmp->Data.ExtendedIrq)); /* * XXX acpi_AppendBufferResource() cannot handle * optional data. */ bzero(&tmp->Data.ExtendedIrq.ResourceSource, sizeof(tmp->Data.ExtendedIrq.ResourceSource)); tmp->Length = ACPI_RS_SIZE(tmp->Data.ExtendedIrq); link->l_num_irqs = res->Data.ExtendedIrq.InterruptCount; ext_irqs = res->Data.ExtendedIrq.Interrupts; } else { bcopy(res, tmp, ACPI_RS_SIZE(tmp->Data.Irq)); link->l_num_irqs = res->Data.Irq.InterruptCount; irqs = res->Data.Irq.Interrupts; } if (link->l_num_irqs == 0) break; /* * Save a list of the valid IRQs. Also, if all of the * valid IRQs are ISA IRQs, then mark this link as * routed via an ISA interrupt. */ link->l_isa_irq = true; link->l_irqs = malloc(sizeof(int) * link->l_num_irqs, M_PCI_LINK, M_WAITOK | M_ZERO); for (i = 0; i < link->l_num_irqs; i++) { if (is_ext_irq) { link->l_irqs[i] = ext_irqs[i]; if (ext_irqs[i] >= NUM_ISA_INTERRUPTS) link->l_isa_irq = false; } else { link->l_irqs[i] = irqs[i]; if (irqs[i] >= NUM_ISA_INTERRUPTS) link->l_isa_irq = false; } } /* * If this is not an ISA IRQ but _CRS used a non-extended * IRQ descriptor, don't use _CRS as a template for _SRS. */ if (!req->sc->pl_crs_bad && !link->l_isa_irq && link->l_crs_type == ACPI_RESOURCE_TYPE_IRQ) req->sc->pl_crs_bad = true; break; default: if (req->in_dpf == DPF_IGNORE) break; if (req->sc->pl_crs_bad) device_printf(req->sc->pl_dev, "Warning: possible resource %d will be lost during _SRS\n", req->res_index); req->res_index++; } return (AE_OK); } static bool link_valid_irq(struct link *link, int irq) { int i; ACPI_SERIAL_ASSERT(pci_link); /* Invalid interrupts are never valid. */ if (!PCI_INTERRUPT_VALID(irq)) return (false); /* Any interrupt in the list of possible interrupts is valid. */ for (i = 0; i < link->l_num_irqs; i++) if (link->l_irqs[i] == irq) return (true); /* * For links routed via an ISA interrupt, if the SCI is routed via * an ISA interrupt, the SCI is always treated as a valid IRQ. */ if (link->l_isa_irq && AcpiGbl_FADT.SciInterrupt == irq && irq < NUM_ISA_INTERRUPTS) return (true); /* If the interrupt wasn't found in the list it is not valid. */ return (false); } static void acpi_pci_link_dump(struct acpi_pci_link_softc *sc, int header, const char *tag) { struct link *link; char buf[16]; int i, j; ACPI_SERIAL_ASSERT(pci_link); if (header) { snprintf(buf, sizeof(buf), "%s:", device_get_nameunit(sc->pl_dev)); printf("%-16.16s Index IRQ Rtd Ref IRQs\n", buf); } for (i = 0; i < sc->pl_num_links; i++) { link = &sc->pl_links[i]; printf(" %-14.14s %5d %3d %c %3d ", i == 0 ? tag : "", i, link->l_irq, link->l_routed ? 'Y' : 'N', link->l_references); if (link->l_num_irqs == 0) printf(" none"); else for (j = 0; j < link->l_num_irqs; j++) printf(" %d", link->l_irqs[j]); printf("\n"); } } static int acpi_pci_link_attach(device_t dev) { struct acpi_pci_link_softc *sc; struct link_count_request creq; struct link_res_request rreq; ACPI_STATUS status; int i; sc = device_get_softc(dev); sc->pl_dev = dev; ACPI_SERIAL_BEGIN(pci_link); /* * Count the number of current resources so we know how big of * a link array to allocate. On some systems, _CRS is broken, * so for those systems try to derive the count from _PRS instead. */ creq.in_dpf = DPF_OUTSIDE; creq.count = 0; status = AcpiWalkResources(acpi_get_handle(dev), "_CRS", acpi_count_irq_resources, &creq); sc->pl_crs_bad = ACPI_FAILURE(status); if (sc->pl_crs_bad) { creq.in_dpf = DPF_OUTSIDE; creq.count = 0; status = AcpiWalkResources(acpi_get_handle(dev), "_PRS", acpi_count_irq_resources, &creq); if (ACPI_FAILURE(status)) { device_printf(dev, "Unable to parse _CRS or _PRS: %s\n", AcpiFormatException(status)); ACPI_SERIAL_END(pci_link); return (ENXIO); } } sc->pl_num_links = creq.count; if (creq.count == 0) { ACPI_SERIAL_END(pci_link); return (0); } sc->pl_links = malloc(sizeof(struct link) * sc->pl_num_links, M_PCI_LINK, M_WAITOK | M_ZERO); /* Initialize the child links. */ for (i = 0; i < sc->pl_num_links; i++) { sc->pl_links[i].l_irq = PCI_INVALID_IRQ; sc->pl_links[i].l_bios_irq = PCI_INVALID_IRQ; sc->pl_links[i].l_sc = sc; sc->pl_links[i].l_isa_irq = false; sc->pl_links[i].l_res_index = -1; } /* Try to read the current settings from _CRS if it is valid. */ if (!sc->pl_crs_bad) { rreq.in_dpf = DPF_OUTSIDE; rreq.link_index = 0; rreq.res_index = 0; rreq.sc = sc; status = AcpiWalkResources(acpi_get_handle(dev), "_CRS", link_add_crs, &rreq); if (ACPI_FAILURE(status)) { device_printf(dev, "Unable to parse _CRS: %s\n", AcpiFormatException(status)); goto fail; } } /* * Try to read the possible settings from _PRS. Note that if the * _CRS is toast, we depend on having a working _PRS. However, if * _CRS works, then it is ok for _PRS to be missing. */ rreq.in_dpf = DPF_OUTSIDE; rreq.link_index = 0; rreq.res_index = 0; rreq.sc = sc; status = AcpiWalkResources(acpi_get_handle(dev), "_PRS", link_add_prs, &rreq); if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND || sc->pl_crs_bad)) { device_printf(dev, "Unable to parse _PRS: %s\n", AcpiFormatException(status)); goto fail; } if (bootverbose) acpi_pci_link_dump(sc, 1, "Initial Probe"); /* Verify initial IRQs if we have _PRS. */ if (status != AE_NOT_FOUND) for (i = 0; i < sc->pl_num_links; i++) if (!link_valid_irq(&sc->pl_links[i], sc->pl_links[i].l_irq)) sc->pl_links[i].l_irq = PCI_INVALID_IRQ; if (bootverbose) acpi_pci_link_dump(sc, 0, "Validation"); /* Save initial IRQs. */ for (i = 0; i < sc->pl_num_links; i++) sc->pl_links[i].l_initial_irq = sc->pl_links[i].l_irq; /* * Try to disable this link. If successful, set the current IRQ to * zero and flags to indicate this link is not routed. If we can't * run _DIS (i.e., the method doesn't exist), assume the initial * IRQ was routed by the BIOS. */ if (ACPI_SUCCESS(AcpiEvaluateObject(acpi_get_handle(dev), "_DIS", NULL, NULL))) for (i = 0; i < sc->pl_num_links; i++) sc->pl_links[i].l_irq = PCI_INVALID_IRQ; else for (i = 0; i < sc->pl_num_links; i++) if (PCI_INTERRUPT_VALID(sc->pl_links[i].l_irq)) sc->pl_links[i].l_routed = true; if (bootverbose) acpi_pci_link_dump(sc, 0, "After Disable"); ACPI_SERIAL_END(pci_link); return (0); fail: ACPI_SERIAL_END(pci_link); for (i = 0; i < sc->pl_num_links; i++) if (sc->pl_links[i].l_irqs != NULL) free(sc->pl_links[i].l_irqs, M_PCI_LINK); free(sc->pl_links, M_PCI_LINK); return (ENXIO); } /* XXX: Note that this is identical to pci_pir_search_irq(). */ static uint8_t -acpi_pci_link_search_irq(int bus, int device, int pin) +acpi_pci_link_search_irq(int domain, int bus, int device, int pin) { uint32_t value; uint8_t func, maxfunc; /* See if we have a valid device at function 0. */ - value = pci_cfgregread(bus, device, 0, PCIR_VENDOR, 2); + value = pci_cfgregread(domain, bus, device, 0, PCIR_VENDOR, 2); if (value == PCIV_INVALID) return (PCI_INVALID_IRQ); - value = pci_cfgregread(bus, device, 0, PCIR_HDRTYPE, 1); + value = pci_cfgregread(domain, bus, device, 0, PCIR_HDRTYPE, 1); if ((value & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) return (PCI_INVALID_IRQ); if (value & PCIM_MFDEV) maxfunc = PCI_FUNCMAX; else maxfunc = 0; /* Scan all possible functions at this device. */ for (func = 0; func <= maxfunc; func++) { - value = pci_cfgregread(bus, device, func, PCIR_VENDOR, 2); + value = pci_cfgregread(domain, bus, device, func, PCIR_VENDOR, + 2); if (value == PCIV_INVALID) continue; - value = pci_cfgregread(bus, device, func, PCIR_INTPIN, 1); + value = pci_cfgregread(domain, bus, device, func, PCIR_INTPIN, + 1); /* * See if it uses the pin in question. Note that the passed * in pin uses 0 for A, .. 3 for D whereas the intpin * register uses 0 for no interrupt, 1 for A, .. 4 for D. */ if (value != pin + 1) continue; - value = pci_cfgregread(bus, device, func, PCIR_INTLINE, 1); + value = pci_cfgregread(domain, bus, device, func, PCIR_INTLINE, + 1); if (bootverbose) printf( "ACPI: Found matching pin for %d.%d.INT%c at func %d: %d\n", bus, device, pin + 'A', func, value); if (value != PCI_INVALID_IRQ) return (value); } return (PCI_INVALID_IRQ); } /* * Find the link structure that corresponds to the resource index passed in * via 'source_index'. */ static struct link * acpi_pci_link_lookup(device_t dev, int source_index) { struct acpi_pci_link_softc *sc; int i; ACPI_SERIAL_ASSERT(pci_link); sc = device_get_softc(dev); for (i = 0; i < sc->pl_num_links; i++) if (sc->pl_links[i].l_res_index == source_index) return (&sc->pl_links[i]); return (NULL); } void acpi_pci_link_add_reference(device_t dev, int index, device_t pcib, int slot, int pin) { struct link *link; uint8_t bios_irq; - uintptr_t bus; + uintptr_t bus, domain; /* - * Look up the PCI bus for the specified PCI bridge device. Note - * that the PCI bridge device might not have any children yet. - * However, looking up its bus number doesn't require a valid child - * device, so we just pass NULL. + * Look up the PCI domain and bus for the specified PCI bridge + * device. Note that the PCI bridge device might not have any + * children yet. However, looking up these IVARs doesn't + * require a valid child device, so we just pass NULL. */ if (BUS_READ_IVAR(pcib, NULL, PCIB_IVAR_BUS, &bus) != 0) { device_printf(pcib, "Unable to read PCI bus number"); panic("PCI bridge without a bus number"); } + if (BUS_READ_IVAR(pcib, NULL, PCIB_IVAR_DOMAIN, &domain) != 0) { + device_printf(pcib, "Unable to read PCI domain number"); + panic("PCI bridge without a domain number"); + } /* Bump the reference count. */ ACPI_SERIAL_BEGIN(pci_link); link = acpi_pci_link_lookup(dev, index); if (link == NULL) { device_printf(dev, "apparently invalid index %d\n", index); ACPI_SERIAL_END(pci_link); return; } link->l_references++; if (link->l_routed) pci_link_interrupt_weights[link->l_irq]++; /* * The BIOS only routes interrupts via ISA IRQs using the ATPICs * (8259As). Thus, if this link is routed via an ISA IRQ, go * look to see if the BIOS routed an IRQ for this link at the - * indicated (bus, slot, pin). If so, we prefer that IRQ for + * indicated (domain, bus, slot, pin). If so, we prefer that IRQ for * this link and add that IRQ to our list of known-good IRQs. * This provides a good work-around for link devices whose _CRS * method is either broken or bogus. We only use the value * returned by _CRS if we can't find a valid IRQ via this method * in fact. * * If this link is not routed via an ISA IRQ (because we are using * APIC for example), then don't bother looking up the BIOS IRQ * as if we find one it won't be valid anyway. */ if (!link->l_isa_irq) { ACPI_SERIAL_END(pci_link); return; } /* Try to find a BIOS IRQ setting from any matching devices. */ - bios_irq = acpi_pci_link_search_irq(bus, slot, pin); + bios_irq = acpi_pci_link_search_irq(domain, bus, slot, pin); if (!PCI_INTERRUPT_VALID(bios_irq)) { ACPI_SERIAL_END(pci_link); return; } /* Validate the BIOS IRQ. */ if (!link_valid_irq(link, bios_irq)) { device_printf(dev, "BIOS IRQ %u for %d.%d.INT%c is invalid\n", bios_irq, (int)bus, slot, pin + 'A'); } else if (!PCI_INTERRUPT_VALID(link->l_bios_irq)) { link->l_bios_irq = bios_irq; if (bios_irq < NUM_ISA_INTERRUPTS) pci_link_bios_isa_irqs |= (1 << bios_irq); if (bios_irq != link->l_initial_irq && PCI_INTERRUPT_VALID(link->l_initial_irq)) device_printf(dev, "BIOS IRQ %u does not match initial IRQ %u\n", bios_irq, link->l_initial_irq); } else if (bios_irq != link->l_bios_irq) device_printf(dev, "BIOS IRQ %u for %d.%d.INT%c does not match previous BIOS IRQ %u\n", bios_irq, (int)bus, slot, pin + 'A', link->l_bios_irq); ACPI_SERIAL_END(pci_link); } static ACPI_STATUS acpi_pci_link_srs_from_crs(struct acpi_pci_link_softc *sc, ACPI_BUFFER *srsbuf) { ACPI_RESOURCE *end, *res; ACPI_STATUS status; struct link *link; int i __diagused, in_dpf; /* Fetch the _CRS. */ ACPI_SERIAL_ASSERT(pci_link); srsbuf->Pointer = NULL; srsbuf->Length = ACPI_ALLOCATE_BUFFER; status = AcpiGetCurrentResources(acpi_get_handle(sc->pl_dev), srsbuf); if (ACPI_SUCCESS(status) && srsbuf->Pointer == NULL) status = AE_NO_MEMORY; if (ACPI_FAILURE(status)) { if (bootverbose) device_printf(sc->pl_dev, "Unable to fetch current resources: %s\n", AcpiFormatException(status)); return (status); } /* Fill in IRQ resources via link structures. */ link = sc->pl_links; i = 0; in_dpf = DPF_OUTSIDE; res = (ACPI_RESOURCE *)srsbuf->Pointer; end = (ACPI_RESOURCE *)((char *)srsbuf->Pointer + srsbuf->Length); for (;;) { switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ panic( "%s: Multiple dependent functions within a current resource", __func__); break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: MPASS(i < sc->pl_num_links); res->Data.Irq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) { KASSERT(link->l_irq < NUM_ISA_INTERRUPTS, ("%s: can't put non-ISA IRQ %d in legacy IRQ resource type", __func__, link->l_irq)); res->Data.Irq.Interrupts[0] = link->l_irq; } else res->Data.Irq.Interrupts[0] = 0; link++; i++; break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: MPASS(i < sc->pl_num_links); res->Data.ExtendedIrq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) res->Data.ExtendedIrq.Interrupts[0] = link->l_irq; else res->Data.ExtendedIrq.Interrupts[0] = 0; link++; i++; break; } if (res->Type == ACPI_RESOURCE_TYPE_END_TAG) break; res = ACPI_NEXT_RESOURCE(res); if (res >= end) break; } return (AE_OK); } static ACPI_STATUS acpi_pci_link_srs_from_links(struct acpi_pci_link_softc *sc, ACPI_BUFFER *srsbuf) { ACPI_RESOURCE newres; ACPI_STATUS status; struct link *link; int i; /* Start off with an empty buffer. */ srsbuf->Pointer = NULL; link = sc->pl_links; for (i = 0; i < sc->pl_num_links; i++) { /* Add a new IRQ resource from each link. */ link = &sc->pl_links[i]; if (link->l_prs_template.Type == ACPI_RESOURCE_TYPE_IRQ) { /* Build an IRQ resource. */ bcopy(&link->l_prs_template, &newres, ACPI_RS_SIZE(newres.Data.Irq)); newres.Data.Irq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) { KASSERT(link->l_irq < NUM_ISA_INTERRUPTS, ("%s: can't put non-ISA IRQ %d in legacy IRQ resource type", __func__, link->l_irq)); newres.Data.Irq.Interrupts[0] = link->l_irq; } else newres.Data.Irq.Interrupts[0] = 0; } else { /* Build an ExtIRQ resuorce. */ bcopy(&link->l_prs_template, &newres, ACPI_RS_SIZE(newres.Data.ExtendedIrq)); newres.Data.ExtendedIrq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) newres.Data.ExtendedIrq.Interrupts[0] = link->l_irq; else newres.Data.ExtendedIrq.Interrupts[0] = 0; } /* Add the new resource to the end of the _SRS buffer. */ status = acpi_AppendBufferResource(srsbuf, &newres); if (ACPI_FAILURE(status)) { device_printf(sc->pl_dev, "Unable to build resources: %s\n", AcpiFormatException(status)); if (srsbuf->Pointer != NULL) { AcpiOsFree(srsbuf->Pointer); srsbuf->Pointer = NULL; } return (status); } } return (AE_OK); } static ACPI_STATUS acpi_pci_link_route_irqs(device_t dev) { struct acpi_pci_link_softc *sc; ACPI_RESOURCE *resource, *end; ACPI_BUFFER srsbuf; ACPI_STATUS status; struct link *link; int i __diagused; ACPI_SERIAL_ASSERT(pci_link); sc = device_get_softc(dev); if (sc->pl_crs_bad) status = acpi_pci_link_srs_from_links(sc, &srsbuf); else status = acpi_pci_link_srs_from_crs(sc, &srsbuf); if (ACPI_FAILURE(status)) return (status); /* Write out new resources via _SRS. */ status = AcpiSetCurrentResources(acpi_get_handle(dev), &srsbuf); if (ACPI_FAILURE(status)) { device_printf(dev, "Unable to route IRQs: %s\n", AcpiFormatException(status)); AcpiOsFree(srsbuf.Pointer); return (status); } /* * Perform acpi_config_intr() on each IRQ resource if it was just * routed for the first time. */ link = sc->pl_links; i = 0; resource = (ACPI_RESOURCE *)srsbuf.Pointer; end = (ACPI_RESOURCE *)((char *)srsbuf.Pointer + srsbuf.Length); for (;;) { if (resource->Type == ACPI_RESOURCE_TYPE_END_TAG) break; switch (resource->Type) { case ACPI_RESOURCE_TYPE_IRQ: case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: MPASS(i < sc->pl_num_links); /* * Only configure the interrupt and update the * weights if this link has a valid IRQ and was * previously unrouted. */ if (!link->l_routed && PCI_INTERRUPT_VALID(link->l_irq)) { link->l_routed = true; acpi_config_intr(dev, resource); pci_link_interrupt_weights[link->l_irq] += link->l_references; } link++; i++; break; } resource = ACPI_NEXT_RESOURCE(resource); if (resource >= end) break; } AcpiOsFree(srsbuf.Pointer); return (AE_OK); } static int acpi_pci_link_resume(device_t dev) { struct acpi_pci_link_softc *sc; ACPI_STATUS status; int i, routed; /* * If all of our links are routed, then restore the link via _SRS, * otherwise, disable the link via _DIS. */ ACPI_SERIAL_BEGIN(pci_link); sc = device_get_softc(dev); routed = 0; for (i = 0; i < sc->pl_num_links; i++) if (sc->pl_links[i].l_routed) routed++; if (routed == sc->pl_num_links) status = acpi_pci_link_route_irqs(dev); else { AcpiEvaluateObject(acpi_get_handle(dev), "_DIS", NULL, NULL); status = AE_OK; } ACPI_SERIAL_END(pci_link); if (ACPI_FAILURE(status)) return (ENXIO); else return (0); } /* * Pick an IRQ to use for this unrouted link. */ static uint8_t acpi_pci_link_choose_irq(device_t dev, struct link *link) { char tunable_buffer[64], link_name[5]; u_int8_t best_irq, pos_irq; int best_weight, pos_weight, i; KASSERT(!link->l_routed, ("%s: link already routed", __func__)); KASSERT(!PCI_INTERRUPT_VALID(link->l_irq), ("%s: link already has an IRQ", __func__)); /* Check for a tunable override. */ if (ACPI_SUCCESS(acpi_short_name(acpi_get_handle(dev), link_name, sizeof(link_name)))) { snprintf(tunable_buffer, sizeof(tunable_buffer), "hw.pci.link.%s.%d.irq", link_name, link->l_res_index); if (getenv_int(tunable_buffer, &i) && PCI_INTERRUPT_VALID(i)) { if (!link_valid_irq(link, i)) device_printf(dev, "Warning, IRQ %d is not listed as valid\n", i); return (i); } snprintf(tunable_buffer, sizeof(tunable_buffer), "hw.pci.link.%s.irq", link_name); if (getenv_int(tunable_buffer, &i) && PCI_INTERRUPT_VALID(i)) { if (!link_valid_irq(link, i)) device_printf(dev, "Warning, IRQ %d is not listed as valid\n", i); return (i); } } /* * If we have a valid BIOS IRQ, use that. We trust what the BIOS * says it routed over what _CRS says the link thinks is routed. */ if (PCI_INTERRUPT_VALID(link->l_bios_irq)) return (link->l_bios_irq); /* * If we don't have a BIOS IRQ but do have a valid IRQ from _CRS, * then use that. */ if (PCI_INTERRUPT_VALID(link->l_initial_irq)) return (link->l_initial_irq); /* * Ok, we have no useful hints, so we have to pick from the * possible IRQs. For ISA IRQs we only use interrupts that * have already been used by the BIOS. */ best_irq = PCI_INVALID_IRQ; best_weight = INT_MAX; for (i = 0; i < link->l_num_irqs; i++) { pos_irq = link->l_irqs[i]; if (pos_irq < NUM_ISA_INTERRUPTS && (pci_link_bios_isa_irqs & 1 << pos_irq) == 0) continue; pos_weight = pci_link_interrupt_weights[pos_irq]; if (pos_weight < best_weight) { best_weight = pos_weight; best_irq = pos_irq; } } /* * If this is an ISA IRQ, try using the SCI if it is also an ISA * interrupt as a fallback. */ if (link->l_isa_irq) { pos_irq = AcpiGbl_FADT.SciInterrupt; pos_weight = pci_link_interrupt_weights[pos_irq]; if (pos_weight < best_weight) { best_weight = pos_weight; best_irq = pos_irq; } } if (PCI_INTERRUPT_VALID(best_irq)) { if (bootverbose) device_printf(dev, "Picked IRQ %u with weight %d\n", best_irq, best_weight); } else device_printf(dev, "Unable to choose an IRQ\n"); return (best_irq); } int acpi_pci_link_route_interrupt(device_t dev, int index) { struct link *link; if (acpi_disabled("pci_link")) return (PCI_INVALID_IRQ); ACPI_SERIAL_BEGIN(pci_link); link = acpi_pci_link_lookup(dev, index); if (link == NULL) panic("%s: apparently invalid index %d", __func__, index); /* * If this link device is already routed to an interrupt, just return * the interrupt it is routed to. */ if (link->l_routed) { KASSERT(PCI_INTERRUPT_VALID(link->l_irq), ("%s: link is routed but has an invalid IRQ", __func__)); ACPI_SERIAL_END(pci_link); return (link->l_irq); } /* Choose an IRQ if we need one. */ if (!PCI_INTERRUPT_VALID(link->l_irq)) { link->l_irq = acpi_pci_link_choose_irq(dev, link); /* * Try to route the interrupt we picked. If it fails, then * assume the interrupt is not routed. */ if (PCI_INTERRUPT_VALID(link->l_irq)) { acpi_pci_link_route_irqs(dev); if (!link->l_routed) link->l_irq = PCI_INVALID_IRQ; } } ACPI_SERIAL_END(pci_link); return (link->l_irq); } /* * This is gross, but we abuse the identify routine to perform one-time * SYSINIT() style initialization for the driver. */ static void acpi_pci_link_identify(driver_t *driver, device_t parent) { /* * If the SCI is an ISA IRQ, add it to the bitmask of known good * ISA IRQs. * * XXX: If we are using the APIC, the SCI might have been * rerouted to an APIC pin in which case this is invalid. However, * if we are using the APIC, we also shouldn't be having any PCI * interrupts routed via ISA IRQs, so this is probably ok. */ if (AcpiGbl_FADT.SciInterrupt < NUM_ISA_INTERRUPTS) pci_link_bios_isa_irqs |= (1 << AcpiGbl_FADT.SciInterrupt); } static device_method_t acpi_pci_link_methods[] = { /* Device interface */ DEVMETHOD(device_identify, acpi_pci_link_identify), DEVMETHOD(device_probe, acpi_pci_link_probe), DEVMETHOD(device_attach, acpi_pci_link_attach), DEVMETHOD(device_resume, acpi_pci_link_resume), DEVMETHOD_END }; static driver_t acpi_pci_link_driver = { "pci_link", acpi_pci_link_methods, sizeof(struct acpi_pci_link_softc), }; DRIVER_MODULE(acpi_pci_link, acpi, acpi_pci_link_driver, 0, 0); MODULE_DEPEND(acpi_pci_link, acpi, 1, 1, 1); diff --git a/sys/dev/acpica/acpi_pcib_acpi.c b/sys/dev/acpica/acpi_pcib_acpi.c index a66ca1199af7..06e6bc233720 100644 --- a/sys/dev/acpica/acpi_pcib_acpi.c +++ b/sys/dev/acpica/acpi_pcib_acpi.c @@ -1,796 +1,800 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. * * 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 #include "opt_acpi.h" #include "opt_pci.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" #include /* Hooks for the ACPI CA debugging infrastructure. */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("PCI_ACPI") struct acpi_hpcib_softc { device_t ap_dev; ACPI_HANDLE ap_handle; bus_dma_tag_t ap_dma_tag; int ap_flags; uint32_t ap_osc_ctl; int ap_segment; /* PCI domain */ int ap_bus; /* bios-assigned bus number */ int ap_addr; /* device/func of PCI-Host bridge */ ACPI_BUFFER ap_prt; /* interrupt routing table */ #ifdef NEW_PCIB struct pcib_host_resources ap_host_res; #endif }; static int acpi_pcib_acpi_probe(device_t bus); static int acpi_pcib_acpi_attach(device_t bus); static int acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); static int acpi_pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value); static uint32_t acpi_pcib_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int bytes); static void acpi_pcib_write_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, uint32_t data, int bytes); static int acpi_pcib_acpi_route_interrupt(device_t pcib, device_t dev, int pin); static int acpi_pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs); static int acpi_pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data); static int acpi_pcib_alloc_msix(device_t pcib, device_t dev, int *irq); static struct resource *acpi_pcib_acpi_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); #ifdef NEW_PCIB static int acpi_pcib_acpi_adjust_resource(device_t dev, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end); #ifdef PCI_RES_BUS static int acpi_pcib_acpi_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r); #endif #endif static int acpi_pcib_request_feature(device_t pcib, device_t dev, enum pci_feature feature); static bus_dma_tag_t acpi_pcib_get_dma_tag(device_t bus, device_t child); static device_method_t acpi_pcib_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_pcib_acpi_probe), DEVMETHOD(device_attach, acpi_pcib_acpi_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, acpi_pcib_read_ivar), DEVMETHOD(bus_write_ivar, acpi_pcib_write_ivar), DEVMETHOD(bus_alloc_resource, acpi_pcib_acpi_alloc_resource), #ifdef NEW_PCIB DEVMETHOD(bus_adjust_resource, acpi_pcib_acpi_adjust_resource), #else DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), #endif #if defined(NEW_PCIB) && defined(PCI_RES_BUS) DEVMETHOD(bus_release_resource, acpi_pcib_acpi_release_resource), #else DEVMETHOD(bus_release_resource, bus_generic_release_resource), #endif DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_get_cpus, acpi_pcib_get_cpus), DEVMETHOD(bus_get_dma_tag, acpi_pcib_get_dma_tag), /* pcib interface */ DEVMETHOD(pcib_maxslots, pcib_maxslots), DEVMETHOD(pcib_read_config, acpi_pcib_read_config), DEVMETHOD(pcib_write_config, acpi_pcib_write_config), DEVMETHOD(pcib_route_interrupt, acpi_pcib_acpi_route_interrupt), DEVMETHOD(pcib_alloc_msi, acpi_pcib_alloc_msi), DEVMETHOD(pcib_release_msi, pcib_release_msi), DEVMETHOD(pcib_alloc_msix, acpi_pcib_alloc_msix), DEVMETHOD(pcib_release_msix, pcib_release_msix), DEVMETHOD(pcib_map_msi, acpi_pcib_map_msi), DEVMETHOD(pcib_power_for_sleep, acpi_pcib_power_for_sleep), DEVMETHOD(pcib_request_feature, acpi_pcib_request_feature), DEVMETHOD_END }; DEFINE_CLASS_0(pcib, acpi_pcib_acpi_driver, acpi_pcib_acpi_methods, sizeof(struct acpi_hpcib_softc)); DRIVER_MODULE(acpi_pcib, acpi, acpi_pcib_acpi_driver, 0, 0); MODULE_DEPEND(acpi_pcib, acpi, 1, 1, 1); static int acpi_pcib_acpi_probe(device_t dev) { ACPI_DEVICE_INFO *devinfo; ACPI_HANDLE h; int root; if (acpi_disabled("pcib") || (h = acpi_get_handle(dev)) == NULL || ACPI_FAILURE(AcpiGetObjectInfo(h, &devinfo))) return (ENXIO); root = (devinfo->Flags & ACPI_PCI_ROOT_BRIDGE) != 0; AcpiOsFree(devinfo); if (!root || pci_cfgregopen() == 0) return (ENXIO); device_set_desc(dev, "ACPI Host-PCI bridge"); return (0); } #ifdef NEW_PCIB static ACPI_STATUS acpi_pcib_producer_handler(ACPI_RESOURCE *res, void *context) { struct acpi_hpcib_softc *sc; UINT64 length, min, max; u_int flags; int error, type; sc = context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: case ACPI_RESOURCE_TYPE_END_DEPENDENT: panic("host bridge has depenedent resources"); case ACPI_RESOURCE_TYPE_ADDRESS16: case ACPI_RESOURCE_TYPE_ADDRESS32: case ACPI_RESOURCE_TYPE_ADDRESS64: case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: if (res->Data.Address.ProducerConsumer != ACPI_PRODUCER) break; switch (res->Type) { case ACPI_RESOURCE_TYPE_ADDRESS16: min = res->Data.Address16.Address.Minimum; max = res->Data.Address16.Address.Maximum; length = res->Data.Address16.Address.AddressLength; break; case ACPI_RESOURCE_TYPE_ADDRESS32: min = res->Data.Address32.Address.Minimum; max = res->Data.Address32.Address.Maximum; length = res->Data.Address32.Address.AddressLength; break; case ACPI_RESOURCE_TYPE_ADDRESS64: min = res->Data.Address64.Address.Minimum; max = res->Data.Address64.Address.Maximum; length = res->Data.Address64.Address.AddressLength; break; default: KASSERT(res->Type == ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64, ("should never happen")); min = res->Data.ExtAddress64.Address.Minimum; max = res->Data.ExtAddress64.Address.Maximum; length = res->Data.ExtAddress64.Address.AddressLength; break; } if (length == 0) break; if (min + length - 1 != max && (res->Data.Address.MinAddressFixed != ACPI_ADDRESS_FIXED || res->Data.Address.MaxAddressFixed != ACPI_ADDRESS_FIXED)) break; flags = 0; switch (res->Data.Address.ResourceType) { case ACPI_MEMORY_RANGE: type = SYS_RES_MEMORY; if (res->Type != ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64) { if (res->Data.Address.Info.Mem.Caching == ACPI_PREFETCHABLE_MEMORY) flags |= RF_PREFETCHABLE; } else { /* * XXX: Parse prefetch flag out of * TypeSpecific. */ } break; case ACPI_IO_RANGE: type = SYS_RES_IOPORT; break; #ifdef PCI_RES_BUS case ACPI_BUS_NUMBER_RANGE: type = PCI_RES_BUS; break; #endif default: return (AE_OK); } if (min + length - 1 != max) device_printf(sc->ap_dev, "Length mismatch for %d range: %jx vs %jx\n", type, (uintmax_t)(max - min + 1), (uintmax_t)length); #ifdef __i386__ if (min > ULONG_MAX) { device_printf(sc->ap_dev, "Ignoring %d range above 4GB (%#jx-%#jx)\n", type, (uintmax_t)min, (uintmax_t)max); break; } if (max > ULONG_MAX) { device_printf(sc->ap_dev, "Truncating end of %d range above 4GB (%#jx-%#jx)\n", type, (uintmax_t)min, (uintmax_t)max); max = ULONG_MAX; } #endif error = pcib_host_res_decodes(&sc->ap_host_res, type, min, max, flags); if (error) panic("Failed to manage %d range (%#jx-%#jx): %d", type, (uintmax_t)min, (uintmax_t)max, error); break; default: break; } return (AE_OK); } #endif #if defined(NEW_PCIB) && defined(PCI_RES_BUS) static bool get_decoded_bus_range(struct acpi_hpcib_softc *sc, rman_res_t *startp, rman_res_t *endp) { struct resource_list_entry *rle; rle = resource_list_find(&sc->ap_host_res.hr_rl, PCI_RES_BUS, 0); if (rle == NULL) return (false); *startp = rle->start; *endp = rle->end; return (true); } #endif static int acpi_pcib_osc(struct acpi_hpcib_softc *sc, uint32_t osc_ctl) { ACPI_STATUS status; uint32_t cap_set[3]; static uint8_t pci_host_bridge_uuid[ACPI_UUID_LENGTH] = { 0x5b, 0x4d, 0xdb, 0x33, 0xf7, 0x1f, 0x1c, 0x40, 0x96, 0x57, 0x74, 0x41, 0xc0, 0x3d, 0xd7, 0x66 }; /* * Don't invoke _OSC if a control is already granted. * However, always invoke _OSC during attach when 0 is passed. */ if (osc_ctl != 0 && (sc->ap_osc_ctl & osc_ctl) == osc_ctl) return (0); /* Support Field: Extended PCI Config Space, PCI Segment Groups, MSI */ cap_set[PCI_OSC_SUPPORT] = PCIM_OSC_SUPPORT_EXT_PCI_CONF | PCIM_OSC_SUPPORT_SEG_GROUP | PCIM_OSC_SUPPORT_MSI; /* Active State Power Management, Clock Power Management Capability */ if (pci_enable_aspm) cap_set[PCI_OSC_SUPPORT] |= PCIM_OSC_SUPPORT_ASPM | PCIM_OSC_SUPPORT_CPMC; /* Control Field */ cap_set[PCI_OSC_CTL] = sc->ap_osc_ctl | osc_ctl; status = acpi_EvaluateOSC(sc->ap_handle, pci_host_bridge_uuid, 1, nitems(cap_set), cap_set, cap_set, false); if (ACPI_FAILURE(status)) { if (status == AE_NOT_FOUND) { sc->ap_osc_ctl |= osc_ctl; return (0); } device_printf(sc->ap_dev, "_OSC failed: %s\n", AcpiFormatException(status)); return (EIO); } /* * _OSC may return an error in the status word, but will * update the control mask always. _OSC should not revoke * previously-granted controls. */ if ((cap_set[PCI_OSC_CTL] & sc->ap_osc_ctl) != sc->ap_osc_ctl) device_printf(sc->ap_dev, "_OSC revoked %#x\n", (cap_set[PCI_OSC_CTL] & sc->ap_osc_ctl) ^ sc->ap_osc_ctl); sc->ap_osc_ctl = cap_set[PCI_OSC_CTL]; if ((sc->ap_osc_ctl & osc_ctl) != osc_ctl) return (EIO); return (0); } static int acpi_pcib_acpi_attach(device_t dev) { struct acpi_hpcib_softc *sc; ACPI_STATUS status; static int bus0_seen = 0; u_int slot, func, busok; #if defined(NEW_PCIB) && defined(PCI_RES_BUS) struct resource *bus_res; rman_res_t end, start; int rid; #endif int error, domain; uint8_t busno; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->ap_dev = dev; sc->ap_handle = acpi_get_handle(dev); /* * Don't attach if we're not really there. */ if (!acpi_DeviceIsPresent(dev)) return (ENXIO); acpi_pcib_osc(sc, 0); /* * Get our segment number by evaluating _SEG. * It's OK for this to not exist. */ status = acpi_GetInteger(sc->ap_handle, "_SEG", &sc->ap_segment); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) { device_printf(dev, "could not evaluate _SEG - %s\n", AcpiFormatException(status)); return_VALUE (ENXIO); } /* If it's not found, assume 0. */ sc->ap_segment = 0; } /* * Get the address (device and function) of the associated * PCI-Host bridge device from _ADR. Assume we don't have one if * it doesn't exist. */ status = acpi_GetInteger(sc->ap_handle, "_ADR", &sc->ap_addr); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) device_printf(dev, "could not evaluate _ADR - %s\n", AcpiFormatException(status)); sc->ap_addr = -1; } #ifdef NEW_PCIB /* * Determine which address ranges this bridge decodes and setup * resource managers for those ranges. */ if (pcib_host_res_init(sc->ap_dev, &sc->ap_host_res) != 0) panic("failed to init hostb resources"); if (!acpi_disabled("hostres")) { status = AcpiWalkResources(sc->ap_handle, "_CRS", acpi_pcib_producer_handler, sc); if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) device_printf(sc->ap_dev, "failed to parse resources: %s\n", AcpiFormatException(status)); } #endif /* * Get our base bus number by evaluating _BBN. * If this doesn't work, we assume we're bus number 0. * * XXX note that it may also not exist in the case where we are * meant to use a private configuration space mechanism for this bus, * so we should dig out our resources and check to see if we have * anything like that. How do we do this? * XXX If we have the requisite information, and if we don't think the * default PCI configuration space handlers can deal with this bus, * we should attach our own handler. * XXX invoke _REG on this for the PCI config space address space? * XXX It seems many BIOS's with multiple Host-PCI bridges do not set * _BBN correctly. They set _BBN to zero for all bridges. Thus, * if _BBN is zero and PCI bus 0 already exists, we try to read our * bus number from the configuration registers at address _ADR. * We only do this for domain/segment 0 in the hopes that this is * only needed for old single-domain machines. */ status = acpi_GetInteger(sc->ap_handle, "_BBN", &sc->ap_bus); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) { device_printf(dev, "could not evaluate _BBN - %s\n", AcpiFormatException(status)); return (ENXIO); } else { /* If it's not found, assume 0. */ sc->ap_bus = 0; } } /* * If this is segment 0, the bus is zero, and PCI bus 0 already * exists, read the bus number via PCI config space. */ busok = 1; if (sc->ap_segment == 0 && sc->ap_bus == 0 && bus0_seen) { busok = 0; if (sc->ap_addr != -1) { /* XXX: We assume bus 0. */ slot = ACPI_ADR_PCI_SLOT(sc->ap_addr); func = ACPI_ADR_PCI_FUNC(sc->ap_addr); if (bootverbose) device_printf(dev, "reading config registers from 0:%d:%d\n", slot, func); if (host_pcib_get_busno(pci_cfgregread, 0, slot, func, &busno) == 0) device_printf(dev, "couldn't read bus number from cfg space\n"); else { sc->ap_bus = busno; busok = 1; } } } #if defined(NEW_PCIB) && defined(PCI_RES_BUS) /* * If nothing else worked, hope that ACPI at least lays out the * Host-PCI bridges in order and that as a result the next free * bus number is our bus number. */ if (busok == 0) { /* * If we have a region of bus numbers, use the first * number for our bus. */ if (get_decoded_bus_range(sc, &start, &end)) sc->ap_bus = start; else { rid = 0; bus_res = pci_domain_alloc_bus(sc->ap_segment, dev, &rid, 0, PCI_BUSMAX, 1, 0); if (bus_res == NULL) { device_printf(dev, "could not allocate bus number\n"); pcib_host_res_free(dev, &sc->ap_host_res); return (ENXIO); } sc->ap_bus = rman_get_start(bus_res); pci_domain_release_bus(sc->ap_segment, dev, rid, bus_res); } } else { /* * If there is a decoded bus range, assume the bus number is * the first value in the range. Warn if _BBN doesn't match. */ if (get_decoded_bus_range(sc, &start, &end)) { if (sc->ap_bus != start) { device_printf(dev, "WARNING: BIOS configured bus number (%d) is " "not within decoded bus number range " "(%ju - %ju).\n", sc->ap_bus, (uintmax_t)start, (uintmax_t)end); device_printf(dev, "Using range start (%ju) as bus number.\n", (uintmax_t)start); sc->ap_bus = start; } } } #else /* * If nothing else worked, hope that ACPI at least lays out the * host-PCI bridges in order and that as a result our unit number * is actually our bus number. There are several reasons this * might not be true. */ if (busok == 0) { sc->ap_bus = device_get_unit(dev); device_printf(dev, "trying bus number %d\n", sc->ap_bus); } #endif /* If this is bus 0 on segment 0, note that it has been seen already. */ if (sc->ap_segment == 0 && sc->ap_bus == 0) bus0_seen = 1; acpi_pcib_fetch_prt(dev, &sc->ap_prt); error = bus_dma_tag_create(bus_get_dma_tag(dev), 1, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXSIZE, BUS_SPACE_UNRESTRICTED, BUS_SPACE_MAXSIZE, 0, NULL, NULL, &sc->ap_dma_tag); if (error != 0) goto errout; error = bus_get_domain(dev, &domain); if (error == 0) error = bus_dma_tag_set_domain(sc->ap_dma_tag, domain); /* Don't fail to attach if the domain can't be queried or set. */ error = 0; bus_generic_probe(dev); if (device_add_child(dev, "pci", -1) == NULL) { bus_dma_tag_destroy(sc->ap_dma_tag); sc->ap_dma_tag = NULL; error = ENXIO; goto errout; } return (bus_generic_attach(dev)); errout: device_printf(device_get_parent(dev), "couldn't attach pci bus\n"); #if defined(NEW_PCIB) && defined(PCI_RES_BUS) pcib_host_res_free(dev, &sc->ap_host_res); #endif return (error); } /* * Support for standard PCI bridge ivars. */ static int acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct acpi_hpcib_softc *sc = device_get_softc(dev); switch (which) { case PCIB_IVAR_DOMAIN: *result = sc->ap_segment; return (0); case PCIB_IVAR_BUS: *result = sc->ap_bus; return (0); case ACPI_IVAR_HANDLE: *result = (uintptr_t)sc->ap_handle; return (0); case ACPI_IVAR_FLAGS: *result = (uintptr_t)sc->ap_flags; return (0); } return (ENOENT); } static int acpi_pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct acpi_hpcib_softc *sc = device_get_softc(dev); switch (which) { case PCIB_IVAR_DOMAIN: return (EINVAL); case PCIB_IVAR_BUS: sc->ap_bus = value; return (0); case ACPI_IVAR_HANDLE: sc->ap_handle = (ACPI_HANDLE)value; return (0); case ACPI_IVAR_FLAGS: sc->ap_flags = (int)value; return (0); } return (ENOENT); } static uint32_t acpi_pcib_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int bytes) { - return (pci_cfgregread(bus, slot, func, reg, bytes)); + struct acpi_hpcib_softc *sc = device_get_softc(dev); + + return (pci_cfgregread(sc->ap_segment, bus, slot, func, reg, bytes)); } static void acpi_pcib_write_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, uint32_t data, int bytes) { - pci_cfgregwrite(bus, slot, func, reg, data, bytes); + struct acpi_hpcib_softc *sc = device_get_softc(dev); + + pci_cfgregwrite(sc->ap_segment, bus, slot, func, reg, data, bytes); } static int acpi_pcib_acpi_route_interrupt(device_t pcib, device_t dev, int pin) { struct acpi_hpcib_softc *sc = device_get_softc(pcib); return (acpi_pcib_route_interrupt(pcib, dev, pin, &sc->ap_prt)); } static int acpi_pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs) { device_t bus; bus = device_get_parent(pcib); return (PCIB_ALLOC_MSI(device_get_parent(bus), dev, count, maxcount, irqs)); } static int acpi_pcib_alloc_msix(device_t pcib, device_t dev, int *irq) { device_t bus; bus = device_get_parent(pcib); return (PCIB_ALLOC_MSIX(device_get_parent(bus), dev, irq)); } static int acpi_pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data) { struct acpi_hpcib_softc *sc; device_t bus, hostb; int error; bus = device_get_parent(pcib); error = PCIB_MAP_MSI(device_get_parent(bus), dev, irq, addr, data); if (error) return (error); sc = device_get_softc(pcib); if (sc->ap_addr == -1) return (0); /* XXX: Assumes all bridges are on bus 0. */ hostb = pci_find_dbsf(sc->ap_segment, 0, ACPI_ADR_PCI_SLOT(sc->ap_addr), ACPI_ADR_PCI_FUNC(sc->ap_addr)); if (hostb != NULL) pci_ht_map_msi(hostb, *addr); return (0); } struct resource * acpi_pcib_acpi_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { #ifdef NEW_PCIB struct acpi_hpcib_softc *sc; struct resource *res; #endif #if defined(__i386__) || defined(__amd64__) start = hostb_alloc_start(type, start, end, count); #endif #ifdef NEW_PCIB sc = device_get_softc(dev); #ifdef PCI_RES_BUS if (type == PCI_RES_BUS) return (pci_domain_alloc_bus(sc->ap_segment, child, rid, start, end, count, flags)); #endif res = pcib_host_res_alloc(&sc->ap_host_res, child, type, rid, start, end, count, flags); /* * XXX: If this is a request for a specific range, assume it is * correct and pass it up to the parent. What we probably want to * do long-term is explicitly trust any firmware-configured * resources during the initial bus scan on boot and then disable * this after that. */ if (res == NULL && start + count - 1 == end) res = bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags); return (res); #else return (bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags)); #endif } #ifdef NEW_PCIB int acpi_pcib_acpi_adjust_resource(device_t dev, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct acpi_hpcib_softc *sc; sc = device_get_softc(dev); #ifdef PCI_RES_BUS if (type == PCI_RES_BUS) return (pci_domain_adjust_bus(sc->ap_segment, child, r, start, end)); #endif return (pcib_host_res_adjust(&sc->ap_host_res, child, type, r, start, end)); } #ifdef PCI_RES_BUS int acpi_pcib_acpi_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct acpi_hpcib_softc *sc; sc = device_get_softc(dev); if (type == PCI_RES_BUS) return (pci_domain_release_bus(sc->ap_segment, child, rid, r)); return (bus_generic_release_resource(dev, child, type, rid, r)); } #endif #endif static int acpi_pcib_request_feature(device_t pcib, device_t dev, enum pci_feature feature) { uint32_t osc_ctl; struct acpi_hpcib_softc *sc; sc = device_get_softc(pcib); switch (feature) { case PCI_FEATURE_HP: osc_ctl = PCIM_OSC_CTL_PCIE_HP; break; case PCI_FEATURE_AER: osc_ctl = PCIM_OSC_CTL_PCIE_AER; break; default: return (EINVAL); } return (acpi_pcib_osc(sc, osc_ctl)); } static bus_dma_tag_t acpi_pcib_get_dma_tag(device_t bus, device_t child) { struct acpi_hpcib_softc *sc; sc = device_get_softc(bus); return (sc->ap_dma_tag); } diff --git a/sys/dev/agp/agp_amd64.c b/sys/dev/agp/agp_amd64.c index a123e6f3bbfd..9b5a4c342dac 100644 --- a/sys/dev/agp/agp_amd64.c +++ b/sys/dev/agp/agp_amd64.c @@ -1,508 +1,513 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004, 2005 Jung-uk Kim * All rights reserved. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void agp_amd64_apbase_fixup(device_t); static void agp_amd64_uli_init(device_t); static int agp_amd64_uli_set_aperture(device_t, uint32_t); static int agp_amd64_nvidia_match(uint16_t); static void agp_amd64_nvidia_init(device_t); static int agp_amd64_nvidia_set_aperture(device_t, uint32_t); static int agp_amd64_via_match(void); static void agp_amd64_via_init(device_t); static int agp_amd64_via_set_aperture(device_t, uint32_t); MALLOC_DECLARE(M_AGP); #define AMD64_MAX_MCTRL 8 struct agp_amd64_softc { struct agp_softc agp; uint32_t initial_aperture; struct agp_gatt *gatt; uint32_t apbase; int mctrl[AMD64_MAX_MCTRL]; int n_mctrl; int via_agp; }; static const char* agp_amd64_match(device_t dev) { if (pci_get_class(dev) != PCIC_BRIDGE || pci_get_subclass(dev) != PCIS_BRIDGE_HOST || agp_find_caps(dev) == 0) return (NULL); switch (pci_get_devid(dev)) { case 0x74541022: return ("AMD 8151 AGP graphics tunnel"); case 0x07551039: return ("SiS 755 host to AGP bridge"); case 0x07601039: return ("SiS 760 host to AGP bridge"); case 0x168910b9: return ("ULi M1689 AGP Controller"); case 0x00d110de: if (agp_amd64_nvidia_match(0x00d2)) return (NULL); return ("NVIDIA nForce3 AGP Controller"); case 0x00e110de: if (agp_amd64_nvidia_match(0x00e2)) return (NULL); return ("NVIDIA nForce3-250 AGP Controller"); case 0x02041106: return ("VIA 8380 host to PCI bridge"); case 0x02381106: return ("VIA 3238 host to PCI bridge"); case 0x02821106: return ("VIA K8T800Pro host to PCI bridge"); case 0x31881106: return ("VIA 8385 host to PCI bridge"); } return (NULL); } static int agp_amd64_nvidia_match(uint16_t devid) { /* XXX nForce3 requires secondary AGP bridge at 0:11:0. */ - if (pci_cfgregread(0, 11, 0, PCIR_CLASS, 1) != PCIC_BRIDGE || - pci_cfgregread(0, 11, 0, PCIR_SUBCLASS, 1) != PCIS_BRIDGE_PCI || - pci_cfgregread(0, 11, 0, PCIR_VENDOR, 2) != 0x10de || - pci_cfgregread(0, 11, 0, PCIR_DEVICE, 2) != devid) + if (pci_cfgregread(0, 0, 11, 0, PCIR_CLASS, 1) != PCIC_BRIDGE || + pci_cfgregread(0, 0, 11, 0, PCIR_SUBCLASS, 1) != PCIS_BRIDGE_PCI || + pci_cfgregread(0, 0, 11, 0, PCIR_VENDOR, 2) != 0x10de || + pci_cfgregread(0, 0, 11, 0, PCIR_DEVICE, 2) != devid) return (ENXIO); return (0); } static int agp_amd64_via_match(void) { /* XXX Some VIA bridge requires secondary AGP bridge at 0:1:0. */ - if (pci_cfgregread(0, 1, 0, PCIR_CLASS, 1) != PCIC_BRIDGE || - pci_cfgregread(0, 1, 0, PCIR_SUBCLASS, 1) != PCIS_BRIDGE_PCI || - pci_cfgregread(0, 1, 0, PCIR_VENDOR, 2) != 0x1106 || - pci_cfgregread(0, 1, 0, PCIR_DEVICE, 2) != 0xb188 || - (pci_cfgregread(0, 1, 0, AGP_VIA_AGPSEL, 1) & 2)) + if (pci_cfgregread(0, 0, 1, 0, PCIR_CLASS, 1) != PCIC_BRIDGE || + pci_cfgregread(0, 0, 1, 0, PCIR_SUBCLASS, 1) != PCIS_BRIDGE_PCI || + pci_cfgregread(0, 0, 1, 0, PCIR_VENDOR, 2) != 0x1106 || + pci_cfgregread(0, 0, 1, 0, PCIR_DEVICE, 2) != 0xb188 || + (pci_cfgregread(0, 0, 1, 0, AGP_VIA_AGPSEL, 1) & 2)) return (0); return (1); } static int agp_amd64_probe(device_t dev) { const char *desc; if (resource_disabled("agp", device_get_unit(dev))) return (ENXIO); if ((desc = agp_amd64_match(dev))) { device_set_desc(dev, desc); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int agp_amd64_attach(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); struct agp_gatt *gatt; uint32_t devid; int i, n, error; for (i = 0, n = 0; i < PCI_SLOTMAX && n < AMD64_MAX_MCTRL; i++) { - devid = pci_cfgregread(0, i, 3, 0, 4); + devid = pci_cfgregread(0, 0, i, 3, 0, 4); if (devid == 0x11031022 || devid == 0x12031022) { sc->mctrl[n] = i; n++; } } if (n == 0) return (ENXIO); sc->n_mctrl = n; if (bootverbose) device_printf(dev, "%d Miscellaneous Control unit(s) found.\n", sc->n_mctrl); if ((error = agp_generic_attach(dev))) return (error); sc->initial_aperture = AGP_GET_APERTURE(dev); for (;;) { gatt = agp_alloc_gatt(dev); if (gatt) break; /* * Probably contigmalloc failure. Try reducing the * aperture so that the gatt size reduces. */ if (AGP_SET_APERTURE(dev, AGP_GET_APERTURE(dev) / 2)) { agp_generic_detach(dev); return (ENOMEM); } } sc->gatt = gatt; switch (pci_get_vendor(dev)) { case 0x10b9: /* ULi */ agp_amd64_uli_init(dev); if (agp_amd64_uli_set_aperture(dev, sc->initial_aperture)) return (ENXIO); break; case 0x10de: /* nVidia */ agp_amd64_nvidia_init(dev); if (agp_amd64_nvidia_set_aperture(dev, sc->initial_aperture)) return (ENXIO); break; case 0x1106: /* VIA */ sc->via_agp = agp_amd64_via_match(); if (sc->via_agp) { agp_amd64_via_init(dev); if (agp_amd64_via_set_aperture(dev, sc->initial_aperture)) return (ENXIO); } break; } /* Install the gatt and enable aperture. */ for (i = 0; i < sc->n_mctrl; i++) { - pci_cfgregwrite(0, sc->mctrl[i], 3, AGP_AMD64_ATTBASE, + pci_cfgregwrite(0, 0, sc->mctrl[i], 3, AGP_AMD64_ATTBASE, (uint32_t)(gatt->ag_physical >> 8) & AGP_AMD64_ATTBASE_MASK, 4); - pci_cfgregwrite(0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, - (pci_cfgregread(0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, 4) | + pci_cfgregwrite(0, 0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, + (pci_cfgregread(0, 0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, 4) | AGP_AMD64_APCTRL_GARTEN) & ~(AGP_AMD64_APCTRL_DISGARTCPU | AGP_AMD64_APCTRL_DISGARTIO), 4); } return (0); } static int agp_amd64_detach(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); int i; agp_free_cdev(dev); for (i = 0; i < sc->n_mctrl; i++) - pci_cfgregwrite(0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, - pci_cfgregread(0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, 4) & + pci_cfgregwrite(0, 0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, + pci_cfgregread(0, 0, sc->mctrl[i], 3, AGP_AMD64_APCTRL, 4) & ~AGP_AMD64_APCTRL_GARTEN, 4); AGP_SET_APERTURE(dev, sc->initial_aperture); agp_free_gatt(sc->gatt); agp_free_res(dev); return (0); } static uint32_t agp_amd64_table[] = { 0x02000000, /* 32 MB */ 0x04000000, /* 64 MB */ 0x08000000, /* 128 MB */ 0x10000000, /* 256 MB */ 0x20000000, /* 512 MB */ 0x40000000, /* 1024 MB */ 0x80000000, /* 2048 MB */ }; #define AGP_AMD64_TABLE_SIZE nitems(agp_amd64_table) static uint32_t agp_amd64_get_aperture(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); uint32_t i; - i = (pci_cfgregread(0, sc->mctrl[0], 3, AGP_AMD64_APCTRL, 4) & + i = (pci_cfgregread(0, 0, sc->mctrl[0], 3, AGP_AMD64_APCTRL, 4) & AGP_AMD64_APCTRL_SIZE_MASK) >> 1; if (i >= AGP_AMD64_TABLE_SIZE) return (0); return (agp_amd64_table[i]); } static int agp_amd64_set_aperture(device_t dev, uint32_t aperture) { struct agp_amd64_softc *sc = device_get_softc(dev); uint32_t i; int j; for (i = 0; i < AGP_AMD64_TABLE_SIZE; i++) if (agp_amd64_table[i] == aperture) break; if (i >= AGP_AMD64_TABLE_SIZE) return (EINVAL); for (j = 0; j < sc->n_mctrl; j++) - pci_cfgregwrite(0, sc->mctrl[j], 3, AGP_AMD64_APCTRL, - (pci_cfgregread(0, sc->mctrl[j], 3, AGP_AMD64_APCTRL, 4) & + pci_cfgregwrite(0, 0, sc->mctrl[j], 3, AGP_AMD64_APCTRL, + (pci_cfgregread(0, 0, sc->mctrl[j], 3, AGP_AMD64_APCTRL, 4) & ~(AGP_AMD64_APCTRL_SIZE_MASK)) | (i << 1), 4); switch (pci_get_vendor(dev)) { case 0x10b9: /* ULi */ return (agp_amd64_uli_set_aperture(dev, aperture)); break; case 0x10de: /* nVidia */ return (agp_amd64_nvidia_set_aperture(dev, aperture)); break; case 0x1106: /* VIA */ if (sc->via_agp) return (agp_amd64_via_set_aperture(dev, aperture)); break; } return (0); } static int agp_amd64_bind_page(device_t dev, vm_offset_t offset, vm_offset_t physical) { struct agp_amd64_softc *sc = device_get_softc(dev); if (offset >= (sc->gatt->ag_entries << AGP_PAGE_SHIFT)) return (EINVAL); sc->gatt->ag_virtual[offset >> AGP_PAGE_SHIFT] = (physical & 0xfffff000) | ((physical >> 28) & 0x00000ff0) | 3; return (0); } static int agp_amd64_unbind_page(device_t dev, vm_offset_t offset) { struct agp_amd64_softc *sc = device_get_softc(dev); if (offset >= (sc->gatt->ag_entries << AGP_PAGE_SHIFT)) return (EINVAL); sc->gatt->ag_virtual[offset >> AGP_PAGE_SHIFT] = 0; return (0); } static void agp_amd64_flush_tlb(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); int i; - for (i = 0; i < sc->n_mctrl; i++) - pci_cfgregwrite(0, sc->mctrl[i], 3, AGP_AMD64_CACHECTRL, - pci_cfgregread(0, sc->mctrl[i], 3, AGP_AMD64_CACHECTRL, 4) | - AGP_AMD64_CACHECTRL_INVGART, 4); + for (i = 0; i < sc->n_mctrl; i++) { + uint32_t val; + + val = pci_cfgregread(0, 0, sc->mctrl[i], 3, AGP_AMD64_CACHECTRL, + 4); + val |= AGP_AMD64_CACHECTRL_INVGART; + pci_cfgregwrite(0, 0, sc->mctrl[i], 3, AGP_AMD64_CACHECTRL, val, + 4); + } } static void agp_amd64_apbase_fixup(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); uint32_t apbase; int i; sc->apbase = rman_get_start(sc->agp.as_aperture); apbase = (sc->apbase >> 25) & AGP_AMD64_APBASE_MASK; for (i = 0; i < sc->n_mctrl; i++) - pci_cfgregwrite(0, sc->mctrl[i], 3, + pci_cfgregwrite(0, 0, sc->mctrl[i], 3, AGP_AMD64_APBASE, apbase, 4); } static void agp_amd64_uli_init(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); agp_amd64_apbase_fixup(dev); pci_write_config(dev, AGP_AMD64_ULI_APBASE, (pci_read_config(dev, AGP_AMD64_ULI_APBASE, 4) & 0x0000000f) | sc->apbase, 4); pci_write_config(dev, AGP_AMD64_ULI_HTT_FEATURE, sc->apbase, 4); } static int agp_amd64_uli_set_aperture(device_t dev, uint32_t aperture) { struct agp_amd64_softc *sc = device_get_softc(dev); switch (aperture) { case 0x02000000: /* 32 MB */ case 0x04000000: /* 64 MB */ case 0x08000000: /* 128 MB */ case 0x10000000: /* 256 MB */ break; default: return (EINVAL); } pci_write_config(dev, AGP_AMD64_ULI_ENU_SCR, sc->apbase + aperture - 1, 4); return (0); } static void agp_amd64_nvidia_init(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); agp_amd64_apbase_fixup(dev); pci_write_config(dev, AGP_AMD64_NVIDIA_0_APBASE, (pci_read_config(dev, AGP_AMD64_NVIDIA_0_APBASE, 4) & 0x0000000f) | sc->apbase, 4); - pci_cfgregwrite(0, 11, 0, AGP_AMD64_NVIDIA_1_APBASE1, sc->apbase, 4); - pci_cfgregwrite(0, 11, 0, AGP_AMD64_NVIDIA_1_APBASE2, sc->apbase, 4); + pci_cfgregwrite(0, 0, 11, 0, AGP_AMD64_NVIDIA_1_APBASE1, sc->apbase, 4); + pci_cfgregwrite(0, 0, 11, 0, AGP_AMD64_NVIDIA_1_APBASE2, sc->apbase, 4); } static int agp_amd64_nvidia_set_aperture(device_t dev, uint32_t aperture) { struct agp_amd64_softc *sc = device_get_softc(dev); uint32_t apsize; switch (aperture) { case 0x02000000: apsize = 0x0f; break; /* 32 MB */ case 0x04000000: apsize = 0x0e; break; /* 64 MB */ case 0x08000000: apsize = 0x0c; break; /* 128 MB */ case 0x10000000: apsize = 0x08; break; /* 256 MB */ case 0x20000000: apsize = 0x00; break; /* 512 MB */ default: return (EINVAL); } - pci_cfgregwrite(0, 11, 0, AGP_AMD64_NVIDIA_1_APSIZE, - (pci_cfgregread(0, 11, 0, AGP_AMD64_NVIDIA_1_APSIZE, 4) & + pci_cfgregwrite(0, 0, 11, 0, AGP_AMD64_NVIDIA_1_APSIZE, + (pci_cfgregread(0, 0, 11, 0, AGP_AMD64_NVIDIA_1_APSIZE, 4) & 0xfffffff0) | apsize, 4); - pci_cfgregwrite(0, 11, 0, AGP_AMD64_NVIDIA_1_APLIMIT1, + pci_cfgregwrite(0, 0, 11, 0, AGP_AMD64_NVIDIA_1_APLIMIT1, sc->apbase + aperture - 1, 4); - pci_cfgregwrite(0, 11, 0, AGP_AMD64_NVIDIA_1_APLIMIT2, + pci_cfgregwrite(0, 0, 11, 0, AGP_AMD64_NVIDIA_1_APLIMIT2, sc->apbase + aperture - 1, 4); return (0); } static void agp_amd64_via_init(device_t dev) { struct agp_amd64_softc *sc = device_get_softc(dev); agp_amd64_apbase_fixup(dev); - pci_cfgregwrite(0, 1, 0, AGP3_VIA_ATTBASE, sc->gatt->ag_physical, 4); - pci_cfgregwrite(0, 1, 0, AGP3_VIA_GARTCTRL, - pci_cfgregread(0, 1, 0, AGP3_VIA_ATTBASE, 4) | 0x180, 4); + pci_cfgregwrite(0, 0, 1, 0, AGP3_VIA_ATTBASE, sc->gatt->ag_physical, 4); + pci_cfgregwrite(0, 0, 1, 0, AGP3_VIA_GARTCTRL, + pci_cfgregread(0, 0, 1, 0, AGP3_VIA_ATTBASE, 4) | 0x180, 4); } static int agp_amd64_via_set_aperture(device_t dev, uint32_t aperture) { uint32_t apsize; apsize = ((aperture - 1) >> 20) ^ 0xff; if ((((apsize ^ 0xff) << 20) | ((1 << 20) - 1)) + 1 != aperture) return (EINVAL); - pci_cfgregwrite(0, 1, 0, AGP3_VIA_APSIZE, apsize, 1); + pci_cfgregwrite(0, 0, 1, 0, AGP3_VIA_APSIZE, apsize, 1); return (0); } static device_method_t agp_amd64_methods[] = { /* Device interface */ DEVMETHOD(device_probe, agp_amd64_probe), DEVMETHOD(device_attach, agp_amd64_attach), DEVMETHOD(device_detach, agp_amd64_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* AGP interface */ DEVMETHOD(agp_get_aperture, agp_amd64_get_aperture), DEVMETHOD(agp_set_aperture, agp_amd64_set_aperture), DEVMETHOD(agp_bind_page, agp_amd64_bind_page), DEVMETHOD(agp_unbind_page, agp_amd64_unbind_page), DEVMETHOD(agp_flush_tlb, agp_amd64_flush_tlb), DEVMETHOD(agp_enable, agp_generic_enable), DEVMETHOD(agp_alloc_memory, agp_generic_alloc_memory), DEVMETHOD(agp_free_memory, agp_generic_free_memory), DEVMETHOD(agp_bind_memory, agp_generic_bind_memory), DEVMETHOD(agp_unbind_memory, agp_generic_unbind_memory), { 0, 0 } }; static driver_t agp_amd64_driver = { "agp", agp_amd64_methods, sizeof(struct agp_amd64_softc), }; DRIVER_MODULE(agp_amd64, hostb, agp_amd64_driver, 0, 0); MODULE_DEPEND(agp_amd64, agp, 1, 1, 1); MODULE_DEPEND(agp_amd64, pci, 1, 1, 1); diff --git a/sys/dev/amdtemp/amdtemp.c b/sys/dev/amdtemp/amdtemp.c index 47c36174d73c..b01ebe101c6b 100644 --- a/sys/dev/amdtemp/amdtemp.c +++ b/sys/dev/amdtemp/amdtemp.c @@ -1,875 +1,876 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2008, 2009 Rui Paulo * Copyright (c) 2009 Norikatsu Shigemura * Copyright (c) 2009-2012 Jung-uk Kim * All rights reserved. * Copyright (c) 2017-2020 Conrad Meyer . All rights reserved. * * 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 ``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 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. */ /* * Driver for the AMD CPU on-die thermal sensors. * Initially based on the k8temp Linux driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef enum { CORE0_SENSOR0, CORE0_SENSOR1, CORE1_SENSOR0, CORE1_SENSOR1, CORE0, CORE1, CCD1, CCD_BASE = CCD1, CCD2, CCD3, CCD4, CCD5, CCD6, CCD7, CCD8, CCD_MAX = CCD8, NUM_CCDS = CCD_MAX - CCD_BASE + 1, } amdsensor_t; struct amdtemp_softc { int sc_ncores; int sc_ntemps; int sc_flags; #define AMDTEMP_FLAG_CS_SWAP 0x01 /* ThermSenseCoreSel is inverted. */ #define AMDTEMP_FLAG_CT_10BIT 0x02 /* CurTmp is 10-bit wide. */ #define AMDTEMP_FLAG_ALT_OFFSET 0x04 /* CurTmp starts at -28C. */ int32_t sc_offset; int32_t sc_temp_base; int32_t (*sc_gettemp)(device_t, amdsensor_t); struct sysctl_oid *sc_sysctl_cpu[MAXCPU]; struct intr_config_hook sc_ich; device_t sc_smn; struct mtx sc_lock; }; /* * N.B. The numbers in macro names below are significant and represent CPU * family and model numbers. Do not make up fictitious family or model numbers * when adding support for new devices. */ #define VENDORID_AMD 0x1022 #define DEVICEID_AMD_MISC0F 0x1103 #define DEVICEID_AMD_MISC10 0x1203 #define DEVICEID_AMD_MISC11 0x1303 #define DEVICEID_AMD_MISC14 0x1703 #define DEVICEID_AMD_MISC15 0x1603 #define DEVICEID_AMD_MISC15_M10H 0x1403 #define DEVICEID_AMD_MISC15_M30H 0x141d #define DEVICEID_AMD_MISC15_M60H_ROOT 0x1576 #define DEVICEID_AMD_MISC16 0x1533 #define DEVICEID_AMD_MISC16_M30H 0x1583 #define DEVICEID_AMD_HOSTB17H_ROOT 0x1450 #define DEVICEID_AMD_HOSTB17H_M10H_ROOT 0x15d0 #define DEVICEID_AMD_HOSTB17H_M30H_ROOT 0x1480 /* Also M70H, F19H M00H/M20H */ #define DEVICEID_AMD_HOSTB17H_M60H_ROOT 0x1630 #define DEVICEID_AMD_HOSTB19H_M60H_ROOT 0x14d8 static const struct amdtemp_product { uint16_t amdtemp_vendorid; uint16_t amdtemp_deviceid; /* * 0xFC register is only valid on the D18F3 PCI device; SMN temp * drivers do not attach to that device. */ bool amdtemp_has_cpuid; } amdtemp_products[] = { { VENDORID_AMD, DEVICEID_AMD_MISC0F, true }, { VENDORID_AMD, DEVICEID_AMD_MISC10, true }, { VENDORID_AMD, DEVICEID_AMD_MISC11, true }, { VENDORID_AMD, DEVICEID_AMD_MISC14, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15_M10H, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15_M30H, true }, { VENDORID_AMD, DEVICEID_AMD_MISC15_M60H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_MISC16, true }, { VENDORID_AMD, DEVICEID_AMD_MISC16_M30H, true }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_M10H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_M30H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB17H_M60H_ROOT, false }, { VENDORID_AMD, DEVICEID_AMD_HOSTB19H_M60H_ROOT, false }, }; /* * Reported Temperature Control Register, family 0Fh-15h (some models), 16h. */ #define AMDTEMP_REPTMP_CTRL 0xa4 #define AMDTEMP_REPTMP10H_CURTMP_MASK 0x7ff #define AMDTEMP_REPTMP10H_CURTMP_SHIFT 21 #define AMDTEMP_REPTMP10H_TJSEL_MASK 0x3 #define AMDTEMP_REPTMP10H_TJSEL_SHIFT 16 /* * Reported Temperature, Family 15h, M60+ * * Same register bit definitions as other Family 15h CPUs, but access is * indirect via SMN, like Family 17h. */ #define AMDTEMP_15H_M60H_REPTMP_CTRL 0xd8200ca4 /* * Reported Temperature, Family 17h * * According to AMD OSRR for 17H, section 4.2.1, bits 31-21 of this register * provide the current temp. bit 19, when clear, means the temp is reported in * a range 0.."225C" (probable typo for 255C), and when set changes the range * to -49..206C. */ #define AMDTEMP_17H_CUR_TMP 0x59800 #define AMDTEMP_17H_CUR_TMP_RANGE_SEL (1u << 19) /* * Bits 16-17, when set, mean that CUR_TMP is read-write. When it is, the * 49 degree offset should apply as well. This was revealed in a Linux * patch from an AMD employee. */ #define AMDTEMP_17H_CUR_TMP_TJ_SEL ((1u << 17) | (1u << 16)) /* * The following register set was discovered experimentally by Ondrej ÄŒerman * and collaborators, but is not (yet) documented in a PPR/OSRR (other than * the M70H PPR SMN memory map showing [0x59800, +0x314] as allocated to * SMU::THM). It seems plausible and the Linux sensor folks have adopted it. */ #define AMDTEMP_17H_CCD_TMP_BASE 0x59954 #define AMDTEMP_17H_CCD_TMP_VALID (1u << 11) #define AMDTEMP_ZEN4_CCD_TMP_BASE 0x59b08 /* * AMD temperature range adjustment, in deciKelvins (i.e., 49.0 Celsius). */ #define AMDTEMP_CURTMP_RANGE_ADJUST 490 /* * Thermaltrip Status Register (Family 0Fh only) */ #define AMDTEMP_THERMTP_STAT 0xe4 #define AMDTEMP_TTSR_SELCORE 0x04 #define AMDTEMP_TTSR_SELSENSOR 0x40 /* * DRAM Configuration High Register */ #define AMDTEMP_DRAM_CONF_HIGH 0x94 /* Function 2 */ #define AMDTEMP_DRAM_MODE_DDR3 0x0100 /* * CPU Family/Model Register */ #define AMDTEMP_CPUID 0xfc /* * Device methods. */ static void amdtemp_identify(driver_t *driver, device_t parent); static int amdtemp_probe(device_t dev); static int amdtemp_attach(device_t dev); static void amdtemp_intrhook(void *arg); static int amdtemp_detach(device_t dev); static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor); static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor); static int32_t amdtemp_gettemp15hm60h(device_t dev, amdsensor_t sensor); static int32_t amdtemp_gettemp17h(device_t dev, amdsensor_t sensor); static void amdtemp_probe_ccd_sensors17h(device_t dev, uint32_t model); static void amdtemp_probe_ccd_sensors19h(device_t dev, uint32_t model); static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t amdtemp_methods[] = { /* Device interface */ DEVMETHOD(device_identify, amdtemp_identify), DEVMETHOD(device_probe, amdtemp_probe), DEVMETHOD(device_attach, amdtemp_attach), DEVMETHOD(device_detach, amdtemp_detach), DEVMETHOD_END }; static driver_t amdtemp_driver = { "amdtemp", amdtemp_methods, sizeof(struct amdtemp_softc), }; DRIVER_MODULE(amdtemp, hostb, amdtemp_driver, NULL, NULL); MODULE_VERSION(amdtemp, 1); MODULE_DEPEND(amdtemp, amdsmn, 1, 1, 1); MODULE_PNP_INFO("U16:vendor;U16:device", pci, amdtemp, amdtemp_products, nitems(amdtemp_products)); static bool amdtemp_match(device_t dev, const struct amdtemp_product **product_out) { int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; i < nitems(amdtemp_products); i++) { if (vendor == amdtemp_products[i].amdtemp_vendorid && devid == amdtemp_products[i].amdtemp_deviceid) { if (product_out != NULL) *product_out = &amdtemp_products[i]; return (true); } } return (false); } static void amdtemp_identify(driver_t *driver, device_t parent) { device_t child; /* Make sure we're not being doubly invoked. */ if (device_find_child(parent, "amdtemp", -1) != NULL) return; if (amdtemp_match(parent, NULL)) { child = device_add_child(parent, "amdtemp", -1); if (child == NULL) device_printf(parent, "add amdtemp child failed\n"); } } static int amdtemp_probe(device_t dev) { uint32_t family, model; if (resource_disabled("amdtemp", 0)) return (ENXIO); if (!amdtemp_match(device_get_parent(dev), NULL)) return (ENXIO); family = CPUID_TO_FAMILY(cpu_id); model = CPUID_TO_MODEL(cpu_id); switch (family) { case 0x0f: if ((model == 0x04 && (cpu_id & CPUID_STEPPING) == 0) || (model == 0x05 && (cpu_id & CPUID_STEPPING) <= 1)) return (ENXIO); break; case 0x10: case 0x11: case 0x12: case 0x14: case 0x15: case 0x16: case 0x17: case 0x19: break; default: return (ENXIO); } device_set_desc(dev, "AMD CPU On-Die Thermal Sensors"); return (BUS_PROBE_GENERIC); } static int amdtemp_attach(device_t dev) { char tn[32]; u_int regs[4]; const struct amdtemp_product *product; struct amdtemp_softc *sc; struct sysctl_ctx_list *sysctlctx; struct sysctl_oid *sysctlnode; uint32_t cpuid, family, model; u_int bid; int erratum319, unit; bool needsmn; sc = device_get_softc(dev); erratum319 = 0; needsmn = false; if (!amdtemp_match(device_get_parent(dev), &product)) return (ENXIO); cpuid = cpu_id; family = CPUID_TO_FAMILY(cpuid); model = CPUID_TO_MODEL(cpuid); /* * This checks for the byzantine condition of running a heterogenous * revision multi-socket system where the attach thread is potentially * probing a remote socket's PCI device. * * Currently, such scenarios are unsupported on models using the SMN * (because on those models, amdtemp(4) attaches to a different PCI * device than the one that contains AMDTEMP_CPUID). * * The ancient 0x0F family of devices only supports this register from * models 40h+. */ if (product->amdtemp_has_cpuid && (family > 0x0f || (family == 0x0f && model >= 0x40))) { cpuid = pci_read_config(device_get_parent(dev), AMDTEMP_CPUID, 4); family = CPUID_TO_FAMILY(cpuid); model = CPUID_TO_MODEL(cpuid); } switch (family) { case 0x0f: /* * Thermaltrip Status Register * * - ThermSenseCoreSel * * Revision F & G: 0 - Core1, 1 - Core0 * Other: 0 - Core0, 1 - Core1 * * - CurTmp * * Revision G: bits 23-14 * Other: bits 23-16 * * XXX According to the BKDG, CurTmp, ThermSenseSel and * ThermSenseCoreSel bits were introduced in Revision F * but CurTmp seems working fine as early as Revision C. * However, it is not clear whether ThermSenseSel and/or * ThermSenseCoreSel work in undocumented cases as well. * In fact, the Linux driver suggests it may not work but * we just assume it does until we find otherwise. * * XXX According to Linux, CurTmp starts at -28C on * Socket AM2 Revision G processors, which is not * documented anywhere. */ if (model >= 0x40) sc->sc_flags |= AMDTEMP_FLAG_CS_SWAP; if (model >= 0x60 && model != 0xc1) { do_cpuid(0x80000001, regs); bid = (regs[1] >> 9) & 0x1f; switch (model) { case 0x68: /* Socket S1g1 */ case 0x6c: case 0x7c: break; case 0x6b: /* Socket AM2 and ASB1 (2 cores) */ if (bid != 0x0b && bid != 0x0c) sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; break; case 0x6f: /* Socket AM2 and ASB1 (1 core) */ case 0x7f: if (bid != 0x07 && bid != 0x09 && bid != 0x0c) sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; break; default: sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; } sc->sc_flags |= AMDTEMP_FLAG_CT_10BIT; } /* * There are two sensors per core. */ sc->sc_ntemps = 2; sc->sc_gettemp = amdtemp_gettemp0f; break; case 0x10: /* * Erratum 319 Inaccurate Temperature Measurement * * http://support.amd.com/us/Processor_TechDocs/41322.pdf */ do_cpuid(0x80000001, regs); switch ((regs[1] >> 28) & 0xf) { case 0: /* Socket F */ erratum319 = 1; break; case 1: /* Socket AM2+ or AM3 */ - if ((pci_cfgregread(pci_get_bus(dev), - pci_get_slot(dev), 2, AMDTEMP_DRAM_CONF_HIGH, 2) & + if ((pci_cfgregread(pci_get_domain(dev), + pci_get_bus(dev), pci_get_slot(dev), 2, + AMDTEMP_DRAM_CONF_HIGH, 2) & AMDTEMP_DRAM_MODE_DDR3) != 0 || model > 0x04 || (model == 0x04 && (cpuid & CPUID_STEPPING) >= 3)) break; /* XXX 00100F42h (RB-C2) exists in both formats. */ erratum319 = 1; break; } /* FALLTHROUGH */ case 0x11: case 0x12: case 0x14: case 0x15: case 0x16: sc->sc_ntemps = 1; /* * Some later (60h+) models of family 15h use a similar SMN * network as family 17h. (However, the register index differs * from 17h and the decoding matches other 10h-15h models, * which differ from 17h.) */ if (family == 0x15 && model >= 0x60) { sc->sc_gettemp = amdtemp_gettemp15hm60h; needsmn = true; } else sc->sc_gettemp = amdtemp_gettemp; break; case 0x17: case 0x19: sc->sc_ntemps = 1; sc->sc_gettemp = amdtemp_gettemp17h; needsmn = true; break; default: device_printf(dev, "Bogus family 0x%x\n", family); return (ENXIO); } if (needsmn) { sc->sc_smn = device_find_child( device_get_parent(dev), "amdsmn", -1); if (sc->sc_smn == NULL) { if (bootverbose) device_printf(dev, "No SMN device found\n"); return (ENXIO); } } /* Find number of cores per package. */ sc->sc_ncores = (amd_feature2 & AMDID2_CMP) != 0 ? (cpu_procinfo2 & AMDID_CMP_CORES) + 1 : 1; if (sc->sc_ncores > MAXCPU) return (ENXIO); mtx_init(&sc->sc_lock, "amdtemp", NULL, MTX_DEF); if (erratum319) device_printf(dev, "Erratum 319: temperature measurement may be inaccurate\n"); if (bootverbose) device_printf(dev, "Found %d cores and %d sensors.\n", sc->sc_ncores, sc->sc_ntemps > 1 ? sc->sc_ntemps * sc->sc_ncores : 1); /* * dev.amdtemp.N tree. */ unit = device_get_unit(dev); snprintf(tn, sizeof(tn), "dev.amdtemp.%d.sensor_offset", unit); TUNABLE_INT_FETCH(tn, &sc->sc_offset); sysctlctx = device_get_sysctl_ctx(dev); SYSCTL_ADD_INT(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor_offset", CTLFLAG_RW, &sc->sc_offset, 0, "Temperature sensor offset"); sysctlnode = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "core0", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Core 0"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE0_SENSOR0, amdtemp_sysctl, "IK", "Core 0 / Sensor 0 temperature"); sc->sc_temp_base = AMDTEMP_17H_CCD_TMP_BASE; if (family == 0x17) amdtemp_probe_ccd_sensors17h(dev, model); else if (family == 0x19) amdtemp_probe_ccd_sensors19h(dev, model); else if (sc->sc_ntemps > 1) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE0_SENSOR1, amdtemp_sysctl, "IK", "Core 0 / Sensor 1 temperature"); if (sc->sc_ncores > 1) { sysctlnode = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "core1", CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Core 1"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE1_SENSOR0, amdtemp_sysctl, "IK", "Core 1 / Sensor 0 temperature"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORE1_SENSOR1, amdtemp_sysctl, "IK", "Core 1 / Sensor 1 temperature"); } } /* * Try to create dev.cpu sysctl entries and setup intrhook function. * This is needed because the cpu driver may be loaded late on boot, * after us. */ amdtemp_intrhook(dev); sc->sc_ich.ich_func = amdtemp_intrhook; sc->sc_ich.ich_arg = dev; if (config_intrhook_establish(&sc->sc_ich) != 0) { device_printf(dev, "config_intrhook_establish failed!\n"); return (ENXIO); } return (0); } void amdtemp_intrhook(void *arg) { struct amdtemp_softc *sc; struct sysctl_ctx_list *sysctlctx; device_t dev = (device_t)arg; device_t acpi, cpu, nexus; amdsensor_t sensor; int i; sc = device_get_softc(dev); /* * dev.cpu.N.temperature. */ nexus = device_find_child(root_bus, "nexus", 0); acpi = device_find_child(nexus, "acpi", 0); for (i = 0; i < sc->sc_ncores; i++) { if (sc->sc_sysctl_cpu[i] != NULL) continue; cpu = device_find_child(acpi, "cpu", device_get_unit(dev) * sc->sc_ncores + i); if (cpu != NULL) { sysctlctx = device_get_sysctl_ctx(cpu); sensor = sc->sc_ntemps > 1 ? (i == 0 ? CORE0 : CORE1) : CORE0_SENSOR0; sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, sensor, amdtemp_sysctl, "IK", "Current temparature"); } } if (sc->sc_ich.ich_arg != NULL) config_intrhook_disestablish(&sc->sc_ich); } int amdtemp_detach(device_t dev) { struct amdtemp_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_ncores; i++) if (sc->sc_sysctl_cpu[i] != NULL) sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0); /* NewBus removes the dev.amdtemp.N tree by itself. */ mtx_destroy(&sc->sc_lock); return (0); } static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t)arg1; struct amdtemp_softc *sc = device_get_softc(dev); amdsensor_t sensor = (amdsensor_t)arg2; int32_t auxtemp[2], temp; int error; switch (sensor) { case CORE0: auxtemp[0] = sc->sc_gettemp(dev, CORE0_SENSOR0); auxtemp[1] = sc->sc_gettemp(dev, CORE0_SENSOR1); temp = imax(auxtemp[0], auxtemp[1]); break; case CORE1: auxtemp[0] = sc->sc_gettemp(dev, CORE1_SENSOR0); auxtemp[1] = sc->sc_gettemp(dev, CORE1_SENSOR1); temp = imax(auxtemp[0], auxtemp[1]); break; default: temp = sc->sc_gettemp(dev, sensor); break; } error = sysctl_handle_int(oidp, &temp, 0, req); return (error); } #define AMDTEMP_ZERO_C_TO_K 2731 static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t mask, offset, temp; mtx_lock(&sc->sc_lock); /* Set Sensor/Core selector. */ temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 1); temp &= ~(AMDTEMP_TTSR_SELCORE | AMDTEMP_TTSR_SELSENSOR); switch (sensor) { case CORE0_SENSOR1: temp |= AMDTEMP_TTSR_SELSENSOR; /* FALLTHROUGH */ case CORE0_SENSOR0: case CORE0: if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) != 0) temp |= AMDTEMP_TTSR_SELCORE; break; case CORE1_SENSOR1: temp |= AMDTEMP_TTSR_SELSENSOR; /* FALLTHROUGH */ case CORE1_SENSOR0: case CORE1: if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) == 0) temp |= AMDTEMP_TTSR_SELCORE; break; default: __assert_unreachable(); } pci_write_config(dev, AMDTEMP_THERMTP_STAT, temp, 1); mask = (sc->sc_flags & AMDTEMP_FLAG_CT_10BIT) != 0 ? 0x3ff : 0x3fc; offset = (sc->sc_flags & AMDTEMP_FLAG_ALT_OFFSET) != 0 ? 28 : 49; temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 4); temp = ((temp >> 14) & mask) * 5 / 2; temp += AMDTEMP_ZERO_C_TO_K + (sc->sc_offset - offset) * 10; mtx_unlock(&sc->sc_lock); return (temp); } static uint32_t amdtemp_decode_fam10h_to_17h(int32_t sc_offset, uint32_t val, bool minus49) { uint32_t temp; /* Convert raw register subfield units (0.125C) to units of 0.1C. */ temp = (val & AMDTEMP_REPTMP10H_CURTMP_MASK) * 5 / 4; if (minus49) temp -= AMDTEMP_CURTMP_RANGE_ADJUST; temp += AMDTEMP_ZERO_C_TO_K + sc_offset * 10; return (temp); } static uint32_t amdtemp_decode_fam10h_to_16h(int32_t sc_offset, uint32_t val) { bool minus49; /* * On Family 15h and higher, if CurTmpTjSel is 11b, the range is * adjusted down by 49.0 degrees Celsius. (This adjustment is not * documented in BKDGs prior to family 15h model 00h.) */ minus49 = (CPUID_TO_FAMILY(cpu_id) >= 0x15 && ((val >> AMDTEMP_REPTMP10H_TJSEL_SHIFT) & AMDTEMP_REPTMP10H_TJSEL_MASK) == 0x3); return (amdtemp_decode_fam10h_to_17h(sc_offset, val >> AMDTEMP_REPTMP10H_CURTMP_SHIFT, minus49)); } static uint32_t amdtemp_decode_fam17h_tctl(int32_t sc_offset, uint32_t val) { bool minus49; minus49 = ((val & AMDTEMP_17H_CUR_TMP_RANGE_SEL) != 0) || ((val & AMDTEMP_17H_CUR_TMP_TJ_SEL) == AMDTEMP_17H_CUR_TMP_TJ_SEL); return (amdtemp_decode_fam10h_to_17h(sc_offset, val >> AMDTEMP_REPTMP10H_CURTMP_SHIFT, minus49)); } static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t temp; temp = pci_read_config(dev, AMDTEMP_REPTMP_CTRL, 4); return (amdtemp_decode_fam10h_to_16h(sc->sc_offset, temp)); } static int32_t amdtemp_gettemp15hm60h(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t val; int error __diagused; error = amdsmn_read(sc->sc_smn, AMDTEMP_15H_M60H_REPTMP_CTRL, &val); KASSERT(error == 0, ("amdsmn_read")); return (amdtemp_decode_fam10h_to_16h(sc->sc_offset, val)); } static int32_t amdtemp_gettemp17h(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t val; int error __diagused; switch (sensor) { case CORE0_SENSOR0: /* Tctl */ error = amdsmn_read(sc->sc_smn, AMDTEMP_17H_CUR_TMP, &val); KASSERT(error == 0, ("amdsmn_read")); return (amdtemp_decode_fam17h_tctl(sc->sc_offset, val)); case CCD_BASE ... CCD_MAX: /* Tccd */ error = amdsmn_read(sc->sc_smn, sc->sc_temp_base + (((int)sensor - CCD_BASE) * sizeof(val)), &val); KASSERT(error == 0, ("amdsmn_read2")); KASSERT((val & AMDTEMP_17H_CCD_TMP_VALID) != 0, ("sensor %d: not valid", (int)sensor)); return (amdtemp_decode_fam10h_to_17h(sc->sc_offset, val, true)); default: __assert_unreachable(); } } static void amdtemp_probe_ccd_sensors(device_t dev, uint32_t maxreg) { char sensor_name[16], sensor_descr[32]; struct amdtemp_softc *sc; uint32_t i, val; int error; sc = device_get_softc(dev); for (i = 0; i < maxreg; i++) { error = amdsmn_read(sc->sc_smn, sc->sc_temp_base + (i * sizeof(val)), &val); if (error != 0) continue; if ((val & AMDTEMP_17H_CCD_TMP_VALID) == 0) continue; snprintf(sensor_name, sizeof(sensor_name), "ccd%u", i); snprintf(sensor_descr, sizeof(sensor_descr), "CCD %u temperature (Tccd%u)", i, i); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, sensor_name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CCD_BASE + i, amdtemp_sysctl, "IK", sensor_descr); } } static void amdtemp_probe_ccd_sensors17h(device_t dev, uint32_t model) { uint32_t maxreg; switch (model) { case 0x00 ... 0x2f: /* Zen1, Zen+ */ maxreg = 4; break; case 0x30 ... 0x3f: /* Zen2 TR (Castle Peak)/EPYC (Rome) */ case 0x60 ... 0x7f: /* Zen2 Ryzen (Renoir APU, Matisse) */ case 0x90 ... 0x9f: /* Zen2 Ryzen (Van Gogh APU) */ maxreg = 8; _Static_assert((int)NUM_CCDS >= 8, ""); break; default: device_printf(dev, "Unrecognized Family 17h Model: %02xh\n", model); return; } amdtemp_probe_ccd_sensors(dev, maxreg); } static void amdtemp_probe_ccd_sensors19h(device_t dev, uint32_t model) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t maxreg; switch (model) { case 0x00 ... 0x0f: /* Zen3 EPYC "Milan" */ case 0x20 ... 0x2f: /* Zen3 Ryzen "Vermeer" */ maxreg = 8; _Static_assert((int)NUM_CCDS >= 8, ""); break; case 0x60 ... 0x6f: /* Zen4 Ryzen "Raphael" */ sc->sc_temp_base = AMDTEMP_ZEN4_CCD_TMP_BASE; maxreg = 8; _Static_assert((int)NUM_CCDS >= 8, ""); break; default: device_printf(dev, "Unrecognized Family 19h Model: %02xh\n", model); return; } amdtemp_probe_ccd_sensors(dev, maxreg); } diff --git a/sys/dev/hpt27xx/hpt27xx_os_bsd.c b/sys/dev/hpt27xx/hpt27xx_os_bsd.c index ebd58e771462..f4bbcfd11e82 100644 --- a/sys/dev/hpt27xx/hpt27xx_os_bsd.c +++ b/sys/dev/hpt27xx/hpt27xx_os_bsd.c @@ -1,315 +1,315 @@ /*- * HighPoint RAID Driver for FreeBSD * * SPDX-License-Identifier: BSD-2-Clause * * Copyright (C) 2005-2011 HighPoint Technologies, Inc. All Rights Reserved. * All rights reserved. * * 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 #include BUS_ADDRESS get_dmapool_phy_addr(void *osext, void * dmapool_virt_addr); /* hardware access */ HPT_U8 os_inb (void *port) { return inb((unsigned)(HPT_UPTR)port); } HPT_U16 os_inw (void *port) { return inw((unsigned)(HPT_UPTR)port); } HPT_U32 os_inl (void *port) { return inl((unsigned)(HPT_UPTR)port); } void os_outb (void *port, HPT_U8 value) { outb((unsigned)(HPT_UPTR)port, (value)); } void os_outw (void *port, HPT_U16 value) { outw((unsigned)(HPT_UPTR)port, (value)); } void os_outl (void *port, HPT_U32 value) { outl((unsigned)(HPT_UPTR)port, (value)); } void os_insw (void *port, HPT_U16 *buffer, HPT_U32 count) { insw((unsigned)(HPT_UPTR)port, (void *)buffer, count); } void os_outsw(void *port, HPT_U16 *buffer, HPT_U32 count) { outsw((unsigned)(HPT_UPTR)port, (void *)buffer, count); } HPT_U32 __dummy_reg = 0; /* PCI configuration space */ HPT_U8 os_pci_readb (void *osext, HPT_U8 offset) { return pci_read_config(((PHBA)osext)->pcidev, offset, 1); } HPT_U16 os_pci_readw (void *osext, HPT_U8 offset) { return pci_read_config(((PHBA)osext)->pcidev, offset, 2); } HPT_U32 os_pci_readl (void *osext, HPT_U8 offset) { return pci_read_config(((PHBA)osext)->pcidev, offset, 4); } void os_pci_writeb (void *osext, HPT_U8 offset, HPT_U8 value) { pci_write_config(((PHBA)osext)->pcidev, offset, value, 1); } void os_pci_writew (void *osext, HPT_U8 offset, HPT_U16 value) { pci_write_config(((PHBA)osext)->pcidev, offset, value, 2); } void os_pci_writel (void *osext, HPT_U8 offset, HPT_U32 value) { pci_write_config(((PHBA)osext)->pcidev, offset, value, 4); } BUS_ADDRESS get_dmapool_phy_addr(void *osext, void * dmapool_virt_addr) { return (BUS_ADDRESS)vtophys(dmapool_virt_addr); } /* PCI space access */ HPT_U8 pcicfg_read_byte (HPT_U8 bus, HPT_U8 dev, HPT_U8 func, HPT_U8 reg) { - return (HPT_U8)pci_cfgregread(bus, dev, func, reg, 1); + return (HPT_U8)pci_cfgregread(0, bus, dev, func, reg, 1); } HPT_U32 pcicfg_read_dword(HPT_U8 bus, HPT_U8 dev, HPT_U8 func, HPT_U8 reg) { - return (HPT_U32)pci_cfgregread(bus, dev, func, reg, 4); + return (HPT_U32)pci_cfgregread(0, bus, dev, func, reg, 4); } void pcicfg_write_byte (HPT_U8 bus, HPT_U8 dev, HPT_U8 func, HPT_U8 reg, HPT_U8 v) { - pci_cfgregwrite(bus, dev, func, reg, v, 1); + pci_cfgregwrite(0, bus, dev, func, reg, v, 1); } void pcicfg_write_dword(HPT_U8 bus, HPT_U8 dev, HPT_U8 func, HPT_U8 reg, HPT_U32 v) { - pci_cfgregwrite(bus, dev, func, reg, v, 4); + pci_cfgregwrite(0, bus, dev, func, reg, v, 4); }/* PCI space access */ void *os_map_pci_bar( void *osext, int index, HPT_U32 offset, HPT_U32 length ) { PHBA hba = (PHBA)osext; HPT_U32 base; hba->pcibar[index].rid = 0x10 + index * 4; base = pci_read_config(hba->pcidev, hba->pcibar[index].rid, 4); if (base & 1) { hba->pcibar[index].type = SYS_RES_IOPORT; hba->pcibar[index].res = bus_alloc_resource_any(hba->pcidev, hba->pcibar[index].type, &hba->pcibar[index].rid, RF_ACTIVE); hba->pcibar[index].base = (void *)(unsigned long)(base & ~0x1); } else { hba->pcibar[index].type = SYS_RES_MEMORY; hba->pcibar[index].res = bus_alloc_resource_any(hba->pcidev, hba->pcibar[index].type, &hba->pcibar[index].rid, RF_ACTIVE); hba->pcibar[index].base = (char *)rman_get_virtual(hba->pcibar[index].res) + offset; } return hba->pcibar[index].base; } void os_unmap_pci_bar(void *osext, void *base) { PHBA hba = (PHBA)osext; int index; for (index=0; index<6; index++) { if (hba->pcibar[index].base==base) { bus_release_resource(hba->pcidev, hba->pcibar[index].type, hba->pcibar[index].rid, hba->pcibar[index].res); hba->pcibar[index].base = 0; return; } } } void freelist_reserve(struct freelist *list, void *osext, HPT_UINT size, HPT_UINT count) { PVBUS_EXT vbus_ext = osext; if (vbus_ext->ext_type!=EXT_TYPE_VBUS) vbus_ext = ((PHBA)osext)->vbus_ext; list->next = vbus_ext->freelist_head; vbus_ext->freelist_head = list; list->dma = 0; list->size = size; list->head = 0; #if DBG list->reserved_count = #endif list->count = count; } void *freelist_get(struct freelist *list) { void * result; if (list->count) { HPT_ASSERT(list->head); result = list->head; list->head = *(void **)result; list->count--; return result; } return 0; } void freelist_put(struct freelist * list, void *p) { HPT_ASSERT(list->dma==0); list->count++; *(void **)p = list->head; list->head = p; } void freelist_reserve_dma(struct freelist *list, void *osext, HPT_UINT size, HPT_UINT alignment, HPT_UINT count) { PVBUS_EXT vbus_ext = osext; if (vbus_ext->ext_type!=EXT_TYPE_VBUS) vbus_ext = ((PHBA)osext)->vbus_ext; list->next = vbus_ext->freelist_dma_head; vbus_ext->freelist_dma_head = list; list->dma = 1; list->alignment = alignment; list->size = size; list->head = 0; #if DBG list->reserved_count = #endif list->count = count; } void *freelist_get_dma(struct freelist *list, BUS_ADDRESS *busaddr) { void *result; HPT_ASSERT(list->dma); result = freelist_get(list); if (result) *busaddr = *(BUS_ADDRESS *)((void **)result+1); return result; } void freelist_put_dma(struct freelist *list, void *p, BUS_ADDRESS busaddr) { HPT_ASSERT(list->dma); list->count++; *(void **)p = list->head; *(BUS_ADDRESS *)((void **)p+1) = busaddr; list->head = p; } HPT_U32 os_get_stamp(void) { HPT_U32 stamp; do { stamp = random(); } while (stamp==0); return stamp; } void os_stallexec(HPT_U32 microseconds) { DELAY(microseconds); } static void os_timer_for_ldm(void *arg) { PVBUS_EXT vbus_ext = (PVBUS_EXT)arg; ldm_on_timer((PVBUS)vbus_ext->vbus); } void os_request_timer(void * osext, HPT_U32 interval) { PVBUS_EXT vbus_ext = osext; HPT_ASSERT(vbus_ext->ext_type==EXT_TYPE_VBUS); callout_reset_sbt(&vbus_ext->timer, SBT_1US * interval, 0, os_timer_for_ldm, vbus_ext, 0); } HPT_TIME os_query_time(void) { return ticks * (1000000 / hz); } void os_schedule_task(void *osext, OSM_TASK *task) { PVBUS_EXT vbus_ext = osext; HPT_ASSERT(task->next==0); if (vbus_ext->tasks==0) vbus_ext->tasks = task; else { OSM_TASK *t = vbus_ext->tasks; while (t->next) t = t->next; t->next = task; } if (vbus_ext->worker.ta_context) TASK_ENQUEUE(&vbus_ext->worker); } int os_revalidate_device(void *osext, int id) { return 0; } int os_query_remove_device(void *osext, int id) { return 0; } HPT_U8 os_get_vbus_seq(void *osext) { return ((PVBUS_EXT)osext)->sim->path_id; } int os_printk(char *fmt, ...) { va_list args; static char buf[512]; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); return printf("%s: %s\n", driver_name, buf); } #if DBG void os_check_stack(const char *location, int size){} void __os_dbgbreak(const char *file, int line) { printf("*** break at %s:%d ***", file, line); while (1); } int hpt_dbg_level = 1; #endif diff --git a/sys/dev/hpt27xx/hpt27xx_osm_bsd.c b/sys/dev/hpt27xx/hpt27xx_osm_bsd.c index 7eb7dbc8fc72..32d3e465c569 100644 --- a/sys/dev/hpt27xx/hpt27xx_osm_bsd.c +++ b/sys/dev/hpt27xx/hpt27xx_osm_bsd.c @@ -1,1395 +1,1400 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 HighPoint Technologies, Inc. * All rights reserved. * * 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 #include #include static HIM *hpt_match(device_t dev, int scan) { PCI_ID pci_id; HIM *him; int i; for (him = him_list; him; him = him->next) { for (i=0; him->get_supported_device_id(i, &pci_id); i++) { if (scan && him->get_controller_count) him->get_controller_count(&pci_id,0,0); if ((pci_get_vendor(dev) == pci_id.vid) && (pci_get_device(dev) == pci_id.did)){ return (him); } } } return (NULL); } static int hpt_probe(device_t dev) { HIM *him; him = hpt_match(dev, 0); if (him != NULL) { KdPrint(("hpt_probe: adapter at PCI %d:%d:%d, IRQ %d", pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev), pci_get_irq(dev) )); device_set_desc(dev, him->name); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int hpt_attach(device_t dev) { PHBA hba = (PHBA)device_get_softc(dev); HIM *him; PCI_ID pci_id; HPT_UINT size; PVBUS vbus; PVBUS_EXT vbus_ext; + if (pci_get_domain(dev) != 0) { + device_printf(dev, "does not support PCI domains\n"); + return (ENXIO); + } + KdPrint(("hpt_attach(%d/%d/%d)", pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev))); him = hpt_match(dev, 1); hba->ext_type = EXT_TYPE_HBA; hba->ldm_adapter.him = him; pci_enable_busmaster(dev); pci_id.vid = pci_get_vendor(dev); pci_id.did = pci_get_device(dev); pci_id.rev = pci_get_revid(dev); pci_id.subsys = (HPT_U32)(pci_get_subdevice(dev)) << 16 | pci_get_subvendor(dev); size = him->get_adapter_size(&pci_id); hba->ldm_adapter.him_handle = malloc(size, M_DEVBUF, M_WAITOK); if (!hba->ldm_adapter.him_handle) return ENXIO; hba->pcidev = dev; hba->pciaddr.tree = 0; hba->pciaddr.bus = pci_get_bus(dev); hba->pciaddr.device = pci_get_slot(dev); hba->pciaddr.function = pci_get_function(dev); if (!him->create_adapter(&pci_id, hba->pciaddr, hba->ldm_adapter.him_handle, hba)) { free(hba->ldm_adapter.him_handle, M_DEVBUF); return ENXIO; } os_printk("adapter at PCI %d:%d:%d, IRQ %d", hba->pciaddr.bus, hba->pciaddr.device, hba->pciaddr.function, pci_get_irq(dev)); if (!ldm_register_adapter(&hba->ldm_adapter)) { size = ldm_get_vbus_size(); vbus_ext = malloc(sizeof(VBUS_EXT) + size, M_DEVBUF, M_WAITOK); if (!vbus_ext) { free(hba->ldm_adapter.him_handle, M_DEVBUF); return ENXIO; } memset(vbus_ext, 0, sizeof(VBUS_EXT)); vbus_ext->ext_type = EXT_TYPE_VBUS; ldm_create_vbus((PVBUS)vbus_ext->vbus, vbus_ext); ldm_register_adapter(&hba->ldm_adapter); } ldm_for_each_vbus(vbus, vbus_ext) { if (hba->ldm_adapter.vbus==vbus) { hba->vbus_ext = vbus_ext; hba->next = vbus_ext->hba_list; vbus_ext->hba_list = hba; break; } } return 0; } /* * Maybe we'd better to use the bus_dmamem_alloc to alloc DMA memory, * but there are some problems currently (alignment, etc). */ static __inline void *__get_free_pages(int order) { /* don't use low memory - other devices may get starved */ return contigmalloc(PAGE_SIZE<hba_list; hba; hba = hba->next) hba->ldm_adapter.him->get_meminfo(hba->ldm_adapter.him_handle); ldm_get_mem_info((PVBUS)vbus_ext->vbus, 0); for (f=vbus_ext->freelist_head; f; f=f->next) { KdPrint(("%s: %d*%d=%d bytes", f->tag, f->count, f->size, f->count*f->size)); for (i=0; icount; i++) { p = (void **)malloc(f->size, M_DEVBUF, M_WAITOK); if (!p) return (ENXIO); *p = f->head; f->head = p; } } for (f=vbus_ext->freelist_dma_head; f; f=f->next) { int order, size, j; HPT_ASSERT((f->size & (f->alignment-1))==0); for (order=0, size=PAGE_SIZE; sizesize; order++, size<<=1) ; KdPrint(("%s: %d*%d=%d bytes, order %d", f->tag, f->count, f->size, f->count*f->size, order)); HPT_ASSERT(f->alignment<=PAGE_SIZE); for (i=0; icount;) { p = (void **)__get_free_pages(order); if (!p) return -1; for (j = size/f->size; j && icount; i++,j--) { *p = f->head; *(BUS_ADDRESS *)(p+1) = (BUS_ADDRESS)vtophys(p); f->head = p; p = (void **)((unsigned long)p + f->size); } } } HPT_ASSERT(PAGE_SIZE==DMAPOOL_PAGE_SIZE); for (i=0; ivbus, p, (BUS_ADDRESS)vtophys(p)); } return 0; } static void hpt_free_mem(PVBUS_EXT vbus_ext) { struct freelist *f; void *p; int i; BUS_ADDRESS bus; for (f=vbus_ext->freelist_head; f; f=f->next) { #if DBG if (f->count!=f->reserved_count) { KdPrint(("memory leak for freelist %s (%d/%d)", f->tag, f->count, f->reserved_count)); } #endif while ((p=freelist_get(f))) free(p, M_DEVBUF); } for (i=0; ivbus, &bus); HPT_ASSERT(p); free_pages(p, 0); } for (f=vbus_ext->freelist_dma_head; f; f=f->next) { int order, size; #if DBG if (f->count!=f->reserved_count) { KdPrint(("memory leak for dma freelist %s (%d/%d)", f->tag, f->count, f->reserved_count)); } #endif for (order=0, size=PAGE_SIZE; sizesize; order++, size<<=1) ; while ((p=freelist_get_dma(f, &bus))) { if (order) free_pages(p, order); else { /* can't free immediately since other blocks in this page may still be in the list */ if (((HPT_UPTR)p & (PAGE_SIZE-1))==0) dmapool_put_page((PVBUS)vbus_ext->vbus, p, bus); } } } while ((p = dmapool_get_page((PVBUS)vbus_ext->vbus, &bus))) free_pages(p, 0); } static int hpt_init_vbus(PVBUS_EXT vbus_ext) { PHBA hba; for (hba = vbus_ext->hba_list; hba; hba = hba->next) if (!hba->ldm_adapter.him->initialize(hba->ldm_adapter.him_handle)) { KdPrint(("fail to initialize %p", hba)); return -1; } ldm_initialize_vbus((PVBUS)vbus_ext->vbus, &vbus_ext->hba_list->ldm_adapter); return 0; } static void hpt_flush_done(PCOMMAND pCmd) { PVDEV vd = pCmd->target; if (mIsArray(vd->type) && vd->u.array.transform && vd!=vd->u.array.transform->target) { vd = vd->u.array.transform->target; HPT_ASSERT(vd); pCmd->target = vd; pCmd->Result = RETURN_PENDING; vdev_queue_cmd(pCmd); return; } *(int *)pCmd->priv = 1; wakeup(pCmd); } /* * flush a vdev (without retry). */ static int hpt_flush_vdev(PVBUS_EXT vbus_ext, PVDEV vd) { PCOMMAND pCmd; int result = 0, done; HPT_UINT count; KdPrint(("flusing dev %p", vd)); hpt_lock_vbus(vbus_ext); if (mIsArray(vd->type) && vd->u.array.transform) count = max(vd->u.array.transform->source->cmds_per_request, vd->u.array.transform->target->cmds_per_request); else count = vd->cmds_per_request; pCmd = ldm_alloc_cmds(vd->vbus, count); if (!pCmd) { hpt_unlock_vbus(vbus_ext); return -1; } pCmd->type = CMD_TYPE_FLUSH; pCmd->flags.hard_flush = 1; pCmd->target = vd; pCmd->done = hpt_flush_done; done = 0; pCmd->priv = &done; ldm_queue_cmd(pCmd); if (!done) { while (hpt_sleep(vbus_ext, pCmd, PPAUSE, "hptfls", HPT_OSM_TIMEOUT)) { ldm_reset_vbus(vd->vbus); } } KdPrint(("flush result %d", pCmd->Result)); if (pCmd->Result!=RETURN_SUCCESS) result = -1; ldm_free_cmds(pCmd); hpt_unlock_vbus(vbus_ext); return result; } static void hpt_stop_tasks(PVBUS_EXT vbus_ext); static void hpt_shutdown_vbus(PVBUS_EXT vbus_ext, int howto) { PVBUS vbus = (PVBUS)vbus_ext->vbus; PHBA hba; int i; KdPrint(("hpt_shutdown_vbus")); /* stop all ctl tasks and disable the worker taskqueue */ hpt_stop_tasks(vbus_ext); vbus_ext->worker.ta_context = 0; /* flush devices */ for (i=0; ihba_list; hba; hba=hba->next) bus_teardown_intr(hba->pcidev, hba->irq_res, hba->irq_handle); hpt_free_mem(vbus_ext); while ((hba=vbus_ext->hba_list)) { vbus_ext->hba_list = hba->next; free(hba->ldm_adapter.him_handle, M_DEVBUF); } callout_drain(&vbus_ext->timer); mtx_destroy(&vbus_ext->lock); free(vbus_ext, M_DEVBUF); KdPrint(("hpt_shutdown_vbus done")); } static void __hpt_do_tasks(PVBUS_EXT vbus_ext) { OSM_TASK *tasks; tasks = vbus_ext->tasks; vbus_ext->tasks = 0; while (tasks) { OSM_TASK *t = tasks; tasks = t->next; t->next = 0; t->func(vbus_ext->vbus, t->data); } } static void hpt_do_tasks(PVBUS_EXT vbus_ext, int pending) { if(vbus_ext){ hpt_lock_vbus(vbus_ext); __hpt_do_tasks(vbus_ext); hpt_unlock_vbus(vbus_ext); } } static void hpt_action(struct cam_sim *sim, union ccb *ccb); static void hpt_poll(struct cam_sim *sim); static void hpt_async(void * callback_arg, u_int32_t code, struct cam_path * path, void * arg); static void hpt_pci_intr(void *arg); static __inline POS_CMDEXT cmdext_get(PVBUS_EXT vbus_ext) { POS_CMDEXT p = vbus_ext->cmdext_list; if (p) vbus_ext->cmdext_list = p->next; return p; } static __inline void cmdext_put(POS_CMDEXT p) { p->next = p->vbus_ext->cmdext_list; p->vbus_ext->cmdext_list = p; } static void hpt_timeout(void *arg) { PCOMMAND pCmd = (PCOMMAND)arg; POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv; KdPrint(("pCmd %p timeout", pCmd)); ldm_reset_vbus((PVBUS)ext->vbus_ext->vbus); } static void os_cmddone(PCOMMAND pCmd) { POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv; union ccb *ccb = ext->ccb; KdPrint(("<8>os_cmddone(%p, %d)", pCmd, pCmd->Result)); callout_stop(&ext->timeout); switch(pCmd->Result) { case RETURN_SUCCESS: ccb->ccb_h.status = CAM_REQ_CMP; break; case RETURN_BAD_DEVICE: ccb->ccb_h.status = CAM_DEV_NOT_THERE; break; case RETURN_DEVICE_BUSY: ccb->ccb_h.status = CAM_BUSY; break; case RETURN_INVALID_REQUEST: ccb->ccb_h.status = CAM_REQ_INVALID; break; case RETURN_SELECTION_TIMEOUT: ccb->ccb_h.status = CAM_SEL_TIMEOUT; break; case RETURN_RETRY: ccb->ccb_h.status = CAM_BUSY; break; default: ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; break; } if (pCmd->flags.data_in) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_POSTREAD); } else if (pCmd->flags.data_out) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_POSTWRITE); } bus_dmamap_unload(ext->vbus_ext->io_dmat, ext->dma_map); cmdext_put(ext); ldm_free_cmds(pCmd); xpt_done(ccb); } static int os_buildsgl(PCOMMAND pCmd, PSG pSg, int logical) { POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv; union ccb *ccb = ext->ccb; if(logical) { os_set_sgptr(pSg, (HPT_U8 *)ccb->csio.data_ptr); pSg->size = ccb->csio.dxfer_len; pSg->eot = 1; return TRUE; } /* since we have provided physical sg, nobody will ask us to build physical sg */ HPT_ASSERT(0); return FALSE; } static void hpt_io_dmamap_callback(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { PCOMMAND pCmd = (PCOMMAND)arg; POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv; PSG psg = pCmd->psg; int idx; HPT_ASSERT(pCmd->flags.physical_sg); if (error) panic("busdma error"); HPT_ASSERT(nsegs<=os_max_sg_descriptors); if (nsegs != 0) { for (idx = 0; idx < nsegs; idx++, psg++) { psg->addr.bus = segs[idx].ds_addr; psg->size = segs[idx].ds_len; psg->eot = 0; } psg[-1].eot = 1; if (pCmd->flags.data_in) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_PREREAD); } else if (pCmd->flags.data_out) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_PREWRITE); } } callout_reset(&ext->timeout, HPT_OSM_TIMEOUT, hpt_timeout, pCmd); ldm_queue_cmd(pCmd); } static void hpt_scsi_io(PVBUS_EXT vbus_ext, union ccb *ccb) { PVBUS vbus = (PVBUS)vbus_ext->vbus; PVDEV vd; PCOMMAND pCmd; POS_CMDEXT ext; HPT_U8 *cdb; int error; if (ccb->ccb_h.flags & CAM_CDB_POINTER) cdb = ccb->csio.cdb_io.cdb_ptr; else cdb = ccb->csio.cdb_io.cdb_bytes; KdPrint(("<8>hpt_scsi_io: ccb %x id %d lun %d cdb %x-%x-%x", ccb, ccb->ccb_h.target_id, ccb->ccb_h.target_lun, *(HPT_U32 *)&cdb[0], *(HPT_U32 *)&cdb[4], *(HPT_U32 *)&cdb[8] )); /* ccb->ccb_h.path_id is not our bus id - don't check it */ if (ccb->ccb_h.target_lun != 0 || ccb->ccb_h.target_id >= osm_max_targets || (ccb->ccb_h.flags & CAM_CDB_PHYS)) { ccb->ccb_h.status = CAM_TID_INVALID; xpt_done(ccb); return; } vd = ldm_find_target(vbus, ccb->ccb_h.target_id); if (!vd) { ccb->ccb_h.status = CAM_SEL_TIMEOUT; xpt_done(ccb); return; } switch (cdb[0]) { case TEST_UNIT_READY: case START_STOP_UNIT: case SYNCHRONIZE_CACHE: ccb->ccb_h.status = CAM_REQ_CMP; break; case INQUIRY: { PINQUIRYDATA inquiryData; memset(ccb->csio.data_ptr, 0, ccb->csio.dxfer_len); inquiryData = (PINQUIRYDATA)ccb->csio.data_ptr; inquiryData->AdditionalLength = 31; inquiryData->CommandQueue = 1; memcpy(&inquiryData->VendorId, "HPT ", 8); memcpy(&inquiryData->ProductId, "DISK 0_0 ", 16); if (vd->target_id / 10) { inquiryData->ProductId[7] = (vd->target_id % 100) / 10 + '0'; inquiryData->ProductId[8] = (vd->target_id % 100) % 10 + '0'; } else inquiryData->ProductId[7] = (vd->target_id % 100) % 10 + '0'; memcpy(&inquiryData->ProductRevisionLevel, "4.00", 4); ccb->ccb_h.status = CAM_REQ_CMP; } break; case READ_CAPACITY: { HPT_U8 *rbuf = ccb->csio.data_ptr; HPT_U32 cap; HPT_U8 sector_size_shift = 0; HPT_U64 new_cap; HPT_U32 sector_size = 0; if (mIsArray(vd->type)) sector_size_shift = vd->u.array.sector_size_shift; else{ if(vd->type == VD_RAW){ sector_size = vd->u.raw.logical_sector_size; } switch (sector_size) { case 0x1000: KdPrint(("set 4k setctor size in READ_CAPACITY")); sector_size_shift = 3; break; default: break; } } new_cap = vd->capacity >> sector_size_shift; if (new_cap > 0xfffffffful) cap = 0xffffffff; else cap = new_cap - 1; rbuf[0] = (HPT_U8)(cap>>24); rbuf[1] = (HPT_U8)(cap>>16); rbuf[2] = (HPT_U8)(cap>>8); rbuf[3] = (HPT_U8)cap; rbuf[4] = 0; rbuf[5] = 0; rbuf[6] = 2 << sector_size_shift; rbuf[7] = 0; ccb->ccb_h.status = CAM_REQ_CMP; break; } case REPORT_LUNS: { HPT_U8 *rbuf = ccb->csio.data_ptr; memset(rbuf, 0, 16); rbuf[3] = 8; ccb->ccb_h.status = CAM_REQ_CMP; break; } case SERVICE_ACTION_IN: { HPT_U8 *rbuf = ccb->csio.data_ptr; HPT_U64 cap = 0; HPT_U8 sector_size_shift = 0; HPT_U32 sector_size = 0; if(mIsArray(vd->type)) sector_size_shift = vd->u.array.sector_size_shift; else{ if(vd->type == VD_RAW){ sector_size = vd->u.raw.logical_sector_size; } switch (sector_size) { case 0x1000: KdPrint(("set 4k setctor size in SERVICE_ACTION_IN")); sector_size_shift = 3; break; default: break; } } cap = (vd->capacity >> sector_size_shift) - 1; rbuf[0] = (HPT_U8)(cap>>56); rbuf[1] = (HPT_U8)(cap>>48); rbuf[2] = (HPT_U8)(cap>>40); rbuf[3] = (HPT_U8)(cap>>32); rbuf[4] = (HPT_U8)(cap>>24); rbuf[5] = (HPT_U8)(cap>>16); rbuf[6] = (HPT_U8)(cap>>8); rbuf[7] = (HPT_U8)cap; rbuf[8] = 0; rbuf[9] = 0; rbuf[10] = 2 << sector_size_shift; rbuf[11] = 0; ccb->ccb_h.status = CAM_REQ_CMP; break; } case READ_6: case READ_10: case READ_16: case WRITE_6: case WRITE_10: case WRITE_16: case 0x13: case 0x2f: case 0x8f: /* VERIFY_16 */ { HPT_U8 sector_size_shift = 0; HPT_U32 sector_size = 0; pCmd = ldm_alloc_cmds(vbus, vd->cmds_per_request); if(!pCmd){ KdPrint(("Failed to allocate command!")); ccb->ccb_h.status = CAM_BUSY; break; } switch (cdb[0]) { case READ_6: case WRITE_6: case 0x13: pCmd->uCmd.Ide.Lba = ((HPT_U32)cdb[1] << 16) | ((HPT_U32)cdb[2] << 8) | (HPT_U32)cdb[3]; pCmd->uCmd.Ide.nSectors = (HPT_U16) cdb[4]; break; case READ_16: case WRITE_16: case 0x8f: /* VERIFY_16 */ { HPT_U64 block = ((HPT_U64)cdb[2]<<56) | ((HPT_U64)cdb[3]<<48) | ((HPT_U64)cdb[4]<<40) | ((HPT_U64)cdb[5]<<32) | ((HPT_U64)cdb[6]<<24) | ((HPT_U64)cdb[7]<<16) | ((HPT_U64)cdb[8]<<8) | ((HPT_U64)cdb[9]); pCmd->uCmd.Ide.Lba = block; pCmd->uCmd.Ide.nSectors = (HPT_U16)cdb[13] | ((HPT_U16)cdb[12]<<8); break; } default: pCmd->uCmd.Ide.Lba = (HPT_U32)cdb[5] | ((HPT_U32)cdb[4] << 8) | ((HPT_U32)cdb[3] << 16) | ((HPT_U32)cdb[2] << 24); pCmd->uCmd.Ide.nSectors = (HPT_U16) cdb[8] | ((HPT_U16)cdb[7]<<8); break; } if(mIsArray(vd->type)) { sector_size_shift = vd->u.array.sector_size_shift; } else{ if(vd->type == VD_RAW){ sector_size = vd->u.raw.logical_sector_size; } switch (sector_size) { case 0x1000: KdPrint(("<8>resize sector size from 4k to 512")); sector_size_shift = 3; break; default: break; } } pCmd->uCmd.Ide.Lba <<= sector_size_shift; pCmd->uCmd.Ide.nSectors <<= sector_size_shift; switch (cdb[0]) { case READ_6: case READ_10: case READ_16: pCmd->flags.data_in = 1; break; case WRITE_6: case WRITE_10: case WRITE_16: pCmd->flags.data_out = 1; break; } pCmd->priv = ext = cmdext_get(vbus_ext); HPT_ASSERT(ext); ext->ccb = ccb; pCmd->target = vd; pCmd->done = os_cmddone; pCmd->buildsgl = os_buildsgl; pCmd->psg = ext->psg; pCmd->flags.physical_sg = 1; error = bus_dmamap_load_ccb(vbus_ext->io_dmat, ext->dma_map, ccb, hpt_io_dmamap_callback, pCmd, BUS_DMA_WAITOK ); KdPrint(("<8>bus_dmamap_load return %d", error)); if (error && error!=EINPROGRESS) { os_printk("bus_dmamap_load error %d", error); cmdext_put(ext); ldm_free_cmds(pCmd); ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); } return; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; } static void hpt_action(struct cam_sim *sim, union ccb *ccb) { PVBUS_EXT vbus_ext = (PVBUS_EXT)cam_sim_softc(sim); KdPrint(("<8>hpt_action(fn=%d, id=%d)", ccb->ccb_h.func_code, ccb->ccb_h.target_id)); hpt_assert_vbus_locked(vbus_ext); switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: hpt_scsi_io(vbus_ext, ccb); return; case XPT_RESET_BUS: ldm_reset_vbus((PVBUS)vbus_ext->vbus); break; case XPT_GET_TRAN_SETTINGS: case XPT_SET_TRAN_SETTINGS: ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; break; case XPT_CALC_GEOMETRY: ccb->ccg.heads = 255; ccb->ccg.secs_per_track = 63; ccb->ccg.cylinders = ccb->ccg.volume_size / (ccb->ccg.heads * ccb->ccg.secs_per_track); ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE; cpi->target_sprt = 0; cpi->hba_misc = PIM_NOBUSRESET; cpi->hba_eng_cnt = 0; cpi->max_target = osm_max_targets; cpi->max_lun = 0; cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->initiator_id = osm_max_targets; cpi->base_transfer_speed = 3300; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "HPT ", HBA_IDLEN); strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; } static void hpt_pci_intr(void *arg) { PVBUS_EXT vbus_ext = (PVBUS_EXT)arg; hpt_lock_vbus(vbus_ext); ldm_intr((PVBUS)vbus_ext->vbus); hpt_unlock_vbus(vbus_ext); } static void hpt_poll(struct cam_sim *sim) { PVBUS_EXT vbus_ext = (PVBUS_EXT)cam_sim_softc(sim); hpt_assert_vbus_locked(vbus_ext); ldm_intr((PVBUS)vbus_ext->vbus); } static void hpt_async(void * callback_arg, u_int32_t code, struct cam_path * path, void * arg) { KdPrint(("<8>hpt_async")); } static int hpt_shutdown(device_t dev) { KdPrint(("hpt_shutdown(dev=%p)", dev)); return 0; } static int hpt_detach(device_t dev) { /* we don't allow the driver to be unloaded. */ return EBUSY; } static void hpt_ioctl_done(struct _IOCTL_ARG *arg) { arg->ioctl_cmnd = 0; wakeup(arg); } static void __hpt_do_ioctl(PVBUS_EXT vbus_ext, IOCTL_ARG *ioctl_args) { ioctl_args->result = -1; ioctl_args->done = hpt_ioctl_done; ioctl_args->ioctl_cmnd = (void *)1; hpt_lock_vbus(vbus_ext); ldm_ioctl((PVBUS)vbus_ext->vbus, ioctl_args); while (ioctl_args->ioctl_cmnd) { if (hpt_sleep(vbus_ext, ioctl_args, PPAUSE, "hptctl", HPT_OSM_TIMEOUT)==0) break; ldm_reset_vbus((PVBUS)vbus_ext->vbus); __hpt_do_tasks(vbus_ext); } /* KdPrint(("ioctl %x result %d", ioctl_args->dwIoControlCode, ioctl_args->result)); */ hpt_unlock_vbus(vbus_ext); } static void hpt_do_ioctl(IOCTL_ARG *ioctl_args) { PVBUS vbus; PVBUS_EXT vbus_ext; ldm_for_each_vbus(vbus, vbus_ext) { __hpt_do_ioctl(vbus_ext, ioctl_args); if (ioctl_args->result!=HPT_IOCTL_RESULT_WRONG_VBUS) return; } } #define HPT_DO_IOCTL(code, inbuf, insize, outbuf, outsize) ({\ IOCTL_ARG arg;\ arg.dwIoControlCode = code;\ arg.lpInBuffer = inbuf;\ arg.lpOutBuffer = outbuf;\ arg.nInBufferSize = insize;\ arg.nOutBufferSize = outsize;\ arg.lpBytesReturned = 0;\ hpt_do_ioctl(&arg);\ arg.result;\ }) #define DEVICEID_VALID(id) ((id) && ((HPT_U32)(id)!=0xffffffff)) static int hpt_get_logical_devices(DEVICEID * pIds, int nMaxCount) { int i; HPT_U32 count = nMaxCount-1; if (HPT_DO_IOCTL(HPT_IOCTL_GET_LOGICAL_DEVICES, &count, sizeof(HPT_U32), pIds, sizeof(DEVICEID)*nMaxCount)) return -1; nMaxCount = (int)pIds[0]; for (i=0; ilock, "hptsleeplock", NULL, MTX_DEF); callout_init_mtx(&vbus_ext->timer, &vbus_ext->lock, 0); if (hpt_init_vbus(vbus_ext)) { os_printk("fail to initialize hardware"); break; /* FIXME */ } } /* register CAM interface */ ldm_for_each_vbus(vbus, vbus_ext) { struct cam_devq *devq; struct ccb_setasync ccb; if (bus_dma_tag_create(NULL,/* parent */ 4, /* alignment */ BUS_SPACE_MAXADDR_32BIT+1, /* boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ PAGE_SIZE * (os_max_sg_descriptors-1), /* maxsize */ os_max_sg_descriptors, /* nsegments */ 0x10000, /* maxsegsize */ BUS_DMA_WAITOK, /* flags */ busdma_lock_mutex, /* lockfunc */ &vbus_ext->lock, /* lockfuncarg */ &vbus_ext->io_dmat /* tag */)) { return ; } for (i=0; ivbus_ext = vbus_ext; ext->next = vbus_ext->cmdext_list; vbus_ext->cmdext_list = ext; if (bus_dmamap_create(vbus_ext->io_dmat, 0, &ext->dma_map)) { os_printk("Can't create dma map(%d)", i); return ; } callout_init_mtx(&ext->timeout, &vbus_ext->lock, 0); } if ((devq = cam_simq_alloc(os_max_queue_comm)) == NULL) { os_printk("cam_simq_alloc failed"); return ; } vbus_ext->sim = cam_sim_alloc(hpt_action, hpt_poll, driver_name, vbus_ext, unit_number, &vbus_ext->lock, os_max_queue_comm, /*tagged*/8, devq); unit_number++; if (!vbus_ext->sim) { os_printk("cam_sim_alloc failed"); cam_simq_free(devq); return ; } hpt_lock_vbus(vbus_ext); if (xpt_bus_register(vbus_ext->sim, NULL, 0) != CAM_SUCCESS) { hpt_unlock_vbus(vbus_ext); os_printk("xpt_bus_register failed"); cam_sim_free(vbus_ext->sim, /*free devq*/ TRUE); vbus_ext->sim = NULL; return ; } if (xpt_create_path(&vbus_ext->path, /*periph */ NULL, cam_sim_path(vbus_ext->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { hpt_unlock_vbus(vbus_ext); os_printk("xpt_create_path failed"); xpt_bus_deregister(cam_sim_path(vbus_ext->sim)); cam_sim_free(vbus_ext->sim, /*free_devq*/TRUE); vbus_ext->sim = NULL; return ; } memset(&ccb, 0, sizeof(ccb)); xpt_setup_ccb(&ccb.ccb_h, vbus_ext->path, /*priority*/5); ccb.ccb_h.func_code = XPT_SASYNC_CB; ccb.event_enable = AC_LOST_DEVICE; ccb.callback = hpt_async; ccb.callback_arg = vbus_ext; xpt_action((union ccb *)&ccb); hpt_unlock_vbus(vbus_ext); for (hba = vbus_ext->hba_list; hba; hba = hba->next) { int rid = 0; if ((hba->irq_res = bus_alloc_resource_any(hba->pcidev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE)) == NULL) { os_printk("can't allocate interrupt"); return ; } if (bus_setup_intr(hba->pcidev, hba->irq_res, INTR_TYPE_CAM | INTR_MPSAFE, NULL, hpt_pci_intr, vbus_ext, &hba->irq_handle)) { os_printk("can't set up interrupt"); return ; } hba->ldm_adapter.him->intr_control(hba->ldm_adapter.him_handle, HPT_TRUE); } vbus_ext->shutdown_eh = EVENTHANDLER_REGISTER(shutdown_final, hpt_shutdown_vbus, vbus_ext, SHUTDOWN_PRI_DEFAULT); if (!vbus_ext->shutdown_eh) os_printk("Shutdown event registration failed"); } ldm_for_each_vbus(vbus, vbus_ext) { TASK_INIT(&vbus_ext->worker, 0, (task_fn_t *)hpt_do_tasks, vbus_ext); if (vbus_ext->tasks) TASK_ENQUEUE(&vbus_ext->worker); } make_dev(&hpt_cdevsw, DRIVER_MINOR, UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "%s", driver_name); } #if defined(KLD_MODULE) typedef struct driverlink *driverlink_t; struct driverlink { kobj_class_t driver; TAILQ_ENTRY(driverlink) link; /* list of drivers in devclass */ }; typedef TAILQ_HEAD(driver_list, driverlink) driver_list_t; struct devclass { TAILQ_ENTRY(devclass) link; devclass_t parent; /* parent in devclass hierarchy */ driver_list_t drivers; /* bus devclasses store drivers for bus */ char *name; device_t *devices; /* array of devices indexed by unit */ int maxunit; /* size of devices array */ }; static void override_kernel_driver(void) { driverlink_t dl, dlfirst; driver_t *tmpdriver; devclass_t dc = devclass_find("pci"); if (dc){ dlfirst = TAILQ_FIRST(&dc->drivers); for (dl = dlfirst; dl; dl = TAILQ_NEXT(dl, link)) { if(strcmp(dl->driver->name, driver_name) == 0) { tmpdriver=dl->driver; dl->driver=dlfirst->driver; dlfirst->driver=tmpdriver; break; } } } } #else #define override_kernel_driver() #endif static void hpt_init(void *dummy) { if (bootverbose) os_printk("%s %s", driver_name_long, driver_ver); override_kernel_driver(); init_config(); hpt_ich.ich_func = hpt_final_init; hpt_ich.ich_arg = NULL; if (config_intrhook_establish(&hpt_ich) != 0) { printf("%s: cannot establish configuration hook\n", driver_name_long); } } SYSINIT(hptinit, SI_SUB_CONFIGURE, SI_ORDER_FIRST, hpt_init, NULL); /* * CAM driver interface */ static device_method_t driver_methods[] = { /* Device interface */ DEVMETHOD(device_probe, hpt_probe), DEVMETHOD(device_attach, hpt_attach), DEVMETHOD(device_detach, hpt_detach), DEVMETHOD(device_shutdown, hpt_shutdown), { 0, 0 } }; static driver_t hpt_pci_driver = { driver_name, driver_methods, sizeof(HBA) }; #ifndef TARGETNAME #error "no TARGETNAME found" #endif /* use this to make TARGETNAME be expanded */ #define __DRIVER_MODULE(p1, p2, p3, p4, p5) DRIVER_MODULE(p1, p2, p3, p4, p5) #define __MODULE_VERSION(p1, p2) MODULE_VERSION(p1, p2) #define __MODULE_DEPEND(p1, p2, p3, p4, p5) MODULE_DEPEND(p1, p2, p3, p4, p5) __DRIVER_MODULE(TARGETNAME, pci, hpt_pci_driver, 0, 0); __MODULE_VERSION(TARGETNAME, 1); __MODULE_DEPEND(TARGETNAME, cam, 1, 1, 1); static int hpt_open(struct cdev *dev, int flags, int devtype, struct thread *td) { return 0; } static int hpt_close(struct cdev *dev, int flags, int devtype, struct thread *td) { return 0; } static int hpt_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { PHPT_IOCTL_PARAM piop=(PHPT_IOCTL_PARAM)data; IOCTL_ARG ioctl_args; HPT_U32 bytesReturned = 0; switch (cmd){ case HPT_DO_IOCONTROL: { if (piop->Magic == HPT_IOCTL_MAGIC || piop->Magic == HPT_IOCTL_MAGIC32) { KdPrint(("<8>ioctl=%x in=%p len=%d out=%p len=%d\n", piop->dwIoControlCode, piop->lpInBuffer, piop->nInBufferSize, piop->lpOutBuffer, piop->nOutBufferSize)); memset(&ioctl_args, 0, sizeof(ioctl_args)); ioctl_args.dwIoControlCode = piop->dwIoControlCode; ioctl_args.nInBufferSize = piop->nInBufferSize; ioctl_args.nOutBufferSize = piop->nOutBufferSize; ioctl_args.lpBytesReturned = &bytesReturned; if (ioctl_args.nInBufferSize) { ioctl_args.lpInBuffer = malloc(ioctl_args.nInBufferSize, M_DEVBUF, M_WAITOK); if (!ioctl_args.lpInBuffer) goto invalid; if (copyin((void*)piop->lpInBuffer, ioctl_args.lpInBuffer, piop->nInBufferSize)) goto invalid; } if (ioctl_args.nOutBufferSize) { ioctl_args.lpOutBuffer = malloc(ioctl_args.nOutBufferSize, M_DEVBUF, M_WAITOK | M_ZERO); if (!ioctl_args.lpOutBuffer) goto invalid; } hpt_do_ioctl(&ioctl_args); if (ioctl_args.result==HPT_IOCTL_RESULT_OK) { if (piop->nOutBufferSize) { if (copyout(ioctl_args.lpOutBuffer, (void*)piop->lpOutBuffer, piop->nOutBufferSize)) goto invalid; } if (piop->lpBytesReturned) { if (copyout(&bytesReturned, (void*)piop->lpBytesReturned, sizeof(HPT_U32))) goto invalid; } if (ioctl_args.lpInBuffer) free(ioctl_args.lpInBuffer, M_DEVBUF); if (ioctl_args.lpOutBuffer) free(ioctl_args.lpOutBuffer, M_DEVBUF); return 0; } invalid: if (ioctl_args.lpInBuffer) free(ioctl_args.lpInBuffer, M_DEVBUF); if (ioctl_args.lpOutBuffer) free(ioctl_args.lpOutBuffer, M_DEVBUF); return EFAULT; } return EFAULT; } case HPT_SCAN_BUS: { return hpt_rescan_bus(); } default: KdPrint(("invalid command!")); return EFAULT; } } static int hpt_rescan_bus(void) { union ccb *ccb; PVBUS vbus; PVBUS_EXT vbus_ext; ldm_for_each_vbus(vbus, vbus_ext) { if ((ccb = xpt_alloc_ccb()) == NULL) { return(ENOMEM); } if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(vbus_ext->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_free_ccb(ccb); return(EIO); } xpt_rescan(ccb); } return(0); } diff --git a/sys/dev/hptnr/hptnr_os_bsd.c b/sys/dev/hptnr/hptnr_os_bsd.c index 62b852ddb6af..59f6fb651db9 100644 --- a/sys/dev/hptnr/hptnr_os_bsd.c +++ b/sys/dev/hptnr/hptnr_os_bsd.c @@ -1,302 +1,302 @@ /* $Id: os_bsd.c,v 1.13 2010/05/11 03:12:11 lcn Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * HighPoint RAID Driver for FreeBSD * Copyright (C) 2005-2011 HighPoint Technologies, Inc. All Rights Reserved. * All rights reserved. * * 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 #include BUS_ADDRESS get_dmapool_phy_addr(void *osext, void * dmapool_virt_addr); /* hardware access */ HPT_U8 os_inb (void *port) { return inb((unsigned)(HPT_UPTR)port); } HPT_U16 os_inw (void *port) { return inw((unsigned)(HPT_UPTR)port); } HPT_U32 os_inl (void *port) { return inl((unsigned)(HPT_UPTR)port); } void os_outb (void *port, HPT_U8 value) { outb((unsigned)(HPT_UPTR)port, (value)); } void os_outw (void *port, HPT_U16 value) { outw((unsigned)(HPT_UPTR)port, (value)); } void os_outl (void *port, HPT_U32 value) { outl((unsigned)(HPT_UPTR)port, (value)); } void os_insw (void *port, HPT_U16 *buffer, HPT_U32 count) { insw((unsigned)(HPT_UPTR)port, (void *)buffer, count); } void os_outsw(void *port, HPT_U16 *buffer, HPT_U32 count) { outsw((unsigned)(HPT_UPTR)port, (void *)buffer, count); } HPT_U32 __dummy_reg = 0; /* PCI configuration space */ HPT_U8 os_pci_readb (void *osext, HPT_U8 offset) { return pci_read_config(((PHBA)osext)->pcidev, offset, 1); } HPT_U16 os_pci_readw (void *osext, HPT_U8 offset) { return pci_read_config(((PHBA)osext)->pcidev, offset, 2); } HPT_U32 os_pci_readl (void *osext, HPT_U8 offset) { return pci_read_config(((PHBA)osext)->pcidev, offset, 4); } void os_pci_writeb (void *osext, HPT_U8 offset, HPT_U8 value) { pci_write_config(((PHBA)osext)->pcidev, offset, value, 1); } void os_pci_writew (void *osext, HPT_U8 offset, HPT_U16 value) { pci_write_config(((PHBA)osext)->pcidev, offset, value, 2); } void os_pci_writel (void *osext, HPT_U8 offset, HPT_U32 value) { pci_write_config(((PHBA)osext)->pcidev, offset, value, 4); } BUS_ADDRESS get_dmapool_phy_addr(void *osext, void * dmapool_virt_addr) { return (BUS_ADDRESS)vtophys(dmapool_virt_addr); } HPT_U32 pcicfg_read_dword(HPT_U8 bus, HPT_U8 dev, HPT_U8 func, HPT_U8 reg) { - return (HPT_U32)pci_cfgregread(bus, dev, func, reg, 4); + return (HPT_U32)pci_cfgregread(0, bus, dev, func, reg, 4); }/* PCI space access */ void *os_map_pci_bar( void *osext, int index, HPT_U32 offset, HPT_U32 length ) { PHBA hba = (PHBA)osext; HPT_U32 base; hba->pcibar[index].rid = 0x10 + index * 4; base = pci_read_config(hba->pcidev, hba->pcibar[index].rid, 4); if (base & 1) { hba->pcibar[index].type = SYS_RES_IOPORT; hba->pcibar[index].res = bus_alloc_resource_any(hba->pcidev, hba->pcibar[index].type, &hba->pcibar[index].rid, RF_ACTIVE); hba->pcibar[index].base = (void *)(unsigned long)(base & ~0x1); } else { hba->pcibar[index].type = SYS_RES_MEMORY; hba->pcibar[index].res = bus_alloc_resource_any(hba->pcidev, hba->pcibar[index].type, &hba->pcibar[index].rid, RF_ACTIVE); hba->pcibar[index].base = (char *)rman_get_virtual(hba->pcibar[index].res) + offset; } return hba->pcibar[index].base; } void os_unmap_pci_bar(void *osext, void *base) { PHBA hba = (PHBA)osext; int index; for (index=0; index<6; index++) { if (hba->pcibar[index].base==base) { bus_release_resource(hba->pcidev, hba->pcibar[index].type, hba->pcibar[index].rid, hba->pcibar[index].res); hba->pcibar[index].base = 0; return; } } } void freelist_reserve(struct freelist *list, void *osext, HPT_UINT size, HPT_UINT count) { PVBUS_EXT vbus_ext = osext; if (vbus_ext->ext_type!=EXT_TYPE_VBUS) vbus_ext = ((PHBA)osext)->vbus_ext; list->next = vbus_ext->freelist_head; vbus_ext->freelist_head = list; list->dma = 0; list->size = size; list->head = 0; #if DBG list->reserved_count = #endif list->count = count; } void *freelist_get(struct freelist *list) { void * result; if (list->count) { HPT_ASSERT(list->head); result = list->head; list->head = *(void **)result; list->count--; return result; } return 0; } void freelist_put(struct freelist * list, void *p) { HPT_ASSERT(list->dma==0); list->count++; *(void **)p = list->head; list->head = p; } void freelist_reserve_dma(struct freelist *list, void *osext, HPT_UINT size, HPT_UINT alignment, HPT_UINT count) { PVBUS_EXT vbus_ext = osext; if (vbus_ext->ext_type!=EXT_TYPE_VBUS) vbus_ext = ((PHBA)osext)->vbus_ext; list->next = vbus_ext->freelist_dma_head; vbus_ext->freelist_dma_head = list; list->dma = 1; list->alignment = alignment; list->size = size; list->head = 0; #if DBG list->reserved_count = #endif list->count = count; } void *freelist_get_dma(struct freelist *list, BUS_ADDRESS *busaddr) { void *result; HPT_ASSERT(list->dma); result = freelist_get(list); if (result) *busaddr = *(BUS_ADDRESS *)((void **)result+1); return result; } void freelist_put_dma(struct freelist *list, void *p, BUS_ADDRESS busaddr) { HPT_ASSERT(list->dma); list->count++; *(void **)p = list->head; *(BUS_ADDRESS *)((void **)p+1) = busaddr; list->head = p; } HPT_U32 os_get_stamp(void) { HPT_U32 stamp; do { stamp = random(); } while (stamp==0); return stamp; } void os_stallexec(HPT_U32 microseconds) { DELAY(microseconds); } static void os_timer_for_ldm(void *arg) { PVBUS_EXT vbus_ext = (PVBUS_EXT)arg; ldm_on_timer((PVBUS)vbus_ext->vbus); } void os_request_timer(void * osext, HPT_U32 interval) { PVBUS_EXT vbus_ext = osext; HPT_ASSERT(vbus_ext->ext_type==EXT_TYPE_VBUS); callout_reset_sbt(&vbus_ext->timer, SBT_1US * interval, 0, os_timer_for_ldm, vbus_ext, 0); } HPT_TIME os_query_time(void) { return ticks * (1000000 / hz); } void os_schedule_task(void *osext, OSM_TASK *task) { PVBUS_EXT vbus_ext = osext; HPT_ASSERT(task->next==0); if (vbus_ext->tasks==0) vbus_ext->tasks = task; else { OSM_TASK *t = vbus_ext->tasks; while (t->next) t = t->next; t->next = task; } if (vbus_ext->worker.ta_context) TASK_ENQUEUE(&vbus_ext->worker); } int os_revalidate_device(void *osext, int id) { return 0; } int os_query_remove_device(void *osext, int id) { return 0; } HPT_U8 os_get_vbus_seq(void *osext) { return ((PVBUS_EXT)osext)->sim->path_id; } int os_printk(char *fmt, ...) { va_list args; static char buf[512]; va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); return printf("%s: %s\n", driver_name, buf); } #if DBG void os_check_stack(const char *location, int size){} void __os_dbgbreak(const char *file, int line) { printf("*** break at %s:%d ***", file, line); while (1); } int hpt_dbg_level = 1; #endif diff --git a/sys/dev/hptnr/hptnr_osm_bsd.c b/sys/dev/hptnr/hptnr_osm_bsd.c index c1c908b62c9d..00774bf9be4a 100644 --- a/sys/dev/hptnr/hptnr_osm_bsd.c +++ b/sys/dev/hptnr/hptnr_osm_bsd.c @@ -1,1677 +1,1682 @@ /* $Id: osm_bsd.c,v 1.36 2010/05/11 03:12:11 lcn Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * HighPoint RAID Driver for FreeBSD * Copyright (C) 2005-2011 HighPoint Technologies, Inc. * All rights reserved. * * 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 #include #include int msi = 0; int debug_flag = 0; static HIM *hpt_match(device_t dev, int scan) { PCI_ID pci_id; HIM *him; int i; for (him = him_list; him; him = him->next) { for (i=0; him->get_supported_device_id(i, &pci_id); i++) { if (scan && him->get_controller_count) him->get_controller_count(&pci_id,0,0); if ((pci_get_vendor(dev) == pci_id.vid) && (pci_get_device(dev) == pci_id.did)){ return (him); } } } return (NULL); } static int hpt_probe(device_t dev) { HIM *him; him = hpt_match(dev, 0); if (him != NULL) { KdPrint(("hpt_probe: adapter at PCI %d:%d:%d, IRQ %d", pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev), pci_get_irq(dev) )); device_set_desc(dev, him->name); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int hpt_attach(device_t dev) { PHBA hba = (PHBA)device_get_softc(dev); HIM *him; PCI_ID pci_id; HPT_UINT size; PVBUS vbus; PVBUS_EXT vbus_ext; + if (pci_get_domain(dev) != 0) { + device_printf(dev, "does not support PCI domains\n"); + return (ENXIO); + } + KdPrint(("hpt_attach(%d/%d/%d)", pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev))); him = hpt_match(dev, 1); hba->ext_type = EXT_TYPE_HBA; hba->ldm_adapter.him = him; pci_enable_busmaster(dev); pci_id.vid = pci_get_vendor(dev); pci_id.did = pci_get_device(dev); pci_id.rev = pci_get_revid(dev); pci_id.subsys = (HPT_U32)(pci_get_subdevice(dev)) << 16 | pci_get_subvendor(dev); size = him->get_adapter_size(&pci_id); hba->ldm_adapter.him_handle = malloc(size, M_DEVBUF, M_WAITOK); hba->pcidev = dev; hba->pciaddr.tree = 0; hba->pciaddr.bus = pci_get_bus(dev); hba->pciaddr.device = pci_get_slot(dev); hba->pciaddr.function = pci_get_function(dev); if (!him->create_adapter(&pci_id, hba->pciaddr, hba->ldm_adapter.him_handle, hba)) { free(hba->ldm_adapter.him_handle, M_DEVBUF); return ENXIO; } os_printk("adapter at PCI %d:%d:%d, IRQ %d", hba->pciaddr.bus, hba->pciaddr.device, hba->pciaddr.function, pci_get_irq(dev)); if (!ldm_register_adapter(&hba->ldm_adapter)) { size = ldm_get_vbus_size(); vbus_ext = malloc(sizeof(VBUS_EXT) + size, M_DEVBUF, M_WAITOK | M_ZERO); vbus_ext->ext_type = EXT_TYPE_VBUS; ldm_create_vbus((PVBUS)vbus_ext->vbus, vbus_ext); ldm_register_adapter(&hba->ldm_adapter); } ldm_for_each_vbus(vbus, vbus_ext) { if (hba->ldm_adapter.vbus==vbus) { hba->vbus_ext = vbus_ext; hba->next = vbus_ext->hba_list; vbus_ext->hba_list = hba; break; } } return 0; } /* * Maybe we'd better to use the bus_dmamem_alloc to alloc DMA memory, * but there are some problems currently (alignment, etc). */ static __inline void *__get_free_pages(int order) { /* don't use low memory - other devices may get starved */ return contigmalloc(PAGE_SIZE<hba_list; hba; hba = hba->next) hba->ldm_adapter.him->get_meminfo(hba->ldm_adapter.him_handle); ldm_get_mem_info((PVBUS)vbus_ext->vbus, 0); for (f=vbus_ext->freelist_head; f; f=f->next) { KdPrint(("%s: %d*%d=%d bytes", f->tag, f->count, f->size, f->count*f->size)); for (i=0; icount; i++) { p = (void **)malloc(f->size, M_DEVBUF, M_WAITOK); if (!p) return (ENXIO); *p = f->head; f->head = p; } } for (f=vbus_ext->freelist_dma_head; f; f=f->next) { int order, size, j; HPT_ASSERT((f->size & (f->alignment-1))==0); for (order=0, size=PAGE_SIZE; sizesize; order++, size<<=1) ; KdPrint(("%s: %d*%d=%d bytes, order %d", f->tag, f->count, f->size, f->count*f->size, order)); HPT_ASSERT(f->alignment<=PAGE_SIZE); for (i=0; icount;) { p = (void **)__get_free_pages(order); if (!p) return -1; for (j = size/f->size; j && icount; i++,j--) { *p = f->head; *(BUS_ADDRESS *)(p+1) = (BUS_ADDRESS)vtophys(p); f->head = p; p = (void **)((unsigned long)p + f->size); } } } HPT_ASSERT(PAGE_SIZE==DMAPOOL_PAGE_SIZE); for (i=0; ivbus, p, (BUS_ADDRESS)vtophys(p)); } return 0; } static void hpt_free_mem(PVBUS_EXT vbus_ext) { struct freelist *f; void *p; int i; BUS_ADDRESS bus; for (f=vbus_ext->freelist_head; f; f=f->next) { #if DBG if (f->count!=f->reserved_count) { KdPrint(("memory leak for freelist %s (%d/%d)", f->tag, f->count, f->reserved_count)); } #endif while ((p=freelist_get(f))) free(p, M_DEVBUF); } for (i=0; ivbus, &bus); HPT_ASSERT(p); free_pages(p, 0); } for (f=vbus_ext->freelist_dma_head; f; f=f->next) { int order, size; #if DBG if (f->count!=f->reserved_count) { KdPrint(("memory leak for dma freelist %s (%d/%d)", f->tag, f->count, f->reserved_count)); } #endif for (order=0, size=PAGE_SIZE; sizesize; order++, size<<=1) ; while ((p=freelist_get_dma(f, &bus))) { if (order) free_pages(p, order); else { /* can't free immediately since other blocks in this page may still be in the list */ if (((HPT_UPTR)p & (PAGE_SIZE-1))==0) dmapool_put_page((PVBUS)vbus_ext->vbus, p, bus); } } } while ((p = dmapool_get_page((PVBUS)vbus_ext->vbus, &bus))) free_pages(p, 0); } static int hpt_init_vbus(PVBUS_EXT vbus_ext) { PHBA hba; for (hba = vbus_ext->hba_list; hba; hba = hba->next) if (!hba->ldm_adapter.him->initialize(hba->ldm_adapter.him_handle)) { KdPrint(("fail to initialize %p", hba)); return -1; } ldm_initialize_vbus((PVBUS)vbus_ext->vbus, &vbus_ext->hba_list->ldm_adapter); return 0; } static void hpt_flush_done(PCOMMAND pCmd) { PVDEV vd = pCmd->target; if (mIsArray(vd->type) && vd->u.array.transform && vd!=vd->u.array.transform->target) { vd = vd->u.array.transform->target; HPT_ASSERT(vd); pCmd->target = vd; pCmd->Result = RETURN_PENDING; vdev_queue_cmd(pCmd); return; } *(int *)pCmd->priv = 1; wakeup(pCmd); } /* * flush a vdev (without retry). */ static int hpt_flush_vdev(PVBUS_EXT vbus_ext, PVDEV vd) { PCOMMAND pCmd; int result = 0, done; HPT_UINT count; KdPrint(("flusing dev %p", vd)); hpt_assert_vbus_locked(vbus_ext); if (mIsArray(vd->type) && vd->u.array.transform) count = max(vd->u.array.transform->source->cmds_per_request, vd->u.array.transform->target->cmds_per_request); else count = vd->cmds_per_request; pCmd = ldm_alloc_cmds(vd->vbus, count); if (!pCmd) { return -1; } pCmd->type = CMD_TYPE_FLUSH; pCmd->flags.hard_flush = 1; pCmd->target = vd; pCmd->done = hpt_flush_done; done = 0; pCmd->priv = &done; ldm_queue_cmd(pCmd); if (!done) { while (hpt_sleep(vbus_ext, pCmd, PPAUSE, "hptfls", HPT_OSM_TIMEOUT)) { ldm_reset_vbus(vd->vbus); } } KdPrint(("flush result %d", pCmd->Result)); if (pCmd->Result!=RETURN_SUCCESS) result = -1; ldm_free_cmds(pCmd); return result; } static void hpt_stop_tasks(PVBUS_EXT vbus_ext); static void hpt_shutdown_vbus(PVBUS_EXT vbus_ext, int howto) { PVBUS vbus = (PVBUS)vbus_ext->vbus; PHBA hba; int i; KdPrint(("hpt_shutdown_vbus")); /* stop all ctl tasks and disable the worker taskqueue */ hpt_stop_tasks(vbus_ext); hpt_lock_vbus(vbus_ext); vbus_ext->worker.ta_context = 0; /* flush devices */ for (i=0; ihba_list; hba; hba=hba->next) bus_teardown_intr(hba->pcidev, hba->irq_res, hba->irq_handle); hpt_free_mem(vbus_ext); while ((hba=vbus_ext->hba_list)) { vbus_ext->hba_list = hba->next; free(hba->ldm_adapter.him_handle, M_DEVBUF); } callout_drain(&vbus_ext->timer); mtx_destroy(&vbus_ext->lock); free(vbus_ext, M_DEVBUF); KdPrint(("hpt_shutdown_vbus done")); } static void __hpt_do_tasks(PVBUS_EXT vbus_ext) { OSM_TASK *tasks; tasks = vbus_ext->tasks; vbus_ext->tasks = 0; while (tasks) { OSM_TASK *t = tasks; tasks = t->next; t->next = 0; t->func(vbus_ext->vbus, t->data); } } static void hpt_do_tasks(PVBUS_EXT vbus_ext, int pending) { if(vbus_ext){ hpt_lock_vbus(vbus_ext); __hpt_do_tasks(vbus_ext); hpt_unlock_vbus(vbus_ext); } } static void hpt_action(struct cam_sim *sim, union ccb *ccb); static void hpt_poll(struct cam_sim *sim); static void hpt_async(void * callback_arg, u_int32_t code, struct cam_path * path, void * arg); static void hpt_pci_intr(void *arg); static __inline POS_CMDEXT cmdext_get(PVBUS_EXT vbus_ext) { POS_CMDEXT p = vbus_ext->cmdext_list; if (p) vbus_ext->cmdext_list = p->next; return p; } static __inline void cmdext_put(POS_CMDEXT p) { p->next = p->vbus_ext->cmdext_list; p->vbus_ext->cmdext_list = p; } static void hpt_timeout(void *arg) { PCOMMAND pCmd = (PCOMMAND)arg; POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv; KdPrint(("pCmd %p timeout", pCmd)); ldm_reset_vbus((PVBUS)ext->vbus_ext->vbus); } static void os_cmddone(PCOMMAND pCmd) { POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv; union ccb *ccb = ext->ccb; HPT_U8 *cdb; if (ccb->ccb_h.flags & CAM_CDB_POINTER) cdb = ccb->csio.cdb_io.cdb_ptr; else cdb = ccb->csio.cdb_io.cdb_bytes; KdPrint(("os_cmddone(%p, %d)", pCmd, pCmd->Result)); callout_stop(&ext->timeout); switch(cdb[0]) { case 0x85: /*ATA_16*/ case 0xA1: /*ATA_12*/ { PassthroughCmd *passthru = &pCmd->uCmd.Passthrough; HPT_U8 *sense_buffer = (HPT_U8 *)&ccb->csio.sense_data; memset(&ccb->csio.sense_data, 0,sizeof(ccb->csio.sense_data)); sense_buffer[0] = 0x72; /* Response Code */ sense_buffer[7] = 14; /* Additional Sense Length */ sense_buffer[8] = 0x9; /* ATA Return Descriptor */ sense_buffer[9] = 0xc; /* Additional Descriptor Length */ sense_buffer[11] = (HPT_U8)passthru->bFeaturesReg; /* Error */ sense_buffer[13] = (HPT_U8)passthru->bSectorCountReg; /* Sector Count (7:0) */ sense_buffer[15] = (HPT_U8)passthru->bLbaLowReg; /* LBA Low (7:0) */ sense_buffer[17] = (HPT_U8)passthru->bLbaMidReg; /* LBA Mid (7:0) */ sense_buffer[19] = (HPT_U8)passthru->bLbaHighReg; /* LBA High (7:0) */ if ((cdb[0] == 0x85) && (cdb[1] & 0x1)) { sense_buffer[10] = 1; sense_buffer[12] = (HPT_U8)(passthru->bSectorCountReg >> 8); /* Sector Count (15:8) */ sense_buffer[14] = (HPT_U8)(passthru->bLbaLowReg >> 8); /* LBA Low (15:8) */ sense_buffer[16] = (HPT_U8)(passthru->bLbaMidReg >> 8); /* LBA Mid (15:8) */ sense_buffer[18] = (HPT_U8)(passthru->bLbaHighReg >> 8); /* LBA High (15:8) */ } sense_buffer[20] = (HPT_U8)passthru->bDriveHeadReg; /* Device */ sense_buffer[21] = (HPT_U8)passthru->bCommandReg; /* Status */ KdPrint(("sts 0x%x err 0x%x low 0x%x mid 0x%x hig 0x%x dh 0x%x sc 0x%x", passthru->bCommandReg, passthru->bFeaturesReg, passthru->bLbaLowReg, passthru->bLbaMidReg, passthru->bLbaHighReg, passthru->bDriveHeadReg, passthru->bSectorCountReg)); KdPrint(("result:0x%x,bFeaturesReg:0x%04x,bSectorCountReg:0x%04x,LBA:0x%04x%04x%04x ", pCmd->Result,passthru->bFeaturesReg,passthru->bSectorCountReg, passthru->bLbaHighReg,passthru->bLbaMidReg,passthru->bLbaLowReg)); } default: break; } switch(pCmd->Result) { case RETURN_SUCCESS: ccb->ccb_h.status = CAM_REQ_CMP; break; case RETURN_BAD_DEVICE: ccb->ccb_h.status = CAM_DEV_NOT_THERE; break; case RETURN_DEVICE_BUSY: ccb->ccb_h.status = CAM_BUSY; break; case RETURN_INVALID_REQUEST: ccb->ccb_h.status = CAM_REQ_INVALID; break; case RETURN_SELECTION_TIMEOUT: ccb->ccb_h.status = CAM_SEL_TIMEOUT; break; case RETURN_RETRY: ccb->ccb_h.status = CAM_BUSY; break; default: ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; break; } if (pCmd->flags.data_in) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_POSTREAD); } else if (pCmd->flags.data_out) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_POSTWRITE); } bus_dmamap_unload(ext->vbus_ext->io_dmat, ext->dma_map); cmdext_put(ext); ldm_free_cmds(pCmd); xpt_done(ccb); } static int os_buildsgl(PCOMMAND pCmd, PSG pSg, int logical) { /* since we have provided physical sg, nobody will ask us to build physical sg */ HPT_ASSERT(0); return FALSE; } static void hpt_io_dmamap_callback(void *arg, bus_dma_segment_t *segs, int nsegs, int error) { PCOMMAND pCmd = (PCOMMAND)arg; POS_CMDEXT ext = (POS_CMDEXT)pCmd->priv; PSG psg = pCmd->psg; int idx; HPT_ASSERT(pCmd->flags.physical_sg); if (error) panic("busdma error"); HPT_ASSERT(nsegs<=os_max_sg_descriptors); if (nsegs != 0) { for (idx = 0; idx < nsegs; idx++, psg++) { psg->addr.bus = segs[idx].ds_addr; psg->size = segs[idx].ds_len; psg->eot = 0; } psg[-1].eot = 1; if (pCmd->flags.data_in) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_PREREAD); } else if (pCmd->flags.data_out) { bus_dmamap_sync(ext->vbus_ext->io_dmat, ext->dma_map, BUS_DMASYNC_PREWRITE); } } callout_reset(&ext->timeout, HPT_OSM_TIMEOUT, hpt_timeout, pCmd); ldm_queue_cmd(pCmd); } static void hpt_scsi_io(PVBUS_EXT vbus_ext, union ccb *ccb) { PVBUS vbus = (PVBUS)vbus_ext->vbus; PVDEV vd; PCOMMAND pCmd; POS_CMDEXT ext; HPT_U8 *cdb; if (ccb->ccb_h.flags & CAM_CDB_POINTER) cdb = ccb->csio.cdb_io.cdb_ptr; else cdb = ccb->csio.cdb_io.cdb_bytes; KdPrint(("hpt_scsi_io: ccb %x id %d lun %d cdb %x-%x-%x", ccb, ccb->ccb_h.target_id, ccb->ccb_h.target_lun, *(HPT_U32 *)&cdb[0], *(HPT_U32 *)&cdb[4], *(HPT_U32 *)&cdb[8] )); /* ccb->ccb_h.path_id is not our bus id - don't check it */ if (ccb->ccb_h.target_lun != 0 || ccb->ccb_h.target_id >= osm_max_targets || (ccb->ccb_h.flags & CAM_CDB_PHYS)) { ccb->ccb_h.status = CAM_TID_INVALID; xpt_done(ccb); return; } vd = ldm_find_target(vbus, ccb->ccb_h.target_id); if (!vd) { ccb->ccb_h.status = CAM_SEL_TIMEOUT; xpt_done(ccb); return; } switch (cdb[0]) { case TEST_UNIT_READY: case START_STOP_UNIT: case SYNCHRONIZE_CACHE: ccb->ccb_h.status = CAM_REQ_CMP; break; case 0x85: /*ATA_16*/ case 0xA1: /*ATA_12*/ { int error; HPT_U8 prot; PassthroughCmd *passthru; if (mIsArray(vd->type)) { ccb->ccb_h.status = CAM_REQ_INVALID; break; } HPT_ASSERT(vd->type == VD_RAW && vd->u.raw.legacy_disk); prot = (cdb[1] & 0x1e) >> 1; if (prot < 3 || prot > 5) { ccb->ccb_h.status = CAM_REQ_INVALID; break; } pCmd = ldm_alloc_cmds(vbus, vd->cmds_per_request); if (!pCmd) { HPT_ASSERT(0); ccb->ccb_h.status = CAM_BUSY; break; } passthru = &pCmd->uCmd.Passthrough; if (cdb[0] == 0x85/*ATA_16*/) { if (cdb[1] & 0x1) { passthru->bFeaturesReg = ((HPT_U16)cdb[3] << 8) | cdb[4]; passthru->bSectorCountReg = ((HPT_U16)cdb[5] << 8) | cdb[6]; passthru->bLbaLowReg = ((HPT_U16)cdb[7] << 8) | cdb[8]; passthru->bLbaMidReg = ((HPT_U16)cdb[9] << 8) | cdb[10]; passthru->bLbaHighReg = ((HPT_U16)cdb[11] << 8) | cdb[12]; } else { passthru->bFeaturesReg = cdb[4]; passthru->bSectorCountReg = cdb[6]; passthru->bLbaLowReg = cdb[8]; passthru->bLbaMidReg = cdb[10]; passthru->bLbaHighReg = cdb[12]; } passthru->bDriveHeadReg = cdb[13]; passthru->bCommandReg = cdb[14]; } else { /*ATA_12*/ passthru->bFeaturesReg = cdb[3]; passthru->bSectorCountReg = cdb[4]; passthru->bLbaLowReg = cdb[5]; passthru->bLbaMidReg = cdb[6]; passthru->bLbaHighReg = cdb[7]; passthru->bDriveHeadReg = cdb[8]; passthru->bCommandReg = cdb[9]; } if (cdb[1] & 0xe0) { if (!(passthru->bCommandReg == ATA_CMD_READ_MULTI || passthru->bCommandReg == ATA_CMD_READ_MULTI_EXT || passthru->bCommandReg == ATA_CMD_WRITE_MULTI || passthru->bCommandReg == ATA_CMD_WRITE_MULTI_EXT || passthru->bCommandReg == ATA_CMD_WRITE_MULTI_FUA_EXT) ) { goto error; } } if (passthru->bFeaturesReg == ATA_SET_FEATURES_XFER && passthru->bCommandReg == ATA_CMD_SET_FEATURES) { goto error; } passthru->nSectors = ccb->csio.dxfer_len/ATA_SECTOR_SIZE; switch (prot) { default: /*None data*/ break; case 4: /*PIO data in, T_DIR=1 match check*/ if ((cdb[2] & 3) && (cdb[2] & 0x8) == 0) { OsPrint(("PIO data in, T_DIR=1 match check")); goto error; } pCmd->flags.data_in = 1; break; case 5: /*PIO data out, T_DIR=0 match check*/ if ((cdb[2] & 3) && (cdb[2] & 0x8)) { OsPrint(("PIO data out, T_DIR=0 match check")); goto error; } pCmd->flags.data_out = 1; break; } pCmd->type = CMD_TYPE_PASSTHROUGH; pCmd->priv = ext = cmdext_get(vbus_ext); HPT_ASSERT(ext); ext->ccb = ccb; pCmd->target = vd; pCmd->done = os_cmddone; pCmd->buildsgl = os_buildsgl; pCmd->psg = ext->psg; if(!ccb->csio.dxfer_len) { ldm_queue_cmd(pCmd); return; } pCmd->flags.physical_sg = 1; error = bus_dmamap_load_ccb(vbus_ext->io_dmat, ext->dma_map, ccb, hpt_io_dmamap_callback, pCmd, BUS_DMA_WAITOK ); KdPrint(("bus_dmamap_load return %d", error)); if (error && error!=EINPROGRESS) { os_printk("bus_dmamap_load error %d", error); cmdext_put(ext); ldm_free_cmds(pCmd); ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); } return; error: ldm_free_cmds(pCmd); ccb->ccb_h.status = CAM_REQ_INVALID; break; } case INQUIRY: { PINQUIRYDATA inquiryData; HIM_DEVICE_CONFIG devconf; HPT_U8 *rbuf; memset(ccb->csio.data_ptr, 0, ccb->csio.dxfer_len); inquiryData = (PINQUIRYDATA)ccb->csio.data_ptr; if (cdb[1] & 1) { rbuf = (HPT_U8 *)inquiryData; switch(cdb[2]) { case 0: rbuf[0] = 0; rbuf[1] = 0; rbuf[2] = 0; rbuf[3] = 3; rbuf[4] = 0; rbuf[5] = 0x80; rbuf[6] = 0x83; ccb->ccb_h.status = CAM_REQ_CMP; break; case 0x80: { rbuf[0] = 0; rbuf[1] = 0x80; rbuf[2] = 0; if (vd->type == VD_RAW) { rbuf[3] = 20; vd->u.raw.him->get_device_config(vd->u.raw.phy_dev,&devconf); memcpy(&rbuf[4], devconf.pIdentifyData->SerialNumber, 20); ldm_ide_fixstring(&rbuf[4], 20); } else { rbuf[3] = 1; rbuf[4] = 0x20; } ccb->ccb_h.status = CAM_REQ_CMP; break; } case 0x83: rbuf[0] = 0; rbuf[1] = 0x83; rbuf[2] = 0; rbuf[3] = 12; rbuf[4] = 1; rbuf[5] = 2; rbuf[6] = 0; rbuf[7] = 8; rbuf[8] = 0; rbuf[9] = 0x19; rbuf[10] = 0x3C; rbuf[11] = 0; rbuf[12] = 0; rbuf[13] = 0; rbuf[14] = 0; rbuf[15] = 0; ccb->ccb_h.status = CAM_REQ_CMP; break; default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } break; } else if (cdb[2]) { ccb->ccb_h.status = CAM_REQ_INVALID; break; } inquiryData->DeviceType = 0; /*DIRECT_ACCESS_DEVICE*/ inquiryData->Versions = 5; /*SPC-3*/ inquiryData->ResponseDataFormat = 2; inquiryData->AdditionalLength = 0x5b; inquiryData->CommandQueue = 1; if (ccb->csio.dxfer_len > 63) { rbuf = (HPT_U8 *)inquiryData; rbuf[58] = 0x60; rbuf[59] = 0x3; rbuf[64] = 0x3; rbuf[66] = 0x3; rbuf[67] = 0x20; } if (vd->type == VD_RAW) { vd->u.raw.him->get_device_config(vd->u.raw.phy_dev,&devconf); if ((devconf.pIdentifyData->GeneralConfiguration & 0x80)) inquiryData->RemovableMedia = 1; memcpy(&inquiryData->VendorId, "ATA ", 8); memcpy(&inquiryData->ProductId, devconf.pIdentifyData->ModelNumber, 16); ldm_ide_fixstring((HPT_U8 *)&inquiryData->ProductId, 16); memcpy(&inquiryData->ProductRevisionLevel, devconf.pIdentifyData->FirmwareRevision, 4); ldm_ide_fixstring((HPT_U8 *)&inquiryData->ProductRevisionLevel, 4); if (inquiryData->ProductRevisionLevel[0] == 0 || inquiryData->ProductRevisionLevel[0] == ' ') memcpy(&inquiryData->ProductRevisionLevel, "n/a ", 4); } else { memcpy(&inquiryData->VendorId, "HPT ", 8); snprintf((char *)&inquiryData->ProductId, 16, "DISK_%d_%d ", os_get_vbus_seq(vbus_ext), vd->target_id); inquiryData->ProductId[15] = ' '; memcpy(&inquiryData->ProductRevisionLevel, "4.00", 4); } ccb->ccb_h.status = CAM_REQ_CMP; break; } case READ_CAPACITY: { HPT_U8 *rbuf = ccb->csio.data_ptr; HPT_U32 cap; HPT_U8 sector_size_shift = 0; HPT_U64 new_cap; HPT_U32 sector_size = 0; if (mIsArray(vd->type)) sector_size_shift = vd->u.array.sector_size_shift; else{ if(vd->type == VD_RAW){ sector_size = vd->u.raw.logical_sector_size; } switch (sector_size) { case 0x1000: KdPrint(("set 4k setctor size in READ_CAPACITY")); sector_size_shift = 3; break; default: break; } } new_cap = vd->capacity >> sector_size_shift; if (new_cap > 0xfffffffful) cap = 0xffffffff; else cap = new_cap - 1; rbuf[0] = (HPT_U8)(cap>>24); rbuf[1] = (HPT_U8)(cap>>16); rbuf[2] = (HPT_U8)(cap>>8); rbuf[3] = (HPT_U8)cap; rbuf[4] = 0; rbuf[5] = 0; rbuf[6] = 2 << sector_size_shift; rbuf[7] = 0; ccb->ccb_h.status = CAM_REQ_CMP; break; } case REPORT_LUNS: { HPT_U8 *rbuf = ccb->csio.data_ptr; memset(rbuf, 0, 16); rbuf[3] = 8; ccb->ccb_h.status = CAM_REQ_CMP; break; } case SERVICE_ACTION_IN: { HPT_U8 *rbuf = ccb->csio.data_ptr; HPT_U64 cap = 0; HPT_U8 sector_size_shift = 0; HPT_U32 sector_size = 0; if(mIsArray(vd->type)) sector_size_shift = vd->u.array.sector_size_shift; else{ if(vd->type == VD_RAW){ sector_size = vd->u.raw.logical_sector_size; } switch (sector_size) { case 0x1000: KdPrint(("set 4k setctor size in SERVICE_ACTION_IN")); sector_size_shift = 3; break; default: break; } } cap = (vd->capacity >> sector_size_shift) - 1; rbuf[0] = (HPT_U8)(cap>>56); rbuf[1] = (HPT_U8)(cap>>48); rbuf[2] = (HPT_U8)(cap>>40); rbuf[3] = (HPT_U8)(cap>>32); rbuf[4] = (HPT_U8)(cap>>24); rbuf[5] = (HPT_U8)(cap>>16); rbuf[6] = (HPT_U8)(cap>>8); rbuf[7] = (HPT_U8)cap; rbuf[8] = 0; rbuf[9] = 0; rbuf[10] = 2 << sector_size_shift; rbuf[11] = 0; if(!mIsArray(vd->type)){ rbuf[13] = vd->u.raw.logicalsectors_per_physicalsector; rbuf[14] = (HPT_U8)((vd->u.raw.lowest_aligned >> 8) & 0x3f); rbuf[15] = (HPT_U8)(vd->u.raw.lowest_aligned); } ccb->ccb_h.status = CAM_REQ_CMP; break; } case READ_6: case READ_10: case READ_16: case WRITE_6: case WRITE_10: case WRITE_16: case 0x13: case 0x2f: case 0x8f: /* VERIFY_16 */ { int error; HPT_U8 sector_size_shift = 0; HPT_U32 sector_size = 0; pCmd = ldm_alloc_cmds(vbus, vd->cmds_per_request); if(!pCmd){ KdPrint(("Failed to allocate command!")); ccb->ccb_h.status = CAM_BUSY; break; } switch (cdb[0]) { case READ_6: case WRITE_6: case 0x13: pCmd->uCmd.Ide.Lba = ((HPT_U32)cdb[1] << 16) | ((HPT_U32)cdb[2] << 8) | (HPT_U32)cdb[3]; pCmd->uCmd.Ide.nSectors = (HPT_U16) cdb[4]; break; case READ_16: case WRITE_16: case 0x8f: /* VERIFY_16 */ { HPT_U64 block = ((HPT_U64)cdb[2]<<56) | ((HPT_U64)cdb[3]<<48) | ((HPT_U64)cdb[4]<<40) | ((HPT_U64)cdb[5]<<32) | ((HPT_U64)cdb[6]<<24) | ((HPT_U64)cdb[7]<<16) | ((HPT_U64)cdb[8]<<8) | ((HPT_U64)cdb[9]); pCmd->uCmd.Ide.Lba = block; pCmd->uCmd.Ide.nSectors = (HPT_U16)cdb[13] | ((HPT_U16)cdb[12]<<8); break; } default: pCmd->uCmd.Ide.Lba = (HPT_U32)cdb[5] | ((HPT_U32)cdb[4] << 8) | ((HPT_U32)cdb[3] << 16) | ((HPT_U32)cdb[2] << 24); pCmd->uCmd.Ide.nSectors = (HPT_U16) cdb[8] | ((HPT_U16)cdb[7]<<8); break; } if(mIsArray(vd->type)) { sector_size_shift = vd->u.array.sector_size_shift; } else{ if(vd->type == VD_RAW){ sector_size = vd->u.raw.logical_sector_size; } switch (sector_size) { case 0x1000: KdPrint(("<8>resize sector size from 4k to 512")); sector_size_shift = 3; break; default: break; } } pCmd->uCmd.Ide.Lba <<= sector_size_shift; pCmd->uCmd.Ide.nSectors <<= sector_size_shift; switch (cdb[0]) { case READ_6: case READ_10: case READ_16: pCmd->flags.data_in = 1; break; case WRITE_6: case WRITE_10: case WRITE_16: pCmd->flags.data_out = 1; break; } pCmd->priv = ext = cmdext_get(vbus_ext); HPT_ASSERT(ext); ext->ccb = ccb; pCmd->target = vd; pCmd->done = os_cmddone; pCmd->buildsgl = os_buildsgl; pCmd->psg = ext->psg; pCmd->flags.physical_sg = 1; error = bus_dmamap_load_ccb(vbus_ext->io_dmat, ext->dma_map, ccb, hpt_io_dmamap_callback, pCmd, BUS_DMA_WAITOK ); KdPrint(("bus_dmamap_load return %d", error)); if (error && error!=EINPROGRESS) { os_printk("bus_dmamap_load error %d", error); cmdext_put(ext); ldm_free_cmds(pCmd); ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); } return; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; } static void hpt_action(struct cam_sim *sim, union ccb *ccb) { PVBUS_EXT vbus_ext = (PVBUS_EXT)cam_sim_softc(sim); KdPrint(("hpt_action(fn=%d, id=%d)", ccb->ccb_h.func_code, ccb->ccb_h.target_id)); hpt_assert_vbus_locked(vbus_ext); switch (ccb->ccb_h.func_code) { case XPT_SCSI_IO: hpt_scsi_io(vbus_ext, ccb); return; case XPT_RESET_BUS: ldm_reset_vbus((PVBUS)vbus_ext->vbus); break; case XPT_GET_TRAN_SETTINGS: case XPT_SET_TRAN_SETTINGS: ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; break; case XPT_CALC_GEOMETRY: ccb->ccg.heads = 255; ccb->ccg.secs_per_track = 63; ccb->ccg.cylinders = ccb->ccg.volume_size / (ccb->ccg.heads * ccb->ccg.secs_per_track); ccb->ccb_h.status = CAM_REQ_CMP; break; case XPT_PATH_INQ: { struct ccb_pathinq *cpi = &ccb->cpi; cpi->version_num = 1; cpi->hba_inquiry = PI_SDTR_ABLE; cpi->target_sprt = 0; cpi->hba_misc = PIM_NOBUSRESET; cpi->hba_eng_cnt = 0; cpi->max_target = osm_max_targets; cpi->max_lun = 0; cpi->unit_number = cam_sim_unit(sim); cpi->bus_id = cam_sim_bus(sim); cpi->initiator_id = osm_max_targets; cpi->base_transfer_speed = 3300; strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); strlcpy(cpi->hba_vid, "HPT ", HBA_IDLEN); strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); cpi->transport = XPORT_SPI; cpi->transport_version = 2; cpi->protocol = PROTO_SCSI; cpi->protocol_version = SCSI_REV_2; cpi->ccb_h.status = CAM_REQ_CMP; break; } default: ccb->ccb_h.status = CAM_REQ_INVALID; break; } xpt_done(ccb); return; } static void hpt_pci_intr(void *arg) { PVBUS_EXT vbus_ext = (PVBUS_EXT)arg; hpt_lock_vbus(vbus_ext); ldm_intr((PVBUS)vbus_ext->vbus); hpt_unlock_vbus(vbus_ext); } static void hpt_poll(struct cam_sim *sim) { PVBUS_EXT vbus_ext = cam_sim_softc(sim); hpt_assert_vbus_locked(vbus_ext); ldm_intr((PVBUS)vbus_ext->vbus); } static void hpt_async(void * callback_arg, u_int32_t code, struct cam_path * path, void * arg) { KdPrint(("hpt_async")); } static int hpt_shutdown(device_t dev) { KdPrint(("hpt_shutdown(dev=%p)", dev)); return 0; } static int hpt_detach(device_t dev) { /* we don't allow the driver to be unloaded. */ return EBUSY; } static void hpt_ioctl_done(struct _IOCTL_ARG *arg) { arg->ioctl_cmnd = 0; wakeup(arg); } static void __hpt_do_ioctl(PVBUS_EXT vbus_ext, IOCTL_ARG *ioctl_args) { ioctl_args->result = -1; ioctl_args->done = hpt_ioctl_done; ioctl_args->ioctl_cmnd = (void *)1; hpt_lock_vbus(vbus_ext); ldm_ioctl((PVBUS)vbus_ext->vbus, ioctl_args); while (ioctl_args->ioctl_cmnd) { if (hpt_sleep(vbus_ext, ioctl_args, PPAUSE, "hptctl", HPT_OSM_TIMEOUT)==0) break; ldm_reset_vbus((PVBUS)vbus_ext->vbus); __hpt_do_tasks(vbus_ext); } /* KdPrint(("ioctl %x result %d", ioctl_args->dwIoControlCode, ioctl_args->result)); */ hpt_unlock_vbus(vbus_ext); } static void hpt_do_ioctl(IOCTL_ARG *ioctl_args) { PVBUS vbus; PVBUS_EXT vbus_ext; ldm_for_each_vbus(vbus, vbus_ext) { __hpt_do_ioctl(vbus_ext, ioctl_args); if (ioctl_args->result!=HPT_IOCTL_RESULT_WRONG_VBUS) return; } } #define HPT_DO_IOCTL(code, inbuf, insize, outbuf, outsize) ({\ IOCTL_ARG arg;\ arg.dwIoControlCode = code;\ arg.lpInBuffer = inbuf;\ arg.lpOutBuffer = outbuf;\ arg.nInBufferSize = insize;\ arg.nOutBufferSize = outsize;\ arg.lpBytesReturned = 0;\ hpt_do_ioctl(&arg);\ arg.result;\ }) #define DEVICEID_VALID(id) ((id) && ((HPT_U32)(id)!=0xffffffff)) static int hpt_get_logical_devices(DEVICEID * pIds, int nMaxCount) { int i; HPT_U32 count = nMaxCount-1; if (HPT_DO_IOCTL(HPT_IOCTL_GET_LOGICAL_DEVICES, &count, sizeof(HPT_U32), pIds, sizeof(DEVICEID)*nMaxCount)) return -1; nMaxCount = (int)pIds[0]; for (i=0; ilock, "hptsleeplock", NULL, MTX_DEF); callout_init_mtx(&vbus_ext->timer, &vbus_ext->lock, 0); if (hpt_init_vbus(vbus_ext)) { os_printk("fail to initialize hardware"); break; /* FIXME */ } } /* register CAM interface */ ldm_for_each_vbus(vbus, vbus_ext) { struct cam_devq *devq; struct ccb_setasync ccb; if (bus_dma_tag_create(NULL,/* parent */ 4, /* alignment */ BUS_SPACE_MAXADDR_32BIT+1, /* boundary */ BUS_SPACE_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ PAGE_SIZE * (os_max_sg_descriptors-1), /* maxsize */ os_max_sg_descriptors, /* nsegments */ 0x10000, /* maxsegsize */ BUS_DMA_WAITOK, /* flags */ busdma_lock_mutex, /* lockfunc */ &vbus_ext->lock, /* lockfuncarg */ &vbus_ext->io_dmat /* tag */)) { return ; } for (i=0; ivbus_ext = vbus_ext; ext->next = vbus_ext->cmdext_list; vbus_ext->cmdext_list = ext; if (bus_dmamap_create(vbus_ext->io_dmat, 0, &ext->dma_map)) { os_printk("Can't create dma map(%d)", i); return ; } callout_init_mtx(&ext->timeout, &vbus_ext->lock, 0); } if ((devq = cam_simq_alloc(os_max_queue_comm)) == NULL) { os_printk("cam_simq_alloc failed"); return ; } hpt_lock_vbus(vbus_ext); vbus_ext->sim = cam_sim_alloc(hpt_action, hpt_poll, driver_name, vbus_ext, unit_number, &vbus_ext->lock, os_max_queue_comm, /*tagged*/8, devq); unit_number++; if (!vbus_ext->sim) { os_printk("cam_sim_alloc failed"); cam_simq_free(devq); hpt_unlock_vbus(vbus_ext); return ; } if (xpt_bus_register(vbus_ext->sim, NULL, 0) != CAM_SUCCESS) { os_printk("xpt_bus_register failed"); cam_sim_free(vbus_ext->sim, /*free devq*/ TRUE); vbus_ext->sim = NULL; return ; } if (xpt_create_path(&vbus_ext->path, /*periph */ NULL, cam_sim_path(vbus_ext->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { os_printk("xpt_create_path failed"); xpt_bus_deregister(cam_sim_path(vbus_ext->sim)); cam_sim_free(vbus_ext->sim, /*free_devq*/TRUE); hpt_unlock_vbus(vbus_ext); vbus_ext->sim = NULL; return ; } hpt_unlock_vbus(vbus_ext); memset(&ccb, 0, sizeof(ccb)); xpt_setup_ccb(&ccb.ccb_h, vbus_ext->path, /*priority*/5); ccb.ccb_h.func_code = XPT_SASYNC_CB; ccb.event_enable = AC_LOST_DEVICE; ccb.callback = hpt_async; ccb.callback_arg = vbus_ext; xpt_action((union ccb *)&ccb); for (hba = vbus_ext->hba_list; hba; hba = hba->next) { int rid = 0; if ((hba->irq_res = bus_alloc_resource_any(hba->pcidev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE)) == NULL) { os_printk("can't allocate interrupt"); return ; } if (bus_setup_intr(hba->pcidev, hba->irq_res, INTR_TYPE_CAM | INTR_MPSAFE, NULL, hpt_pci_intr, vbus_ext, &hba->irq_handle)) { os_printk("can't set up interrupt"); return ; } hba->ldm_adapter.him->intr_control(hba->ldm_adapter.him_handle, HPT_TRUE); } vbus_ext->shutdown_eh = EVENTHANDLER_REGISTER(shutdown_final, hpt_shutdown_vbus, vbus_ext, SHUTDOWN_PRI_DEFAULT); if (!vbus_ext->shutdown_eh) os_printk("Shutdown event registration failed"); } ldm_for_each_vbus(vbus, vbus_ext) { TASK_INIT(&vbus_ext->worker, 0, (task_fn_t *)hpt_do_tasks, vbus_ext); if (vbus_ext->tasks) TASK_ENQUEUE(&vbus_ext->worker); } make_dev(&hpt_cdevsw, DRIVER_MINOR, UID_ROOT, GID_OPERATOR, S_IRUSR | S_IWUSR, "%s", driver_name); } #if defined(KLD_MODULE) typedef struct driverlink *driverlink_t; struct driverlink { kobj_class_t driver; TAILQ_ENTRY(driverlink) link; /* list of drivers in devclass */ }; typedef TAILQ_HEAD(driver_list, driverlink) driver_list_t; struct devclass { TAILQ_ENTRY(devclass) link; devclass_t parent; /* parent in devclass hierarchy */ driver_list_t drivers; /* bus devclasses store drivers for bus */ char *name; device_t *devices; /* array of devices indexed by unit */ int maxunit; /* size of devices array */ }; static void override_kernel_driver(void) { driverlink_t dl, dlfirst; driver_t *tmpdriver; devclass_t dc = devclass_find("pci"); if (dc){ dlfirst = TAILQ_FIRST(&dc->drivers); for (dl = dlfirst; dl; dl = TAILQ_NEXT(dl, link)) { if(strcmp(dl->driver->name, driver_name) == 0) { tmpdriver=dl->driver; dl->driver=dlfirst->driver; dlfirst->driver=tmpdriver; break; } } } } #else #define override_kernel_driver() #endif static void hpt_init(void *dummy) { if (bootverbose) os_printk("%s %s", driver_name_long, driver_ver); override_kernel_driver(); init_config(); hpt_ich.ich_func = hpt_final_init; hpt_ich.ich_arg = NULL; if (config_intrhook_establish(&hpt_ich) != 0) { printf("%s: cannot establish configuration hook\n", driver_name_long); } } SYSINIT(hptinit, SI_SUB_CONFIGURE, SI_ORDER_FIRST, hpt_init, NULL); /* * CAM driver interface */ static device_method_t driver_methods[] = { /* Device interface */ DEVMETHOD(device_probe, hpt_probe), DEVMETHOD(device_attach, hpt_attach), DEVMETHOD(device_detach, hpt_detach), DEVMETHOD(device_shutdown, hpt_shutdown), { 0, 0 } }; static driver_t hpt_pci_driver = { driver_name, driver_methods, sizeof(HBA) }; #ifndef TARGETNAME #error "no TARGETNAME found" #endif /* use this to make TARGETNAME be expanded */ #define __DRIVER_MODULE(p1, p2, p3, p4, p5) DRIVER_MODULE(p1, p2, p3, p4, p5) #define __MODULE_VERSION(p1, p2) MODULE_VERSION(p1, p2) #define __MODULE_DEPEND(p1, p2, p3, p4, p5) MODULE_DEPEND(p1, p2, p3, p4, p5) __DRIVER_MODULE(TARGETNAME, pci, hpt_pci_driver, 0, 0); __MODULE_VERSION(TARGETNAME, 1); __MODULE_DEPEND(TARGETNAME, cam, 1, 1, 1); static int hpt_open(struct cdev *dev, int flags, int devtype, struct thread *td) { return 0; } static int hpt_close(struct cdev *dev, int flags, int devtype, struct thread *td) { return 0; } static int hpt_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { PHPT_IOCTL_PARAM piop=(PHPT_IOCTL_PARAM)data; IOCTL_ARG ioctl_args; HPT_U32 bytesReturned = 0; switch (cmd){ case HPT_DO_IOCONTROL: { if (piop->Magic == HPT_IOCTL_MAGIC || piop->Magic == HPT_IOCTL_MAGIC32) { KdPrint(("ioctl=%x in=%p len=%d out=%p len=%d\n", piop->dwIoControlCode, piop->lpInBuffer, piop->nInBufferSize, piop->lpOutBuffer, piop->nOutBufferSize)); memset(&ioctl_args, 0, sizeof(ioctl_args)); ioctl_args.dwIoControlCode = piop->dwIoControlCode; ioctl_args.nInBufferSize = piop->nInBufferSize; ioctl_args.nOutBufferSize = piop->nOutBufferSize; ioctl_args.lpBytesReturned = &bytesReturned; if (ioctl_args.nInBufferSize) { ioctl_args.lpInBuffer = malloc(ioctl_args.nInBufferSize, M_DEVBUF, M_WAITOK); if (!ioctl_args.lpInBuffer) goto invalid; if (copyin((void*)piop->lpInBuffer, ioctl_args.lpInBuffer, piop->nInBufferSize)) goto invalid; } if (ioctl_args.nOutBufferSize) { ioctl_args.lpOutBuffer = malloc(ioctl_args.nOutBufferSize, M_DEVBUF, M_WAITOK | M_ZERO); if (!ioctl_args.lpOutBuffer) goto invalid; } hpt_do_ioctl(&ioctl_args); if (ioctl_args.result==HPT_IOCTL_RESULT_OK) { if (piop->nOutBufferSize) { if (copyout(ioctl_args.lpOutBuffer, (void*)piop->lpOutBuffer, piop->nOutBufferSize)) goto invalid; } if (piop->lpBytesReturned) { if (copyout(&bytesReturned, (void*)piop->lpBytesReturned, sizeof(HPT_U32))) goto invalid; } if (ioctl_args.lpInBuffer) free(ioctl_args.lpInBuffer, M_DEVBUF); if (ioctl_args.lpOutBuffer) free(ioctl_args.lpOutBuffer, M_DEVBUF); return 0; } invalid: if (ioctl_args.lpInBuffer) free(ioctl_args.lpInBuffer, M_DEVBUF); if (ioctl_args.lpOutBuffer) free(ioctl_args.lpOutBuffer, M_DEVBUF); return EFAULT; } return EFAULT; } case HPT_SCAN_BUS: { return hpt_rescan_bus(); } default: KdPrint(("invalid command!")); return EFAULT; } } static int hpt_rescan_bus(void) { union ccb *ccb; PVBUS vbus; PVBUS_EXT vbus_ext; ldm_for_each_vbus(vbus, vbus_ext) { if ((ccb = xpt_alloc_ccb()) == NULL) { return(ENOMEM); } if (xpt_create_path(&ccb->ccb_h.path, NULL, cam_sim_path(vbus_ext->sim), CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { xpt_free_ccb(ccb); return(EIO); } xpt_rescan(ccb); } return(0); } diff --git a/sys/dev/ichwd/ichwd.c b/sys/dev/ichwd/ichwd.c index e07618f5548a..0270128f0766 100644 --- a/sys/dev/ichwd/ichwd.c +++ b/sys/dev/ichwd/ichwd.c @@ -1,987 +1,987 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2004 Texas A&M University * All rights reserved. * * Developer: Wm. Daryl Hawkins * * 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. */ /* * Intel ICH Watchdog Timer (WDT) driver * * Originally developed by Wm. Daryl Hawkins of Texas A&M * Heavily modified by * * This is a tricky one. The ICH WDT can't be treated as a regular PCI * device as it's actually an integrated function of the ICH LPC interface * bridge. Detection is also awkward, because we can only infer the * presence of the watchdog timer from the fact that the machine has an * ICH chipset, or, on ACPI 2.x systems, by the presence of the 'WDDT' * ACPI table (although this driver does not support the ACPI detection * method). * * There is one slight problem on non-ACPI or ACPI 1.x systems: we have no * way of knowing if the WDT is permanently disabled (either by the BIOS * or in hardware). * * The WDT is programmed through I/O registers in the ACPI I/O space. * Intel swears it's always at offset 0x60, so we use that. * * For details about the ICH WDT, see Intel Application Note AP-725 * (document no. 292273-001). The WDT is also described in the individual * chipset datasheets, e.g. Intel82801EB ICH5 / 82801ER ICH5R Datasheet * (document no. 252516-001) sections 9.10 and 9.11. * * ICH6/7/8 support by Takeharu KATO * SoC PMC support by Denir Li */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct ichwd_device ichwd_devices[] = { { DEVICEID_82801AA, "Intel 82801AA watchdog timer", 1, 1 }, { DEVICEID_82801AB, "Intel 82801AB watchdog timer", 1, 1 }, { DEVICEID_82801BA, "Intel 82801BA watchdog timer", 2, 1 }, { DEVICEID_82801BAM, "Intel 82801BAM watchdog timer", 2, 1 }, { DEVICEID_82801CA, "Intel 82801CA watchdog timer", 3, 1 }, { DEVICEID_82801CAM, "Intel 82801CAM watchdog timer", 3, 1 }, { DEVICEID_82801DB, "Intel 82801DB watchdog timer", 4, 1 }, { DEVICEID_82801DBM, "Intel 82801DBM watchdog timer", 4, 1 }, { DEVICEID_82801E, "Intel 82801E watchdog timer", 5, 1 }, { DEVICEID_82801EB, "Intel 82801EB watchdog timer", 5, 1 }, { DEVICEID_82801EBR, "Intel 82801EB/ER watchdog timer", 5, 1 }, { DEVICEID_6300ESB, "Intel 6300ESB watchdog timer", 5, 1 }, { DEVICEID_82801FBR, "Intel 82801FB/FR watchdog timer", 6, 2 }, { DEVICEID_ICH6M, "Intel ICH6M watchdog timer", 6, 2 }, { DEVICEID_ICH6W, "Intel ICH6W watchdog timer", 6, 2 }, { DEVICEID_ICH7, "Intel ICH7 watchdog timer", 7, 2 }, { DEVICEID_ICH7DH, "Intel ICH7DH watchdog timer", 7, 2 }, { DEVICEID_ICH7M, "Intel ICH7M watchdog timer", 7, 2 }, { DEVICEID_ICH7MDH, "Intel ICH7MDH watchdog timer", 7, 2 }, { DEVICEID_NM10, "Intel NM10 watchdog timer", 7, 2 }, { DEVICEID_ICH8, "Intel ICH8 watchdog timer", 8, 2 }, { DEVICEID_ICH8DH, "Intel ICH8DH watchdog timer", 8, 2 }, { DEVICEID_ICH8DO, "Intel ICH8DO watchdog timer", 8, 2 }, { DEVICEID_ICH8M, "Intel ICH8M watchdog timer", 8, 2 }, { DEVICEID_ICH8ME, "Intel ICH8M-E watchdog timer", 8, 2 }, { DEVICEID_63XXESB, "Intel 63XXESB watchdog timer", 8, 2 }, { DEVICEID_ICH9, "Intel ICH9 watchdog timer", 9, 2 }, { DEVICEID_ICH9DH, "Intel ICH9DH watchdog timer", 9, 2 }, { DEVICEID_ICH9DO, "Intel ICH9DO watchdog timer", 9, 2 }, { DEVICEID_ICH9M, "Intel ICH9M watchdog timer", 9, 2 }, { DEVICEID_ICH9ME, "Intel ICH9M-E watchdog timer", 9, 2 }, { DEVICEID_ICH9R, "Intel ICH9R watchdog timer", 9, 2 }, { DEVICEID_ICH10, "Intel ICH10 watchdog timer", 10, 2 }, { DEVICEID_ICH10D, "Intel ICH10D watchdog timer", 10, 2 }, { DEVICEID_ICH10DO, "Intel ICH10DO watchdog timer", 10, 2 }, { DEVICEID_ICH10R, "Intel ICH10R watchdog timer", 10, 2 }, { DEVICEID_PCH, "Intel PCH watchdog timer", 10, 2 }, { DEVICEID_PCHM, "Intel PCH watchdog timer", 10, 2 }, { DEVICEID_P55, "Intel P55 watchdog timer", 10, 2 }, { DEVICEID_PM55, "Intel PM55 watchdog timer", 10, 2 }, { DEVICEID_H55, "Intel H55 watchdog timer", 10, 2 }, { DEVICEID_QM57, "Intel QM57 watchdog timer", 10, 2 }, { DEVICEID_H57, "Intel H57 watchdog timer", 10, 2 }, { DEVICEID_HM55, "Intel HM55 watchdog timer", 10, 2 }, { DEVICEID_Q57, "Intel Q57 watchdog timer", 10, 2 }, { DEVICEID_HM57, "Intel HM57 watchdog timer", 10, 2 }, { DEVICEID_PCHMSFF, "Intel PCHMSFF watchdog timer", 10, 2 }, { DEVICEID_QS57, "Intel QS57 watchdog timer", 10, 2 }, { DEVICEID_3400, "Intel 3400 watchdog timer", 10, 2 }, { DEVICEID_3420, "Intel 3420 watchdog timer", 10, 2 }, { DEVICEID_3450, "Intel 3450 watchdog timer", 10, 2 }, { DEVICEID_CPT0, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT1, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT2, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT3, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT4, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT5, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT6, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT7, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT8, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT9, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT10, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT11, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT12, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT13, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT14, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT15, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT16, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT17, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT18, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT19, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT20, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT21, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT22, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT23, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT24, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT25, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT26, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT27, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT28, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT29, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT30, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_CPT31, "Intel Cougar Point watchdog timer", 10, 2 }, { DEVICEID_PATSBURG_LPC1, "Intel Patsburg watchdog timer", 10, 2 }, { DEVICEID_PATSBURG_LPC2, "Intel Patsburg watchdog timer", 10, 2 }, { DEVICEID_PPT0, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT1, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT2, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT3, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT4, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT5, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT6, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT7, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT8, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT9, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT10, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT11, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT12, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT13, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT14, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT15, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT16, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT17, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT18, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT19, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT20, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT21, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT22, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT23, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT24, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT25, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT26, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT27, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT28, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT29, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT30, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_PPT31, "Intel Panther Point watchdog timer", 10, 2 }, { DEVICEID_LPT0, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT1, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT2, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT3, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT4, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT5, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT6, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT7, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT8, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT9, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT10, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT11, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT12, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT13, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT14, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT15, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT16, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT17, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT18, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT19, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT20, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT21, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT22, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT23, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT24, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT25, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT26, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT27, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT28, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT29, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT30, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_LPT31, "Intel Lynx Point watchdog timer", 10, 2 }, { DEVICEID_WCPT1, "Intel Wildcat Point watchdog timer", 10, 2 }, { DEVICEID_WCPT2, "Intel Wildcat Point watchdog timer", 10, 2 }, { DEVICEID_WCPT3, "Intel Wildcat Point watchdog timer", 10, 2 }, { DEVICEID_WCPT4, "Intel Wildcat Point watchdog timer", 10, 2 }, { DEVICEID_WCPT6, "Intel Wildcat Point watchdog timer", 10, 2 }, { DEVICEID_WBG0, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG1, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG2, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG3, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG4, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG5, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG6, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG7, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG8, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG9, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG10, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG11, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG12, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG13, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG14, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG15, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG16, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG17, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG18, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG19, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG20, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG21, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG22, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG23, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG24, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG25, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG26, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG27, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG28, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG29, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG30, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_WBG31, "Intel Wellsburg watchdog timer", 10, 2 }, { DEVICEID_LPT_LP0, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_LPT_LP1, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_LPT_LP2, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_LPT_LP3, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_LPT_LP4, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_LPT_LP5, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_LPT_LP6, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_LPT_LP7, "Intel Lynx Point-LP watchdog timer", 10, 2 }, { DEVICEID_WCPT_LP1, "Intel Wildcat Point-LP watchdog timer", 10, 2 }, { DEVICEID_WCPT_LP2, "Intel Wildcat Point-LP watchdog timer", 10, 2 }, { DEVICEID_WCPT_LP3, "Intel Wildcat Point-LP watchdog timer", 10, 2 }, { DEVICEID_WCPT_LP5, "Intel Wildcat Point-LP watchdog timer", 10, 2 }, { DEVICEID_WCPT_LP6, "Intel Wildcat Point-LP watchdog timer", 10, 2 }, { DEVICEID_WCPT_LP7, "Intel Wildcat Point-LP watchdog timer", 10, 2 }, { DEVICEID_WCPT_LP9, "Intel Wildcat Point-LP watchdog timer", 10, 2 }, { DEVICEID_DH89XXCC_LPC, "Intel DH89xxCC watchdog timer", 10, 2 }, { DEVICEID_COLETOCRK_LPC, "Intel Coleto Creek watchdog timer", 10, 2 }, { DEVICEID_AVN0, "Intel Avoton/Rangeley SoC watchdog timer",10, 3 }, { DEVICEID_AVN1, "Intel Avoton/Rangeley SoC watchdog timer",10, 3 }, { DEVICEID_AVN2, "Intel Avoton/Rangeley SoC watchdog timer",10, 3 }, { DEVICEID_AVN3, "Intel Avoton/Rangeley SoC watchdog timer",10, 3 }, { DEVICEID_BAYTRAIL, "Intel Bay Trail SoC watchdog timer", 10, 3 }, { DEVICEID_BRASWELL, "Intel Braswell SoC watchdog timer", 10, 3 }, { 0, NULL, 0, 0 }, }; static struct ichwd_device ichwd_smb_devices[] = { { DEVICEID_LEWISBURG_SMB, "Lewisburg watchdog timer", 10, 4 }, { DEVICEID_LEWISBURG_SMB_SSKU, "Lewisburg watchdog timer", 10, 4 }, { DEVICEID_CANNON_SMB, "Cannon Lake watchdog timer", 10, 4, PMC_HIDDEN}, { DEVICEID_COMET_SMB, "Comet Lake watchdog timer", 10, 4, PMC_HIDDEN}, { DEVICEID_SRPTLP_SMB, "Sunrise Point-LP watchdog timer", 10, 4 }, { DEVICEID_C3000, "Intel Atom C3000 watchdog timer", 10, 4 }, { 0, NULL, 0, 0 }, }; #define ichwd_read_tco_1(sc, off) \ bus_read_1((sc)->tco_res, (off)) #define ichwd_read_tco_2(sc, off) \ bus_read_2((sc)->tco_res, (off)) #define ichwd_read_tco_4(sc, off) \ bus_read_4((sc)->tco_res, (off)) #define ichwd_read_smi_4(sc, off) \ bus_read_4((sc)->smi_res, (off)) #define ichwd_read_gcs_4(sc, off) \ bus_read_4((sc)->gcs_res, (off)) /* NB: TCO version 3 devices use the gcs_res resource for the PMC register. */ #define ichwd_read_pmc_4(sc, off) \ bus_read_4((sc)->gcs_res, (off)) #define ichwd_read_gc_4(sc, off) \ bus_read_4((sc)->gc_res, (off)) #define ichwd_write_tco_1(sc, off, val) \ bus_write_1((sc)->tco_res, (off), (val)) #define ichwd_write_tco_2(sc, off, val) \ bus_write_2((sc)->tco_res, (off), (val)) #define ichwd_write_tco_4(sc, off, val) \ bus_write_4((sc)->tco_res, (off), (val)) #define ichwd_write_smi_4(sc, off, val) \ bus_write_4((sc)->smi_res, (off), (val)) #define ichwd_write_gcs_4(sc, off, val) \ bus_write_4((sc)->gcs_res, (off), (val)) /* NB: TCO version 3 devices use the gcs_res resource for the PMC register. */ #define ichwd_write_pmc_4(sc, off, val) \ bus_write_4((sc)->gcs_res, (off), (val)) #define ichwd_write_gc_4(sc, off, val) \ bus_write_4((sc)->gc_res, (off), (val)) #define ichwd_verbose_printf(dev, ...) \ do { \ if (bootverbose) \ device_printf(dev, __VA_ARGS__);\ } while (0) /* * Disable the watchdog timeout SMI handler. * * Apparently, some BIOSes install handlers that reset or disable the * watchdog timer instead of resetting the system, so we disable the SMI * (by clearing the SMI_TCO_EN bit of the SMI_EN register) to prevent this * from happening. */ static __inline void ichwd_smi_disable(struct ichwd_softc *sc) { ichwd_write_smi_4(sc, SMI_EN, ichwd_read_smi_4(sc, SMI_EN) & ~SMI_TCO_EN); } /* * Enable the watchdog timeout SMI handler. See above for details. */ static __inline void ichwd_smi_enable(struct ichwd_softc *sc) { ichwd_write_smi_4(sc, SMI_EN, ichwd_read_smi_4(sc, SMI_EN) | SMI_TCO_EN); } /* * Check if the watchdog SMI triggering is enabled. */ static __inline int ichwd_smi_is_enabled(struct ichwd_softc *sc) { return ((ichwd_read_smi_4(sc, SMI_EN) & SMI_TCO_EN) != 0); } /* * Reset the watchdog status bits. */ static __inline void ichwd_sts_reset(struct ichwd_softc *sc) { /* * The watchdog status bits are set to 1 by the hardware to * indicate various conditions. They can be cleared by software * by writing a 1, not a 0. */ ichwd_write_tco_2(sc, TCO1_STS, TCO_TIMEOUT); /* * According to Intel's docs, clearing SECOND_TO_STS and BOOT_STS must * be done in two separate operations. */ ichwd_write_tco_2(sc, TCO2_STS, TCO_SECOND_TO_STS); if (sc->tco_version < 4) ichwd_write_tco_2(sc, TCO2_STS, TCO_BOOT_STS); } /* * Enable the watchdog timer by clearing the TCO_TMR_HALT bit in the * TCO1_CNT register. This is complicated by the need to preserve bit 9 * of that same register, and the requirement that all other bits must be * written back as zero. */ static __inline void ichwd_tmr_enable(struct ichwd_softc *sc) { uint16_t cnt; cnt = ichwd_read_tco_2(sc, TCO1_CNT) & TCO_CNT_PRESERVE; ichwd_write_tco_2(sc, TCO1_CNT, cnt & ~TCO_TMR_HALT); sc->active = 1; ichwd_verbose_printf(sc->device, "timer enabled\n"); } /* * Disable the watchdog timer. See above for details. */ static __inline void ichwd_tmr_disable(struct ichwd_softc *sc) { uint16_t cnt; cnt = ichwd_read_tco_2(sc, TCO1_CNT) & TCO_CNT_PRESERVE; ichwd_write_tco_2(sc, TCO1_CNT, cnt | TCO_TMR_HALT); sc->active = 0; ichwd_verbose_printf(sc->device, "timer disabled\n"); } /* * Reload the watchdog timer: writing anything to any of the lower five * bits of the TCO_RLD register reloads the timer from the last value * written to TCO_TMR. */ static __inline void ichwd_tmr_reload(struct ichwd_softc *sc) { if (sc->tco_version == 1) ichwd_write_tco_1(sc, TCO_RLD, 1); else ichwd_write_tco_2(sc, TCO_RLD, 1); } /* * Set the initial timeout value. Note that this must always be followed * by a reload. */ static __inline void ichwd_tmr_set(struct ichwd_softc *sc, unsigned int timeout) { if (timeout < TCO_RLD_TMR_MIN) timeout = TCO_RLD_TMR_MIN; if (sc->tco_version == 1) { uint8_t tmr_val8 = ichwd_read_tco_1(sc, TCO_TMR1); tmr_val8 &= (~TCO_RLD1_TMR_MAX & 0xff); if (timeout > TCO_RLD1_TMR_MAX) timeout = TCO_RLD1_TMR_MAX; tmr_val8 |= timeout; ichwd_write_tco_1(sc, TCO_TMR1, tmr_val8); } else { uint16_t tmr_val16 = ichwd_read_tco_2(sc, TCO_TMR2); tmr_val16 &= (~TCO_RLD2_TMR_MAX & 0xffff); if (timeout > TCO_RLD2_TMR_MAX) timeout = TCO_RLD2_TMR_MAX; tmr_val16 |= timeout; ichwd_write_tco_2(sc, TCO_TMR2, tmr_val16); } sc->timeout = timeout; ichwd_verbose_printf(sc->device, "timeout set to %u ticks\n", timeout); } static __inline int ichwd_clear_noreboot(struct ichwd_softc *sc) { uint32_t status; int rc = 0; /* try to clear the NO_REBOOT bit */ switch (sc->tco_version) { case 1: status = pci_read_config(sc->ich, ICH_GEN_STA, 1); status &= ~ICH_GEN_STA_NO_REBOOT; pci_write_config(sc->ich, ICH_GEN_STA, status, 1); status = pci_read_config(sc->ich, ICH_GEN_STA, 1); if (status & ICH_GEN_STA_NO_REBOOT) rc = EIO; break; case 2: status = ichwd_read_gcs_4(sc, 0); status &= ~ICH_GCS_NO_REBOOT; ichwd_write_gcs_4(sc, 0, status); status = ichwd_read_gcs_4(sc, 0); if (status & ICH_GCS_NO_REBOOT) rc = EIO; break; case 3: status = ichwd_read_pmc_4(sc, 0); status &= ~ICH_PMC_NO_REBOOT; ichwd_write_pmc_4(sc, 0, status); status = ichwd_read_pmc_4(sc, 0); if (status & ICH_PMC_NO_REBOOT) rc = EIO; break; case 4: status = ichwd_read_gc_4(sc, 0); status &= ~SMB_GC_NO_REBOOT; ichwd_write_gc_4(sc, 0, status); status = ichwd_read_gc_4(sc, 0); if (status & SMB_GC_NO_REBOOT) rc = EIO; break; default: ichwd_verbose_printf(sc->device, "Unknown TCO Version: %d, can't set NO_REBOOT.\n", sc->tco_version); break; } if (rc) device_printf(sc->device, "ICH WDT present but disabled in BIOS or hardware\n"); return (rc); } /* * Watchdog event handler - called by the framework to enable or disable * the watchdog or change the initial timeout value. */ static void ichwd_event(void *arg, unsigned int cmd, int *error) { struct ichwd_softc *sc = arg; unsigned int timeout; /* convert from power-of-two-ns to WDT ticks */ cmd &= WD_INTERVAL; if (sc->tco_version == 3) { timeout = ((uint64_t)1 << cmd) / ICHWD_TCO_V3_TICK; } else { timeout = ((uint64_t)1 << cmd) / ICHWD_TICK; } if (cmd) { if (!sc->active) ichwd_tmr_enable(sc); if (timeout != sc->timeout) ichwd_tmr_set(sc, timeout); ichwd_tmr_reload(sc); *error = 0; } else { if (sc->active) ichwd_tmr_disable(sc); } } static device_t ichwd_find_ich_lpc_bridge(device_t isa, struct ichwd_device **id_p) { struct ichwd_device *id; device_t isab, pci; uint16_t devid; /* Check whether parent ISA bridge looks familiar. */ isab = device_get_parent(isa); pci = device_get_parent(isab); if (pci == NULL || device_get_devclass(pci) != devclass_find("pci")) return (NULL); if (pci_get_vendor(isab) != VENDORID_INTEL) return (NULL); devid = pci_get_device(isab); for (id = ichwd_devices; id->desc != NULL; ++id) { if (devid == id->device) { if (id_p != NULL) *id_p = id; return (isab); } } return (NULL); } static device_t ichwd_find_smb_dev(device_t isa, struct ichwd_device **id_p) { struct ichwd_device *id; device_t isab, smb; uint16_t devid; /* * Check if SMBus controller provides TCO configuration. * The controller's device and function are fixed and we expect * it to be on the same bus as ISA bridge. */ isab = device_get_parent(isa); smb = pci_find_dbsf(pci_get_domain(isab), pci_get_bus(isab), 31, 4); if (smb == NULL) return (NULL); if (pci_get_vendor(smb) != VENDORID_INTEL) return (NULL); devid = pci_get_device(smb); for (id = ichwd_smb_devices; id->desc != NULL; ++id) { if (devid == id->device) { if (id_p != NULL) *id_p = id; return (smb); } } return (NULL); } /* * Look for an ICH LPC interface bridge. If one is found, register an * ichwd device. There can be only one. */ static void ichwd_identify(driver_t *driver, device_t parent) { struct ichwd_device *id_p; device_t ich, smb; device_t dev; uint64_t base_address64; uint32_t base_address; uint32_t ctl; int rc; ich = ichwd_find_ich_lpc_bridge(parent, &id_p); if (ich == NULL) { smb = ichwd_find_smb_dev(parent, &id_p); if (smb == NULL) return; } KASSERT(id_p->tco_version >= 1, ("unexpected TCO version %d", id_p->tco_version)); KASSERT(id_p->tco_version != 4 || smb != NULL, ("could not find PCI SMBus device for TCOv4")); KASSERT(id_p->tco_version >= 4 || ich != NULL, ("could not find PCI LPC bridge device for TCOv1-3")); /* good, add child to bus */ if ((dev = device_find_child(parent, driver->name, 0)) == NULL) dev = BUS_ADD_CHILD(parent, 0, driver->name, 0); if (dev == NULL) return; switch (id_p->tco_version) { case 1: break; case 2: /* get RCBA (root complex base address) */ base_address = pci_read_config(ich, ICH_RCBA, 4); rc = bus_set_resource(ich, SYS_RES_MEMORY, 0, (base_address & 0xffffc000) + ICH_GCS_OFFSET, ICH_GCS_SIZE); if (rc) ichwd_verbose_printf(dev, "Can not set TCO v%d memory resource for RCBA\n", id_p->tco_version); break; case 3: /* get PBASE (Power Management Controller base address) */ base_address = pci_read_config(ich, ICH_PBASE, 4); rc = bus_set_resource(ich, SYS_RES_MEMORY, 0, (base_address & 0xfffffe00) + ICH_PMC_OFFSET, ICH_PMC_SIZE); if (rc) ichwd_verbose_printf(dev, "Can not set TCO v%d memory resource for PBASE\n", id_p->tco_version); break; case 4: /* Get TCO base address. */ ctl = pci_read_config(smb, ICH_TCOCTL, 4); if ((ctl & ICH_TCOCTL_TCO_BASE_EN) == 0) { ichwd_verbose_printf(dev, "TCO v%d decoding is not enabled\n", id_p->tco_version); break; } base_address = pci_read_config(smb, ICH_TCOBASE, 4); rc = bus_set_resource(dev, SYS_RES_IOPORT, 0, base_address & ICH_TCOBASE_ADDRMASK, ICH_TCOBASE_SIZE); if (rc != 0) { ichwd_verbose_printf(dev, "Can not set TCO v%d I/O resource (err = %d)\n", id_p->tco_version, rc); } /* * Unhide Primary to Sideband Bridge (P2SB) PCI device, so that * we can discover the base address of Private Configuration * Space via the bridge's BAR. * Then hide back the bridge. */ - pci_cfgregwrite(0, 31, 1, 0xe1, 0, 1); - base_address64 = pci_cfgregread(0, 31, 1, SBREG_BAR + 4, 4); + pci_cfgregwrite(0, 0, 31, 1, 0xe1, 0, 1); + base_address64 = pci_cfgregread(0, 0, 31, 1, SBREG_BAR + 4, 4); base_address64 <<= 32; - base_address64 |= pci_cfgregread(0, 31, 1, SBREG_BAR, 4); + base_address64 |= pci_cfgregread(0, 0, 31, 1, SBREG_BAR, 4); base_address64 &= ~0xfull; - pci_cfgregwrite(0, 31, 1, 0xe1, 1, 1); + pci_cfgregwrite(0, 0, 31, 1, 0xe1, 1, 1); /* * No Reboot bit is in General Control register, offset 0xc, * within the SMBus target port, ID 0xc6. */ base_address64 += PCR_REG_OFF(SMB_PORT_ID, SMB_GC_REG); rc = bus_set_resource(dev, SYS_RES_MEMORY, 1, base_address64, SMB_GC_SIZE); if (rc != 0) { ichwd_verbose_printf(dev, "Can not set TCO v%d PCR I/O resource (err = %d)\n", id_p->tco_version, rc); } break; default: ichwd_verbose_printf(dev, "Can not set unknown TCO v%d memory resource for unknown base address\n", id_p->tco_version); break; } } static int ichwd_probe(device_t dev) { struct ichwd_device *id_p; /* Do not claim some ISA PnP device by accident. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); if (ichwd_find_ich_lpc_bridge(device_get_parent(dev), &id_p) == NULL && ichwd_find_smb_dev(device_get_parent(dev), &id_p) == NULL) return (ENXIO); device_set_desc_copy(dev, id_p->desc); return (0); } static int ichwd_smb_attach(device_t dev) { struct ichwd_softc *sc; struct ichwd_device *id_p; device_t isab, pmdev; device_t smb; uint32_t acpi_base; sc = device_get_softc(dev); smb = ichwd_find_smb_dev(device_get_parent(dev), &id_p); if (smb == NULL) return (ENXIO); sc->ich_version = id_p->ich_version; sc->tco_version = id_p->tco_version; /* Allocate TCO control I/O register space. */ sc->tco_rid = 0; sc->tco_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->tco_rid, RF_ACTIVE | RF_SHAREABLE); if (sc->tco_res == NULL) { device_printf(dev, "unable to reserve TCO registers\n"); return (ENXIO); } /* * Allocate General Control I/O register in PCH * Private Configuration Space (PCR). */ sc->gc_rid = 1; sc->gc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->gc_rid, RF_ACTIVE | RF_SHAREABLE); if (sc->gc_res == NULL) { device_printf(dev, "unable to reserve hidden P2SB registers\n"); return (ENXIO); } /* Get ACPI base address. */ isab = device_get_parent(device_get_parent(dev)); pmdev = pci_find_dbsf(pci_get_domain(isab), pci_get_bus(isab), 31, 2); if (pmdev == NULL) { if (id_p->quirks & PMC_HIDDEN) { /* * Since the PMC is hidden, we take the default value for the * given device, which happens to be the same for the ones we * support. */ acpi_base = ACPI_DEFAULT_CANNON; } else { device_printf(dev, "unable to find Power Management device\n"); return (ENXIO); } } else { acpi_base = pci_read_config(pmdev, ICH_PMBASE, 4) & 0xffffff00; if (acpi_base == 0) { device_printf(dev, "ACPI base address is not set\n"); return (ENXIO); } } /* Allocate SMI control I/O register space. */ sc->smi_rid = 2; sc->smi_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->smi_rid, acpi_base + SMI_BASE, acpi_base + SMI_BASE + SMI_LEN - 1, SMI_LEN, RF_ACTIVE | RF_SHAREABLE); if (sc->smi_res == NULL) { device_printf(dev, "unable to reserve SMI registers\n"); return (ENXIO); } return (0); } static int ichwd_lpc_attach(device_t dev) { struct ichwd_softc *sc; struct ichwd_device *id_p; device_t ich; unsigned int pmbase = 0; sc = device_get_softc(dev); ich = ichwd_find_ich_lpc_bridge(device_get_parent(dev), &id_p); if (ich == NULL) return (ENXIO); sc->ich = ich; sc->ich_version = id_p->ich_version; sc->tco_version = id_p->tco_version; /* get ACPI base address */ pmbase = pci_read_config(ich, ICH_PMBASE, 2) & ICH_PMBASE_MASK; if (pmbase == 0) { device_printf(dev, "ICH PMBASE register is empty\n"); return (ENXIO); } /* allocate I/O register space */ sc->smi_rid = 0; sc->smi_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->smi_rid, pmbase + SMI_BASE, pmbase + SMI_BASE + SMI_LEN - 1, SMI_LEN, RF_ACTIVE | RF_SHAREABLE); if (sc->smi_res == NULL) { device_printf(dev, "unable to reserve SMI registers\n"); return (ENXIO); } sc->tco_rid = 1; sc->tco_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->tco_rid, pmbase + TCO_BASE, pmbase + TCO_BASE + TCO_LEN - 1, TCO_LEN, RF_ACTIVE | RF_SHAREABLE); if (sc->tco_res == NULL) { device_printf(dev, "unable to reserve TCO registers\n"); return (ENXIO); } sc->gcs_rid = 0; if (sc->tco_version >= 2) { sc->gcs_res = bus_alloc_resource_any(ich, SYS_RES_MEMORY, &sc->gcs_rid, RF_ACTIVE|RF_SHAREABLE); if (sc->gcs_res == NULL) { device_printf(dev, "unable to reserve GCS registers\n"); return (ENXIO); } } return (0); } static int ichwd_attach(device_t dev) { struct ichwd_softc *sc; sc = device_get_softc(dev); sc->device = dev; if (ichwd_lpc_attach(dev) != 0 && ichwd_smb_attach(dev) != 0) goto fail; if (ichwd_clear_noreboot(sc) != 0) goto fail; /* * Determine if we are coming up after a watchdog-induced reset. Some * BIOSes may clear this bit at bootup, preventing us from reporting * this case on such systems. We clear this bit in ichwd_sts_reset(). */ if ((ichwd_read_tco_2(sc, TCO2_STS) & TCO_SECOND_TO_STS) != 0) device_printf(dev, "resuming after hardware watchdog timeout\n"); /* reset the watchdog status registers */ ichwd_sts_reset(sc); /* make sure the WDT starts out inactive */ ichwd_tmr_disable(sc); /* register the watchdog event handler */ sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, ichwd_event, sc, 0); /* disable the SMI handler */ sc->smi_enabled = ichwd_smi_is_enabled(sc); ichwd_smi_disable(sc); return (0); fail: sc = device_get_softc(dev); if (sc->tco_res != NULL) bus_release_resource(dev, SYS_RES_IOPORT, sc->tco_rid, sc->tco_res); if (sc->smi_res != NULL) bus_release_resource(dev, SYS_RES_IOPORT, sc->smi_rid, sc->smi_res); if (sc->gcs_res != NULL) bus_release_resource(sc->ich, SYS_RES_MEMORY, sc->gcs_rid, sc->gcs_res); if (sc->gc_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->gc_rid, sc->gc_res); return (ENXIO); } static int ichwd_detach(device_t dev) { struct ichwd_softc *sc; sc = device_get_softc(dev); /* halt the watchdog timer */ if (sc->active) ichwd_tmr_disable(sc); /* enable the SMI handler */ if (sc->smi_enabled != 0) ichwd_smi_enable(sc); /* deregister event handler */ if (sc->ev_tag != NULL) EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); sc->ev_tag = NULL; /* reset the watchdog status registers */ ichwd_sts_reset(sc); /* deallocate I/O register space */ bus_release_resource(dev, SYS_RES_IOPORT, sc->tco_rid, sc->tco_res); bus_release_resource(dev, SYS_RES_IOPORT, sc->smi_rid, sc->smi_res); /* deallocate memory resource */ if (sc->gcs_res) bus_release_resource(sc->ich, SYS_RES_MEMORY, sc->gcs_rid, sc->gcs_res); if (sc->gc_res) bus_release_resource(dev, SYS_RES_MEMORY, sc->gc_rid, sc->gc_res); return (0); } static device_method_t ichwd_methods[] = { DEVMETHOD(device_identify, ichwd_identify), DEVMETHOD(device_probe, ichwd_probe), DEVMETHOD(device_attach, ichwd_attach), DEVMETHOD(device_detach, ichwd_detach), DEVMETHOD(device_shutdown, ichwd_detach), {0,0} }; static driver_t ichwd_driver = { "ichwd", ichwd_methods, sizeof(struct ichwd_softc), }; DRIVER_MODULE(ichwd, isa, ichwd_driver, NULL, NULL); diff --git a/sys/dev/ipmi/ipmi_isa.c b/sys/dev/ipmi/ipmi_isa.c index f873a9ce2ae5..144b5be1ec00 100644 --- a/sys/dev/ipmi/ipmi_isa.c +++ b/sys/dev/ipmi/ipmi_isa.c @@ -1,293 +1,293 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2006 IronPort Systems Inc. * All rights reserved. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LOCAL_MODULE #include #include #else #include #include #endif static void ipmi_isa_identify(driver_t *driver, device_t parent) { struct ipmi_get_info info; uint32_t devid; if (ipmi_smbios_identify(&info) && info.iface_type != SSIF_MODE && device_find_child(parent, "ipmi", -1) == NULL) { /* * XXX: Hack alert. On some broken systems, the IPMI * interface is described via SMBIOS, but the actual * IO resource is in a PCI device BAR, so we have to let * the PCI device attach ipmi instead. In that case don't * create an isa ipmi device. For now we hardcode the list * of bus, device, function tuples. */ - devid = pci_cfgregread(0, 4, 2, PCIR_DEVVENDOR, 4); + devid = pci_cfgregread(0, 0, 4, 2, PCIR_DEVVENDOR, 4); if (devid != 0xffffffff && ipmi_pci_match(devid & 0xffff, devid >> 16) != NULL) return; BUS_ADD_CHILD(parent, 0, "ipmi", -1); } } static int ipmi_isa_probe(device_t dev) { /* * Give other drivers precedence. Unfortunately, this doesn't * work if we have an SMBIOS table that duplicates a PCI device * that's later on the bus than the PCI-ISA bridge. */ if (ipmi_attached) return (ENXIO); /* Skip any PNP devices. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); device_set_desc(dev, "IPMI System Interface"); return (BUS_PROBE_DEFAULT); } static int ipmi_hint_identify(device_t dev, struct ipmi_get_info *info) { const char *mode, *name; int i, unit, val; /* We require at least a "mode" hint. */ name = device_get_name(dev); unit = device_get_unit(dev); if (resource_string_value(name, unit, "mode", &mode) != 0) return (0); /* Set the mode and default I/O resources for each mode. */ bzero(info, sizeof(struct ipmi_get_info)); if (strcasecmp(mode, "KCS") == 0) { info->iface_type = KCS_MODE; info->address = 0xca2; info->io_mode = 1; info->offset = 1; } else if (strcasecmp(mode, "SMIC") == 0) { info->iface_type = SMIC_MODE; info->address = 0xca9; info->io_mode = 1; info->offset = 1; } else if (strcasecmp(mode, "BT") == 0) { info->iface_type = BT_MODE; info->address = 0xe4; info->io_mode = 1; info->offset = 1; } else { device_printf(dev, "Invalid mode %s\n", mode); return (0); } /* * Kill any resources that isahint.c might have setup for us * since it will conflict with how we do resources. */ for (i = 0; i < 2; i++) { bus_delete_resource(dev, SYS_RES_MEMORY, i); bus_delete_resource(dev, SYS_RES_IOPORT, i); } /* Allow the I/O address to be overridden via hints. */ if (resource_int_value(name, unit, "port", &val) == 0 && val != 0) { info->address = val; info->io_mode = 1; } else if (resource_int_value(name, unit, "maddr", &val) == 0 && val != 0) { info->address = val; info->io_mode = 0; } /* Allow the spacing to be overridden. */ if (resource_int_value(name, unit, "spacing", &val) == 0) { switch (val) { case 8: info->offset = 1; break; case 16: info->offset = 2; break; case 32: info->offset = 4; break; default: device_printf(dev, "Invalid register spacing\n"); return (0); } } return (1); } static int ipmi_isa_attach(device_t dev) { struct ipmi_softc *sc = device_get_softc(dev); struct ipmi_get_info info; const char *mode; int count, error, i, type; /* * Pull info out of the SMBIOS table. If that doesn't work, use * hints to enumerate a device. */ if (!ipmi_smbios_identify(&info) && !ipmi_hint_identify(dev, &info)) return (ENXIO); switch (info.iface_type) { case KCS_MODE: count = IPMI_IF_KCS_NRES; mode = "KCS"; break; case SMIC_MODE: count = IPMI_IF_SMIC_NRES; mode = "SMIC"; break; case BT_MODE: count = IPMI_IF_BT_NRES; mode = "BT"; break; default: return (ENXIO); } error = 0; sc->ipmi_dev = dev; device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n", mode, info.io_mode ? "io" : "mem", (uintmax_t)info.address, info.offset, device_get_name(device_get_parent(dev))); if (info.io_mode) type = SYS_RES_IOPORT; else type = SYS_RES_MEMORY; sc->ipmi_io_type = type; sc->ipmi_io_spacing = info.offset; if (info.offset == 1) { sc->ipmi_io_rid = 0; sc->ipmi_io_res[0] = bus_alloc_resource(dev, type, &sc->ipmi_io_rid, info.address, info.address + count - 1, count, RF_ACTIVE); if (sc->ipmi_io_res[0] == NULL) { device_printf(dev, "couldn't configure I/O resource\n"); return (ENXIO); } } else { for (i = 0; i < count; i++) { sc->ipmi_io_rid = i; sc->ipmi_io_res[i] = bus_alloc_resource(dev, type, &sc->ipmi_io_rid, info.address + i * info.offset, info.address + i * info.offset, 1, RF_ACTIVE); if (sc->ipmi_io_res[i] == NULL) { device_printf(dev, "couldn't configure I/O resource\n"); error = ENXIO; sc->ipmi_io_rid = 0; goto bad; } } sc->ipmi_io_rid = 0; } if (info.irq != 0) { sc->ipmi_irq_rid = 0; sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->ipmi_irq_rid, info.irq, info.irq, 1, RF_SHAREABLE | RF_ACTIVE); } error = ENXIO; switch (info.iface_type) { case KCS_MODE: error = ipmi_kcs_attach(sc); break; case SMIC_MODE: error = ipmi_smic_attach(sc); break; case BT_MODE: error = ipmi_bt_attach(sc); break; } if (error) goto bad; error = ipmi_attach(dev); if (error) goto bad; return (0); bad: ipmi_release_resources(dev); return (error); } static device_method_t ipmi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ipmi_isa_identify), DEVMETHOD(device_probe, ipmi_isa_probe), DEVMETHOD(device_attach, ipmi_isa_attach), DEVMETHOD(device_detach, ipmi_detach), { 0, 0 } }; static driver_t ipmi_isa_driver = { "ipmi", ipmi_methods, sizeof(struct ipmi_softc), }; DRIVER_MODULE(ipmi_isa, isa, ipmi_isa_driver, 0, 0); #ifdef ARCH_MAY_USE_EFI MODULE_DEPEND(ipmi_isa, efirt, 1, 1, 1); #endif diff --git a/sys/dev/pci/pci_subr.c b/sys/dev/pci/pci_subr.c index c869959da73d..71485875ebe3 100644 --- a/sys/dev/pci/pci_subr.c +++ b/sys/dev/pci/pci_subr.c @@ -1,384 +1,384 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Hudson River Trading LLC * Written by: John H. Baldwin * All rights reserved. * * 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 /* * Support APIs for Host to PCI bridge drivers and drivers that * provide PCI domains. */ #include #include #include #include #include #include #include #include /* * Try to read the bus number of a host-PCI bridge using appropriate config * registers. */ int host_pcib_get_busno(pci_read_config_fn read_config, int bus, int slot, int func, uint8_t *busnum) { uint32_t id; - id = read_config(bus, slot, func, PCIR_DEVVENDOR, 4); + id = read_config(0, bus, slot, func, PCIR_DEVVENDOR, 4); if (id == 0xffffffff) return (0); switch (id) { case 0x12258086: /* Intel 824?? */ /* XXX This is a guess */ - /* *busnum = read_config(bus, slot, func, 0x41, 1); */ + /* *busnum = read_config(0, bus, slot, func, 0x41, 1); */ *busnum = bus; break; case 0x84c48086: /* Intel 82454KX/GX (Orion) */ - *busnum = read_config(bus, slot, func, 0x4a, 1); + *busnum = read_config(0, bus, slot, func, 0x4a, 1); break; case 0x84ca8086: /* * For the 450nx chipset, there is a whole bundle of * things pretending to be host bridges. The MIOC will * be seen first and isn't really a pci bridge (the * actual buses are attached to the PXB's). We need to * read the registers of the MIOC to figure out the * bus numbers for the PXB channels. * * Since the MIOC doesn't have a pci bus attached, we * pretend it wasn't there. */ return (0); case 0x84cb8086: switch (slot) { case 0x12: /* Intel 82454NX PXB#0, Bus#A */ - *busnum = read_config(bus, 0x10, func, 0xd0, 1); + *busnum = read_config(0, bus, 0x10, func, 0xd0, 1); break; case 0x13: /* Intel 82454NX PXB#0, Bus#B */ - *busnum = read_config(bus, 0x10, func, 0xd1, 1) + 1; + *busnum = read_config(0, bus, 0x10, func, 0xd1, 1) + 1; break; case 0x14: /* Intel 82454NX PXB#1, Bus#A */ - *busnum = read_config(bus, 0x10, func, 0xd3, 1); + *busnum = read_config(0, bus, 0x10, func, 0xd3, 1); break; case 0x15: /* Intel 82454NX PXB#1, Bus#B */ - *busnum = read_config(bus, 0x10, func, 0xd4, 1) + 1; + *busnum = read_config(0, bus, 0x10, func, 0xd4, 1) + 1; break; } break; /* ServerWorks -- vendor 0x1166 */ case 0x00051166: case 0x00061166: case 0x00081166: case 0x00091166: case 0x00101166: case 0x00111166: case 0x00171166: case 0x01011166: case 0x010f1014: case 0x01101166: case 0x02011166: case 0x02251166: case 0x03021014: - *busnum = read_config(bus, slot, func, 0x44, 1); + *busnum = read_config(0, bus, slot, func, 0x44, 1); break; /* Compaq/HP -- vendor 0x0e11 */ case 0x60100e11: - *busnum = read_config(bus, slot, func, 0xc8, 1); + *busnum = read_config(0, bus, slot, func, 0xc8, 1); break; default: /* Don't know how to read bus number. */ return 0; } return 1; } #ifdef NEW_PCIB /* * Return a pointer to a pretty name for a PCI device. If the device * has a driver attached, the device's name is used, otherwise a name * is generated from the device's PCI address. */ const char * pcib_child_name(device_t child) { static char buf[64]; if (device_get_nameunit(child) != NULL) return (device_get_nameunit(child)); snprintf(buf, sizeof(buf), "pci%d:%d:%d:%d", pci_get_domain(child), pci_get_bus(child), pci_get_slot(child), pci_get_function(child)); return (buf); } /* * Some Host-PCI bridge drivers know which resource ranges they can * decode and should only allocate subranges to child PCI devices. * This API provides a way to manage this. The bridge driver should * initialize this structure during attach and call * pcib_host_res_decodes() on each resource range it decodes. It can * then use pcib_host_res_alloc() and pcib_host_res_adjust() as helper * routines for BUS_ALLOC_RESOURCE() and BUS_ADJUST_RESOURCE(). This * API assumes that resources for any decoded ranges can be safely * allocated from the parent via bus_generic_alloc_resource(). */ int pcib_host_res_init(device_t pcib, struct pcib_host_resources *hr) { hr->hr_pcib = pcib; resource_list_init(&hr->hr_rl); return (0); } int pcib_host_res_free(device_t pcib, struct pcib_host_resources *hr) { resource_list_free(&hr->hr_rl); return (0); } int pcib_host_res_decodes(struct pcib_host_resources *hr, int type, rman_res_t start, rman_res_t end, u_int flags) { struct resource_list_entry *rle; int rid; if (bootverbose) device_printf(hr->hr_pcib, "decoding %d %srange %#jx-%#jx\n", type, flags & RF_PREFETCHABLE ? "prefetchable ": "", start, end); rid = resource_list_add_next(&hr->hr_rl, type, start, end, end - start + 1); if (flags & RF_PREFETCHABLE) { KASSERT(type == SYS_RES_MEMORY, ("only memory is prefetchable")); rle = resource_list_find(&hr->hr_rl, type, rid); rle->flags = RLE_PREFETCH; } return (0); } struct resource * pcib_host_res_alloc(struct pcib_host_resources *hr, device_t dev, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource_list_entry *rle; struct resource *r; rman_res_t new_start, new_end; if (flags & RF_PREFETCHABLE) KASSERT(type == SYS_RES_MEMORY, ("only memory is prefetchable")); rle = resource_list_find(&hr->hr_rl, type, 0); if (rle == NULL) { /* * No decoding ranges for this resource type, just pass * the request up to the parent. */ return (bus_generic_alloc_resource(hr->hr_pcib, dev, type, rid, start, end, count, flags)); } restart: /* Try to allocate from each decoded range. */ for (; rle != NULL; rle = STAILQ_NEXT(rle, link)) { if (rle->type != type) continue; if (((flags & RF_PREFETCHABLE) != 0) != ((rle->flags & RLE_PREFETCH) != 0)) continue; new_start = ummax(start, rle->start); new_end = ummin(end, rle->end); if (new_start > new_end || new_start + count - 1 > new_end || new_start + count < new_start) continue; r = bus_generic_alloc_resource(hr->hr_pcib, dev, type, rid, new_start, new_end, count, flags); if (r != NULL) { if (bootverbose) device_printf(hr->hr_pcib, "allocated type %d (%#jx-%#jx) for rid %x of %s\n", type, rman_get_start(r), rman_get_end(r), *rid, pcib_child_name(dev)); return (r); } } /* * If we failed to find a prefetch range for a memory * resource, try again without prefetch. */ if (flags & RF_PREFETCHABLE) { flags &= ~RF_PREFETCHABLE; rle = resource_list_find(&hr->hr_rl, type, 0); goto restart; } return (NULL); } int pcib_host_res_adjust(struct pcib_host_resources *hr, device_t dev, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct resource_list_entry *rle; rle = resource_list_find(&hr->hr_rl, type, 0); if (rle == NULL) { /* * No decoding ranges for this resource type, just pass * the request up to the parent. */ return (bus_generic_adjust_resource(hr->hr_pcib, dev, type, r, start, end)); } /* Only allow adjustments that stay within a decoded range. */ for (; rle != NULL; rle = STAILQ_NEXT(rle, link)) { if (rle->start <= start && rle->end >= end) return (bus_generic_adjust_resource(hr->hr_pcib, dev, type, r, start, end)); } return (ERANGE); } #ifdef PCI_RES_BUS struct pci_domain { int pd_domain; struct rman pd_bus_rman; TAILQ_ENTRY(pci_domain) pd_link; }; static TAILQ_HEAD(, pci_domain) domains = TAILQ_HEAD_INITIALIZER(domains); /* * Each PCI domain maintains its own resource manager for PCI bus * numbers in that domain. Domain objects are created on first use. * Host to PCI bridge drivers and PCI-PCI bridge drivers should * allocate their bus ranges from their domain. */ static struct pci_domain * pci_find_domain(int domain) { struct pci_domain *d; char buf[64]; int error; TAILQ_FOREACH(d, &domains, pd_link) { if (d->pd_domain == domain) return (d); } snprintf(buf, sizeof(buf), "PCI domain %d bus numbers", domain); d = malloc(sizeof(*d) + strlen(buf) + 1, M_DEVBUF, M_WAITOK | M_ZERO); d->pd_domain = domain; d->pd_bus_rman.rm_start = 0; d->pd_bus_rman.rm_end = PCI_BUSMAX; d->pd_bus_rman.rm_type = RMAN_ARRAY; strcpy((char *)(d + 1), buf); d->pd_bus_rman.rm_descr = (char *)(d + 1); error = rman_init(&d->pd_bus_rman); if (error == 0) error = rman_manage_region(&d->pd_bus_rman, 0, PCI_BUSMAX); if (error) panic("Failed to initialize PCI domain %d rman", domain); TAILQ_INSERT_TAIL(&domains, d, pd_link); return (d); } struct resource * pci_domain_alloc_bus(int domain, device_t dev, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct pci_domain *d; struct resource *res; if (domain < 0 || domain > PCI_DOMAINMAX) return (NULL); d = pci_find_domain(domain); res = rman_reserve_resource(&d->pd_bus_rman, start, end, count, flags, dev); if (res == NULL) return (NULL); rman_set_rid(res, *rid); return (res); } int pci_domain_adjust_bus(int domain, device_t dev, struct resource *r, rman_res_t start, rman_res_t end) { #ifdef INVARIANTS struct pci_domain *d; #endif if (domain < 0 || domain > PCI_DOMAINMAX) return (EINVAL); #ifdef INVARIANTS d = pci_find_domain(domain); KASSERT(rman_is_region_manager(r, &d->pd_bus_rman), ("bad resource")); #endif return (rman_adjust_resource(r, start, end)); } int pci_domain_release_bus(int domain, device_t dev, int rid, struct resource *r) { #ifdef INVARIANTS struct pci_domain *d; #endif if (domain < 0 || domain > PCI_DOMAINMAX) return (EINVAL); #ifdef INVARIANTS d = pci_find_domain(domain); KASSERT(rman_is_region_manager(r, &d->pd_bus_rman), ("bad resource")); #endif return (rman_release_resource(r)); } #endif /* PCI_RES_BUS */ #endif /* NEW_PCIB */ diff --git a/sys/dev/pci/pcib_private.h b/sys/dev/pci/pcib_private.h index e1e15caa17e4..cf689026a033 100644 --- a/sys/dev/pci/pcib_private.h +++ b/sys/dev/pci/pcib_private.h @@ -1,204 +1,205 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1994,1995 Stefan Esser, Wolfgang StanglMeier * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. * * 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. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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. */ #ifndef __PCIB_PRIVATE_H__ #define __PCIB_PRIVATE_H__ #include #ifdef NEW_PCIB /* * Data structure and routines that Host to PCI bridge drivers can use * to restrict allocations for child devices to ranges decoded by the * bridge. */ struct pcib_host_resources { device_t hr_pcib; struct resource_list hr_rl; }; int pcib_host_res_init(device_t pcib, struct pcib_host_resources *hr); int pcib_host_res_free(device_t pcib, struct pcib_host_resources *hr); int pcib_host_res_decodes(struct pcib_host_resources *hr, int type, rman_res_t start, rman_res_t end, u_int flags); struct resource *pcib_host_res_alloc(struct pcib_host_resources *hr, device_t dev, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); int pcib_host_res_adjust(struct pcib_host_resources *hr, device_t dev, int type, struct resource *r, rman_res_t start, rman_res_t end); #endif /* * Export portions of generic PCI:PCI bridge support so that it can be * used by subclasses. */ DECLARE_CLASS(pcib_driver); #ifdef NEW_PCIB #define WIN_IO 0x1 #define WIN_MEM 0x2 #define WIN_PMEM 0x4 struct pcib_window { pci_addr_t base; /* base address */ pci_addr_t limit; /* topmost address */ struct rman rman; struct resource **res; int count; /* size of 'res' array */ int reg; /* resource id from parent */ int valid; int mask; /* WIN_* bitmask of this window */ int step; /* log_2 of window granularity */ const char *name; }; #endif struct pcib_secbus { u_int sec; u_int sub; #if defined(NEW_PCIB) && defined(PCI_RES_BUS) device_t dev; struct rman rman; struct resource *res; const char *name; int sub_reg; #endif }; /* * Bridge-specific data. */ struct pcib_softc { device_t dev; device_t child; uint32_t flags; /* flags */ #define PCIB_SUBTRACTIVE 0x1 #define PCIB_DISABLE_MSI 0x2 #define PCIB_DISABLE_MSIX 0x4 #define PCIB_ENABLE_ARI 0x8 #define PCIB_HOTPLUG 0x10 #define PCIB_HOTPLUG_CMD_PENDING 0x20 #define PCIB_DETACH_PENDING 0x40 #define PCIB_DETACHING 0x80 u_int domain; /* domain number */ u_int pribus; /* primary bus number */ struct pcib_secbus bus; /* secondary bus numbers */ #ifdef NEW_PCIB struct pcib_window io; /* I/O port window */ struct pcib_window mem; /* memory window */ struct pcib_window pmem; /* prefetchable memory window */ #else pci_addr_t pmembase; /* base address of prefetchable memory */ pci_addr_t pmemlimit; /* topmost address of prefetchable memory */ pci_addr_t membase; /* base address of memory window */ pci_addr_t memlimit; /* topmost address of memory window */ uint32_t iobase; /* base address of port window */ uint32_t iolimit; /* topmost address of port window */ #endif uint16_t bridgectl; /* bridge control register */ uint16_t pcie_link_sta; uint16_t pcie_slot_sta; uint32_t pcie_slot_cap; struct resource *pcie_mem; struct resource *pcie_irq; void *pcie_ihand; struct task pcie_hp_task; struct timeout_task pcie_ab_task; struct timeout_task pcie_cc_task; struct timeout_task pcie_dll_task; struct mtx *pcie_hp_lock; }; #define PCIB_HP_LOCK(sc) mtx_lock((sc)->pcie_hp_lock) #define PCIB_HP_UNLOCK(sc) mtx_unlock((sc)->pcie_hp_lock) #define PCIB_HP_LOCK_ASSERT(sc) mtx_assert((sc)->pcie_hp_lock, MA_OWNED) #define PCIB_SUPPORTED_ARI_VER 1 -typedef uint32_t pci_read_config_fn(int b, int s, int f, int reg, int width); +typedef uint32_t pci_read_config_fn(int d, int b, int s, int f, int reg, + int width); int host_pcib_get_busno(pci_read_config_fn read_config, int bus, int slot, int func, uint8_t *busnum); #if defined(NEW_PCIB) && defined(PCI_RES_BUS) struct resource *pci_domain_alloc_bus(int domain, device_t dev, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); int pci_domain_adjust_bus(int domain, device_t dev, struct resource *r, rman_res_t start, rman_res_t end); int pci_domain_release_bus(int domain, device_t dev, int rid, struct resource *r); struct resource *pcib_alloc_subbus(struct pcib_secbus *bus, device_t child, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); void pcib_free_secbus(device_t dev, struct pcib_secbus *bus); void pcib_setup_secbus(device_t dev, struct pcib_secbus *bus, int min_count); #endif int pcib_attach(device_t dev); int pcib_attach_child(device_t dev); void pcib_attach_common(device_t dev); void pcib_bridge_init(device_t dev); #ifdef NEW_PCIB const char *pcib_child_name(device_t child); #endif int pcib_child_present(device_t dev, device_t child); int pcib_detach(device_t dev); int pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); int pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value); struct resource *pcib_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); #ifdef NEW_PCIB int pcib_adjust_resource(device_t bus, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end); int pcib_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r); #endif int pcib_maxslots(device_t dev); int pcib_maxfuncs(device_t dev); int pcib_route_interrupt(device_t pcib, device_t dev, int pin); int pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs); int pcib_release_msi(device_t pcib, device_t dev, int count, int *irqs); int pcib_alloc_msix(device_t pcib, device_t dev, int *irq); int pcib_release_msix(device_t pcib, device_t dev, int irq); int pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data); int pcib_get_id(device_t pcib, device_t dev, enum pci_id_type type, uintptr_t *id); void pcib_decode_rid(device_t pcib, uint16_t rid, int *bus, int *slot, int *func); int pcib_request_feature(device_t dev, enum pci_feature feature); int pcib_request_feature_allow(device_t pcib, device_t dev, enum pci_feature feature); #endif diff --git a/sys/i386/pci/pci_cfgreg.c b/sys/i386/pci/pci_cfgreg.c index a838f0a276f3..a512e3ea2c66 100644 --- a/sys/i386/pci/pci_cfgreg.c +++ b/sys/i386/pci/pci_cfgreg.c @@ -1,634 +1,642 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, Stefan Esser * Copyright (c) 2000, Michael Smith * Copyright (c) 2000, BSDi * Copyright (c) 2004, Scott Long * All rights reserved. * * 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 unmodified, 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 ``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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PRVERB(a) do { \ if (bootverbose) \ printf a ; \ } while(0) #define PCIE_CACHE 8 struct pcie_cfg_elem { TAILQ_ENTRY(pcie_cfg_elem) elem; vm_offset_t vapage; vm_paddr_t papage; }; SYSCTL_DECL(_hw_pci); static TAILQ_HEAD(pcie_cfg_list, pcie_cfg_elem) pcie_list[MAXCPU]; static uint64_t pcie_base; static int pcie_minbus, pcie_maxbus; static uint32_t pcie_badslots; int cfgmech; static int devmax; static struct mtx pcicfg_mtx; static int mcfg_enable = 1; SYSCTL_INT(_hw_pci, OID_AUTO, mcfg, CTLFLAG_RDTUN, &mcfg_enable, 0, "Enable support for PCI-e memory mapped config access"); -static uint32_t pci_docfgregread(int bus, int slot, int func, int reg, - int bytes); +static uint32_t pci_docfgregread(int domain, int bus, int slot, int func, + int reg, int bytes); static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes); static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes); static int pcireg_cfgopen(void); -static int pciereg_cfgread(int bus, unsigned slot, unsigned func, - unsigned reg, unsigned bytes); -static void pciereg_cfgwrite(int bus, unsigned slot, unsigned func, - unsigned reg, int data, unsigned bytes); +static int pciereg_cfgread(int domain, int bus, unsigned slot, + unsigned func, unsigned reg, unsigned bytes); +static void pciereg_cfgwrite(int domain, int bus, unsigned slot, + unsigned func, unsigned reg, int data, unsigned bytes); /* * Some BIOS writers seem to want to ignore the spec and put * 0 in the intline rather than 255 to indicate none. Some use * numbers in the range 128-254 to indicate something strange and * apparently undocumented anywhere. Assume these are completely bogus * and map them to 255, which means "none". */ static __inline int pci_i386_map_intline(int line) { if (line == 0 || line >= 128) return (PCI_INVALID_IRQ); return (line); } static u_int16_t pcibios_get_version(void) { struct bios_regs args; if (PCIbios.ventry == 0) { PRVERB(("pcibios: No call entry point\n")); return (0); } args.eax = PCIBIOS_BIOS_PRESENT; if (bios32(&args, PCIbios.ventry, GSEL(GCODE_SEL, SEL_KPL))) { PRVERB(("pcibios: BIOS_PRESENT call failed\n")); return (0); } if (args.edx != 0x20494350) { PRVERB(("pcibios: BIOS_PRESENT didn't return 'PCI ' in edx\n")); return (0); } return (args.ebx & 0xffff); } /* * Initialise access to PCI configuration space */ int pci_cfgregopen(void) { uint16_t v; static int opened = 0; if (opened) return (1); if (cfgmech == CFGMECH_NONE && pcireg_cfgopen() == 0) return (0); v = pcibios_get_version(); if (v > 0) PRVERB(("pcibios: BIOS version %x.%02x\n", (v & 0xff00) >> 8, v & 0xff)); mtx_init(&pcicfg_mtx, "pcicfg", NULL, MTX_SPIN); opened = 1; /* $PIR requires PCI BIOS 2.10 or greater. */ if (v >= 0x0210) pci_pir_open(); return (1); } static uint32_t -pci_docfgregread(int bus, int slot, int func, int reg, int bytes) +pci_docfgregread(int domain, int bus, int slot, int func, int reg, int bytes) { + if (domain == 0 && bus == 0 && (1 << slot & pcie_badslots) != 0) + return (pcireg_cfgread(bus, slot, func, reg, bytes)); if (cfgmech == CFGMECH_PCIE && - (bus >= pcie_minbus && bus <= pcie_maxbus) && - (bus != 0 || !(1 << slot & pcie_badslots))) - return (pciereg_cfgread(bus, slot, func, reg, bytes)); - else + (bus >= pcie_minbus && bus <= pcie_maxbus)) + return (pciereg_cfgread(domain, bus, slot, func, reg, bytes)); + else if (domain == 0) return (pcireg_cfgread(bus, slot, func, reg, bytes)); + else + return (-1); } /* * Read configuration space register */ u_int32_t -pci_cfgregread(int bus, int slot, int func, int reg, int bytes) +pci_cfgregread(int domain, int bus, int slot, int func, int reg, int bytes) { uint32_t line; /* * Some BIOS writers seem to want to ignore the spec and put * 0 in the intline rather than 255 to indicate none. The rest of * the code uses 255 as an invalid IRQ. */ if (reg == PCIR_INTLINE && bytes == 1) { - line = pci_docfgregread(bus, slot, func, PCIR_INTLINE, 1); + line = pci_docfgregread(domain, bus, slot, func, PCIR_INTLINE, + 1); return (pci_i386_map_intline(line)); } - return (pci_docfgregread(bus, slot, func, reg, bytes)); + return (pci_docfgregread(domain, bus, slot, func, reg, bytes)); } /* * Write configuration space register */ void -pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes) +pci_cfgregwrite(int domain, int bus, int slot, int func, int reg, uint32_t data, + int bytes) { + if (domain == 0 && bus == 0 && (1 << slot & pcie_badslots) != 0) { + pcireg_cfgwrite(bus, slot, func, reg, data, bytes); + return; + } if (cfgmech == CFGMECH_PCIE && - (bus >= pcie_minbus && bus <= pcie_maxbus) && - (bus != 0 || !(1 << slot & pcie_badslots))) - pciereg_cfgwrite(bus, slot, func, reg, data, bytes); - else + (bus >= pcie_minbus && bus <= pcie_maxbus)) + pciereg_cfgwrite(domain, bus, slot, func, reg, data, bytes); + else if (domain == 0) pcireg_cfgwrite(bus, slot, func, reg, data, bytes); } /* * Configuration space access using direct register operations */ /* enable configuration space accesses and return data port address */ static int pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes) { int dataport = 0; if (bus <= PCI_BUSMAX && slot < devmax && func <= PCI_FUNCMAX && (unsigned)reg <= PCI_REGMAX && bytes != 3 && (unsigned)bytes <= 4 && (reg & (bytes - 1)) == 0) { switch (cfgmech) { case CFGMECH_PCIE: case CFGMECH_1: outl(CONF1_ADDR_PORT, (1U << 31) | (bus << 16) | (slot << 11) | (func << 8) | (reg & ~0x03)); dataport = CONF1_DATA_PORT + (reg & 0x03); break; case CFGMECH_2: outb(CONF2_ENABLE_PORT, 0xf0 | (func << 1)); outb(CONF2_FORWARD_PORT, bus); dataport = 0xc000 | (slot << 8) | reg; break; } } return (dataport); } /* disable configuration space accesses */ static void pci_cfgdisable(void) { switch (cfgmech) { case CFGMECH_PCIE: case CFGMECH_1: /* * Do nothing for the config mechanism 1 case. * Writing a 0 to the address port can apparently * confuse some bridges and cause spurious * access failures. */ break; case CFGMECH_2: outb(CONF2_ENABLE_PORT, 0); break; } } static int pcireg_cfgread(int bus, int slot, int func, int reg, int bytes) { int data = -1; int port; mtx_lock_spin(&pcicfg_mtx); port = pci_cfgenable(bus, slot, func, reg, bytes); if (port != 0) { switch (bytes) { case 1: data = inb(port); break; case 2: data = inw(port); break; case 4: data = inl(port); break; } pci_cfgdisable(); } mtx_unlock_spin(&pcicfg_mtx); return (data); } static void pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes) { int port; mtx_lock_spin(&pcicfg_mtx); port = pci_cfgenable(bus, slot, func, reg, bytes); if (port != 0) { switch (bytes) { case 1: outb(port, data); break; case 2: outw(port, data); break; case 4: outl(port, data); break; } pci_cfgdisable(); } mtx_unlock_spin(&pcicfg_mtx); } /* check whether the configuration mechanism has been correctly identified */ static int pci_cfgcheck(int maxdev) { uint32_t id, class; uint8_t header; uint8_t device; int port; if (bootverbose) printf("pci_cfgcheck:\tdevice "); for (device = 0; device < maxdev; device++) { if (bootverbose) printf("%d ", device); port = pci_cfgenable(0, device, 0, 0, 4); id = inl(port); if (id == 0 || id == 0xffffffff) continue; port = pci_cfgenable(0, device, 0, 8, 4); class = inl(port) >> 8; if (bootverbose) printf("[class=%06x] ", class); if (class == 0 || (class & 0xf870ff) != 0) continue; port = pci_cfgenable(0, device, 0, 14, 1); header = inb(port); if (bootverbose) printf("[hdr=%02x] ", header); if ((header & 0x7e) != 0) continue; if (bootverbose) printf("is there (id=%08x)\n", id); pci_cfgdisable(); return (1); } if (bootverbose) printf("-- nothing found\n"); pci_cfgdisable(); return (0); } static int pcireg_cfgopen(void) { uint32_t mode1res, oldval1; uint8_t mode2res, oldval2; /* Check for type #1 first. */ oldval1 = inl(CONF1_ADDR_PORT); if (bootverbose) { printf("pci_open(1):\tmode 1 addr port (0x0cf8) is 0x%08x\n", oldval1); } cfgmech = CFGMECH_1; devmax = 32; outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK); DELAY(1); mode1res = inl(CONF1_ADDR_PORT); outl(CONF1_ADDR_PORT, oldval1); if (bootverbose) printf("pci_open(1a):\tmode1res=0x%08x (0x%08lx)\n", mode1res, CONF1_ENABLE_CHK); if (mode1res) { if (pci_cfgcheck(32)) return (cfgmech); } outl(CONF1_ADDR_PORT, CONF1_ENABLE_CHK1); mode1res = inl(CONF1_ADDR_PORT); outl(CONF1_ADDR_PORT, oldval1); if (bootverbose) printf("pci_open(1b):\tmode1res=0x%08x (0x%08lx)\n", mode1res, CONF1_ENABLE_CHK1); if ((mode1res & CONF1_ENABLE_MSK1) == CONF1_ENABLE_RES1) { if (pci_cfgcheck(32)) return (cfgmech); } /* Type #1 didn't work, so try type #2. */ oldval2 = inb(CONF2_ENABLE_PORT); if (bootverbose) { printf("pci_open(2):\tmode 2 enable port (0x0cf8) is 0x%02x\n", oldval2); } if ((oldval2 & 0xf0) == 0) { cfgmech = CFGMECH_2; devmax = 16; outb(CONF2_ENABLE_PORT, CONF2_ENABLE_CHK); mode2res = inb(CONF2_ENABLE_PORT); outb(CONF2_ENABLE_PORT, oldval2); if (bootverbose) printf("pci_open(2a):\tmode2res=0x%02x (0x%02x)\n", mode2res, CONF2_ENABLE_CHK); if (mode2res == CONF2_ENABLE_RES) { if (bootverbose) printf("pci_open(2a):\tnow trying mechanism 2\n"); if (pci_cfgcheck(16)) return (cfgmech); } } /* Nothing worked, so punt. */ cfgmech = CFGMECH_NONE; devmax = 0; return (cfgmech); } int pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus) { struct pcie_cfg_list *pcielist; struct pcie_cfg_elem *pcie_array, *elem; #ifdef SMP struct pcpu *pc; #endif vm_offset_t va; uint32_t val1, val2; int i, slot; if (!mcfg_enable) return (0); if (minbus != 0) return (0); if (!pae_mode && base >= 0x100000000) { if (bootverbose) printf( "PCI: Memory Mapped PCI configuration area base 0x%jx too high\n", (uintmax_t)base); return (0); } if (bootverbose) printf("PCIe: Memory Mapped configuration base @ 0x%jx\n", (uintmax_t)base); #ifdef SMP STAILQ_FOREACH(pc, &cpuhead, pc_allcpu) #endif { pcie_array = malloc(sizeof(struct pcie_cfg_elem) * PCIE_CACHE, M_DEVBUF, M_NOWAIT); if (pcie_array == NULL) return (0); va = kva_alloc(PCIE_CACHE * PAGE_SIZE); if (va == 0) { free(pcie_array, M_DEVBUF); return (0); } #ifdef SMP pcielist = &pcie_list[pc->pc_cpuid]; #else pcielist = &pcie_list[0]; #endif TAILQ_INIT(pcielist); for (i = 0; i < PCIE_CACHE; i++) { elem = &pcie_array[i]; elem->vapage = va + (i * PAGE_SIZE); elem->papage = 0; TAILQ_INSERT_HEAD(pcielist, elem, elem); } } pcie_base = base; pcie_minbus = minbus; pcie_maxbus = maxbus; cfgmech = CFGMECH_PCIE; devmax = 32; /* * On some AMD systems, some of the devices on bus 0 are * inaccessible using memory-mapped PCI config access. Walk * bus 0 looking for such devices. For these devices, we will * fall back to using type 1 config access instead. */ if (pci_cfgregopen() != 0) { for (slot = 0; slot <= PCI_SLOTMAX; slot++) { val1 = pcireg_cfgread(0, slot, 0, 0, 4); if (val1 == 0xffffffff) continue; - val2 = pciereg_cfgread(0, slot, 0, 0, 4); + val2 = pciereg_cfgread(0, 0, slot, 0, 0, 4); if (val2 != val1) pcie_badslots |= (1 << slot); } } return (1); } #define PCIE_PADDR(base, reg, bus, slot, func) \ ((base) + \ ((((bus) & 0xff) << 20) | \ (((slot) & 0x1f) << 15) | \ (((func) & 0x7) << 12) | \ ((reg) & 0xfff))) static __inline vm_offset_t pciereg_findaddr(int bus, unsigned slot, unsigned func, unsigned reg) { struct pcie_cfg_list *pcielist; struct pcie_cfg_elem *elem; vm_paddr_t pa, papage; pa = PCIE_PADDR(pcie_base, reg, bus, slot, func); papage = pa & ~PAGE_MASK; /* * Find an element in the cache that matches the physical page desired, * or create a new mapping from the least recently used element. * A very simple LRU algorithm is used here, does it need to be more * efficient? */ pcielist = &pcie_list[PCPU_GET(cpuid)]; TAILQ_FOREACH(elem, pcielist, elem) { if (elem->papage == papage) break; } if (elem == NULL) { elem = TAILQ_LAST(pcielist, pcie_cfg_list); if (elem->papage != 0) { pmap_kremove(elem->vapage); invlpg(elem->vapage); } pmap_kenter(elem->vapage, papage); elem->papage = papage; } if (elem != TAILQ_FIRST(pcielist)) { TAILQ_REMOVE(pcielist, elem, elem); TAILQ_INSERT_HEAD(pcielist, elem, elem); } return (elem->vapage | (pa & PAGE_MASK)); } /* * AMD BIOS And Kernel Developer's Guides for CPU families starting with 10h * have a requirement that all accesses to the memory mapped PCI configuration * space are done using AX class of registers. * Since other vendors do not currently have any contradicting requirements * the AMD access pattern is applied universally. */ static int -pciereg_cfgread(int bus, unsigned slot, unsigned func, unsigned reg, +pciereg_cfgread(int domain, int bus, unsigned slot, unsigned func, unsigned reg, unsigned bytes) { vm_offset_t va; int data = -1; - if (bus < pcie_minbus || bus > pcie_maxbus || slot > PCI_SLOTMAX || - func > PCI_FUNCMAX || reg > PCIE_REGMAX) + if (domain != 0 || bus < pcie_minbus || bus > pcie_maxbus || + slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > PCIE_REGMAX) return (-1); critical_enter(); va = pciereg_findaddr(bus, slot, func, reg); switch (bytes) { case 4: __asm("movl %1, %0" : "=a" (data) : "m" (*(volatile uint32_t *)va)); break; case 2: __asm("movzwl %1, %0" : "=a" (data) : "m" (*(volatile uint16_t *)va)); break; case 1: __asm("movzbl %1, %0" : "=a" (data) : "m" (*(volatile uint8_t *)va)); break; } critical_exit(); return (data); } static void -pciereg_cfgwrite(int bus, unsigned slot, unsigned func, unsigned reg, int data, - unsigned bytes) +pciereg_cfgwrite(int domain, int bus, unsigned slot, unsigned func, + unsigned reg, int data, unsigned bytes) { vm_offset_t va; - if (bus < pcie_minbus || bus > pcie_maxbus || slot > PCI_SLOTMAX || - func > PCI_FUNCMAX || reg > PCIE_REGMAX) + if (domain != 0 || bus < pcie_minbus || bus > pcie_maxbus || + slot > PCI_SLOTMAX || func > PCI_FUNCMAX || reg > PCIE_REGMAX) return; critical_enter(); va = pciereg_findaddr(bus, slot, func, reg); switch (bytes) { case 4: __asm("movl %1, %0" : "=m" (*(volatile uint32_t *)va) : "a" (data)); break; case 2: __asm("movw %1, %0" : "=m" (*(volatile uint16_t *)va) : "a" ((uint16_t)data)); break; case 1: __asm("movb %1, %0" : "=m" (*(volatile uint8_t *)va) : "a" ((uint8_t)data)); break; } critical_exit(); } diff --git a/sys/i386/pci/pci_pir.c b/sys/i386/pci/pci_pir.c index fe21b5faef87..d3ae100a43d0 100644 --- a/sys/i386/pci/pci_pir.c +++ b/sys/i386/pci/pci_pir.c @@ -1,741 +1,741 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, Stefan Esser * Copyright (c) 2000, Michael Smith * Copyright (c) 2000, BSDi * All rights reserved. * Copyright (c) 2004, John Baldwin * * 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 unmodified, 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 ``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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NUM_ISA_INTERRUPTS 16 /* * A link device. Loosely based on the ACPI PCI link device. This doesn't * try to support priorities for different ISA interrupts. */ struct pci_link { TAILQ_ENTRY(pci_link) pl_links; uint8_t pl_id; uint8_t pl_irq; uint16_t pl_irqmask; int pl_references; int pl_routed; }; struct pci_link_lookup { struct pci_link **pci_link_ptr; int bus; int device; int pin; }; struct pci_dev_lookup { uint8_t link; int bus; int device; int pin; }; typedef void pir_entry_handler(struct PIR_entry *entry, struct PIR_intpin* intpin, void *arg); static void pci_print_irqmask(u_int16_t irqs); static int pci_pir_biosroute(int bus, int device, int func, int pin, int irq); static int pci_pir_choose_irq(struct pci_link *pci_link, int irqmask); static void pci_pir_create_links(struct PIR_entry *entry, struct PIR_intpin *intpin, void *arg); static void pci_pir_dump_links(void); static struct pci_link *pci_pir_find_link(uint8_t link_id); static void pci_pir_find_link_handler(struct PIR_entry *entry, struct PIR_intpin *intpin, void *arg); static void pci_pir_initial_irqs(struct PIR_entry *entry, struct PIR_intpin *intpin, void *arg); static void pci_pir_parse(void); static uint8_t pci_pir_search_irq(int bus, int device, int pin); static int pci_pir_valid_irq(struct pci_link *pci_link, int irq); static void pci_pir_walk_table(pir_entry_handler *handler, void *arg); static MALLOC_DEFINE(M_PIR, "$PIR", "$PIR structures"); static struct PIR_table *pci_route_table; static device_t pir_device; static int pci_route_count, pir_bios_irqs, pir_parsed; static TAILQ_HEAD(, pci_link) pci_links; static int pir_interrupt_weight[NUM_ISA_INTERRUPTS]; /* sysctl vars */ SYSCTL_DECL(_hw_pci); /* XXX this likely should live in a header file */ /* IRQs 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, 15 */ #define PCI_IRQ_OVERRIDE_MASK 0xdef8 static uint32_t pci_irq_override_mask = PCI_IRQ_OVERRIDE_MASK; SYSCTL_INT(_hw_pci, OID_AUTO, irq_override_mask, CTLFLAG_RDTUN, &pci_irq_override_mask, PCI_IRQ_OVERRIDE_MASK, "Mask of allowed irqs to try to route when it has no good clue about\n" "which irqs it should use."); /* * Look for the interrupt routing table. * * We use PCI BIOS's PIR table if it's available. $PIR is the standard way * to do this. Sadly, some machines are not standards conforming and have * _PIR instead. We shrug and cope by looking for both. */ void pci_pir_open(void) { struct PIR_table *pt; uint32_t sigaddr; int i; uint8_t ck, *cv; /* Don't try if we've already found a table. */ if (pci_route_table != NULL) return; /* Look for $PIR and then _PIR. */ sigaddr = bios_sigsearch(0, "$PIR", 4, 16, 0); if (sigaddr == 0) sigaddr = bios_sigsearch(0, "_PIR", 4, 16, 0); if (sigaddr == 0) return; /* If we found something, check the checksum and length. */ /* XXX - Use pmap_mapdev()? */ pt = (struct PIR_table *)(uintptr_t)BIOS_PADDRTOVADDR(sigaddr); if (pt->pt_header.ph_length <= sizeof(struct PIR_header)) return; for (cv = (u_int8_t *)pt, ck = 0, i = 0; i < (pt->pt_header.ph_length); i++) ck += cv[i]; if (ck != 0) return; /* Ok, we've got a valid table. */ pci_route_table = pt; pci_route_count = (pt->pt_header.ph_length - sizeof(struct PIR_header)) / sizeof(struct PIR_entry); } /* * Find the pci_link structure for a given link ID. */ static struct pci_link * pci_pir_find_link(uint8_t link_id) { struct pci_link *pci_link; TAILQ_FOREACH(pci_link, &pci_links, pl_links) { if (pci_link->pl_id == link_id) return (pci_link); } return (NULL); } /* * Find the link device associated with a PCI device in the table. */ static void pci_pir_find_link_handler(struct PIR_entry *entry, struct PIR_intpin *intpin, void *arg) { struct pci_link_lookup *lookup; lookup = (struct pci_link_lookup *)arg; if (entry->pe_bus == lookup->bus && entry->pe_device == lookup->device && intpin - entry->pe_intpin == lookup->pin) *lookup->pci_link_ptr = pci_pir_find_link(intpin->link); } /* * Check to see if a possible IRQ setting is valid. */ static int pci_pir_valid_irq(struct pci_link *pci_link, int irq) { if (!PCI_INTERRUPT_VALID(irq)) return (0); return (pci_link->pl_irqmask & (1 << irq)); } /* * Walk the $PIR executing the worker function for each valid intpin entry * in the table. The handler is passed a pointer to both the entry and * the intpin in the entry. */ static void pci_pir_walk_table(pir_entry_handler *handler, void *arg) { struct PIR_entry *entry; struct PIR_intpin *intpin; int i, pin; entry = &pci_route_table->pt_entry[0]; for (i = 0; i < pci_route_count; i++, entry++) { intpin = &entry->pe_intpin[0]; for (pin = 0; pin < 4; pin++, intpin++) if (intpin->link != 0) handler(entry, intpin, arg); } } static void pci_pir_create_links(struct PIR_entry *entry, struct PIR_intpin *intpin, void *arg) { struct pci_link *pci_link; pci_link = pci_pir_find_link(intpin->link); if (pci_link != NULL) { pci_link->pl_references++; if (intpin->irqs != pci_link->pl_irqmask) { if (bootverbose) printf( "$PIR: Entry %d.%d.INT%c has different mask for link %#x, merging\n", entry->pe_bus, entry->pe_device, (intpin - entry->pe_intpin) + 'A', pci_link->pl_id); pci_link->pl_irqmask &= intpin->irqs; } } else { pci_link = malloc(sizeof(struct pci_link), M_PIR, M_WAITOK); pci_link->pl_id = intpin->link; pci_link->pl_irqmask = intpin->irqs; pci_link->pl_irq = PCI_INVALID_IRQ; pci_link->pl_references = 1; pci_link->pl_routed = 0; TAILQ_INSERT_TAIL(&pci_links, pci_link, pl_links); } } /* * Look to see if any of the functions on the PCI device at bus/device * have an interrupt routed to intpin 'pin' by the BIOS. */ static uint8_t pci_pir_search_irq(int bus, int device, int pin) { uint32_t value; uint8_t func, maxfunc; /* See if we have a valid device at function 0. */ - value = pci_cfgregread(bus, device, 0, PCIR_VENDOR, 2); + value = pci_cfgregread(0, bus, device, 0, PCIR_VENDOR, 2); if (value == PCIV_INVALID) return (PCI_INVALID_IRQ); - value = pci_cfgregread(bus, device, 0, PCIR_HDRTYPE, 1); + value = pci_cfgregread(0, bus, device, 0, PCIR_HDRTYPE, 1); if ((value & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) return (PCI_INVALID_IRQ); if (value & PCIM_MFDEV) maxfunc = PCI_FUNCMAX; else maxfunc = 0; /* Scan all possible functions at this device. */ for (func = 0; func <= maxfunc; func++) { - value = pci_cfgregread(bus, device, func, PCIR_VENDOR, 2); + value = pci_cfgregread(0, bus, device, func, PCIR_VENDOR, 2); if (value == PCIV_INVALID) continue; - value = pci_cfgregread(bus, device, func, PCIR_INTPIN, 1); + value = pci_cfgregread(0, bus, device, func, PCIR_INTPIN, 1); /* * See if it uses the pin in question. Note that the passed * in pin uses 0 for A, .. 3 for D whereas the intpin * register uses 0 for no interrupt, 1 for A, .. 4 for D. */ if (value != pin + 1) continue; - value = pci_cfgregread(bus, device, func, PCIR_INTLINE, 1); + value = pci_cfgregread(0, bus, device, func, PCIR_INTLINE, 1); if (bootverbose) printf( "$PIR: Found matching pin for %d.%d.INT%c at func %d: %d\n", bus, device, pin + 'A', func, value); if (value != PCI_INVALID_IRQ) return (value); } return (PCI_INVALID_IRQ); } /* * Try to initialize IRQ based on this device's IRQ. */ static void pci_pir_initial_irqs(struct PIR_entry *entry, struct PIR_intpin *intpin, void *arg) { struct pci_link *pci_link; uint8_t irq, pin; pin = intpin - entry->pe_intpin; pci_link = pci_pir_find_link(intpin->link); irq = pci_pir_search_irq(entry->pe_bus, entry->pe_device, pin); if (irq == PCI_INVALID_IRQ || irq == pci_link->pl_irq) return; /* Don't trust any BIOS IRQs greater than 15. */ if (irq >= NUM_ISA_INTERRUPTS) { printf( "$PIR: Ignoring invalid BIOS IRQ %d from %d.%d.INT%c for link %#x\n", irq, entry->pe_bus, entry->pe_device, pin + 'A', pci_link->pl_id); return; } /* * If we don't have an IRQ for this link yet, then we trust the * BIOS, even if it seems invalid from the $PIR entries. */ if (pci_link->pl_irq == PCI_INVALID_IRQ) { if (!pci_pir_valid_irq(pci_link, irq)) printf( "$PIR: Using invalid BIOS IRQ %d from %d.%d.INT%c for link %#x\n", irq, entry->pe_bus, entry->pe_device, pin + 'A', pci_link->pl_id); pci_link->pl_irq = irq; pci_link->pl_routed = 1; return; } /* * We have an IRQ and it doesn't match the current IRQ for this * link. If the new IRQ is invalid, then warn about it and ignore * it. If the old IRQ is invalid and the new IRQ is valid, then * prefer the new IRQ instead. If both IRQs are valid, then just * use the first one. Note that if we ever get into this situation * we are having to guess which setting the BIOS actually routed. * Perhaps we should just give up instead. */ if (!pci_pir_valid_irq(pci_link, irq)) { printf( "$PIR: BIOS IRQ %d for %d.%d.INT%c is not valid for link %#x\n", irq, entry->pe_bus, entry->pe_device, pin + 'A', pci_link->pl_id); } else if (!pci_pir_valid_irq(pci_link, pci_link->pl_irq)) { printf( "$PIR: Preferring valid BIOS IRQ %d from %d.%d.INT%c for link %#x to IRQ %d\n", irq, entry->pe_bus, entry->pe_device, pin + 'A', pci_link->pl_id, pci_link->pl_irq); pci_link->pl_irq = irq; pci_link->pl_routed = 1; } else printf( "$PIR: BIOS IRQ %d for %d.%d.INT%c does not match link %#x irq %d\n", irq, entry->pe_bus, entry->pe_device, pin + 'A', pci_link->pl_id, pci_link->pl_irq); } /* * Parse $PIR to enumerate link devices and attempt to determine their * initial state. This could perhaps be cleaner if we had drivers for the * various interrupt routers as they could read the initial IRQ for each * link. */ static void pci_pir_parse(void) { char tunable_buffer[64]; struct pci_link *pci_link; int i, irq; /* Only parse once. */ if (pir_parsed) return; pir_parsed = 1; /* Enumerate link devices. */ TAILQ_INIT(&pci_links); pci_pir_walk_table(pci_pir_create_links, NULL); if (bootverbose) { printf("$PIR: Links after initial probe:\n"); pci_pir_dump_links(); } /* * Check to see if the BIOS has already routed any of the links by * checking each device connected to each link to see if it has a * valid IRQ. */ pci_pir_walk_table(pci_pir_initial_irqs, NULL); if (bootverbose) { printf("$PIR: Links after initial IRQ discovery:\n"); pci_pir_dump_links(); } /* * Allow the user to override the IRQ for a given link device. We * allow invalid IRQs to be specified but warn about them. An IRQ * of 255 or 0 clears any preset IRQ. */ i = 0; TAILQ_FOREACH(pci_link, &pci_links, pl_links) { snprintf(tunable_buffer, sizeof(tunable_buffer), "hw.pci.link.%#x.irq", pci_link->pl_id); if (getenv_int(tunable_buffer, &irq) == 0) continue; if (irq == 0) irq = PCI_INVALID_IRQ; if (irq != PCI_INVALID_IRQ && !pci_pir_valid_irq(pci_link, irq) && bootverbose) printf( "$PIR: Warning, IRQ %d for link %#x is not listed as valid\n", irq, pci_link->pl_id); pci_link->pl_routed = 0; pci_link->pl_irq = irq; i = 1; } if (bootverbose && i) { printf("$PIR: Links after tunable overrides:\n"); pci_pir_dump_links(); } /* * Build initial interrupt weights as well as bitmap of "known-good" * IRQs that the BIOS has already used for PCI link devices. */ TAILQ_FOREACH(pci_link, &pci_links, pl_links) { if (!PCI_INTERRUPT_VALID(pci_link->pl_irq)) continue; pir_bios_irqs |= 1 << pci_link->pl_irq; pir_interrupt_weight[pci_link->pl_irq] += pci_link->pl_references; } if (bootverbose) { printf("$PIR: IRQs used by BIOS: "); pci_print_irqmask(pir_bios_irqs); printf("\n"); printf("$PIR: Interrupt Weights:\n[ "); for (i = 0; i < NUM_ISA_INTERRUPTS; i++) printf(" %3d", i); printf(" ]\n[ "); for (i = 0; i < NUM_ISA_INTERRUPTS; i++) printf(" %3d", pir_interrupt_weight[i]); printf(" ]\n"); } } /* * Use the PCI BIOS to route an interrupt for a given device. * * Input: * AX = PCIBIOS_ROUTE_INTERRUPT * BH = bus * BL = device [7:3] / function [2:0] * CH = IRQ * CL = Interrupt Pin (0x0A = A, ... 0x0D = D) */ static int pci_pir_biosroute(int bus, int device, int func, int pin, int irq) { struct bios_regs args; args.eax = PCIBIOS_ROUTE_INTERRUPT; args.ebx = (bus << 8) | (device << 3) | func; args.ecx = (irq << 8) | (0xa + pin); return (bios32(&args, PCIbios.ventry, GSEL(GCODE_SEL, SEL_KPL))); } /* * Route a PCI interrupt using a link device from the $PIR. */ int pci_pir_route_interrupt(int bus, int device, int func, int pin) { struct pci_link_lookup lookup; struct pci_link *pci_link; int error, irq; if (pci_route_table == NULL) return (PCI_INVALID_IRQ); /* Lookup link device for this PCI device/pin. */ pci_link = NULL; lookup.bus = bus; lookup.device = device; lookup.pin = pin - 1; lookup.pci_link_ptr = &pci_link; pci_pir_walk_table(pci_pir_find_link_handler, &lookup); if (pci_link == NULL) { printf("$PIR: No matching entry for %d.%d.INT%c\n", bus, device, pin - 1 + 'A'); return (PCI_INVALID_IRQ); } /* * Pick a new interrupt if we don't have one already. We look * for an interrupt from several different sets. First, if * this link only has one valid IRQ, use that. Second, we * check the set of PCI only interrupts from the $PIR. Third, * we check the set of known-good interrupts that the BIOS has * already used. Lastly, we check the "all possible valid * IRQs" set. */ if (!PCI_INTERRUPT_VALID(pci_link->pl_irq)) { if (pci_link->pl_irqmask != 0 && powerof2(pci_link->pl_irqmask)) irq = ffs(pci_link->pl_irqmask) - 1; else irq = pci_pir_choose_irq(pci_link, pci_route_table->pt_header.ph_pci_irqs); if (!PCI_INTERRUPT_VALID(irq)) irq = pci_pir_choose_irq(pci_link, pir_bios_irqs); if (!PCI_INTERRUPT_VALID(irq)) irq = pci_pir_choose_irq(pci_link, pci_irq_override_mask); if (!PCI_INTERRUPT_VALID(irq)) { if (bootverbose) printf( "$PIR: Failed to route interrupt for %d:%d INT%c\n", bus, device, pin - 1 + 'A'); return (PCI_INVALID_IRQ); } pci_link->pl_irq = irq; } /* Ask the BIOS to route this IRQ if we haven't done so already. */ if (!pci_link->pl_routed) { error = pci_pir_biosroute(bus, device, func, pin - 1, pci_link->pl_irq); /* Ignore errors when routing a unique interrupt. */ if (error && !powerof2(pci_link->pl_irqmask)) { printf("$PIR: ROUTE_INTERRUPT failed.\n"); return (PCI_INVALID_IRQ); } pci_link->pl_routed = 1; /* Ensure the interrupt is set to level/low trigger. */ KASSERT(pir_device != NULL, ("missing pir device")); BUS_CONFIG_INTR(pir_device, pci_link->pl_irq, INTR_TRIGGER_LEVEL, INTR_POLARITY_LOW); } if (bootverbose) printf("$PIR: %d:%d INT%c routed to irq %d\n", bus, device, pin - 1 + 'A', pci_link->pl_irq); return (pci_link->pl_irq); } /* * Try to pick an interrupt for the specified link from the interrupts * set in the mask. */ static int pci_pir_choose_irq(struct pci_link *pci_link, int irqmask) { int i, irq, realmask; /* XXX: Need to have a #define of known bad IRQs to also mask out? */ realmask = pci_link->pl_irqmask & irqmask; if (realmask == 0) return (PCI_INVALID_IRQ); /* Find IRQ with lowest weight. */ irq = PCI_INVALID_IRQ; for (i = 0; i < NUM_ISA_INTERRUPTS; i++) { if (!(realmask & 1 << i)) continue; if (irq == PCI_INVALID_IRQ || pir_interrupt_weight[i] < pir_interrupt_weight[irq]) irq = i; } if (bootverbose && PCI_INTERRUPT_VALID(irq)) { printf("$PIR: Found IRQ %d for link %#x from ", irq, pci_link->pl_id); pci_print_irqmask(realmask); printf("\n"); } return (irq); } static void pci_print_irqmask(u_int16_t irqs) { int i, first; if (irqs == 0) { printf("none"); return; } first = 1; for (i = 0; i < 16; i++, irqs >>= 1) if (irqs & 1) { if (!first) printf(" "); else first = 0; printf("%d", i); } } /* * Display link devices. */ static void pci_pir_dump_links(void) { struct pci_link *pci_link; printf("Link IRQ Rtd Ref IRQs\n"); TAILQ_FOREACH(pci_link, &pci_links, pl_links) { printf("%#4x %3d %c %3d ", pci_link->pl_id, pci_link->pl_irq, pci_link->pl_routed ? 'Y' : 'N', pci_link->pl_references); pci_print_irqmask(pci_link->pl_irqmask); printf("\n"); } } /* * See if any interrupts for a given PCI bus are routed in the PIR. Don't * even bother looking if the BIOS doesn't support routing anyways. If we * are probing a PCI-PCI bridge, then require_parse will be true and we should * only succeed if a host-PCI bridge has already attached and parsed the PIR. */ int pci_pir_probe(int bus, int require_parse) { int i; if (pci_route_table == NULL || (require_parse && !pir_parsed)) return (0); for (i = 0; i < pci_route_count; i++) if (pci_route_table->pt_entry[i].pe_bus == bus) return (1); return (0); } /* * The driver for the new-bus pseudo device pir0 for the $PIR table. */ static int pir_probe(device_t dev) { char buf[64]; snprintf(buf, sizeof(buf), "PCI Interrupt Routing Table: %d Entries", pci_route_count); device_set_desc_copy(dev, buf); return (0); } static int pir_attach(device_t dev) { pci_pir_parse(); KASSERT(pir_device == NULL, ("Multiple pir devices")); pir_device = dev; return (0); } static void pir_resume_find_device(struct PIR_entry *entry, struct PIR_intpin *intpin, void *arg) { struct pci_dev_lookup *pd; pd = (struct pci_dev_lookup *)arg; if (intpin->link != pd->link || pd->bus != -1) return; pd->bus = entry->pe_bus; pd->device = entry->pe_device; pd->pin = intpin - entry->pe_intpin; } static int pir_resume(device_t dev) { struct pci_dev_lookup pd; struct pci_link *pci_link; int error; /* Ask the BIOS to re-route each link that was already routed. */ TAILQ_FOREACH(pci_link, &pci_links, pl_links) { if (!PCI_INTERRUPT_VALID(pci_link->pl_irq)) { KASSERT(!pci_link->pl_routed, ("link %#x is routed but has invalid PCI IRQ", pci_link->pl_id)); continue; } if (pci_link->pl_routed) { pd.bus = -1; pd.link = pci_link->pl_id; pci_pir_walk_table(pir_resume_find_device, &pd); KASSERT(pd.bus != -1, ("did not find matching entry for link %#x in the $PIR table", pci_link->pl_id)); if (bootverbose) device_printf(dev, "Using %d.%d.INT%c to route link %#x to IRQ %d\n", pd.bus, pd.device, pd.pin + 'A', pci_link->pl_id, pci_link->pl_irq); error = pci_pir_biosroute(pd.bus, pd.device, 0, pd.pin, pci_link->pl_irq); if (error) device_printf(dev, "ROUTE_INTERRUPT on resume for link %#x failed.\n", pci_link->pl_id); } } return (0); } static device_method_t pir_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pir_probe), DEVMETHOD(device_attach, pir_attach), DEVMETHOD(device_resume, pir_resume), { 0, 0 } }; static driver_t pir_driver = { "pir", pir_methods, 1, }; DRIVER_MODULE(pir, legacy, pir_driver, 0, 0); diff --git a/sys/x86/include/pci_cfgreg.h b/sys/x86/include/pci_cfgreg.h index 890d7233f658..370113b9d7bd 100644 --- a/sys/x86/include/pci_cfgreg.h +++ b/sys/x86/include/pci_cfgreg.h @@ -1,69 +1,69 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, Stefan Esser * All rights reserved. * * 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 unmodified, 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 ``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 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. * */ #ifndef __X86_PCI_CFGREG_H__ #define __X86_PCI_CFGREG_H__ #define CONF1_ADDR_PORT 0x0cf8 #define CONF1_DATA_PORT 0x0cfc #define CONF1_ENABLE 0x80000000ul #define CONF1_ENABLE_CHK 0x80000000ul #define CONF1_ENABLE_MSK 0x7f000000ul #define CONF1_ENABLE_CHK1 0xff000001ul #define CONF1_ENABLE_MSK1 0x80000001ul #define CONF1_ENABLE_RES1 0x80000000ul #define CONF2_ENABLE_PORT 0x0cf8 #define CONF2_FORWARD_PORT 0x0cfa #define CONF2_ENABLE_CHK 0x0e #define CONF2_ENABLE_RES 0x0e enum { CFGMECH_NONE = 0, CFGMECH_1, CFGMECH_2, CFGMECH_PCIE, }; extern int cfgmech; rman_res_t hostb_alloc_start(int type, rman_res_t start, rman_res_t end, rman_res_t count); int pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus); int pci_cfgregopen(void); -u_int32_t pci_cfgregread(int bus, int slot, int func, int reg, int bytes); -void pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes); +u_int32_t pci_cfgregread(int domain, int bus, int slot, int func, int reg, int bytes); +void pci_cfgregwrite(int domain, int bus, int slot, int func, int reg, u_int32_t data, int bytes); #ifdef __HAVE_PIR void pci_pir_open(void); int pci_pir_probe(int bus, int require_parse); int pci_pir_route_interrupt(int bus, int device, int func, int pin); #endif #endif /* !__X86_PCI_CFGREG_H__ */ diff --git a/sys/x86/iommu/intel_drv.c b/sys/x86/iommu/intel_drv.c index c4bb3a1745f4..7346162d1502 100644 --- a/sys/x86/iommu/intel_drv.c +++ b/sys/x86/iommu/intel_drv.c @@ -1,1357 +1,1357 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013-2015 The FreeBSD Foundation * * This software was developed by Konstantin Belousov * 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 "opt_acpi.h" #if defined(__amd64__) #define DEV_APIC #else #include "opt_apic.h" #endif #include "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEV_APIC #include "pcib_if.h" #include #include #include #endif #define DMAR_FAULT_IRQ_RID 0 #define DMAR_QI_IRQ_RID 1 #define DMAR_REG_RID 2 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; } AcpiPutTable((ACPI_TABLE_HEADER *)dmartbl); } 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); } int dmar_rmrr_enable = 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; TUNABLE_INT_FETCH("hw.dmar.rmrr_enable", &dmar_rmrr_enable); 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"); } AcpiPutTable((ACPI_TABLE_HEADER *)dmartbl); 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; iommu_fini_busdma(&unit->iommu); 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, "%s", 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; uint64_t timeout; int disable_pmr; int i, error; unit = device_get_softc(dev); unit->dev = dev; unit->iommu.unit = device_get_unit(dev); unit->iommu.dev = dev; dmaru = dmar_find_by_index(unit->iommu.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); timeout = dmar_get_timeout(); TUNABLE_UINT64_FETCH("hw.iommu.dmar.timeout", &timeout); dmar_update_timeout(timeout); 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->iommu.lock, "dmarhw", NULL, MTX_DEF); unit->domids = new_unrhdr(0, dmar_nd2mask(DMAR_CAP_ND(unit->hw_cap)), &unit->iommu.lock); LIST_INIT(&unit->domains); /* * 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, IOMMU_PGF_WAITOK | IOMMU_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); } disable_pmr = 0; TUNABLE_INT_FETCH("hw.dmar.pmr.disable", &disable_pmr); if (disable_pmr) { error = dmar_disable_protected_regions(unit); if (error != 0) device_printf(dev, "Failed to disable protected regions\n"); } error = iommu_init_busdma(&unit->iommu); 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, 0, 0); MODULE_DEPEND(dmar, acpi, 1, 1, 1); static void dmar_print_path(int busno, int depth, const ACPI_DMAR_PCI_PATH *path) { int i; printf("[%d, ", busno); for (i = 0; i < depth; i++) { if (i != 0) printf(", "); printf("(%d, %d)", path[i].Device, path[i].Function); } printf("]"); } 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; } } void dmar_dev_path(device_t child, int *busno, void *path1, int depth) { devclass_t pci_class; device_t bus, pcib; ACPI_DMAR_PCI_PATH *path; pci_class = devclass_find("pci"); path = path1; 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, 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_match_devscope: 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_match_devscope: 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_match_devscope: corrupted DMAR table, dl %d\n", devscope->Length); return (-1); } return (dmar_match_pathes(devscope->Bus, path, path_len, dev_busno, dev_path, dev_path_len, devscope->EntryType)); } static bool dmar_match_by_path(struct dmar_unit *unit, int dev_domain, int dev_busno, const ACPI_DMAR_PCI_PATH *dev_path, int dev_path_len, const char **banner) { ACPI_DMAR_HARDWARE_UNIT *dmarh; ACPI_DMAR_DEVICE_SCOPE *devscope; char *ptr, *ptrend; int match; dmarh = dmar_find_by_index(unit->iommu.unit); if (dmarh == NULL) return (false); if (dmarh->Segment != dev_domain) return (false); if ((dmarh->Flags & ACPI_DMAR_INCLUDE_ALL) != 0) { if (banner != NULL) *banner = "INCLUDE_ALL"; return (true); } ptr = (char *)dmarh + sizeof(*dmarh); ptrend = (char *)dmarh + dmarh->Header.Length; while (ptr < ptrend) { devscope = (ACPI_DMAR_DEVICE_SCOPE *)ptr; ptr += devscope->Length; match = dmar_match_devscope(devscope, dev_busno, dev_path, dev_path_len); if (match == -1) return (false); if (match == 1) { if (banner != NULL) *banner = "specific match"; return (true); } } return (false); } static struct dmar_unit * dmar_find_by_scope(int dev_domain, int dev_busno, const ACPI_DMAR_PCI_PATH *dev_path, int dev_path_len) { struct dmar_unit *unit; int i; for (i = 0; i < dmar_devcnt; i++) { if (dmar_devs[i] == NULL) continue; unit = device_get_softc(dmar_devs[i]); if (dmar_match_by_path(unit, dev_domain, dev_busno, dev_path, dev_path_len, NULL)) return (unit); } return (NULL); } struct dmar_unit * dmar_find(device_t dev, bool verbose) { struct dmar_unit *unit; const char *banner; int i, dev_domain, dev_busno, dev_path_len; /* * This function can only handle PCI(e) devices. */ if (device_get_devclass(device_get_parent(dev)) != devclass_find("pci")) return (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); banner = ""; for (i = 0; i < dmar_devcnt; i++) { if (dmar_devs[i] == NULL) continue; unit = device_get_softc(dmar_devs[i]); if (dmar_match_by_path(unit, dev_domain, dev_busno, dev_path, dev_path_len, &banner)) break; } if (i == dmar_devcnt) return (NULL); if (verbose) { device_printf(dev, "pci%d:%d:%d:%d matched dmar%d by %s", dev_domain, pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev), unit->iommu.unit, banner); printf(" scope path "); dmar_print_path(dev_busno, dev_path_len, dev_path); printf("\n"); } return (unit); } 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; #ifdef DEV_APIC int error; #endif 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; #ifdef DEV_APIC if (entry_type == ACPI_DMAR_SCOPE_TYPE_IOAPIC) { error = ioapic_get_rid(id, rid); /* * If our IOAPIC has PCI bindings then * use the PCI device rid. */ if (error == 0) return (unit); } #endif 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); } printf( "dmar_find_nonpci: id %d type %d path length != 2\n", id, entry_type); break; } } return (NULL); } struct dmar_unit * dmar_find_hpet(device_t dev, uint16_t *rid) { return (dmar_find_nonpci(hpet_get_uid(dev), 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_domain *domain; int dev_domain; int dev_busno; const ACPI_DMAR_PCI_PATH *dev_path; int dev_path_len; struct iommu_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 iommu_map_entry *entry; char *ptr, *ptrend; int match; if (!dmar_rmrr_enable) return (1); if (dmarh->Type != ACPI_DMAR_TYPE_RESERVED_MEMORY) return (1); ria = arg; resmem = (ACPI_DMAR_RESERVED_MEMORY *)dmarh; 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_busno, ria->dev_path, ria->dev_path_len); if (match == 1) { entry = iommu_gas_alloc_entry(DOM2IODOM(ria->domain), IOMMU_PGF_WAITOK); entry->start = resmem->BaseAddress; /* The RMRR entry end address is inclusive. */ entry->end = resmem->EndAddress; TAILQ_INSERT_TAIL(ria->rmrr_entries, entry, dmamap_link); } } return (1); } void dmar_dev_parse_rmrr(struct dmar_domain *domain, int dev_domain, int dev_busno, const void *dev_path, int dev_path_len, struct iommu_map_entries_tailq *rmrr_entries) { struct rmrr_iter_args ria; ria.domain = domain; ria.dev_domain = dev_domain; ria.dev_busno = dev_busno; ria.dev_path = (const ACPI_DMAR_PCI_PATH *)dev_path; ria.dev_path_len = dev_path_len; 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, uint16_t *rid) { device_t dev; int i; dev = NULL; for (i = 0; i < path_len; i++) { dev = pci_find_dbsf(segment, busno, path->Device, path->Function); if (i != path_len - 1) { - busno = pci_cfgregread(busno, path->Device, + busno = pci_cfgregread(segment, busno, path->Device, path->Function, PCIR_SECBUS_1, 1); path++; } } *rid = PCI_RID(busno, path->Device, path->Function); 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; device_t dev; struct dmar_unit *unit; int dev_path_len; uint16_t rid; iria = arg; if (!dmar_rmrr_enable) return (1); if (dmarh->Type != ACPI_DMAR_TYPE_RESERVED_MEMORY) return (1); resmem = (ACPI_DMAR_RESERVED_MEMORY *)dmarh; if (resmem->Segment != iria->dmar->segment) return (1); 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; rid = 0; dev_path_len = (devscope->Length - sizeof(ACPI_DMAR_DEVICE_SCOPE)) / 2; dev = dmar_path_dev(resmem->Segment, dev_path_len, devscope->Bus, (const ACPI_DMAR_PCI_PATH *)(devscope + 1), &rid); if (dev == NULL) { if (bootverbose) { printf("dmar%d no dev found for RMRR " "[%#jx, %#jx] rid %#x scope path ", iria->dmar->iommu.unit, (uintmax_t)resmem->BaseAddress, (uintmax_t)resmem->EndAddress, rid); dmar_print_path(devscope->Bus, dev_path_len, (const ACPI_DMAR_PCI_PATH *)(devscope + 1)); printf("\n"); } unit = dmar_find_by_scope(resmem->Segment, devscope->Bus, (const ACPI_DMAR_PCI_PATH *)(devscope + 1), dev_path_len); if (iria->dmar != unit) continue; dmar_get_ctx_for_devpath(iria->dmar, rid, resmem->Segment, devscope->Bus, (const ACPI_DMAR_PCI_PATH *)(devscope + 1), dev_path_len, false, true); } else { unit = dmar_find(dev, false); if (iria->dmar != unit) continue; iommu_instantiate_ctx(&(iria)->dmar->iommu, dev, true); } } return (1); } /* * Pre-create all contexts for the DMAR which have RMRR entries. */ int dmar_instantiate_rmrr_ctxs(struct iommu_unit *unit) { struct dmar_unit *dmar; struct inst_rmrr_iter_args iria; int error; dmar = IOMMU2DMAR(unit); if (!dmar_barrier_enter(dmar, DMAR_BARRIER_RMRR)) return (0); error = 0; iria.dmar = dmar; dmar_iterate_tbl(dmar_inst_rmrr_iter, &iria); DMAR_LOCK(dmar); if (!LIST_EMPTY(&dmar->domains)) { KASSERT((dmar->hw_gcmd & DMAR_GCMD_TE) == 0, ("dmar%d: RMRR not handled but translation is already enabled", dmar->iommu.unit)); error = dmar_disable_protected_regions(dmar); if (error != 0) printf("dmar%d: Failed to disable protected regions\n", dmar->iommu.unit); error = dmar_enable_translation(dmar); if (bootverbose) { if (error == 0) { printf("dmar%d: enabled translation\n", dmar->iommu.unit); } else { printf("dmar%d: enabling translation failed, " "error %d\n", dmar->iommu.unit, error); } } } dmar_barrier_exit(dmar, DMAR_BARRIER_RMRR); return (error); } #ifdef DDB #include #include static void dmar_print_domain_entry(const struct iommu_map_entry *entry) { struct iommu_map_entry *l, *r; db_printf( " start %jx end %jx first %jx last %jx free_down %jx flags %x ", entry->start, entry->end, entry->first, entry->last, 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) { db_printf( " @%p pci%d:%d:%d refs %d flags %x loads %lu unloads %lu\n", ctx, pci_get_bus(ctx->context.tag->owner), pci_get_slot(ctx->context.tag->owner), pci_get_function(ctx->context.tag->owner), ctx->refs, ctx->context.flags, ctx->context.loads, ctx->context.unloads); } static void dmar_print_domain(struct dmar_domain *domain, bool show_mappings) { struct iommu_domain *iodom; struct iommu_map_entry *entry; struct dmar_ctx *ctx; iodom = DOM2IODOM(domain); db_printf( " @%p dom %d mgaw %d agaw %d pglvl %d end %jx refs %d\n" " ctx_cnt %d flags %x pgobj %p map_ents %u\n", domain, domain->domain, domain->mgaw, domain->agaw, domain->pglvl, (uintmax_t)domain->iodom.end, domain->refs, domain->ctx_cnt, domain->iodom.flags, domain->pgtbl_obj, domain->iodom.entries_cnt); if (!LIST_EMPTY(&domain->contexts)) { db_printf(" Contexts:\n"); LIST_FOREACH(ctx, &domain->contexts, link) dmar_print_ctx(ctx); } if (!show_mappings) return; db_printf(" mapped:\n"); RB_FOREACH(entry, iommu_gas_entries_tree, &iodom->rb_root) { dmar_print_domain_entry(entry); if (db_pager_quit) break; } if (db_pager_quit) return; db_printf(" unloading:\n"); TAILQ_FOREACH(entry, &domain->iodom.unload_entries, dmamap_link) { dmar_print_domain_entry(entry); if (db_pager_quit) break; } } DB_SHOW_COMMAND_FLAGS(dmar_domain, db_dmar_print_domain, CS_OWN) { struct dmar_unit *unit; struct dmar_domain *domain; struct dmar_ctx *ctx; bool show_mappings, valid; int pci_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) { pci_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_domain [/m] " " \n"); return; } for (i = 0; i < dmar_devcnt; i++) { unit = device_get_softc(dmar_devs[i]); LIST_FOREACH(domain, &unit->domains, link) { LIST_FOREACH(ctx, &domain->contexts, link) { if (pci_domain == unit->segment && bus == pci_get_bus(ctx->context.tag->owner) && device == pci_get_slot(ctx->context.tag->owner) && function == pci_get_function(ctx->context.tag->owner)) { dmar_print_domain(domain, show_mappings); goto out; } } } } out:; } static void dmar_print_one(int idx, bool show_domains, bool show_mappings) { struct dmar_unit *unit; struct dmar_domain *domain; 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->iommu.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)); if (unit->ir_enabled) { db_printf("ir is enabled; IRT @%p phys 0x%jx maxcnt %d\n", unit->irt, (uintmax_t)unit->irt_phys, unit->irte_cnt); } 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_domains) { db_printf("domains:\n"); LIST_FOREACH(domain, &unit->domains, link) { dmar_print_domain(domain, show_mappings); if (db_pager_quit) break; } } } DB_SHOW_COMMAND(dmar, db_dmar_print) { bool show_domains, show_mappings; show_domains = strchr(modif, 'd') != NULL; show_mappings = strchr(modif, 'm') != NULL; if (!have_addr) { db_printf("usage: show dmar [/d] [/m] index\n"); return; } dmar_print_one((int)addr, show_domains, show_mappings); } DB_SHOW_ALL_COMMAND(dmars, db_show_all_dmars) { int i; bool show_domains, show_mappings; show_domains = strchr(modif, 'd') != NULL; show_mappings = strchr(modif, 'm') != NULL; for (i = 0; i < dmar_devcnt; i++) { dmar_print_one(i, show_domains, show_mappings); if (db_pager_quit) break; } } #endif struct iommu_unit * iommu_find(device_t dev, bool verbose) { struct dmar_unit *dmar; dmar = dmar_find(dev, verbose); return (&dmar->iommu); } diff --git a/sys/x86/pci/pci_bus.c b/sys/x86/pci/pci_bus.c index 685c10fb843c..d06cfca53673 100644 --- a/sys/x86/pci/pci_bus.c +++ b/sys/x86/pci/pci_bus.c @@ -1,756 +1,756 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, Stefan Esser * All rights reserved. * * 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 unmodified, 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 ``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 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 #include "opt_cpu.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CPU_ELAN #include #endif #include #include #include #include "pcib_if.h" int legacy_pcib_maxslots(device_t dev) { return 31; } /* read configuration space register */ uint32_t legacy_pcib_read_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, int bytes) { - return(pci_cfgregread(bus, slot, func, reg, bytes)); + return(pci_cfgregread(0, bus, slot, func, reg, bytes)); } /* write configuration space register */ void legacy_pcib_write_config(device_t dev, u_int bus, u_int slot, u_int func, u_int reg, uint32_t data, int bytes) { - pci_cfgregwrite(bus, slot, func, reg, data, bytes); + pci_cfgregwrite(0, bus, slot, func, reg, data, bytes); } /* route interrupt */ static int legacy_pcib_route_interrupt(device_t pcib, device_t dev, int pin) { #ifdef __HAVE_PIR return (pci_pir_route_interrupt(pci_get_bus(dev), pci_get_slot(dev), pci_get_function(dev), pin)); #else /* No routing possible */ return (PCI_INVALID_IRQ); #endif } /* Pass MSI requests up to the nexus. */ int legacy_pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs) { device_t bus; bus = device_get_parent(pcib); return (PCIB_ALLOC_MSI(device_get_parent(bus), dev, count, maxcount, irqs)); } int legacy_pcib_alloc_msix(device_t pcib, device_t dev, int *irq) { device_t bus; bus = device_get_parent(pcib); return (PCIB_ALLOC_MSIX(device_get_parent(bus), dev, irq)); } int legacy_pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data) { device_t bus, hostb; int error, func, slot; bus = device_get_parent(pcib); error = PCIB_MAP_MSI(device_get_parent(bus), dev, irq, addr, data); if (error) return (error); slot = legacy_get_pcislot(pcib); func = legacy_get_pcifunc(pcib); if (slot == -1 || func == -1) return (0); hostb = pci_find_bsf(0, slot, func); KASSERT(hostb != NULL, ("%s: missing hostb for 0:%d:%d", __func__, slot, func)); pci_ht_map_msi(hostb, *addr); return (0); } static const char * legacy_pcib_is_host_bridge(int bus, int slot, int func, uint32_t id, uint8_t class, uint8_t subclass, uint8_t *busnum) { #ifdef __i386__ const char *s = NULL; static uint8_t pxb[4]; /* hack for 450nx */ *busnum = 0; switch (id) { case 0x12258086: s = "Intel 824?? host to PCI bridge"; /* XXX This is a guess */ /* *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x41, 1); */ *busnum = bus; break; case 0x71208086: s = "Intel 82810 (i810 GMCH) Host To Hub bridge"; break; case 0x71228086: s = "Intel 82810-DC100 (i810-DC100 GMCH) Host To Hub bridge"; break; case 0x71248086: s = "Intel 82810E (i810E GMCH) Host To Hub bridge"; break; case 0x11308086: s = "Intel 82815 (i815 GMCH) Host To Hub bridge"; break; case 0x71808086: s = "Intel 82443LX (440 LX) host to PCI bridge"; break; case 0x71908086: s = "Intel 82443BX (440 BX) host to PCI bridge"; break; case 0x71928086: s = "Intel 82443BX host to PCI bridge (AGP disabled)"; break; case 0x71948086: s = "Intel 82443MX host to PCI bridge"; break; case 0x71a08086: s = "Intel 82443GX host to PCI bridge"; break; case 0x71a18086: s = "Intel 82443GX host to AGP bridge"; break; case 0x71a28086: s = "Intel 82443GX host to PCI bridge (AGP disabled)"; break; case 0x84c48086: s = "Intel 82454KX/GX (Orion) host to PCI bridge"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x4a, 1); break; case 0x84ca8086: /* * For the 450nx chipset, there is a whole bundle of * things pretending to be host bridges. The MIOC will * be seen first and isn't really a pci bridge (the * actual buses are attached to the PXB's). We need to * read the registers of the MIOC to figure out the * bus numbers for the PXB channels. * * Since the MIOC doesn't have a pci bus attached, we * pretend it wasn't there. */ pxb[0] = legacy_pcib_read_config(0, bus, slot, func, 0xd0, 1); /* BUSNO[0] */ pxb[1] = legacy_pcib_read_config(0, bus, slot, func, 0xd1, 1) + 1; /* SUBA[0]+1 */ pxb[2] = legacy_pcib_read_config(0, bus, slot, func, 0xd3, 1); /* BUSNO[1] */ pxb[3] = legacy_pcib_read_config(0, bus, slot, func, 0xd4, 1) + 1; /* SUBA[1]+1 */ return NULL; case 0x84cb8086: switch (slot) { case 0x12: s = "Intel 82454NX PXB#0, Bus#A"; *busnum = pxb[0]; break; case 0x13: s = "Intel 82454NX PXB#0, Bus#B"; *busnum = pxb[1]; break; case 0x14: s = "Intel 82454NX PXB#1, Bus#A"; *busnum = pxb[2]; break; case 0x15: s = "Intel 82454NX PXB#1, Bus#B"; *busnum = pxb[3]; break; } break; case 0x1A308086: s = "Intel 82845 Host to PCI bridge"; break; /* AMD -- vendor 0x1022 */ case 0x30001022: s = "AMD Elan SC520 host to PCI bridge"; #ifdef CPU_ELAN init_AMD_Elan_sc520(); #else printf( "*** WARNING: missing CPU_ELAN -- timekeeping may be wrong\n"); #endif break; case 0x70061022: s = "AMD-751 host to PCI bridge"; break; case 0x700e1022: s = "AMD-761 host to PCI bridge"; break; /* SiS -- vendor 0x1039 */ case 0x04961039: s = "SiS 85c496"; break; case 0x04061039: s = "SiS 85c501"; break; case 0x06011039: s = "SiS 85c601"; break; case 0x55911039: s = "SiS 5591 host to PCI bridge"; break; case 0x00011039: s = "SiS 5591 host to AGP bridge"; break; /* VLSI -- vendor 0x1004 */ case 0x00051004: s = "VLSI 82C592 Host to PCI bridge"; break; /* XXX Here is MVP3, I got the datasheet but NO M/B to test it */ /* totally. Please let me know if anything wrong. -F */ /* XXX need info on the MVP3 -- any takers? */ case 0x05981106: s = "VIA 82C598MVP (Apollo MVP3) host bridge"; break; /* AcerLabs -- vendor 0x10b9 */ /* Funny : The datasheet told me vendor id is "10b8",sub-vendor */ /* id is '10b9" but the register always shows "10b9". -Foxfair */ case 0x154110b9: s = "AcerLabs M1541 (Aladdin-V) PCI host bridge"; break; /* OPTi -- vendor 0x1045 */ case 0xc7011045: s = "OPTi 82C700 host to PCI bridge"; break; case 0xc8221045: s = "OPTi 82C822 host to PCI Bridge"; break; /* ServerWorks -- vendor 0x1166 */ case 0x00051166: s = "ServerWorks NB6536 2.0HE host to PCI bridge"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x44, 1); break; case 0x00061166: /* FALLTHROUGH */ case 0x00081166: /* FALLTHROUGH */ case 0x02011166: /* FALLTHROUGH */ case 0x010f1014: /* IBM re-badged ServerWorks chipset */ s = "ServerWorks host to PCI bridge"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x44, 1); break; case 0x00091166: s = "ServerWorks NB6635 3.0LE host to PCI bridge"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x44, 1); break; case 0x00101166: s = "ServerWorks CIOB30 host to PCI bridge"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x44, 1); break; case 0x00111166: /* FALLTHROUGH */ case 0x03021014: /* IBM re-badged ServerWorks chipset */ s = "ServerWorks CMIC-HE host to PCI-X bridge"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x44, 1); break; /* XXX unknown chipset, but working */ case 0x00171166: /* FALLTHROUGH */ case 0x01011166: case 0x01101166: case 0x02251166: s = "ServerWorks host to PCI bridge(unknown chipset)"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0x44, 1); break; /* Compaq/HP -- vendor 0x0e11 */ case 0x60100e11: s = "Compaq/HP Model 6010 HotPlug PCI Bridge"; *busnum = legacy_pcib_read_config(0, bus, slot, func, 0xc8, 1); break; /* Integrated Micro Solutions -- vendor 0x10e0 */ case 0x884910e0: s = "Integrated Micro Solutions VL Bridge"; break; default: if (class == PCIC_BRIDGE && subclass == PCIS_BRIDGE_HOST) s = "Host to PCI bridge"; break; } return s; #else const char *s = NULL; *busnum = 0; if (class == PCIC_BRIDGE && subclass == PCIS_BRIDGE_HOST) s = "Host to PCI bridge"; return s; #endif } /* * Scan the first pci bus for host-pci bridges and add pcib instances * to the nexus for each bridge. */ static void legacy_pcib_identify(driver_t *driver, device_t parent) { int bus, slot, func; uint8_t hdrtype; int found = 0; int pcifunchigh; int found824xx = 0; int found_orion = 0; device_t child; devclass_t pci_devclass; if (pci_cfgregopen() == 0) return; /* * Check to see if we haven't already had a PCI bus added * via some other means. If we have, bail since otherwise * we're going to end up duplicating it. */ if ((pci_devclass = devclass_find("pci")) && devclass_get_device(pci_devclass, 0)) return; bus = 0; retry: for (slot = 0; slot <= PCI_SLOTMAX; slot++) { func = 0; hdrtype = legacy_pcib_read_config(0, bus, slot, func, PCIR_HDRTYPE, 1); /* * When enumerating bus devices, the standard says that * one should check the header type and ignore the slots whose * header types that the software doesn't know about. We use * this to filter out devices. */ if ((hdrtype & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) continue; if ((hdrtype & PCIM_MFDEV) && (!found_orion || hdrtype != 0xff)) pcifunchigh = PCI_FUNCMAX; else pcifunchigh = 0; for (func = 0; func <= pcifunchigh; func++) { /* * Read the IDs and class from the device. */ uint32_t id; uint8_t class, subclass, busnum; const char *s; device_t *devs; int ndevs, i; id = legacy_pcib_read_config(0, bus, slot, func, PCIR_DEVVENDOR, 4); if (id == -1) continue; class = legacy_pcib_read_config(0, bus, slot, func, PCIR_CLASS, 1); subclass = legacy_pcib_read_config(0, bus, slot, func, PCIR_SUBCLASS, 1); s = legacy_pcib_is_host_bridge(bus, slot, func, id, class, subclass, &busnum); if (s == NULL) continue; /* * Check to see if the physical bus has already * been seen. Eg: hybrid 32 and 64 bit host * bridges to the same logical bus. */ if (device_get_children(parent, &devs, &ndevs) == 0) { for (i = 0; s != NULL && i < ndevs; i++) { if (strcmp(device_get_name(devs[i]), "pcib") != 0) continue; if (legacy_get_pcibus(devs[i]) == busnum) s = NULL; } free(devs, M_TEMP); } if (s == NULL) continue; /* * Add at priority 100 to make sure we * go after any motherboard resources */ child = BUS_ADD_CHILD(parent, 100, "pcib", busnum); device_set_desc(child, s); legacy_set_pcibus(child, busnum); legacy_set_pcislot(child, slot); legacy_set_pcifunc(child, func); found = 1; if (id == 0x12258086) found824xx = 1; if (id == 0x84c48086) found_orion = 1; } } if (found824xx && bus == 0) { bus++; goto retry; } /* * Make sure we add at least one bridge since some old * hardware doesn't actually have a host-pci bridge device. * Note that pci_cfgregopen() thinks we have PCI devices.. */ if (!found) { #ifndef NO_LEGACY_PCIB if (bootverbose) printf( "legacy_pcib_identify: no bridge found, adding pcib0 anyway\n"); child = BUS_ADD_CHILD(parent, 100, "pcib", 0); legacy_set_pcibus(child, 0); #endif } } static int legacy_pcib_probe(device_t dev) { if (pci_cfgregopen() == 0) return ENXIO; return -100; } static int legacy_pcib_attach(device_t dev) { #ifdef __HAVE_PIR device_t pir; int bus; bus = pcib_get_bus(dev); /* * Look for a PCI BIOS interrupt routing table as that will be * our method of routing interrupts if we have one. */ if (pci_pir_probe(bus, 0)) { pir = BUS_ADD_CHILD(device_get_parent(dev), 0, "pir", 0); if (pir != NULL) device_probe_and_attach(pir); } #endif device_add_child(dev, "pci", -1); return bus_generic_attach(dev); } int legacy_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { switch (which) { case PCIB_IVAR_DOMAIN: *result = 0; return 0; case PCIB_IVAR_BUS: *result = legacy_get_pcibus(dev); return 0; } return ENOENT; } int legacy_pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { switch (which) { case PCIB_IVAR_DOMAIN: return EINVAL; case PCIB_IVAR_BUS: legacy_set_pcibus(dev, value); return 0; } return ENOENT; } /* * Helper routine for x86 Host-PCI bridge driver resource allocation. * This is used to adjust the start address of wildcard allocation * requests to avoid low addresses that are known to be problematic. * * If no memory preference is given, use upper 32MB slot most BIOSes * use for their memory window. This is typically only used on older * laptops that don't have PCI buses behind a PCI bridge, so assuming * > 32MB is likely OK. * * However, this can cause problems for other chipsets, so we make * this tunable by hw.pci.host_mem_start. */ SYSCTL_DECL(_hw_pci); static unsigned long host_mem_start = 0x80000000; SYSCTL_ULONG(_hw_pci, OID_AUTO, host_mem_start, CTLFLAG_RDTUN, &host_mem_start, 0, "Limit the host bridge memory to being above this address."); rman_res_t hostb_alloc_start(int type, rman_res_t start, rman_res_t end, rman_res_t count) { if (start + count - 1 != end) { if (type == SYS_RES_MEMORY && start < host_mem_start) start = host_mem_start; if (type == SYS_RES_IOPORT && start < 0x1000) start = 0x1000; } return (start); } struct resource * legacy_pcib_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { #if defined(NEW_PCIB) && defined(PCI_RES_BUS) if (type == PCI_RES_BUS) return (pci_domain_alloc_bus(0, child, rid, start, end, count, flags)); #endif start = hostb_alloc_start(type, start, end, count); return (bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags)); } #if defined(NEW_PCIB) && defined(PCI_RES_BUS) int legacy_pcib_adjust_resource(device_t dev, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { if (type == PCI_RES_BUS) return (pci_domain_adjust_bus(0, child, r, start, end)); return (bus_generic_adjust_resource(dev, child, type, r, start, end)); } int legacy_pcib_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { if (type == PCI_RES_BUS) return (pci_domain_release_bus(0, child, rid, r)); return (bus_generic_release_resource(dev, child, type, rid, r)); } #endif static device_method_t legacy_pcib_methods[] = { /* Device interface */ DEVMETHOD(device_identify, legacy_pcib_identify), DEVMETHOD(device_probe, legacy_pcib_probe), DEVMETHOD(device_attach, legacy_pcib_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, legacy_pcib_read_ivar), DEVMETHOD(bus_write_ivar, legacy_pcib_write_ivar), DEVMETHOD(bus_alloc_resource, legacy_pcib_alloc_resource), #if defined(NEW_PCIB) && defined(PCI_RES_BUS) DEVMETHOD(bus_adjust_resource, legacy_pcib_adjust_resource), DEVMETHOD(bus_release_resource, legacy_pcib_release_resource), #else DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), #endif DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), /* pcib interface */ DEVMETHOD(pcib_maxslots, legacy_pcib_maxslots), DEVMETHOD(pcib_read_config, legacy_pcib_read_config), DEVMETHOD(pcib_write_config, legacy_pcib_write_config), DEVMETHOD(pcib_route_interrupt, legacy_pcib_route_interrupt), DEVMETHOD(pcib_alloc_msi, legacy_pcib_alloc_msi), DEVMETHOD(pcib_release_msi, pcib_release_msi), DEVMETHOD(pcib_alloc_msix, legacy_pcib_alloc_msix), DEVMETHOD(pcib_release_msix, pcib_release_msix), DEVMETHOD(pcib_map_msi, legacy_pcib_map_msi), DEVMETHOD(pcib_request_feature, pcib_request_feature_allow), DEVMETHOD_END }; DEFINE_CLASS_0(pcib, legacy_pcib_driver, legacy_pcib_methods, 1); DRIVER_MODULE(pcib, legacy, legacy_pcib_driver, 0, 0); /* * Install placeholder to claim the resources owned by the * PCI bus interface. This could be used to extract the * config space registers in the extreme case where the PnP * ID is available and the PCI BIOS isn't, but for now we just * eat the PnP ID and do nothing else. * * we silence this probe, as it will generally confuse people. */ static struct isa_pnp_id pcibus_pnp_ids[] = { { 0x030ad041 /* PNP0A03 */, "PCI Bus" }, { 0x080ad041 /* PNP0A08 */, "PCIe Bus" }, { 0 } }; static int pcibus_pnp_probe(device_t dev) { int result; if ((result = ISA_PNP_PROBE(device_get_parent(dev), dev, pcibus_pnp_ids)) <= 0) device_quiet(dev); return(result); } static int pcibus_pnp_attach(device_t dev) { return(0); } static device_method_t pcibus_pnp_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pcibus_pnp_probe), DEVMETHOD(device_attach, pcibus_pnp_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), { 0, 0 } }; DEFINE_CLASS_0(pcibus_pnp, pcibus_pnp_driver, pcibus_pnp_methods, 1); DRIVER_MODULE(pcibus_pnp, isa, pcibus_pnp_driver, 0, 0); #ifdef __HAVE_PIR /* * Provide a PCI-PCI bridge driver for PCI buses behind PCI-PCI bridges * that appear in the PCIBIOS Interrupt Routing Table to use the routing * table for interrupt routing when possible. */ static int pcibios_pcib_probe(device_t bus); static device_method_t pcibios_pcib_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pcibios_pcib_probe), /* pcib interface */ DEVMETHOD(pcib_route_interrupt, legacy_pcib_route_interrupt), {0, 0} }; DEFINE_CLASS_1(pcib, pcibios_pcib_driver, pcibios_pcib_pci_methods, sizeof(struct pcib_softc), pcib_driver); DRIVER_MODULE(pcibios_pcib, pci, pcibios_pcib_driver, 0, 0); ISA_PNP_INFO(pcibus_pnp_ids); static int pcibios_pcib_probe(device_t dev) { int bus; if ((pci_get_class(dev) != PCIC_BRIDGE) || (pci_get_subclass(dev) != PCIS_BRIDGE_PCI)) return (ENXIO); bus = pci_read_config(dev, PCIR_SECBUS_1, 1); if (bus == 0) return (ENXIO); if (!pci_pir_probe(bus, 1)) return (ENXIO); device_set_desc(dev, "PCIBIOS PCI-PCI bridge"); return (-2000); } #endif diff --git a/sys/x86/pci/pci_early_quirks.c b/sys/x86/pci/pci_early_quirks.c index 1786ae2cb668..40340c76005a 100644 --- a/sys/x86/pci/pci_early_quirks.c +++ b/sys/x86/pci/pci_early_quirks.c @@ -1,322 +1,323 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 Johannes Lundberg * * 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 #include #include #include #include #include #include #include /* XXX: enable this once the KPI is available */ /* #include */ #include #include #include #include #include #define MiB(v) ((unsigned long)(v) << 20) struct pci_device_id { uint32_t vendor; uint32_t device; const struct intel_stolen_ops *data; }; /* * These global variables are read by LinuxKPI. * LinuxKPI provide this information to the i915 driver. */ vm_paddr_t intel_graphics_stolen_base = 0; vm_paddr_t intel_graphics_stolen_size = 0; SYSCTL_U64(_hw, OID_AUTO, intel_graphics_stolen_base, CTLFLAG_RD, &intel_graphics_stolen_base, 0, "Base address of the intel graphics stolen memory."); SYSCTL_U64(_hw, OID_AUTO, intel_graphics_stolen_size, CTLFLAG_RD, &intel_graphics_stolen_size, 0, "Size of the intel graphics stolen memory."); /* * Intel early quirks functions */ static vm_paddr_t -intel_stolen_base_gen3(int bus, int slot, int func) +intel_stolen_base_gen3(int domain, int bus, int slot, int func) { uint32_t ctrl; vm_paddr_t val; - ctrl = pci_cfgregread(bus, slot, func, INTEL_BSM, 4); + ctrl = pci_cfgregread(domain, bus, slot, func, INTEL_BSM, 4); val = ctrl & INTEL_BSM_MASK; return (val); } static vm_paddr_t -intel_stolen_base_gen11(int bus, int slot, int func) +intel_stolen_base_gen11(int domain, int bus, int slot, int func) { uint32_t ctrl; vm_paddr_t val; - ctrl = pci_cfgregread(bus, slot, func, INTEL_GEN11_BSM_DW0, 4); + ctrl = pci_cfgregread(domain, bus, slot, func, INTEL_GEN11_BSM_DW0, 4); val = ctrl & INTEL_BSM_MASK; val |= (uint64_t)pci_cfgregread( - bus, slot, func, INTEL_GEN11_BSM_DW1, 4) << 32; + domain, bus, slot, func, INTEL_GEN11_BSM_DW1, 4) << 32; return (val); } static vm_paddr_t -intel_stolen_size_gen3(int bus, int slot, int func) +intel_stolen_size_gen3(int domain, int bus, int slot, int func) { uint32_t ctrl; vm_paddr_t val; - ctrl = pci_cfgregread(0, 0, 0, I830_GMCH_CTRL, 2); + ctrl = pci_cfgregread(0, 0, 0, 0, I830_GMCH_CTRL, 2); val = ctrl & I855_GMCH_GMS_MASK; switch (val) { case I855_GMCH_GMS_STOLEN_1M: return (MiB(1)); case I855_GMCH_GMS_STOLEN_4M: return (MiB(4)); case I855_GMCH_GMS_STOLEN_8M: return (MiB(8)); case I855_GMCH_GMS_STOLEN_16M: return (MiB(16)); case I855_GMCH_GMS_STOLEN_32M: return (MiB(32)); case I915_GMCH_GMS_STOLEN_48M: return (MiB(48)); case I915_GMCH_GMS_STOLEN_64M: return (MiB(64)); case G33_GMCH_GMS_STOLEN_128M: return (MiB(128)); case G33_GMCH_GMS_STOLEN_256M: return (MiB(256)); case INTEL_GMCH_GMS_STOLEN_96M: return (MiB(96)); case INTEL_GMCH_GMS_STOLEN_160M: return (MiB(160)); case INTEL_GMCH_GMS_STOLEN_224M: return (MiB(224)); case INTEL_GMCH_GMS_STOLEN_352M: return (MiB(352)); } return (0); } static vm_paddr_t -intel_stolen_size_gen6(int bus, int slot, int func) +intel_stolen_size_gen6(int domain, int bus, int slot, int func) { uint32_t ctrl; vm_paddr_t val; - ctrl = pci_cfgregread(bus, slot, func, SNB_GMCH_CTRL, 2); + ctrl = pci_cfgregread(domain, bus, slot, func, SNB_GMCH_CTRL, 2); val = (ctrl >> SNB_GMCH_GMS_SHIFT) & SNB_GMCH_GMS_MASK; return (val * MiB(32)); } static vm_paddr_t -intel_stolen_size_gen8(int bus, int slot, int func) +intel_stolen_size_gen8(int domain, int bus, int slot, int func) { uint32_t ctrl; vm_paddr_t val; - ctrl = pci_cfgregread(bus, slot, func, SNB_GMCH_CTRL, 2); + ctrl = pci_cfgregread(domain, bus, slot, func, SNB_GMCH_CTRL, 2); val = (ctrl >> BDW_GMCH_GMS_SHIFT) & BDW_GMCH_GMS_MASK; return (val * MiB(32)); } static vm_paddr_t -intel_stolen_size_chv(int bus, int slot, int func) +intel_stolen_size_chv(int domain, int bus, int slot, int func) { uint32_t ctrl; vm_paddr_t val; - ctrl = pci_cfgregread(bus, slot, func, SNB_GMCH_CTRL, 2); + ctrl = pci_cfgregread(domain, bus, slot, func, SNB_GMCH_CTRL, 2); val = (ctrl >> SNB_GMCH_GMS_SHIFT) & SNB_GMCH_GMS_MASK; /* * 0x0 to 0x10: 32MB increments starting at 0MB * 0x11 to 0x16: 4MB increments starting at 8MB * 0x17 to 0x1d: 4MB increments start at 36MB */ if (val < 0x11) return (val * MiB(32)); else if (val < 0x17) return ((val - 0x11) * MiB(4) + MiB(8)); else return ((val - 0x17) * MiB(4) + MiB(36)); } static vm_paddr_t -intel_stolen_size_gen9(int bus, int slot, int func) +intel_stolen_size_gen9(int domain, int bus, int slot, int func) { uint32_t ctrl; vm_paddr_t val; - ctrl = pci_cfgregread(bus, slot, func, SNB_GMCH_CTRL, 2); + ctrl = pci_cfgregread(domain, bus, slot, func, SNB_GMCH_CTRL, 2); val = (ctrl >> BDW_GMCH_GMS_SHIFT) & BDW_GMCH_GMS_MASK; /* 0x0 to 0xEF: 32MB increments starting at 0MB */ /* 0xF0 to 0xFE: 4MB increments starting at 4MB */ if (val < 0xF0) return (val * MiB(32)); return ((val - 0xF0) * MiB(4) + MiB(4)); } struct intel_stolen_ops { - vm_paddr_t (*base)(int bus, int slot, int func); - vm_paddr_t (*size)(int bus, int slot, int func); + vm_paddr_t (*base)(int domain, int bus, int slot, int func); + vm_paddr_t (*size)(int domain, int bus, int slot, int func); }; static const struct intel_stolen_ops intel_stolen_ops_gen3 = { .base = intel_stolen_base_gen3, .size = intel_stolen_size_gen3, }; static const struct intel_stolen_ops intel_stolen_ops_gen6 = { .base = intel_stolen_base_gen3, .size = intel_stolen_size_gen6, }; static const struct intel_stolen_ops intel_stolen_ops_gen8 = { .base = intel_stolen_base_gen3, .size = intel_stolen_size_gen8, }; static const struct intel_stolen_ops intel_stolen_ops_gen9 = { .base = intel_stolen_base_gen3, .size = intel_stolen_size_gen9, }; static const struct intel_stolen_ops intel_stolen_ops_chv = { .base = intel_stolen_base_gen3, .size = intel_stolen_size_chv, }; static const struct intel_stolen_ops intel_stolen_ops_gen11 = { .base = intel_stolen_base_gen11, .size = intel_stolen_size_gen9, }; static const struct pci_device_id intel_ids[] = { INTEL_I915G_IDS(&intel_stolen_ops_gen3), INTEL_I915GM_IDS(&intel_stolen_ops_gen3), INTEL_I945G_IDS(&intel_stolen_ops_gen3), INTEL_I945GM_IDS(&intel_stolen_ops_gen3), INTEL_VLV_IDS(&intel_stolen_ops_gen6), INTEL_PINEVIEW_IDS(&intel_stolen_ops_gen3), INTEL_I965G_IDS(&intel_stolen_ops_gen3), INTEL_G33_IDS(&intel_stolen_ops_gen3), INTEL_I965GM_IDS(&intel_stolen_ops_gen3), INTEL_GM45_IDS(&intel_stolen_ops_gen3), INTEL_G45_IDS(&intel_stolen_ops_gen3), INTEL_IRONLAKE_D_IDS(&intel_stolen_ops_gen3), INTEL_IRONLAKE_M_IDS(&intel_stolen_ops_gen3), INTEL_SNB_D_IDS(&intel_stolen_ops_gen6), INTEL_SNB_M_IDS(&intel_stolen_ops_gen6), INTEL_IVB_M_IDS(&intel_stolen_ops_gen6), INTEL_IVB_D_IDS(&intel_stolen_ops_gen6), INTEL_HSW_IDS(&intel_stolen_ops_gen6), INTEL_BDW_IDS(&intel_stolen_ops_gen8), INTEL_CHV_IDS(&intel_stolen_ops_chv), INTEL_SKL_IDS(&intel_stolen_ops_gen9), INTEL_BXT_IDS(&intel_stolen_ops_gen9), INTEL_KBL_IDS(&intel_stolen_ops_gen9), INTEL_CFL_IDS(&intel_stolen_ops_gen9), INTEL_GLK_IDS(&intel_stolen_ops_gen9), INTEL_CNL_IDS(&intel_stolen_ops_gen9), INTEL_ICL_11_IDS(&intel_stolen_ops_gen11), INTEL_EHL_IDS(&intel_stolen_ops_gen11), INTEL_JSL_IDS(&intel_stolen_ops_gen11), INTEL_TGL_12_IDS(&intel_stolen_ops_gen11), INTEL_RKL_IDS(&intel_stolen_ops_gen11), INTEL_ADLS_IDS(&intel_stolen_ops_gen11), INTEL_ADLP_IDS(&intel_stolen_ops_gen11), INTEL_RPLS_IDS(&intel_stolen_ops_gen11), }; /* * Buggy BIOS don't reserve memory for the GPU properly and the OS * can claim it before the GPU driver is loaded. This function will * check the registers for base and size of this memory and reserve * it for the GPU driver. * gen3 (2004) and newer devices are supported. Support for older hw * can be ported from Linux if needed. */ static void intel_graphics_stolen(void) { const struct intel_stolen_ops *ops; uint32_t vendor, device, class; int i; - /* XXX: Scan bus instead of assuming 0:2:0? */ + /* XXX: Scan bus instead of assuming 0:0:2:0? */ + const int domain = 0; const int bus = 0; const int slot = 2; const int func = 0; if (pci_cfgregopen() == 0) return; - vendor = pci_cfgregread(bus, slot, func, PCIR_VENDOR, 2); + vendor = pci_cfgregread(domain, bus, slot, func, PCIR_VENDOR, 2); if (vendor != PCI_VENDOR_INTEL) return; - class = pci_cfgregread(bus, slot, func, PCIR_SUBCLASS, 2); + class = pci_cfgregread(domain, bus, slot, func, PCIR_SUBCLASS, 2); if (class != PCI_CLASS_VGA) return; - device = pci_cfgregread(bus, slot, func, PCIR_DEVICE, 2); + device = pci_cfgregread(domain, bus, slot, func, PCIR_DEVICE, 2); if (device == 0xFFFF) return; for (i = 0; i < nitems(intel_ids); i++) { if (intel_ids[i].device != device) continue; ops = intel_ids[i].data; - intel_graphics_stolen_base = ops->base(bus, slot, func); - intel_graphics_stolen_size = ops->size(bus, slot, func); + intel_graphics_stolen_base = ops->base(domain, bus, slot, func); + intel_graphics_stolen_size = ops->size(domain, bus, slot, func); break; } /* XXX: enable this once the KPI is available */ /* phys_avail_reserve(intel_graphics_stolen_base, */ /* intel_graphics_stolen_base + intel_graphics_stolen_size); */ } void pci_early_quirks(void) { TSENTER(); intel_graphics_stolen(); TSEXIT(); } diff --git a/sys/x86/pci/qpi.c b/sys/x86/pci/qpi.c index 4272e1866b6a..6aea8992a420 100644 --- a/sys/x86/pci/qpi.c +++ b/sys/x86/pci/qpi.c @@ -1,311 +1,311 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2010 Hudson River Trading LLC * Written by: John H. Baldwin * All rights reserved. * * 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. */ /* * This driver provides a pseudo-bus to enumerate the PCI buses * present on a system using a QPI chipset. It creates a qpi0 bus that * is a child of nexus0 and then creates Host-PCI bridges as a * child of that. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" struct qpi_device { int qd_pcibus; }; static MALLOC_DEFINE(M_QPI, "qpidrv", "qpi system device"); static void qpi_identify(driver_t *driver, device_t parent) { int do_qpi; /* Check CPUID to ensure this is an i7 CPU of some sort. */ if (cpu_vendor_id != CPU_VENDOR_INTEL || CPUID_TO_FAMILY(cpu_id) != 0x6) return; /* Only discover buses with configuration devices if allowed by user */ do_qpi = 0; TUNABLE_INT_FETCH("hw.attach_intel_csr_pci", &do_qpi); if (!do_qpi) return; /* PCI config register access is required. */ if (pci_cfgregopen() == 0) return; /* Add a qpi bus device. */ if (BUS_ADD_CHILD(parent, 20, "qpi", -1) == NULL) panic("Failed to add qpi bus"); } static int qpi_probe(device_t dev) { device_set_desc(dev, "QPI system bus"); return (BUS_PROBE_SPECIFIC); } /* * Look for a PCI bus with the specified bus address. If one is found, * add a pcib device and return 0. Otherwise, return an error code. */ static int qpi_probe_pcib(device_t dev, int bus) { struct qpi_device *qdev; device_t child; uint32_t devid; int s; /* * If a PCI bus already exists for this bus number, then * fail. */ if (pci_find_bsf(bus, 0, 0) != NULL) return (EEXIST); /* * Attempt to read the device id for every slot, function 0 on * the bus. If all read values are 0xffffffff this means that * the bus is not present. */ for (s = 0; s <= PCI_SLOTMAX; s++) { - devid = pci_cfgregread(bus, s, 0, PCIR_DEVVENDOR, 4); + devid = pci_cfgregread(0, bus, s, 0, PCIR_DEVVENDOR, 4); if (devid != 0xffffffff) break; } if (devid == 0xffffffff) return (ENOENT); if ((devid & 0xffff) != 0x8086) { if (bootverbose) device_printf(dev, "Device at pci%d.%d.0 has non-Intel vendor 0x%x\n", bus, s, devid & 0xffff); return (ENXIO); } child = BUS_ADD_CHILD(dev, 0, "pcib", -1); if (child == NULL) panic("%s: failed to add pci bus %d", device_get_nameunit(dev), bus); qdev = malloc(sizeof(struct qpi_device), M_QPI, M_WAITOK); qdev->qd_pcibus = bus; device_set_ivars(child, qdev); return (0); } static int qpi_attach(device_t dev) { int bus; /* * Each processor socket has a dedicated PCI bus, sometimes * not enumerated by ACPI. Probe all unattached buses from 0 * to 255. */ for (bus = PCI_BUSMAX; bus >= 0; bus--) qpi_probe_pcib(dev, bus); return (bus_generic_attach(dev)); } static int qpi_print_child(device_t bus, device_t child) { struct qpi_device *qdev; int retval = 0; qdev = device_get_ivars(child); retval += bus_print_child_header(bus, child); if (qdev->qd_pcibus != -1) retval += printf(" pcibus %d", qdev->qd_pcibus); retval += bus_print_child_footer(bus, child); return (retval); } static int qpi_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct qpi_device *qdev; qdev = device_get_ivars(child); switch (which) { case PCIB_IVAR_BUS: *result = qdev->qd_pcibus; break; default: return (ENOENT); } return (0); } static device_method_t qpi_methods[] = { /* Device interface */ DEVMETHOD(device_identify, qpi_identify), DEVMETHOD(device_probe, qpi_probe), DEVMETHOD(device_attach, qpi_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, qpi_print_child), DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_read_ivar, qpi_read_ivar), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), { 0, 0 } }; DEFINE_CLASS_0(qpi, qpi_driver, qpi_methods, 0); DRIVER_MODULE(qpi, nexus, qpi_driver, 0, 0); static int qpi_pcib_probe(device_t dev) { device_set_desc(dev, "QPI Host-PCI bridge"); return (BUS_PROBE_SPECIFIC); } static int qpi_pcib_attach(device_t dev) { device_add_child(dev, "pci", -1); return (bus_generic_attach(dev)); } static int qpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { switch (which) { case PCIB_IVAR_DOMAIN: *result = 0; return (0); case PCIB_IVAR_BUS: *result = pcib_get_bus(dev); return (0); default: return (ENOENT); } } #if defined(NEW_PCIB) && defined(PCI_RES_BUS) static struct resource * qpi_pcib_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { if (type == PCI_RES_BUS) return (pci_domain_alloc_bus(0, child, rid, start, end, count, flags)); return (bus_generic_alloc_resource(dev, child, type, rid, start, end, count, flags)); } #endif static int qpi_pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data) { device_t bus; bus = device_get_parent(pcib); return (PCIB_MAP_MSI(device_get_parent(bus), dev, irq, addr, data)); } static device_method_t qpi_pcib_methods[] = { /* Device interface */ DEVMETHOD(device_probe, qpi_pcib_probe), DEVMETHOD(device_attach, qpi_pcib_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, qpi_pcib_read_ivar), #if defined(NEW_PCIB) && defined(PCI_RES_BUS) DEVMETHOD(bus_alloc_resource, qpi_pcib_alloc_resource), DEVMETHOD(bus_adjust_resource, legacy_pcib_adjust_resource), DEVMETHOD(bus_release_resource, legacy_pcib_release_resource), #else DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), #endif DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), /* pcib interface */ DEVMETHOD(pcib_maxslots, pcib_maxslots), DEVMETHOD(pcib_read_config, legacy_pcib_read_config), DEVMETHOD(pcib_write_config, legacy_pcib_write_config), DEVMETHOD(pcib_alloc_msi, legacy_pcib_alloc_msi), DEVMETHOD(pcib_release_msi, pcib_release_msi), DEVMETHOD(pcib_alloc_msix, legacy_pcib_alloc_msix), DEVMETHOD(pcib_release_msix, pcib_release_msix), DEVMETHOD(pcib_map_msi, qpi_pcib_map_msi), DEVMETHOD_END }; DEFINE_CLASS_0(pcib, qpi_pcib_driver, qpi_pcib_methods, 0); DRIVER_MODULE(pcib, qpi, qpi_pcib_driver, 0, 0); diff --git a/sys/x86/x86/legacy.c b/sys/x86/x86/legacy.c index 1421279551d0..4e76e5b674a9 100644 --- a/sys/x86/x86/legacy.c +++ b/sys/x86/x86/legacy.c @@ -1,386 +1,386 @@ /*- * Copyright 1998 Massachusetts Institute of Technology * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that both the above copyright notice and this * permission notice appear in all copies, that both the above * copyright notice and this permission notice appear in all * supporting documentation, and that the name of M.I.T. not be used * in advertising or publicity pertaining to distribution of the * software without specific, written prior permission. M.I.T. makes * no representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied * warranty. * * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT * SHALL M.I.T. 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 /* * This code implements a system driver for legacy systems that do not * support ACPI or when ACPI support is not present in the kernel. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_LEGACYDEV, "legacydrv", "legacy system device"); struct legacy_device { int lg_pcibus; int lg_pcislot; int lg_pcifunc; }; #define DEVTOAT(dev) ((struct legacy_device *)device_get_ivars(dev)) static int legacy_probe(device_t); static int legacy_attach(device_t); static int legacy_print_child(device_t, device_t); static device_t legacy_add_child(device_t bus, u_int order, const char *name, int unit); static int legacy_read_ivar(device_t, device_t, int, uintptr_t *); static int legacy_write_ivar(device_t, device_t, int, uintptr_t); static device_method_t legacy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, legacy_probe), DEVMETHOD(device_attach, legacy_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_print_child, legacy_print_child), DEVMETHOD(bus_add_child, legacy_add_child), DEVMETHOD(bus_read_ivar, legacy_read_ivar), DEVMETHOD(bus_write_ivar, legacy_write_ivar), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), { 0, 0 } }; static driver_t legacy_driver = { "legacy", legacy_methods, 1, /* no softc */ }; DRIVER_MODULE(legacy, nexus, legacy_driver, 0, 0); static int legacy_probe(device_t dev) { device_set_desc(dev, "legacy system"); device_quiet(dev); return (0); } /* * Grope around in the PCI config space to see if this is a chipset * that is capable of doing memory-mapped config cycles. This also * implies that it can do PCIe extended config cycles. */ static void legacy_pci_cfgregopen(device_t dev) { uint64_t pciebar; u_int16_t did, vid; if (cfgmech == CFGMECH_NONE || cfgmech == CFGMECH_PCIE) return; /* Check for supported chipsets */ - vid = pci_cfgregread(0, 0, 0, PCIR_VENDOR, 2); - did = pci_cfgregread(0, 0, 0, PCIR_DEVICE, 2); + vid = pci_cfgregread(0, 0, 0, 0, PCIR_VENDOR, 2); + did = pci_cfgregread(0, 0, 0, 0, PCIR_DEVICE, 2); switch (vid) { case 0x8086: switch (did) { case 0x3590: case 0x3592: /* Intel 7520 or 7320 */ - pciebar = pci_cfgregread(0, 0, 0, 0xce, 2) << 16; + pciebar = pci_cfgregread(0, 0, 0, 0, 0xce, 2) << 16; pcie_cfgregopen(pciebar, 0, 255); break; case 0x2580: case 0x2584: case 0x2590: /* Intel 915, 925, or 915GM */ - pciebar = pci_cfgregread(0, 0, 0, 0x48, 4); + pciebar = pci_cfgregread(0, 0, 0, 0, 0x48, 4); pcie_cfgregopen(pciebar, 0, 255); break; } } if (bootverbose && cfgmech == CFGMECH_PCIE) device_printf(dev, "Enabled ECAM PCIe accesses\n"); } static int legacy_attach(device_t dev) { device_t child; legacy_pci_cfgregopen(dev); /* * Let our child drivers identify any child devices that they * can find. Once that is done attach any devices that we * found. */ bus_generic_probe(dev); bus_generic_attach(dev); /* * If we didn't see ISA on a PCI bridge, add a top-level bus. */ if (!devclass_get_device(devclass_find("isa"), 0)) { child = BUS_ADD_CHILD(dev, 0, "isa", 0); if (child == NULL) panic("legacy_attach isa"); device_probe_and_attach(child); } return 0; } static int legacy_print_child(device_t bus, device_t child) { struct legacy_device *atdev = DEVTOAT(child); int retval = 0; retval += bus_print_child_header(bus, child); if (atdev->lg_pcibus != -1) retval += printf(" pcibus %d", atdev->lg_pcibus); retval += printf("\n"); return (retval); } static device_t legacy_add_child(device_t bus, u_int order, const char *name, int unit) { device_t child; struct legacy_device *atdev; atdev = malloc(sizeof(struct legacy_device), M_LEGACYDEV, M_NOWAIT | M_ZERO); if (atdev == NULL) return(NULL); atdev->lg_pcibus = -1; atdev->lg_pcislot = -1; atdev->lg_pcifunc = -1; child = device_add_child_ordered(bus, order, name, unit); if (child == NULL) free(atdev, M_LEGACYDEV); else /* should we free this in legacy_child_detached? */ device_set_ivars(child, atdev); return (child); } static int legacy_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct legacy_device *atdev = DEVTOAT(child); switch (which) { case LEGACY_IVAR_PCIDOMAIN: *result = 0; break; case LEGACY_IVAR_PCIBUS: *result = atdev->lg_pcibus; break; case LEGACY_IVAR_PCISLOT: *result = atdev->lg_pcislot; break; case LEGACY_IVAR_PCIFUNC: *result = atdev->lg_pcifunc; break; default: return ENOENT; } return 0; } static int legacy_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct legacy_device *atdev = DEVTOAT(child); switch (which) { case LEGACY_IVAR_PCIDOMAIN: return EINVAL; case LEGACY_IVAR_PCIBUS: atdev->lg_pcibus = value; break; case LEGACY_IVAR_PCISLOT: atdev->lg_pcislot = value; break; case LEGACY_IVAR_PCIFUNC: atdev->lg_pcifunc = value; break; default: return ENOENT; } return 0; } /* * Legacy CPU attachment when ACPI is not available. Drivers like * cpufreq(4) hang off this. */ static void cpu_identify(driver_t *driver, device_t parent); static int cpu_read_ivar(device_t dev, device_t child, int index, uintptr_t *result); static device_t cpu_add_child(device_t bus, u_int order, const char *name, int unit); static struct resource_list *cpu_get_rlist(device_t dev, device_t child); struct cpu_device { struct resource_list cd_rl; struct pcpu *cd_pcpu; }; static device_method_t cpu_methods[] = { /* Device interface */ DEVMETHOD(device_identify, cpu_identify), DEVMETHOD(device_probe, bus_generic_probe), DEVMETHOD(device_attach, bus_generic_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_add_child, cpu_add_child), DEVMETHOD(bus_read_ivar, cpu_read_ivar), DEVMETHOD(bus_get_resource_list, cpu_get_rlist), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t cpu_driver = { "cpu", cpu_methods, 1, /* no softc */ }; DRIVER_MODULE(cpu, legacy, cpu_driver, 0, 0); static void cpu_identify(driver_t *driver, device_t parent) { device_t child; int i; /* * Attach a cpuX device for each CPU. We use an order of 150 * so that these devices are attached after the Host-PCI * bridges (which are added at order 100). */ CPU_FOREACH(i) { child = BUS_ADD_CHILD(parent, 150, "cpu", i); if (child == NULL) panic("legacy_attach cpu"); } } static device_t cpu_add_child(device_t bus, u_int order, const char *name, int unit) { struct cpu_device *cd; device_t child; struct pcpu *pc; if ((cd = malloc(sizeof(*cd), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) return (NULL); resource_list_init(&cd->cd_rl); pc = pcpu_find(device_get_unit(bus)); cd->cd_pcpu = pc; child = device_add_child_ordered(bus, order, name, unit); if (child != NULL) { pc->pc_device = child; device_set_ivars(child, cd); } else free(cd, M_DEVBUF); return (child); } static struct resource_list * cpu_get_rlist(device_t dev, device_t child) { struct cpu_device *cpdev; cpdev = device_get_ivars(child); return (&cpdev->cd_rl); } static int cpu_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { struct cpu_device *cpdev; switch (index) { case CPU_IVAR_PCPU: cpdev = device_get_ivars(child); *result = (uintptr_t)cpdev->cd_pcpu; break; case CPU_IVAR_NOMINAL_MHZ: if (tsc_is_invariant) { *result = (uintptr_t)(atomic_load_acq_64(&tsc_freq) / 1000000); break; } /* FALLTHROUGH */ default: return (ENOENT); } return (0); }