haswell NRI: Collect SPD info

Collect SPD data from DIMMs and memory-down, and find the common
supported settings.

Original-Change-Id: I4e6a1408a638a463ecae37a447cfed1d6556e44a
Original-Signed-off-by: Angel Pons <th3fanbus@gmail.com>
Signed-off-by: Bill XIE <persmule@hardenedlinux.org>
Change-Id: I7948554eb02113bdca380222a11cfb322f9615f8
Reviewed-on: https://review.coreboot.org/c/coreboot/+/77049
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Martin L Roth <gaumless@gmail.com>
This commit is contained in:
Angel Pons 2022-05-07 13:48:53 +02:00 committed by Felix Held
parent abaa4b5a96
commit 1b25422215
4 changed files with 265 additions and 0 deletions

View File

@ -2,3 +2,4 @@
romstage-y += raminit_main.c romstage-y += raminit_main.c
romstage-y += raminit_native.c romstage-y += raminit_native.c
romstage-y += spd_bitmunching.c

View File

@ -20,6 +20,7 @@ struct task_entry {
}; };
static const struct task_entry cold_boot[] = { static const struct task_entry cold_boot[] = {
{ collect_spd_info, true, "PROCSPD", },
}; };
/* Return a generic stepping value to make stepping checks simpler */ /* Return a generic stepping value to make stepping checks simpler */

View File

@ -3,6 +3,15 @@
#ifndef HASWELL_RAMINIT_NATIVE_H #ifndef HASWELL_RAMINIT_NATIVE_H
#define HASWELL_RAMINIT_NATIVE_H #define HASWELL_RAMINIT_NATIVE_H
#include <device/dram/ddr3.h>
#include <northbridge/intel/haswell/haswell.h>
#define SPD_LEN 256
/* 8 data lanes + 1 ECC lane */
#define NUM_LANES 9
#define NUM_LANES_NO_ECC 8
enum raminit_boot_mode { enum raminit_boot_mode {
BOOTMODE_COLD, BOOTMODE_COLD,
BOOTMODE_WARM, BOOTMODE_WARM,
@ -12,6 +21,8 @@ enum raminit_boot_mode {
enum raminit_status { enum raminit_status {
RAMINIT_STATUS_SUCCESS = 0, RAMINIT_STATUS_SUCCESS = 0,
RAMINIT_STATUS_NO_MEMORY_INSTALLED,
RAMINIT_STATUS_UNSUPPORTED_MEMORY,
RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/ RAMINIT_STATUS_UNSPECIFIED_ERROR, /** TODO: Deprecated in favor of specific values **/
}; };
@ -21,14 +32,60 @@ enum generic_stepping {
STEPPING_C0 = 3, STEPPING_C0 = 3,
}; };
struct raminit_dimm_info {
spd_raw_data raw_spd;
struct dimm_attr_ddr3_st data;
uint8_t spd_addr;
bool valid;
};
struct sysinfo { struct sysinfo {
enum raminit_boot_mode bootmode; enum raminit_boot_mode bootmode;
enum generic_stepping stepping; enum generic_stepping stepping;
uint32_t cpu; /* CPUID value */ uint32_t cpu; /* CPUID value */
bool dq_pins_interleaved; bool dq_pins_interleaved;
/** TODO: ECC support untested **/
bool is_ecc;
/**
* FIXME: LPDDR support is incomplete. The largest chunks are missing,
* but some LPDDR-specific variations in algorithms have been handled.
* LPDDR-specific functions have stubs which will halt upon execution.
*/
bool lpddr;
struct raminit_dimm_info dimms[NUM_CHANNELS][NUM_SLOTS];
union dimm_flags_ddr3_st flags;
uint16_t cas_supported;
/* Except for tCK, everything is eventually stored in DCLKs */
uint32_t tCK;
uint32_t tAA; /* Also known as tCL */
uint32_t tWR;
uint32_t tRCD;
uint32_t tRRD;
uint32_t tRP;
uint32_t tRAS;
uint32_t tRC;
uint32_t tRFC;
uint32_t tWTR;
uint32_t tRTP;
uint32_t tFAW;
uint32_t tCWL;
uint32_t tCMD;
uint8_t lanes; /* 8 or 9 */
uint8_t chanmap;
uint8_t dpc[NUM_CHANNELS]; /* DIMMs per channel */
uint8_t rankmap[NUM_CHANNELS];
uint8_t rank_mirrored[NUM_CHANNELS];
uint32_t channel_size_mb[NUM_CHANNELS];
}; };
void raminit_main(enum raminit_boot_mode bootmode); void raminit_main(enum raminit_boot_mode bootmode);
enum raminit_status collect_spd_info(struct sysinfo *ctrl);
#endif #endif

View File

@ -0,0 +1,206 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cbfs.h>
#include <commonlib/bsd/clamp.h>
#include <console/console.h>
#include <device/dram/ddr3.h>
#include <device/smbus_host.h>
#include <northbridge/intel/haswell/haswell.h>
#include <northbridge/intel/haswell/raminit.h>
#include <string.h>
#include <types.h>
#include "raminit_native.h"
static const uint8_t *get_spd_data_from_cbfs(struct spd_info *spdi)
{
if (!CONFIG(HAVE_SPD_IN_CBFS))
return NULL;
printk(RAM_DEBUG, "SPD index %u\n", spdi->spd_index);
size_t spd_file_len;
uint8_t *spd_file = cbfs_map("spd.bin", &spd_file_len);
if (!spd_file) {
printk(BIOS_ERR, "SPD data not found in CBFS\n");
return NULL;
}
if (spd_file_len < ((spdi->spd_index + 1) * SPD_LEN)) {
printk(BIOS_ERR, "SPD index override to 0 - old hardware?\n");
spdi->spd_index = 0;
}
if (spd_file_len < SPD_LEN) {
printk(BIOS_ERR, "Invalid SPD data in CBFS\n");
return NULL;
}
return spd_file + (spdi->spd_index * SPD_LEN);
}
static void get_spd_for_dimm(struct raminit_dimm_info *const dimm, const uint8_t *cbfs_spd)
{
if (dimm->spd_addr == SPD_MEMORY_DOWN) {
if (cbfs_spd) {
memcpy(dimm->raw_spd, cbfs_spd, SPD_LEN);
dimm->valid = true;
printk(RAM_DEBUG, "memory-down\n");
return;
} else {
printk(RAM_DEBUG, "memory-down but no CBFS SPD data, ignoring\n");
return;
}
}
printk(RAM_DEBUG, "slotted ");
const uint8_t spd_mem_type = smbus_read_byte(dimm->spd_addr, SPD_MEMORY_TYPE);
if (spd_mem_type != SPD_MEMORY_TYPE_SDRAM_DDR3) {
printk(RAM_DEBUG, "and not DDR3, ignoring\n");
return;
}
printk(RAM_DEBUG, "and DDR3\n");
if (i2c_eeprom_read(dimm->spd_addr, 0, SPD_LEN, dimm->raw_spd) != SPD_LEN) {
printk(BIOS_WARNING, "I2C block read failed, trying SMBus byte reads\n");
for (uint32_t i = 0; i < SPD_LEN; i++)
dimm->raw_spd[i] = smbus_read_byte(dimm->spd_addr, i);
}
dimm->valid = true;
}
static void get_spd_data(struct sysinfo *ctrl)
{
struct spd_info spdi = {0};
mb_get_spd_map(&spdi);
const uint8_t *cbfs_spd = get_spd_data_from_cbfs(&spdi);
for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot];
dimm->spd_addr = spdi.addresses[NUM_SLOTS * channel + slot];
if (!dimm->spd_addr)
continue;
printk(RAM_DEBUG, "CH%uS%u is ", channel, slot);
get_spd_for_dimm(dimm, cbfs_spd);
}
}
}
static void decode_spd(struct raminit_dimm_info *const dimm)
{
/** TODO: Hook up somewhere, and handle lack of XMP data **/
const bool enable_xmp = false;
memset(&dimm->data, 0, sizeof(dimm->data));
if (enable_xmp)
spd_xmp_decode_ddr3(&dimm->data, dimm->raw_spd, DDR3_XMP_PROFILE_1);
else
spd_decode_ddr3(&dimm->data, dimm->raw_spd);
if (CONFIG(DEBUG_RAM_SETUP))
dram_print_spd_ddr3(&dimm->data);
}
static enum raminit_status find_common_spd_parameters(struct sysinfo *ctrl)
{
ctrl->cas_supported = 0xffff;
ctrl->flags.raw = 0xffffffff;
ctrl->tCK = 0;
ctrl->tAA = 0;
ctrl->tWR = 0;
ctrl->tRCD = 0;
ctrl->tRRD = 0;
ctrl->tRP = 0;
ctrl->tRAS = 0;
ctrl->tRC = 0;
ctrl->tRFC = 0;
ctrl->tWTR = 0;
ctrl->tRTP = 0;
ctrl->tFAW = 0;
ctrl->tCWL = 0;
ctrl->tCMD = 0;
ctrl->chanmap = 0;
bool yes_ecc = false;
bool not_ecc = false;
for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
ctrl->dpc[channel] = 0;
ctrl->rankmap[channel] = 0;
ctrl->rank_mirrored[channel] = 0;
ctrl->channel_size_mb[channel] = 0;
for (uint8_t slot = 0; slot < NUM_SLOTS; slot++) {
struct raminit_dimm_info *const dimm = &ctrl->dimms[channel][slot];
if (!dimm->valid)
continue;
printk(RAM_DEBUG, "\nCH%uS%u SPD:\n", channel, slot);
decode_spd(dimm);
ctrl->chanmap |= BIT(channel);
ctrl->dpc[channel]++;
ctrl->channel_size_mb[channel] += dimm->data.size_mb;
/* The first rank of a populated slot is always present */
const uint8_t rank = slot + slot;
assert(dimm->data.ranks);
ctrl->rankmap[channel] |= (BIT(dimm->data.ranks) - 1) << rank;
if (dimm->data.flags.pins_mirrored)
ctrl->rank_mirrored[channel] |= BIT(rank + 1);
/* Find common settings */
ctrl->cas_supported &= dimm->data.cas_supported;
ctrl->flags.raw &= dimm->data.flags.raw;
ctrl->tCK = MAX(ctrl->tCK, dimm->data.tCK);
ctrl->tAA = MAX(ctrl->tAA, dimm->data.tAA);
ctrl->tWR = MAX(ctrl->tWR, dimm->data.tWR);
ctrl->tRCD = MAX(ctrl->tRCD, dimm->data.tRCD);
ctrl->tRRD = MAX(ctrl->tRRD, dimm->data.tRRD);
ctrl->tRP = MAX(ctrl->tRP, dimm->data.tRP);
ctrl->tRAS = MAX(ctrl->tRAS, dimm->data.tRAS);
ctrl->tRC = MAX(ctrl->tRC, dimm->data.tRC);
ctrl->tRFC = MAX(ctrl->tRFC, dimm->data.tRFC);
ctrl->tWTR = MAX(ctrl->tWTR, dimm->data.tWTR);
ctrl->tRTP = MAX(ctrl->tRTP, dimm->data.tRTP);
ctrl->tFAW = MAX(ctrl->tFAW, dimm->data.tFAW);
ctrl->tCWL = MAX(ctrl->tCWL, dimm->data.tCWL);
ctrl->tCMD = MAX(ctrl->tCMD, dimm->data.tCMD);
yes_ecc |= dimm->data.flags.is_ecc;
not_ecc |= !dimm->data.flags.is_ecc;
}
}
if (!ctrl->chanmap) {
printk(BIOS_ERR, "No DIMMs were found\n");
return RAMINIT_STATUS_NO_MEMORY_INSTALLED;
}
if (!ctrl->cas_supported) {
printk(BIOS_ERR, "Could not resolve common CAS latency\n");
return RAMINIT_STATUS_UNSUPPORTED_MEMORY;
}
/** TODO: Properly handle ECC support and ECC forced **/
if (yes_ecc && not_ecc) {
/** TODO: Test if the ECC DIMMs can be operated as non-ECC DIMMs **/
printk(BIOS_ERR, "Both ECC and non-ECC DIMMs present, this is unsupported\n");
return RAMINIT_STATUS_UNSUPPORTED_MEMORY;
}
if (yes_ecc)
ctrl->lanes = NUM_LANES;
else
ctrl->lanes = NUM_LANES_NO_ECC;
ctrl->is_ecc = yes_ecc;
/** TODO: Complete LPDDR support **/
ctrl->lpddr = false;
return RAMINIT_STATUS_SUCCESS;
}
enum raminit_status collect_spd_info(struct sysinfo *ctrl)
{
get_spd_data(ctrl);
return find_common_spd_parameters(ctrl);
}