/* $OpenBSD: amas.c,v 1.4 2009/05/07 22:25:31 ariane Exp $ */ /* * Copyright (c) 2009 Ariane van der Steldt * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Device: amas (AMD memory access/address switch). * * Driver for the amd athlon/opteron 64 address map. * This device is integrated in 64-bit Athlon and Opteron cpus * and contains mappings for memory to processor nodes. */ #include #include #include #include #include #include #include int amas_match(struct device*, void*, void*); void amas_attach(struct device*, struct device*, void*); /* * Amas device layout: * * - base/limit registers (on 0x0f, 0x10, 0x11) * - extended base/limit registers (on 0x10) * * 0x0f, 0x10 support up to 8 nodes * 0x11 supports up to 1 nodes * * base/limit registers use bits [31..16] to indicate address [39..24] * extended base/limit registers use bits [7..0] to indicate address [47..40] * base/limit addresses need to be shifted <<24 for memory address * extended base/limit addresses need to be shifted <<40 for memory address */ #define AMAS_REG_BASE(node) (0x0040 + 0x08 * (node)) #define AMAS_REG_LIMIT(node) (0x0044 + 0x08 * (node)) #define AMAS_REG_EXTBASE(node) (0x0140 + 0x08 * (node)) #define AMAS_REG_EXTLIMIT(node) (0x0144 + 0x08 * (node)) #define AMAS_REG_BL_ADDR(reg) (((reg) >> 16) & 0xffff) #define AMAS_REG_EBL_ADDR(ereg) ((ereg) & 0xff) #define AMAS_REG_BL_SHIFT (24) #define AMAS_REG_EBL_SHIFT (40) #define AMAS_REG_BL_PGSHIFT (AMAS_REG_BL_SHIFT - PAGE_SHIFT) #define AMAS_REG_EBL_PGSHIFT (AMAS_REG_EBL_SHIFT - PAGE_SHIFT) /* * Convert an address in amas to a page number. * * The device uses an inclusive mapping, where the upper bound address * must be all 1's after shifting. * The device driver uses C-style array indices, hence the +1 in the _LIMIT * macro. */ #define AMAS_ADDR2PAGE_BASE(base, ebase) \ (((base) << AMAS_REG_BL_PGSHIFT) | ((ebase) << AMAS_REG_EBL_PGSHIFT)) #define AMAS_ADDR2PAGE_LIMIT(base, ebase) \ (((base + 1) << AMAS_REG_BL_PGSHIFT) | ((ebase) << AMAS_REG_EBL_PGSHIFT)) /* * Node and interleave description. * - base contains node selection [10..8] (on 0x0f, 0x10) * - limit contains node selection bitmask [10..8] (on 0x0f, 0x10) * - limit contains destination node [2..0] (on 0x0f, 0x10) */ #define AMAS_DST_NODE(base, limit) ((limit) & 0x07) #define AMAS_INTL_ENABLE(base, limit) (((base) >> 8) & 0x07) #define AMAS_INTL_SELECTOR(base, limit) (((limit) >> 8) & 0x07) /* * Defines for family. * Corresponds to the amas_feature[] constant below. */ #define AMAS_FAM_0Fh (0) #define AMAS_FAM_10h (1) #define AMAS_FAM_11h (2) /* * Feature tests. * * 0x11 supports at max 1 node, 0x0f and 0x10 support up to 8 nodes. * 0x11 has extended address registers. * 0x0f, 0x10 can interleave memory. */ struct amas_feature_t { int maxnodes; int can_intl; int has_extended_bl; }; static const struct amas_feature_t amas_feature[] = { /* Family 0x0f */ { 8, 1, 0 }, /* Family 0x10 */ { 8, 1, 1 }, /* Family 0x11 */ { 1, 0, 0 }, }; /* Probe code. */ struct cfattach amas_ca = { sizeof(struct amas_softc), amas_match, amas_attach }; struct cfdriver amas_cd = { NULL, "amas", DV_DULL }; const struct pci_matchid amas_devices[] = { { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_AMD64_0F_ADDR }, { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_AMD64_10_ADDR }, { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_AMD64_11_ADDR }, }; int amas_match(struct device *parent, void *match, void *aux) { struct pci_attach_args* pa = aux; if (pci_matchbyid(pa, amas_devices, nitems(amas_devices))) return 2; /* override pchb */ return 0; } void amas_attach(struct device *parent, struct device *self, void *aux) { struct pci_attach_args *pa = aux; struct amas_softc *amas = (struct amas_softc*)self; #ifdef DEBUG paddr_t start_pg, end_pg; int nodes, i; #endif /* DEBUG */ amas->pa_tag = pa->pa_tag; amas->pa_pc = pa->pa_pc; switch (PCI_PRODUCT(pa->pa_id)) { case PCI_PRODUCT_AMD_AMD64_0F_ADDR: amas->family = AMAS_FAM_0Fh; break; case PCI_PRODUCT_AMD_AMD64_10_ADDR: amas->family = AMAS_FAM_10h; break; case PCI_PRODUCT_AMD_AMD64_11_ADDR: amas->family = AMAS_FAM_11h; break; } #ifdef DEBUG nodes = amas_intl_nodes(amas); printf(":"); if (nodes != 0) { printf(" interleaved"); } else { for (i = 0; i < AMAS_MAX_NODES; i++) { amas_get_pagerange(amas, i, &start_pg, &end_pg); if (!(start_pg == 0 && end_pg == 0)) printf(" [%p, %p]", start_pg, end_pg); } } #endif /* DEBUG */ printf("\n"); return; } /* * Returns the number of nodes across which the memory is interleaved. * Returns 0 if the memory is not interleaved. */ int amas_intl_nodes(struct amas_softc *amas) { pcireg_t base_reg, limit_reg; int mask; if (!amas_feature[amas->family].can_intl) return 0; /* * Use node 0 on amas device to find interleave information. * Node 0 is always present. */ base_reg = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_BASE(0)); limit_reg = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_LIMIT(0)); mask = AMAS_INTL_ENABLE(base_reg, limit_reg); return mask == 0 ? 0 : mask + 1; } /* * Returns the range of memory that is contained on the given node. * If the memory is interleaved, the result is undefined. * * The range is written in {start,end}_pg_idx. * Note that these are page numbers and that these use array indices: * pages are in this range if start <= pg_no < end. * * This device supports at most 8 nodes. */ void amas_get_pagerange(struct amas_softc *amas, int node, paddr_t *start_pg_idx, paddr_t *end_pg_idx) { pcireg_t base, ebase, limit, elimit; paddr_t base_addr, ebase_addr, limit_addr, elimit_addr; /* Sanity check: max AMAS_MAX_NODES supported. */ KASSERT(node >= 0 && node < AMAS_MAX_NODES); if (node >= amas_feature[amas->family].maxnodes) { /* Unsupported node: bail out early. */ *start_pg_idx = 0; *end_pg_idx = 0; return; } base = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_BASE(node)); limit = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_LIMIT(node)); base_addr = AMAS_REG_BL_ADDR(base); limit_addr = AMAS_REG_BL_ADDR(limit); ebase = 0; elimit = 0; ebase_addr = 0; elimit_addr = 0; #if 0 /* Needs extended pci registers. */ if (amas_feature[amas->family].has_extended_bl) { ebase = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_EXTBASE(node)); elimit = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_EXTLIMIT(node)); ebase_addr = AMAS_REG_EBL_ADDR(ebase); elimit_addr = AMAS_REG_EBL_ADDR(elimit); } #endif /* 0 */ if (ebase_addr > elimit_addr || (ebase_addr == elimit_addr && base_addr >= limit_addr)) { /* no memory present */ *start_pg_idx = 0; *end_pg_idx = 0; return; } /* Guaranteed by spec. */ KASSERT(node == AMAS_DST_NODE(base, limit)); *start_pg_idx = AMAS_ADDR2PAGE_BASE(base_addr, ebase_addr); *end_pg_idx = AMAS_ADDR2PAGE_LIMIT(limit_addr, elimit_addr); return; }