|
@@ -434,4 +434,298 @@ int amd64_get_dram_hole_info(struct mem_ctl_info *mci, u64 *hole_base,
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(amd64_get_dram_hole_info);
|
|
|
|
|
|
+/*
|
|
|
+ * Return the DramAddr that the SysAddr given by @sys_addr maps to. It is
|
|
|
+ * assumed that sys_addr maps to the node given by mci.
|
|
|
+ *
|
|
|
+ * The first part of section 3.4.4 (p. 70) shows how the DRAM Base (section
|
|
|
+ * 3.4.4.1) and DRAM Limit (section 3.4.4.2) registers are used to translate a
|
|
|
+ * SysAddr to a DramAddr. If the DRAM Hole Address Register (DHAR) is enabled,
|
|
|
+ * then it is also involved in translating a SysAddr to a DramAddr. Sections
|
|
|
+ * 3.4.8 and 3.5.8.2 describe the DHAR and how it is used for memory hoisting.
|
|
|
+ * These parts of the documentation are unclear. I interpret them as follows:
|
|
|
+ *
|
|
|
+ * When node n receives a SysAddr, it processes the SysAddr as follows:
|
|
|
+ *
|
|
|
+ * 1. It extracts the DRAMBase and DRAMLimit values from the DRAM Base and DRAM
|
|
|
+ * Limit registers for node n. If the SysAddr is not within the range
|
|
|
+ * specified by the base and limit values, then node n ignores the Sysaddr
|
|
|
+ * (since it does not map to node n). Otherwise continue to step 2 below.
|
|
|
+ *
|
|
|
+ * 2. If the DramHoleValid bit of the DHAR for node n is clear, the DHAR is
|
|
|
+ * disabled so skip to step 3 below. Otherwise see if the SysAddr is within
|
|
|
+ * the range of relocated addresses (starting at 0x100000000) from the DRAM
|
|
|
+ * hole. If not, skip to step 3 below. Else get the value of the
|
|
|
+ * DramHoleOffset field from the DHAR. To obtain the DramAddr, subtract the
|
|
|
+ * offset defined by this value from the SysAddr.
|
|
|
+ *
|
|
|
+ * 3. Obtain the base address for node n from the DRAMBase field of the DRAM
|
|
|
+ * Base register for node n. To obtain the DramAddr, subtract the base
|
|
|
+ * address from the SysAddr, as shown near the start of section 3.4.4 (p.70).
|
|
|
+ */
|
|
|
+static u64 sys_addr_to_dram_addr(struct mem_ctl_info *mci, u64 sys_addr)
|
|
|
+{
|
|
|
+ u64 dram_base, hole_base, hole_offset, hole_size, dram_addr;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ dram_base = get_dram_base(mci);
|
|
|
+
|
|
|
+ ret = amd64_get_dram_hole_info(mci, &hole_base, &hole_offset,
|
|
|
+ &hole_size);
|
|
|
+ if (!ret) {
|
|
|
+ if ((sys_addr >= (1ull << 32)) &&
|
|
|
+ (sys_addr < ((1ull << 32) + hole_size))) {
|
|
|
+ /* use DHAR to translate SysAddr to DramAddr */
|
|
|
+ dram_addr = sys_addr - hole_offset;
|
|
|
+
|
|
|
+ debugf2("using DHAR to translate SysAddr 0x%lx to "
|
|
|
+ "DramAddr 0x%lx\n",
|
|
|
+ (unsigned long)sys_addr,
|
|
|
+ (unsigned long)dram_addr);
|
|
|
+
|
|
|
+ return dram_addr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Translate the SysAddr to a DramAddr as shown near the start of
|
|
|
+ * section 3.4.4 (p. 70). Although sys_addr is a 64-bit value, the k8
|
|
|
+ * only deals with 40-bit values. Therefore we discard bits 63-40 of
|
|
|
+ * sys_addr below. If bit 39 of sys_addr is 1 then the bits we
|
|
|
+ * discard are all 1s. Otherwise the bits we discard are all 0s. See
|
|
|
+ * section 3.4.2 of AMD publication 24592: AMD x86-64 Architecture
|
|
|
+ * Programmer's Manual Volume 1 Application Programming.
|
|
|
+ */
|
|
|
+ dram_addr = (sys_addr & 0xffffffffffull) - dram_base;
|
|
|
+
|
|
|
+ debugf2("using DRAM Base register to translate SysAddr 0x%lx to "
|
|
|
+ "DramAddr 0x%lx\n", (unsigned long)sys_addr,
|
|
|
+ (unsigned long)dram_addr);
|
|
|
+ return dram_addr;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * @intlv_en is the value of the IntlvEn field from a DRAM Base register
|
|
|
+ * (section 3.4.4.1). Return the number of bits from a SysAddr that are used
|
|
|
+ * for node interleaving.
|
|
|
+ */
|
|
|
+static int num_node_interleave_bits(unsigned intlv_en)
|
|
|
+{
|
|
|
+ static const int intlv_shift_table[] = { 0, 1, 0, 2, 0, 0, 0, 3 };
|
|
|
+ int n;
|
|
|
+
|
|
|
+ BUG_ON(intlv_en > 7);
|
|
|
+ n = intlv_shift_table[intlv_en];
|
|
|
+ return n;
|
|
|
+}
|
|
|
+
|
|
|
+/* Translate the DramAddr given by @dram_addr to an InputAddr. */
|
|
|
+static u64 dram_addr_to_input_addr(struct mem_ctl_info *mci, u64 dram_addr)
|
|
|
+{
|
|
|
+ struct amd64_pvt *pvt;
|
|
|
+ int intlv_shift;
|
|
|
+ u64 input_addr;
|
|
|
+
|
|
|
+ pvt = mci->pvt_info;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * See the start of section 3.4.4 (p. 70, BKDG #26094, K8, revA-E)
|
|
|
+ * concerning translating a DramAddr to an InputAddr.
|
|
|
+ */
|
|
|
+ intlv_shift = num_node_interleave_bits(pvt->dram_IntlvEn[0]);
|
|
|
+ input_addr = ((dram_addr >> intlv_shift) & 0xffffff000ull) +
|
|
|
+ (dram_addr & 0xfff);
|
|
|
+
|
|
|
+ debugf2(" Intlv Shift=%d DramAddr=0x%lx maps to InputAddr=0x%lx\n",
|
|
|
+ intlv_shift, (unsigned long)dram_addr,
|
|
|
+ (unsigned long)input_addr);
|
|
|
+
|
|
|
+ return input_addr;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Translate the SysAddr represented by @sys_addr to an InputAddr. It is
|
|
|
+ * assumed that @sys_addr maps to the node given by mci.
|
|
|
+ */
|
|
|
+static u64 sys_addr_to_input_addr(struct mem_ctl_info *mci, u64 sys_addr)
|
|
|
+{
|
|
|
+ u64 input_addr;
|
|
|
+
|
|
|
+ input_addr =
|
|
|
+ dram_addr_to_input_addr(mci, sys_addr_to_dram_addr(mci, sys_addr));
|
|
|
+
|
|
|
+ debugf2("SysAdddr 0x%lx translates to InputAddr 0x%lx\n",
|
|
|
+ (unsigned long)sys_addr, (unsigned long)input_addr);
|
|
|
+
|
|
|
+ return input_addr;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * @input_addr is an InputAddr associated with the node represented by mci.
|
|
|
+ * Translate @input_addr to a DramAddr and return the result.
|
|
|
+ */
|
|
|
+static u64 input_addr_to_dram_addr(struct mem_ctl_info *mci, u64 input_addr)
|
|
|
+{
|
|
|
+ struct amd64_pvt *pvt;
|
|
|
+ int node_id, intlv_shift;
|
|
|
+ u64 bits, dram_addr;
|
|
|
+ u32 intlv_sel;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Near the start of section 3.4.4 (p. 70, BKDG #26094, K8, revA-E)
|
|
|
+ * shows how to translate a DramAddr to an InputAddr. Here we reverse
|
|
|
+ * this procedure. When translating from a DramAddr to an InputAddr, the
|
|
|
+ * bits used for node interleaving are discarded. Here we recover these
|
|
|
+ * bits from the IntlvSel field of the DRAM Limit register (section
|
|
|
+ * 3.4.4.2) for the node that input_addr is associated with.
|
|
|
+ */
|
|
|
+ pvt = mci->pvt_info;
|
|
|
+ node_id = pvt->mc_node_id;
|
|
|
+ BUG_ON((node_id < 0) || (node_id > 7));
|
|
|
+
|
|
|
+ intlv_shift = num_node_interleave_bits(pvt->dram_IntlvEn[0]);
|
|
|
+
|
|
|
+ if (intlv_shift == 0) {
|
|
|
+ debugf1(" InputAddr 0x%lx translates to DramAddr of "
|
|
|
+ "same value\n", (unsigned long)input_addr);
|
|
|
+
|
|
|
+ return input_addr;
|
|
|
+ }
|
|
|
+
|
|
|
+ bits = ((input_addr & 0xffffff000ull) << intlv_shift) +
|
|
|
+ (input_addr & 0xfff);
|
|
|
+
|
|
|
+ intlv_sel = pvt->dram_IntlvSel[node_id] & ((1 << intlv_shift) - 1);
|
|
|
+ dram_addr = bits + (intlv_sel << 12);
|
|
|
+
|
|
|
+ debugf1("InputAddr 0x%lx translates to DramAddr 0x%lx "
|
|
|
+ "(%d node interleave bits)\n", (unsigned long)input_addr,
|
|
|
+ (unsigned long)dram_addr, intlv_shift);
|
|
|
+
|
|
|
+ return dram_addr;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * @dram_addr is a DramAddr that maps to the node represented by mci. Convert
|
|
|
+ * @dram_addr to a SysAddr.
|
|
|
+ */
|
|
|
+static u64 dram_addr_to_sys_addr(struct mem_ctl_info *mci, u64 dram_addr)
|
|
|
+{
|
|
|
+ struct amd64_pvt *pvt = mci->pvt_info;
|
|
|
+ u64 hole_base, hole_offset, hole_size, base, limit, sys_addr;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ ret = amd64_get_dram_hole_info(mci, &hole_base, &hole_offset,
|
|
|
+ &hole_size);
|
|
|
+ if (!ret) {
|
|
|
+ if ((dram_addr >= hole_base) &&
|
|
|
+ (dram_addr < (hole_base + hole_size))) {
|
|
|
+ sys_addr = dram_addr + hole_offset;
|
|
|
+
|
|
|
+ debugf1("using DHAR to translate DramAddr 0x%lx to "
|
|
|
+ "SysAddr 0x%lx\n", (unsigned long)dram_addr,
|
|
|
+ (unsigned long)sys_addr);
|
|
|
+
|
|
|
+ return sys_addr;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ amd64_get_base_and_limit(pvt, pvt->mc_node_id, &base, &limit);
|
|
|
+ sys_addr = dram_addr + base;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The sys_addr we have computed up to this point is a 40-bit value
|
|
|
+ * because the k8 deals with 40-bit values. However, the value we are
|
|
|
+ * supposed to return is a full 64-bit physical address. The AMD
|
|
|
+ * x86-64 architecture specifies that the most significant implemented
|
|
|
+ * address bit through bit 63 of a physical address must be either all
|
|
|
+ * 0s or all 1s. Therefore we sign-extend the 40-bit sys_addr to a
|
|
|
+ * 64-bit value below. See section 3.4.2 of AMD publication 24592:
|
|
|
+ * AMD x86-64 Architecture Programmer's Manual Volume 1 Application
|
|
|
+ * Programming.
|
|
|
+ */
|
|
|
+ sys_addr |= ~((sys_addr & (1ull << 39)) - 1);
|
|
|
+
|
|
|
+ debugf1(" Node %d, DramAddr 0x%lx to SysAddr 0x%lx\n",
|
|
|
+ pvt->mc_node_id, (unsigned long)dram_addr,
|
|
|
+ (unsigned long)sys_addr);
|
|
|
+
|
|
|
+ return sys_addr;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * @input_addr is an InputAddr associated with the node given by mci. Translate
|
|
|
+ * @input_addr to a SysAddr.
|
|
|
+ */
|
|
|
+static inline u64 input_addr_to_sys_addr(struct mem_ctl_info *mci,
|
|
|
+ u64 input_addr)
|
|
|
+{
|
|
|
+ return dram_addr_to_sys_addr(mci,
|
|
|
+ input_addr_to_dram_addr(mci, input_addr));
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Find the minimum and maximum InputAddr values that map to the given @csrow.
|
|
|
+ * Pass back these values in *input_addr_min and *input_addr_max.
|
|
|
+ */
|
|
|
+static void find_csrow_limits(struct mem_ctl_info *mci, int csrow,
|
|
|
+ u64 *input_addr_min, u64 *input_addr_max)
|
|
|
+{
|
|
|
+ struct amd64_pvt *pvt;
|
|
|
+ u64 base, mask;
|
|
|
+
|
|
|
+ pvt = mci->pvt_info;
|
|
|
+ BUG_ON((csrow < 0) || (csrow >= CHIPSELECT_COUNT));
|
|
|
+
|
|
|
+ base = base_from_dct_base(pvt, csrow);
|
|
|
+ mask = mask_from_dct_mask(pvt, csrow);
|
|
|
+
|
|
|
+ *input_addr_min = base & ~mask;
|
|
|
+ *input_addr_max = base | mask | pvt->dcs_mask_notused;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Extract error address from MCA NB Address Low (section 3.6.4.5) and MCA NB
|
|
|
+ * Address High (section 3.6.4.6) register values and return the result. Address
|
|
|
+ * is located in the info structure (nbeah and nbeal), the encoding is device
|
|
|
+ * specific.
|
|
|
+ */
|
|
|
+static u64 extract_error_address(struct mem_ctl_info *mci,
|
|
|
+ struct amd64_error_info_regs *info)
|
|
|
+{
|
|
|
+ struct amd64_pvt *pvt = mci->pvt_info;
|
|
|
+
|
|
|
+ return pvt->ops->get_error_address(mci, info);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* Map the Error address to a PAGE and PAGE OFFSET. */
|
|
|
+static inline void error_address_to_page_and_offset(u64 error_address,
|
|
|
+ u32 *page, u32 *offset)
|
|
|
+{
|
|
|
+ *page = (u32) (error_address >> PAGE_SHIFT);
|
|
|
+ *offset = ((u32) error_address) & ~PAGE_MASK;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * @sys_addr is an error address (a SysAddr) extracted from the MCA NB Address
|
|
|
+ * Low (section 3.6.4.5) and MCA NB Address High (section 3.6.4.6) registers
|
|
|
+ * of a node that detected an ECC memory error. mci represents the node that
|
|
|
+ * the error address maps to (possibly different from the node that detected
|
|
|
+ * the error). Return the number of the csrow that sys_addr maps to, or -1 on
|
|
|
+ * error.
|
|
|
+ */
|
|
|
+static int sys_addr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr)
|
|
|
+{
|
|
|
+ int csrow;
|
|
|
+
|
|
|
+ csrow = input_addr_to_csrow(mci, sys_addr_to_input_addr(mci, sys_addr));
|
|
|
+
|
|
|
+ if (csrow == -1)
|
|
|
+ amd64_mc_printk(mci, KERN_ERR,
|
|
|
+ "Failed to translate InputAddr to csrow for "
|
|
|
+ "address 0x%lx\n", (unsigned long)sys_addr);
|
|
|
+ return csrow;
|
|
|
+}
|
|
|
|