283 lines
7.6 KiB
C
283 lines
7.6 KiB
C
/* $OpenBSD: amas.c,v 1.7 2022/03/11 18:00:45 mpi Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2009 Ariane van der Steldt <ariane@stack.nl>
|
|
*
|
|
* 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 <dev/pci/amas.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/device.h>
|
|
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcidevs.h>
|
|
|
|
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. */
|
|
const 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_0F_ADDR },
|
|
{ PCI_VENDOR_AMD, PCI_PRODUCT_AMD_10_ADDR },
|
|
{ PCI_VENDOR_AMD, PCI_PRODUCT_AMD_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_0F_ADDR:
|
|
amas->family = AMAS_FAM_0Fh;
|
|
break;
|
|
case PCI_PRODUCT_AMD_10_ADDR:
|
|
amas->family = AMAS_FAM_10h;
|
|
break;
|
|
case PCI_PRODUCT_AMD_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(" [%#lx, %#lx]", 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;
|
|
}
|