/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Given a physical address and an optional syndrome, determine the
* name of the memory module that contains it.
*/
#include <mcamd_api.h>
#include <mcamd_err.h>
/*
* iaddr_gen generates a "normalized" DRAM controller input address
* from a system address (physical address) if it falls within the
* mapped range for this memory controller. Normalisation is
* performed by subtracting the node base address from the system address,
* allowing from hoisting, and excising any bits being used in node
* interleaving.
*/
static int
{
if (!mcamd_get_numprops(hdl,
NULL)) {
"lookup required properties");
}
/*
* A node with no mapped memory (no active chip-selects is usually
* mapped with base and lim both zero. We'll cover that case and
* any other where the range is 0.
*/
(int)mcnum);
}
/*
* Rev E and later added the DRAM Hole Address Register for
* memory hoisting. In earlier revisions memory hoisting is
* achieved by following some algorithm to modify the CS bases etc,
* and this pa to unum algorithm will simply see those modified
* values. But if the Hole Address Register is being used then
* we need to reduce any address at or above 4GB by the size of
* the hole.
*/
"valid; pa decremented from 0x%llx to 0x%llx for "
}
if (ilen != 0) {
int pailsel;
}
"PA 0x%llx in a %d-way node interleave indicates "
"selection %d, MC %d has ilsel of %d\n",
}
if (ilen == 1)
else if (ilen == 3)
else if (ilen == 7)
} else {
}
"[0x%llx, 0x%llx] of MC %d; normalized address for cs compare "
return (0);
}
/*
* cs_match determines whether the given DRAM controller input address
* would be responded to by the given chip-select (which may or may not
* be interleaved with other chip-selects). Since we include nodes
* for spare chip-selects (if any) and those marked TestFail (if any)
* we must check chip-select-bank-enable.
*/
static int
{
int match = 0;
if (!mcamd_get_numprops(hdl,
NULL)) {
"required properties\n");
return (0);
}
if (csbe) {
"does %smatch CS %d (base 0x%llx, mask 0x%llx)\n", iaddr,
} else {
}
return (match);
}
/*
* Given a chip-select node determine whether it has been substituted
* by the online spare chip-select.
*/
static mcamd_node_t *
{
if (!mcamd_get_numprops(hdl,
NULL)) {
"lookup required properties\n");
return (NULL);
}
return (NULL);
"fail to lookup csnum - cannot reroute to spare\n");
return (NULL);
}
if (tmpcsnum == sparecsnum)
break;
}
"redirected to active online spare of cs#%d\n", csnum,
} else {
"redirected but cannot find spare cs# - cannout reroute to "
}
return (cs);
}
/*
* Having determined which node and chip-select an address maps to,
* involved, fill the unum structure including an optional dimm offset
* member.
*/
static int
{
int offsetdimm;
int i;
NULL)) {
"lookup required properties\n");
}
}
unump->unum_board = 0;
for (i = 0; i < MC_UNUM_NDIMM; i++) {
}
switch (which) {
case CSDIMM1:
offsetdimm = (int)dimm1;
break;
case CSDIMM2:
offsetdimm = (int)dimm2;
break;
offsetdimm = (int)dimm1;
break;
}
if (!incloff) {
return (0);
}
/*
* We wish to calculate a dimm offset. In the paired case we will
* lookup dimm1 (see offsetdimm above).
*/
"to lookup dimm number property\n");
continue;
}
if (dnum == offsetdimm)
break;
}
"find dimm with number %d for offset calculation\n",
}
/*
* mc_pa_to_offset sets the offset to an invalid value if
* it hits an error.
*/
return (0);
}
/*
* We have translated a system address to a (node, chip-select), and wish
* to determine the associated dimm or dimms.
*
* A (node, chip-select) pair identifies one (in 64-bit MC mode) or two (in
* 128-bit MC mode) DIMMs. In the case of a single dimm it is usually in a
* lodimm (channel A) slot, but if mismatched dimm support is present it may
* be an updimm (channel B).
*
* Where just one dimm is associated with the chip-select we are done.
* Where there are two dimms associated with the chip-select we can
* resolve to, if the error is correctable. If the error is uncorrectable
* then in 64/8 ECC mode we can still resolve to a single dimm (since ECC
* is calculated and checked on each half of the data separately), but
* in ChipKill mode we cannot resolve down to a single dimm.
*/
static int
{
int ndimm;
/*
* Read the associated dimm instance numbers. The provider must
* assure that if there is just one dimm then it is in the first
* property, and if there are two then the first must be on
* channel A.
*/
if (!mcamd_get_numprops(hdl,
NULL)) {
"lookup required properties");
}
if (ndimm == 0) {
"dimms associated with chip-select");
}
if (ndimm == 1) {
"dimm associated with this chip-select");
return (CSDIMM1);
}
/*
* 64/8 ECC is checked separately for the upper and lower
* halves, so even an uncorrectable error is contained within
* one of the two halves. If we have sufficient address resolution
* then we can determine which DIMM.
*/
if (syndtype == AMD_SYNDTYPE_ECC) {
if (valid_lo <= MC_SYSADDR_LSB) {
"ECC in 128-bit mode, PA 0x%llx is in %s half\n",
} else {
"64/8 ECC in 128-bit mode, PA 0x%llx with least "
"significant valid bit %d cannot be resolved to "
}
}
/*
* ChipKill ECC
*/
/*
* A correctable ChipKill syndrome and we can tell
* which half the error was in from the symbol number.
*/
&check) == 0)
"ChipKill symbol %d (%s %d..%d), so LODIMM\n", sym,
return (CSDIMM1);
} else {
"ChipKill symbol %d (%s %d..%d), so UPDIMM\n", sym,
return (CSDIMM2);
}
} else {
/*
* An uncorrectable error while in ChipKill ECC mode - can't
* tell which dimm or dimms the errors lie within.
*/
"uncorrectable ChipKill, could be either LODIMM "
"or UPDIMM\n");
}
}
/*
* Brute-force BKDG pa to cs translation, coded to look as much like the
* BKDG code as possible.
*/
static int
{
int which;
/*
* Raw registers as per BKDG
*/
/*
* Variables as per BKDG
*/
int Ilog;
if (!mcamd_get_numprops(hdl,
"to lookup required properties and registers\n");
}
/*
* BKDG line to skip Why
*
* F1Offset = ... Register already read,
* DramBase = Get_PCI() and retrieved above.
* DramEn = ... Function only called for enabled nodes.
*/
DramBase &= 0xffff0000;
/* DramLimit = Get_PCI() Retrieved above */
DramLimit |= 0x0000ffff;
/* HoleEn = ... Retrieved above */
HoleEn &= 0x00000001;
"SystemAddr 0x%x derived from PA 0x%llx is not in the "
"address range [0x%x, 0x%x] of MC %d\n",
}
if (IntlvEn) {
switch (IntlvEn) {
case 1:
Ilog = 1;
break;
case 3:
Ilog = 2;
break;
case 7:
Ilog = 3;
break;
default:
return (mcamd_set_errno(hdl,
}
} else {
/* not this node */
"Node interleaving, MC node %d not selected\n",
(int)mcnum);
}
}
InputAddr <<= 4;
if (!mcamd_get_cfgregs(hdl,
NULL) ||
NULL)) {
"failed to read cs registers\n");
}
/*
* BKDG line to skip Why
*
* F2Offset = Register already read,
* F2MaskOffset (rev F) Register already read
* CSBase = Register already read
* CSEn = We only keep enabled cs.
*/
CSBase &= 0x1ff83fe0;
/* CSMask = Get_PCI() Retrieved above */
} else {
CSBase &= 0xffe0fe00;
/* CSMask = Get_PCI() Retrieved above */
}
"match for chip select %d of MC %d\n", (int)csnum,
(int)mcnum);
return (-1); /* errno is set for us */
/*
* The BKDG algorithm drops low-order bits that
* are unimportant in deriving chip-select but are
* perform offset calculation in this case.
*/
return (-1); /* errno is set for us */
return (0);
}
}
"for MC %d but no cs responds\n", (int)mcnum);
}
/*
* Called for each memory controller to see if the given address is
* mapped to this node (as determined in iaddr_gen) and, if so, which
* chip-select on this node responds.
*/
/*ARGSUSED*/
static int
{
int which;
#ifdef DEBUG
int bkdgres;
/*
* We perform the translation twice, once using the brute-force
* approach of the BKDG and again using a more elegant but more
* difficult to review against the BKDG approach.
*/
#endif
return (-1); /* errno is set for us */
break;
}
/*
* If the spare chip-select has been swapped in for the one just
* matched then it is really the spare that we are after. Note that
* when the swap is done the csbase, csmask and CSBE of the spare
* rank do not change - accesses to the bad rank (as nominated in
* the Online Spare Control Register) are redirect to the spare.
*/
}
syndtype)) < 0)
return (-1); /* errno is set for us */
return (-1); /* errno is set for us */
#ifdef DEBUG
/* offset is not checked - see note in BKDG algorithm */
if (bkdgres != 0) {
"ours succeeded\n");
"BKDG: node %d mc %d cs %d dimm(s) %d/%d\n"
"Ours: node 5d mc %d cs %d dimm(s) %d/%d\n",
}
#endif /* DEBUG */
return (0);
}
int
{
if (valid_hi < MC_SYSADDR_MSB) {
"pa<%d> to be valid\n", MC_SYSADDR_MSB);
}
return (0);
break;
}
return (-1); /* errno is set for us */
}