diff --git a/sys/amd64/conf/NOTES b/sys/amd64/conf/NOTES --- a/sys/amd64/conf/NOTES +++ b/sys/amd64/conf/NOTES @@ -464,6 +464,10 @@ # PMC-Sierra SAS/SATA controller device pmspcv +# +# Intel Pondicherry2 memory controller +device pnd2_edac + # # Intel QuickAssist driver with OpenCrypto support # diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -384,6 +384,7 @@ dev/p2sb/p2sb.c optional p2sb pci dev/p2sb/lewisburg_gpiocm.c optional lbggpiocm p2sb dev/p2sb/lewisburg_gpio.c optional lbggpio lbggpiocm +dev/pnd2_edac/pnd2_edac.c optional pnd2_edac isa/syscons_isa.c optional sc isa/vga_isa.c optional vga kern/imgact_aout.c optional compat_aout diff --git a/sys/dev/pnd2_edac/pnd2_edac.c b/sys/dev/pnd2_edac/pnd2_edac.c new file mode 100644 --- /dev/null +++ b/sys/dev/pnd2_edac/pnd2_edac.c @@ -0,0 +1,662 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * Copyright (c) 2020 Semihalf + * Copyright (c) 2020 Juniper Networks, Inc. + * + * 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. + * + * $FreeBSD$ + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#define BIT(x) (1 << (x)) +#define EXTRACT_BIT(x, y) (((x) >> (y)) & 1) + +#define PCICONF_MCHBAR_LOADDR 0x48 +#define PCICONF_MCHBAR_LOADDR_SHIFT 16 +#define PCICONF_MCHBAR_LOADDR_MASK (0xFFFF << PCICONF_MCHBAR_LOADDR_SHIFT) +#define PCICONF_MCHBAR_HIADDR 0x4C +#define PCICONF_MCHBAR_HIADDR_MASK 0xDF +#define PCICONF_TOOUD_LOADDR 0xA8 +#define PCICONF_TOOUD_LOADDR_SHIFT 20 +#define PCICONF_TOOUD_LOADDR_MASK ((BIT(12) - 1) << PCICONF_TOOUD_LOADDR_SHIFT) +#define PCICONF_TOOUD_HIADDR 0xAC +#define PCICONF_TOOUD_HIADDR_MASK (BIT(7) - 1) +#define PCICONF_TOLUD 0xBC +#define PCICONF_TOLUD_SHIFT 20 +#define PCICONF_TOLUD_MASK ((BIT(12) - 1) << PCICONF_TOLUD_SHIFT) +#define PCICONF_P2SB_LOADDR 0x10 +#define PCICONF_P2SB_LOADDR_MASK (0xFF << 24) +#define PCICONF_P2SB_HIADDR 0x14 + +#define P2SB_DMAP 0xB4 +#define P2SB_DMAP_BANKXOR BIT(6) + +#define P2SB_UCELOG 0x64 +#define P2SB_UCEADDR 0x68 +#define P2SB_SBELOG 0x6C +#define P2SB_SBEADDR 0x70 + +#define P2SB_SBECNT0 0x74 +#define P2SB_SBECNT(x) (P2SB_SBECNT0 + 4*(x)) +#define P2SB_SBECNT_MASK 0xFFFF + +#define P2SB_SBELOG1 0xD0 + +#define P2SB_DPATROL_SCRUB_CFG 0xB0 +#define P2SB_DPATROL_SCRUB_CFG_PAT_SCRUB_ENABLE BIT(31) + +#define P2SB_DERRSTS 0x124 + +#define P2SB_DERCNTSEL 0x12C +#define P2SB_DERCNTSEL_SELALL 0x1E + +#define P2SB_DERRCNT 0x130 + +#define P2SB_ERRINJCTL 0x134 +#define P2SB_ERRINJCTL_INJEN BIT(0) +#define P2SB_ERRINJCTL_INJONCE BIT(1) +#define P2SB_ERRINJCTL_INJADREN BIT(2) +#define P2SB_ERRINJCTL_INJSRCEN BIT(3) +#define P2SB_ERRINJCTL_INJECC BIT(4) +#define P2SB_ERRINJCTL_INJWRITE BIT(5) +#define P2SB_ERRINJCTL_INJREAD BIT(6) +#define P2SB_ERRINJCTL_INJPAT BIT(7) +#define P2SB_ERRINJCTL_INJECCPAR (0xFF << 24) + +#define P2SB_ERRINJADDR 0x138 +#define P2SB_ERRINJAMSK 0x13C +#define P2SB_ERRINJDATA0 0x140 +#define P2SB_ERRINJDATA1 0x144 + +#define P2SB_DRP 0x158 +#define P2SB_DRP_RKEN0 BIT(0) +#define P2SB_DRP_RKEN1 BIT(1) +#define P2SB_DRP_RKEN2 BIT(2) +#define P2SB_DRP_RKEN3 BIT(3) +#define P2SB_DRP_DIMMM0_WID (BIT(4) | BIT(5)) +#define P2SB_DRP_DIMMM1_WID (BIT(8) | BIT(9)) +#define P2SB_DRP_DIMM_FLIP BIT(16) + +#define P2SB_DMAP0 0x148 +#define P2SB_DMAP1 0x14C +#define P2SB_DMAP2 0x150 +#define P2SB_DMAPX_ROW(x, y) (((x) >> (5*(y))) & 0x1F) + +#define P2SB_DMAP3 0x154 +#define P2SB_DMAP3_COL(x, y) (((x) >> (4*(y))) & 0xF) + +#define P2SB_DMAP4 0x174 +#define P2SB_DMAP4_BNK0 0x1F +#define P2SB_DMAP4_BNK1_SHIFT 5 +#define P2SB_DMAP4_BNK1 (0x1F << P2SB_DMAP4_BNK1_SHIFT) +#define P2SB_DMAP4_BNK2_SHIFT 10 +#define P2SB_DMAP4_BNK2 (0x1F << P2SB_DMAP4_BNK2_SHIFT) +#define P2SB_DMAP4_BNK3_SHIFT 15 +#define P2SB_DMAP4_BNK3 (0x1F << P2SB_DMAP4_BNK3_SHIFT) +#define P2SB_DMAP4_RK0_SHIFT 20 +#define P2SB_DMAP4_RK0 (0x1F << P2SB_DMAP4_RK0_SHIFT) +#define P2SB_DMAP4_RK1_SHIFT 25 +#define P2SB_DMAP4_RK1 (0x1F << P2SB_DMAP4_RK1_SHIFT) + +#define P2SB_DECCTRL 0x180 +#define P2SB_DECCTRL_ECCEN BIT(0) + +#define P2SB_PCI_SLOT 0x1F +#define P2SB_PCI_FUNC 0x1 +#define P2SB_CTRL 0xE0 +#define P2SB_CTRL_HIDE BIT(8) + +#define P2SB_PORT10_OFFSET (0x10 << 16) +#define P2SB_PORT12_OFFSET (0x12 << 16) + +#define MCH_CHASH 0x65C0 +#define MCH_CHASH_SLICE1_DIS BIT(0) +#define MCH_CHASH_INTLV_SHIFT 1 +#define MCH_CHASH_INTLV_MASK (BIT(1) | BIT(2)) + +#define P2SB_PORT_SIZE 0x10000 +#define MCHBAR_SIZE 0x8000 + +#define DECODE_BASE_SHIFT 6 +#define DECODE_RANK_SHIFT 13 +#define ADDR_4GB (1ul << 32) + +#define P2SB_NUM_RANKS 4 + +#define P2SB_ERR_MULTIBIT 2 +#define P2SB_ERR_SINGLEBIT 1 + +struct pnd2_slice { + uint8_t rank0_shift; /* Which bit in pmi corresponds to dram rank. */ + uint8_t rank1_shift; + + bool dimm_flip; + + uint8_t bank0_shift; + uint8_t bank1_shift; + uint8_t bank2_shift; + uint8_t bank3_shift; + bool bank_xor; + + uint8_t row_shifts[18]; + + uint8_t col_shifts[7]; +}; + +struct dram_pos { + int channel; + int dimm; + int rank; + int bank; + int row; + int col; +}; + +struct pnd2_softc { + device_t dev; + + struct resource *sc_mch_res; + struct resource *sc_p2sb_res; + + uint64_t sc_max_low_addr; + uint64_t sc_max_high_addr; + + uint8_t sc_slice_bit; + bool sc_two_slices; + + struct pnd2_slice sc_slice_desc; + + uint64_t sc_ce_count; +}; + +static int pnd2_detect_dimm_geom(struct pnd2_softc *); +static int pnd2_decode_pmi(struct pnd2_softc *, uint64_t, uint64_t *); +static void pnd2_decode_geom(struct pnd2_softc *, uint64_t, struct dram_pos *); +static int pnd2_sysctl_inject(SYSCTL_HANDLER_ARGS); +static int pnd2_sysctl_scrub(SYSCTL_HANDLER_ARGS); + +static void pnd2_identify(driver_t *, device_t); +static int pnd2_probe(device_t); +static int pnd2_attach(device_t); +static int pnd2_detach(device_t); + +static int +pnd2_detect_dimm_geom(struct pnd2_softc *sc) +{ + uint64_t reg64; + uint32_t reg32; + int i; + + /* + * We have two windows of DRAM physical addresses: + * 1. [0, sc_max_low_addr) + * 2. [4GB, sc_max_high_addr) + */ + sc->sc_max_low_addr = pci_cfgregread(0, 0, 0, PCICONF_TOLUD, 4) & + PCICONF_TOLUD_MASK; + sc->sc_max_high_addr = pci_cfgregread(0, 0, 0, PCICONF_TOOUD_HIADDR, 4) & + PCICONF_TOOUD_HIADDR_MASK; + sc->sc_max_high_addr <<= 32; + sc->sc_max_high_addr |= pci_cfgregread(0, 0, 0, PCICONF_TOOUD_LOADDR, 4) & + PCICONF_TOOUD_LOADDR_MASK; + + reg64 = bus_read_8(sc->sc_mch_res, MCH_CHASH); + sc->sc_two_slices = ((reg64 & MCH_CHASH_SLICE1_DIS) == 0); + if (sc->sc_two_slices) { + device_printf(sc->dev, + "Two slices DIMM configuration is currently unsupported\n"); + return (ENXIO); + } + sc->sc_slice_bit = (reg64 & MCH_CHASH_INTLV_MASK) >> MCH_CHASH_INTLV_SHIFT; + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DECCTRL); + if ((reg32 & P2SB_DECCTRL_ECCEN) == 0) { + device_printf(sc->dev, "ECC has been disabled in firmware\n"); + return (ENXIO); + } + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DRP); + sc->sc_slice_desc.dimm_flip = (reg32 & P2SB_DRP_DIMM_FLIP) != 0; + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DMAP4); + sc->sc_slice_desc.rank0_shift = ((reg32 & P2SB_DMAP4_RK0) >> + P2SB_DMAP4_RK0_SHIFT) + DECODE_RANK_SHIFT; + sc->sc_slice_desc.rank1_shift = ((reg32 & P2SB_DMAP4_RK1) >> + P2SB_DMAP4_RK1_SHIFT) + DECODE_RANK_SHIFT; + sc->sc_slice_desc.bank0_shift = (reg32 & P2SB_DMAP4_BNK0) + + DECODE_BASE_SHIFT; + sc->sc_slice_desc.bank1_shift = ((reg32 & P2SB_DMAP4_BNK1) >> + P2SB_DMAP4_BNK1_SHIFT) + DECODE_BASE_SHIFT; + sc->sc_slice_desc.bank2_shift = ((reg32 & P2SB_DMAP4_BNK2) >> + P2SB_DMAP4_BNK2_SHIFT) + DECODE_BASE_SHIFT; + sc->sc_slice_desc.bank3_shift = ((reg32 & P2SB_DMAP4_BNK3) >> + P2SB_DMAP4_BNK3_SHIFT) + DECODE_BASE_SHIFT; + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DMAP); + sc->sc_slice_desc.bank_xor = (reg32 & P2SB_DMAP_BANKXOR) != 0; + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DMAP0); + for (i = 0; i < 6; i++) + sc->sc_slice_desc.row_shifts[i] = P2SB_DMAPX_ROW(reg32, i) + + DECODE_BASE_SHIFT; + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DMAP1); + for (i = 0; i < 6; i++) + sc->sc_slice_desc.row_shifts[i + 6] = P2SB_DMAPX_ROW(reg32, i) + + DECODE_BASE_SHIFT; + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DMAP2); + for (i = 0; i < 6; i++) + sc->sc_slice_desc.row_shifts[i + 12] = P2SB_DMAPX_ROW(reg32, i) + + DECODE_BASE_SHIFT; + + reg32 = bus_read_4(sc->sc_p2sb_res, P2SB_DMAP3); + for (i = 0; i < 7; i++) + sc->sc_slice_desc.col_shifts[i] = P2SB_DMAP3_COL(reg32, i) + + DECODE_BASE_SHIFT; + + return (0); +} + +static void +pnd2_decode_geom(struct pnd2_softc *sc, uint64_t pmiaddr, struct dram_pos *pos) +{ + struct pnd2_slice *desc = &sc->sc_slice_desc; + int i; + + pos->channel = 0; + + pos->rank = EXTRACT_BIT(pmiaddr, desc->rank0_shift); + pos->rank |= EXTRACT_BIT(pmiaddr, desc->rank1_shift) << 1; + + pos->dimm = (pos->rank >= 2) ^ desc->dimm_flip; + + pos->bank = EXTRACT_BIT(pmiaddr, desc->bank0_shift); + pos->bank |= EXTRACT_BIT(pmiaddr, desc->bank1_shift) << 1; + pos->bank |= EXTRACT_BIT(pmiaddr, desc->bank2_shift) << 2; + pos->bank |= EXTRACT_BIT(pmiaddr, desc->bank3_shift) << 3; + if (desc->bank_xor) { + pos->bank ^= EXTRACT_BIT(pmiaddr, desc->row_shifts[6]); + pos->bank ^= EXTRACT_BIT(pmiaddr, desc->row_shifts[7]) << 1; + pos->bank ^= EXTRACT_BIT(pmiaddr, desc->col_shifts[0]) << 2; + pos->bank ^= EXTRACT_BIT(pmiaddr, desc->row_shifts[2]) << 3; + } + + pos->row = 0; + for (i = 0; i < 18; i++) { + if (i >= 14 && desc->row_shifts[i] == (31 + DECODE_BASE_SHIFT)) + continue; + + pos->row |= EXTRACT_BIT(pmiaddr, desc->row_shifts[i]) << i; + } + + pos->col = 0; + for (i = 0; i < 7; i++) + pos->col |= EXTRACT_BIT(pmiaddr, desc->col_shifts[i]) << + (i + 3); +} + +static int +pnd2_decode_pmi(struct pnd2_softc *sc, uint64_t addr, uint64_t *pmiaddr) +{ + + if ((addr < ADDR_4GB && addr >= sc->sc_max_low_addr) || + addr > sc->sc_max_high_addr) + return (EINVAL); + + if (addr >= ADDR_4GB) + addr -= (ADDR_4GB - sc->sc_max_low_addr); + + *pmiaddr = addr; + + return (0); +} + +static int +pnd2_sysctl_inject(SYSCTL_HANDLER_ARGS) +{ + struct pnd2_softc *sc = (struct pnd2_softc*) arg1; + int type = 0; /* 1 - generate CE, 2 - UE */ + void *addr; + vm_offset_t pmiaddr; + uint32_t reg; + int error; + + error = sysctl_handle_int(oidp, &type, 0, req); + if (error || req->newptr == NULL) + return (error); + + if (!type || type > 2) + return (EINVAL); + + addr = kmem_alloc_contig(PAGE_SIZE, M_WAITOK | M_ZERO, + 0, ~0, PAGE_SIZE, 0, VM_MEMATTR_UNCACHEABLE); + error = pnd2_decode_pmi(sc, pmap_kextract((vm_offset_t)addr), &pmiaddr); + if (error != 0) + goto out; + + /* + * Bits 0:4 are specified in another register. + * Since we allocated page aligned memory there is no need + * to do that in our case. + * sc->sc_max_high_addr should guarantee that address fits in 36 bits. + * Higher addresses are used for DIMM1, which is not supported right now. + */ + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJADDR, pmiaddr >> 5); + bus_barrier(sc->sc_p2sb_res, P2SB_ERRINJADDR, 4, + BUS_SPACE_BARRIER_WRITE | BUS_SPACE_BARRIER_READ); + reg = bus_read_4(sc->sc_p2sb_res, P2SB_ERRINJADDR); + if (reg == 0) { + device_printf(sc->dev, "Error injection has been disabled in firmware."); + return (ENXIO); + } + + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJAMSK, 0); + if (type == P2SB_ERR_SINGLEBIT) + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJDATA0, 1); + else + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJDATA0, 3); + + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJDATA1, 0); + + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJCTL, + P2SB_ERRINJCTL_INJEN | P2SB_ERRINJCTL_INJONCE | + P2SB_ERRINJCTL_INJWRITE | P2SB_ERRINJCTL_INJADREN | + P2SB_ERRINJCTL_INJECC); + bus_barrier(sc->sc_p2sb_res, P2SB_ERRINJCTL, 4, + BUS_SPACE_BARRIER_WRITE | BUS_SPACE_BARRIER_READ); + + DELAY(10000); + *((volatile uint32_t*)addr) = 0xdeadbeef; + DELAY(10000); + if (*((volatile uint32_t*)addr) != 0xdeadbeef) + printf("Wrong value read after error injection.\n"); +out: + /* Just to be safe. */ + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJCTL, 0); + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJADDR, 0); + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJDATA0, 0); + kmem_free(addr, PAGE_SIZE); + return (error); + +} + +static int +pnd2_sysctl_scrub(SYSCTL_HANDLER_ARGS) +{ + struct pnd2_softc *sc = (struct pnd2_softc*) arg1; + int enable, error; + uint32_t reg; + + reg = bus_read_4(sc->sc_p2sb_res, P2SB_DPATROL_SCRUB_CFG); + enable = (reg & P2SB_DPATROL_SCRUB_CFG_PAT_SCRUB_ENABLE) ? 1 : 0; + + error = sysctl_handle_int(oidp, &enable, 0, req); + if (error || req->newptr == NULL) + return (error); + + if (enable > 1) + return (EINVAL); + + if (enable) + reg |= P2SB_DPATROL_SCRUB_CFG_PAT_SCRUB_ENABLE; + else + reg &= ~P2SB_DPATROL_SCRUB_CFG_PAT_SCRUB_ENABLE; + + bus_write_4(sc->sc_p2sb_res, P2SB_DPATROL_SCRUB_CFG, reg); + + return (0); +} + +static void +pnd2_cmc(void *aux, const struct mca_record *rec) +{ + struct pnd2_softc *sc = (struct pnd2_softc*)aux; + struct dram_pos pos; + uint64_t pmi; + uint32_t reg; + int i, error; + + for (i = 0; i < P2SB_NUM_RANKS; i++) { + reg = bus_read_4(sc->sc_p2sb_res, P2SB_SBECNT(i)) & + P2SB_SBECNT_MASK; + sc->sc_ce_count += reg; + bus_write_4(sc->sc_p2sb_res, P2SB_SBECNT(i), P2SB_SBECNT_MASK); + } + + if (rec->mr_addr == 0) + return; + + error = pnd2_decode_pmi(sc, rec->mr_addr, &pmi); + if (error != 0) + return; + pnd2_decode_geom(sc, pmi, &pos); + + printf("MCA: channel: %d, DIMM: %d, rank: 0x%x, bank: 0x%x," + " col: 0x%x, row: 0x%x\n", + pos.channel, pos.dimm, pos.rank, pos.bank, pos.col, pos.row); +} + +static void +pnd2_identify(driver_t *driver, device_t parent) +{ + uint64_t p2sb_base_addr, mch_base_addr; + uint32_t p2sb_ctrl; + device_t dev; + + if (device_find_child(parent, driver->name, 0) != NULL) + return; + + if (cpu_vendor_id != CPU_VENDOR_INTEL || + CPUID_TO_FAMILY(cpu_id) != 0x6 || /* Atom */ + CPUID_TO_MODEL(cpu_id) != 0x5F) /* Denverton */ + return; + + /* + * Get Primary to Side Band Bridge base address. + * If P2SB_CTRL_HIDE is set all reads from cfg space will return ffs. + * Setting/clearing is done by setting one byte of CTRL register, + * it has to be done that way since we don't know other bits in + * this register. It is safe as the adjacent 7 bits are reserved and + * writes to them is ignored. + */ + p2sb_ctrl = pci_cfgregread(0, P2SB_PCI_SLOT, P2SB_PCI_FUNC, P2SB_CTRL, + sizeof(uint32_t)); + if (p2sb_ctrl == 0xffffffff) + pci_cfgregwrite(0, P2SB_PCI_SLOT, P2SB_PCI_FUNC, P2SB_CTRL + 1, + 0, 1); + + p2sb_base_addr = pci_cfgregread(0, P2SB_PCI_SLOT, P2SB_PCI_FUNC, + PCICONF_P2SB_HIADDR, 4); + p2sb_base_addr <<= 32; + p2sb_base_addr |= pci_cfgregread(0, P2SB_PCI_SLOT, P2SB_PCI_FUNC, + PCICONF_P2SB_LOADDR, 4) & PCICONF_P2SB_LOADDR_MASK; + + /* Restore previous setting. */ + if (p2sb_ctrl == 0xffffffff) + pci_cfgregwrite(0, P2SB_PCI_SLOT, P2SB_PCI_FUNC, P2SB_CTRL + 1, + 1, 1); + + /* Read MCHBAR base address from base pci hub. */ + mch_base_addr = pci_cfgregread(0, 0, 0, PCICONF_MCHBAR_HIADDR, 4) & + PCICONF_MCHBAR_HIADDR_MASK; + mch_base_addr <<= 32; + mch_base_addr |= pci_cfgregread(0, 0, 0, PCICONF_MCHBAR_LOADDR, 4) & + PCICONF_MCHBAR_LOADDR_MASK; + + dev = BUS_ADD_CHILD(parent, 0, driver->name, 0); + if (dev == NULL) + return; + + bus_set_resource(dev, SYS_RES_MEMORY, 0, mch_base_addr, MCHBAR_SIZE); + bus_set_resource(dev, SYS_RES_MEMORY, 1, + p2sb_base_addr + P2SB_PORT10_OFFSET, P2SB_PORT_SIZE); + bus_set_resource(dev, SYS_RES_MEMORY, 2, + p2sb_base_addr + P2SB_PORT12_OFFSET, P2SB_PORT_SIZE); +} + +static void +pnd2_check_uce(device_t dev) +{ + struct pnd2_softc *sc = device_get_softc(dev); + uint32_t reg; + + reg = bus_read_4(sc->sc_p2sb_res, P2SB_DERRCNT); + + if (reg != 0) { + device_printf(dev, "Uncorrectable ECC error has been detected.\n"); + /* Write to clear the register. */ + bus_write_4(sc->sc_p2sb_res, P2SB_DERRCNT, 0xFF); + } +} + +static int +pnd2_probe(device_t dev) +{ + + device_set_desc(dev, "Pondicherry2 memory controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +pnd2_attach(device_t dev) +{ + struct pnd2_softc *sc = device_get_softc(dev); + int error; + int rid; + + sc->dev = dev; + + rid = 0; + sc->sc_mch_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->sc_mch_res == NULL) { + device_printf(dev, "Failed to allocate MCHBAR\n"); + return (ENXIO); + } + rid = 1; + sc->sc_p2sb_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->sc_p2sb_res == NULL) { + device_printf(dev, "Failed to allocate P2SB PORT10\n"); + pnd2_detach(dev); + return (ENXIO); + } + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "err_inject", CTLTYPE_INT | CTLFLAG_RW, + sc, 0, pnd2_sysctl_inject, "U", + "Inject correctable(1), or uncorrectable(2) ECC error"); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "patrol_scrub", CTLTYPE_INT | CTLFLAG_RW, + sc, 0, pnd2_sysctl_scrub, "U", + "Enable, or disable (1/0) DRAM patrol scrubbing."); + + SYSCTL_ADD_ULONG(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "ce_count", CTLFLAG_RD, &sc->sc_ce_count, + "Amount of correctable ECC errors."); + + error = pnd2_detect_dimm_geom(sc); + if (error != 0) { + pnd2_detach(dev); + return (error); + } + + /* Disable error injection */ + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJCTL, 0); + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJADDR, 0); + bus_write_4(sc->sc_p2sb_res, P2SB_ERRINJDATA0, 0); + /* Unmask logging of uncorrectable errors. */ + bus_write_4(sc->sc_p2sb_res, P2SB_DERCNTSEL, P2SB_DERCNTSEL_SELALL); + pnd2_check_uce(dev); + + mca_register_decode_func(pnd2_cmc, sc); + + return (0); +} + +static int +pnd2_detach(device_t dev) +{ + struct pnd2_softc *sc = device_get_softc(dev); + + mca_deregister_decode_func(pnd2_cmc); + + if (sc->sc_mch_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mch_res); + + if (sc->sc_p2sb_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, 1, sc->sc_p2sb_res); + + return (0); +} + +static device_method_t pnd2_edac_methods[] = { + DEVMETHOD(device_identify, pnd2_identify), + DEVMETHOD(device_probe, pnd2_probe), + DEVMETHOD(device_attach, pnd2_attach), + DEVMETHOD(device_detach, pnd2_detach), + DEVMETHOD_END +}; + +static driver_t pnd2_edac_driver = { + .name = "pnd2_edac", + .methods = pnd2_edac_methods, + .size = sizeof(struct pnd2_softc), +}; + +DRIVER_MODULE(pnd2_edac, cpu, pnd2_edac_driver, 0, 0);