soc/intel/common: Add support for populating meminit data

This change adds support for a common block memory driver that can be
used for performing the required operations to read SPD data for
different memory channel DIMMs. This data can then be used by the SoC
code to populate different memory related UPDs.

Most recent Intel platforms follow a similar pattern for configuring
FSP-M UPDs for initializing memory. These platforms use one of the
following topologies:
1. Memory down
2. DIMM modules
3. Mixed

Thus, SPD data is either obtained from CBFS (for memory down topology)
or from on-module EEPROM (for DIMM modules). This SPD data read from
CBFS or EEPROM is then passed into FSP-M using SPD UPDs for different
channels/DIMMs as per the memory organization.

Similarly, DQ/DQS configuration is accepted from mainboard and passed
into FSP-M using UPDs as per the FSP-M/MRC organization of memory
channels.

Different memory technologies on a platform support physical channels
of different widths. Since the data bus width is fixed for a platform,
the number of physical channels is determined by data bus width /
physical channel width. The number of physical channels are different
depending upon the size of physical channel supported by the memory
technology. FSP-M for a platform uses the same set of UPDs for
different memory technologies and aims at providing maximum
flexibility. Thus, the platform code needs to format mainboard inputs
for DQ, DQS and SPD into the UPDs appropriately as per the memory
technology used by the board.

Example: DDR4 on TGL supports 2 physical channels each 64-bit
wide. However, FSP-M UPDs assume channels 16-bit wide. Thus, FSP-M
provides 16 UPDs for SPDs (considering 2 DIMMs per channel and 8
channels with each channel 16-bit wide). Hence, for DDR4, only the SPD
UPDs for MRC channel 0 and 4 are supposed to be used.

This common driver allows the SoC to define the attributes of the
platform:
1. DIMMS_PER_CHANNEL: Maximum DIMMs that are supported per channel by
any memory technology on the platform
2. DATA_BUS_WIDTH: Width of the data bus.
3. MRC_CHANNEL_WIDTH: Width of the channel as used by the MRC to
define UPDs.

In addition to this, the SoC can define different attributes of each
memory technology supported by the platform using `struct
soc_mem_cfg`:
1. Number of physical channels
2. Physical channel to MRC channel mapping
3. Masks for memory down topologies

Using the above information about different memory technologies
supported by the platform and the mainboard configuration for SPD,
the common block memory driver reads SPD data and provides pointers to
this data for each dimm within each channel back to the SoC code. SoC
code can then use this information to configure FSP-M UPDs
accordingly. In addition to that, the common block driver also returns
information about how the channels are populated so that the SoC code
can use this information to expose DQ/DQS information in FSP-M UPDs.

This driver aims at minimizing the effort required for supporting
different memory technologies on any new Intel SoC by reducing per-SoC
effort to a table of configurations rather than having to implement
similar logic for each SoC.

BUG=b:172978729

Change-Id: I256747f0ffc49fb326cd8bc54a6a7b493af139c0
Signed-off-by: Furquan Shaikh <furquan@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/49040
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Reviewed-by: EricR Lai <ericr_lai@compal.corp-partner.google.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Furquan Shaikh 2021-01-15 17:12:12 -08:00 committed by Patrick Georgi
parent 99157c1f4a
commit 859ca18ced
4 changed files with 362 additions and 0 deletions

View File

@ -0,0 +1,159 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__
#define __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__
#include <stddef.h>
#include <stdint.h>
#include <types.h>
/*
* Calculates the number of channels depending upon the data bus width of the
* platform and the channel width.
*/
#define CHANNEL_COUNT(ch_width) (CONFIG_DATA_BUS_WIDTH / (ch_width))
/*
* UPDs for FSP-M are organized depending upon the MRC's view of channel. Thus,
* the number of channels as seen by the MRC are dependent on the channel width
* assumption in the UPDs. These channels might not necessarily be the same as
* the physical channels supported by the platform.
*/
#define MRC_CHANNELS CHANNEL_COUNT(CONFIG_MRC_CHANNEL_WIDTH)
/* Different memory topologies supported by the platform. */
enum mem_topology {
MEM_TOPO_MEMORY_DOWN = BIT(0),
MEM_TOPO_DIMM_MODULE = BIT(1),
MEM_TOPO_MIXED = MEM_TOPO_MEMORY_DOWN | MEM_TOPO_DIMM_MODULE,
};
/*
* SPD provides information about the memory module. Depending upon the memory
* topology, the SPD data can be obtained from different sources. Example: for
* memory down topology, SPD is read from CBFS using cbfs_index. For DIMM
* modules, SPD is read from EEPROM using the DIMM addresses provided by the
* mainboard.
*/
struct mem_spd {
enum mem_topology topo;
/*
* SPD data is read from CBFS spd.bin file using cbfs_index to locate
* the entry. This is used in case of MEM_TOPO_MEMORY_DOWN and
* MEM_TOPO_MIXED topologies.
*/
size_t cbfs_index;
/*
* SPD data is read from on-module EEPROM using the DIMM addresses
* provided by the mainboard. This is used in case of
* MEM_TOPO_DIMM_MODULE and MEM_TOPO_MIXED topologies.
*
* Up to a maximum of MRC_CHANNELS * CONFIG_DIMMS_PER_CHANNEL addresses
* can be provided by mainboard. However, depending upon the memory
* technology being used and the number of physical channels supported
* by that technology, the actual channels might be less than
* MRC_CHANNELS.
*/
struct {
uint8_t addr_dimm[CONFIG_DIMMS_PER_CHANNEL];
} smbus[MRC_CHANNELS];
};
/* Information about memory technology supported by SoC */
struct soc_mem_cfg {
/*
* Number of physical channels that are supported by the memory
* technology.
*/
size_t num_phys_channels;
/*
* Map of physical channel numbers to MRC channel numbers. This is
* helpful in identifying what SPD entries need to be filled for a
* physical channel.
*
* Example: MRC supports 8 channels 0 - 7, but a memory technology
* supports only 2 physical channels 0 - 1. In this case, the map could
* be:
* [0] = 0,
* [1] = 4,
* indicating that physical channel 0 is mapped to MRC channel 0 and
* physical channel 1 is mapped to MRC channel 4.
*/
size_t phys_to_mrc_map[MRC_CHANNELS];
/*
* Masks to be applied in case of memory down topology. For memory down
* topology, there is no separate EEPROM. Thus, the masks need to be
* hard-coded by the SoC to indicate what combinations are supported.
* This is a mask of physical channels for the memory technology.
*
* Example: For the memory technology supporting 2 physical channels,
* where the population rules restrict use of channel 0 for
* half-channel, half_channel mask would be set to 0x1 indicating
* channel 0 is always populated.
*/
struct {
/*
* Mask of physical channels that are populated in case of
* half-channel configuration.
*/
uint32_t half_channel;
/*
* Mask of physical channels that are populated with memory
* down parts in case of mixed topology.
*/
uint32_t mixed_topo;
} md_phy_masks;
};
/* Flags indicating how the channels are populated. */
enum channel_population {
NO_CHANNEL_POPULATED = 0,
TOP_HALF_POPULATED = BIT(0),
BOTTOM_HALF_POPULATED = BIT(1),
FULLY_POPULATED = TOP_HALF_POPULATED | BOTTOM_HALF_POPULATED,
};
/*
* Data for the memory channels that can be used by SoC code to populate FSP
* UPDs.
*/
struct mem_channel_data {
/* Pointer to SPD data for each DIMM of each channel */
uintptr_t spd[MRC_CHANNELS][CONFIG_DIMMS_PER_CHANNEL];
/* Length of SPD data */
size_t spd_len;
/* Flags indicating how channels are populated */
enum channel_population ch_population_flags;
};
/*
* This change populates data regarding memory channels in `struct
* mem_channel_data` using the following inputs from SoC code:
* soc_mem_cfg : SoC-specific information about the memory technology used by
* the mainboard.
* spd_info : Information about the memory topology.
* half_populated: Hint from mainboard if channels are half populated.
*/
void mem_populate_channel_data(const struct soc_mem_cfg *soc_mem_cfg,
const struct mem_spd *spd_info,
bool half_populated,
struct mem_channel_data *data);
/*
* Given a channel number and the maximum number of supported channels, this
* function returns if a channel is populated. This is useful for populating
* DQ/DQS UPDs by the SoC code.
*/
static inline bool channel_is_populated(size_t curr_ch, size_t max_ch,
enum channel_population flags)
{
if ((curr_ch * 2) < max_ch)
return !!(flags & BOTTOM_HALF_POPULATED);
return !!(flags & TOP_HALF_POPULATED);
}
#endif /* __SOC_INTEL_COMMON_BLOCK_MEMINIT_H__ */

View File

@ -0,0 +1,31 @@
config SOC_INTEL_COMMON_BLOCK_MEMINIT
bool
help
Intel common block support for performing initialization
of FSPM UPDs.
if SOC_INTEL_COMMON_BLOCK_MEMINIT
config DIMMS_PER_CHANNEL
int
default 0
help
Maximum number of DIMMs per channel if the memory controller
supports DIMM modules for any memory technology.
config DATA_BUS_WIDTH
int
default 0
help
Data bus width of the platform.
config MRC_CHANNEL_WIDTH
int
default 0
help
Width of the memory channel from the perspective of MRC. This
determines the UPD organization. SoC using this common block
support is expected to set MRC_CHANNEL_WIDTH as per the FSP
MRC expectation.
endif

View File

@ -0,0 +1 @@
romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_MEMINIT) += meminit.c

View File

@ -0,0 +1,171 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <assert.h>
#include <console/console.h>
#include <intelblocks/meminit.h>
#include <commonlib/region.h>
#include <spd_bin.h>
#include <string.h>
_Static_assert(CONFIG_MRC_CHANNEL_WIDTH > 0, "MRC channel width must be >0!");
_Static_assert(CONFIG_DATA_BUS_WIDTH > 0, "Data bus width must be >0!");
_Static_assert(CONFIG_DIMMS_PER_CHANNEL > 0, "DIMMS per channel must be >0!");
/*
* Given mask of channels that are populated, this function returns the flags
* indicating which half of the channels are populated.
*/
static enum channel_population populated_mask_to_flag(uint32_t pop_mask, size_t max_channels)
{
uint32_t full_mask = BIT(max_channels) - 1;
uint32_t bottom_mask = BIT(max_channels / 2) - 1;
uint32_t top_mask = ~bottom_mask & full_mask;
if (pop_mask == full_mask)
return FULLY_POPULATED;
else if (pop_mask == bottom_mask)
return BOTTOM_HALF_POPULATED;
else if (pop_mask == top_mask)
return TOP_HALF_POPULATED;
else if (pop_mask == 0)
return NO_CHANNEL_POPULATED;
die("Unsupported channel population mask(0x%x)\n", pop_mask);
}
static void read_spd_md(const struct soc_mem_cfg *soc_mem_cfg, const struct mem_spd *info,
bool half_populated, struct mem_channel_data *channel_data,
size_t *spd_len)
{
size_t ch;
size_t num_phys_ch = soc_mem_cfg->num_phys_channels;
struct region_device spd_rdev;
uintptr_t spd_data;
/*
* For memory down topologies, start with full mask as per the number
* of physical channels and mask out any channels based on mixed
* topology or half populated flag as set by the mainboard.
*/
uint32_t pop_mask = BIT(num_phys_ch) - 1;
if (!(info->topo & MEM_TOPO_MEMORY_DOWN))
return;
if (info->topo == MEM_TOPO_MIXED)
pop_mask &= soc_mem_cfg->md_phy_masks.mixed_topo;
if (half_populated)
pop_mask &= soc_mem_cfg->md_phy_masks.half_channel;
if (pop_mask == 0)
die("Memory technology does not support the selected configuration!\n");
printk(BIOS_DEBUG, "SPD index = %zu\n", info->cbfs_index);
if (get_spd_cbfs_rdev(&spd_rdev, info->cbfs_index) < 0)
die("SPD not found in CBFS or incorrect index!\n");
/* Memory leak is ok as long as we have memory mapped boot media */
_Static_assert(CONFIG(BOOT_DEVICE_MEMORY_MAPPED),
"Function assumes memory-mapped boot media");
spd_data = (uintptr_t)rdev_mmap_full(&spd_rdev);
*spd_len = region_device_sz(&spd_rdev);
print_spd_info((uint8_t *)spd_data);
for (ch = 0; ch < num_phys_ch; ch++) {
if (!(pop_mask & BIT(ch)))
continue;
int mrc_ch = soc_mem_cfg->phys_to_mrc_map[ch];
/*
* Memory down topology simulates a DIMM. So, the assumption is
* that there is a single DIMM per channel when using memory
* down topology. As SPD describes a DIMM, only DIMM0 for each
* physical channel is filled here.
*/
channel_data->spd[mrc_ch][0] = spd_data;
}
channel_data->ch_population_flags |= populated_mask_to_flag(pop_mask, num_phys_ch);
}
#define CH_DIMM_OFFSET(ch, dimm) ((ch) * CONFIG_DIMMS_PER_CHANNEL + (dimm))
static void read_spd_dimm(const struct soc_mem_cfg *soc_mem_cfg, const struct mem_spd *info,
bool half_populated, struct mem_channel_data *channel_data,
size_t *spd_len)
{
size_t ch, dimm;
struct spd_block blk = { 0 };
size_t num_phys_ch = soc_mem_cfg->num_phys_channels;
/*
* For DIMM modules, start with mask set to no channels populated. If
* SPD is read successfully from EEPROM for any channel, then that
* channel is marked as populated.
*/
uint32_t pop_mask = 0;
if (!(info->topo & MEM_TOPO_DIMM_MODULE))
return;
for (ch = 0; ch < num_phys_ch; ch++) {
for (dimm = 0; dimm < CONFIG_DIMMS_PER_CHANNEL; dimm++) {
blk.addr_map[CH_DIMM_OFFSET(ch, dimm)] =
info->smbus[ch].addr_dimm[dimm];
}
}
get_spd_smbus(&blk);
*spd_len = blk.len;
for (ch = 0; ch < num_phys_ch; ch++) {
size_t mrc_ch = soc_mem_cfg->phys_to_mrc_map[ch];
for (dimm = 0; dimm < CONFIG_DIMMS_PER_CHANNEL; dimm++) {
uint8_t *spd_data = blk.spd_array[CH_DIMM_OFFSET(ch, dimm)];
if (spd_data == NULL)
continue;
print_spd_info(spd_data);
channel_data->spd[mrc_ch][dimm] = (uintptr_t)(void *)spd_data;
pop_mask |= BIT(ch);
}
}
channel_data->ch_population_flags |= populated_mask_to_flag(pop_mask, num_phys_ch);
}
void mem_populate_channel_data(const struct soc_mem_cfg *soc_mem_cfg,
const struct mem_spd *spd_info,
bool half_populated,
struct mem_channel_data *data)
{
size_t spd_md_len = 0, spd_dimm_len = 0;
memset(data, 0, sizeof(*data));
read_spd_md(soc_mem_cfg, spd_info, half_populated, data, &spd_md_len);
read_spd_dimm(soc_mem_cfg, spd_info, half_populated, data, &spd_dimm_len);
if (data->ch_population_flags == NO_CHANNEL_POPULATED)
die("No channels are populated. Incorrect memory configuration!\n");
if (spd_info->topo == MEM_TOPO_MEMORY_DOWN) {
data->spd_len = spd_md_len;
} else if (spd_info->topo == MEM_TOPO_DIMM_MODULE) {
data->spd_len = spd_dimm_len;
} else {
/*
* SPD lengths must match for CBFS and EEPROM SPD for mixed
* topology.
*/
if (spd_md_len != spd_dimm_len)
die("Length of SPD does not match for mixed topology!\n");
}
}