Patrick Rudolph has submitted this change. ( https://review.coreboot.org/c/coreboot/+/40721 )
Change subject: nb/intel/sandybridge/raminit: Fix ECC scrub ......................................................................
nb/intel/sandybridge/raminit: Fix ECC scrub
The scrubbing method was never correct nor tested. Fix that by observations made on mrc.bin.
Tested on HP Z220 with ECC memory and Xeon E3 CPU: The whole memory is now scrubbed.
Change-Id: Ia9fcc236fbf73f51fe944c6dda5d22ba9d334ec7 Signed-off-by: Patrick Rudolph patrick.rudolph@9elements.com Reviewed-on: https://review.coreboot.org/c/coreboot/+/40721 Reviewed-by: Angel Pons th3fanbus@gmail.com Tested-by: build bot (Jenkins) no-reply@coreboot.org --- M src/northbridge/intel/sandybridge/raminit_common.c 1 file changed, 116 insertions(+), 87 deletions(-)
Approvals: build bot (Jenkins): Verified Angel Pons: Looks good to me, approved
diff --git a/src/northbridge/intel/sandybridge/raminit_common.c b/src/northbridge/intel/sandybridge/raminit_common.c index 6588db5..126acbe 100644 --- a/src/northbridge/intel/sandybridge/raminit_common.c +++ b/src/northbridge/intel/sandybridge/raminit_common.c @@ -344,7 +344,8 @@ MCHBAR32(MAD_DIMM(channel)) = ctrl->mad_dimm[channel] | ecc; }
- //udelay(10); /* TODO: Might be needed for ECC configurations; so far works without. */ + if (ctrl->ecc_enabled) + udelay(10); }
void dram_zones(ramctr_timing *ctrl, int training) @@ -4260,98 +4261,126 @@ void channel_scrub(ramctr_timing *ctrl) { int channel, slotrank, row, rowsize; + u8 bank;
+ FOR_ALL_POPULATED_CHANNELS { + wait_for_iosav(channel); + fill_pattern0(ctrl, channel, 0, 0); + MCHBAR32(IOSAV_DATA_CTL_ch(channel)) = 0; + } + + /* + * During runtime the "scrubber" will periodically scan through the memory in the + * physical address space, to identify and fix CRC errors. + * The following loops writes to every DRAM address, setting the ECC bits to the + * correct value. A read from this location will no longer return a CRC error, + * except when a bit has toggled due to external events. + * The same could be accieved by writing to the physical memory map, but it's + * much more difficult due to SMM remapping, ME stolen memory, GFX stolen memory, + * and firmware running in x86_32. + */ FOR_ALL_POPULATED_CHANNELS FOR_ALL_POPULATED_RANKS { rowsize = 1 << ctrl->info.dimm[channel][slotrank >> 1].row_bits; - for (row = 0; row < rowsize; row += 16) { + for (bank = 0; bank < 8; bank++) { + for (row = 0; row < rowsize; row += 16) {
- wait_for_iosav(channel); + /* + * DRAM command ACT + * Opens the row for writing. + */ + { + u8 gap = MAX((ctrl->tFAW >> 2) + 1, ctrl->tRRD); + const struct iosav_ssq ssq = { + .sp_cmd_ctrl = { + .command = IOSAV_ACT, + .ranksel_ap = 1, + }, + .subseq_ctrl = { + .cmd_executions = 1, + .cmd_delay_gap = gap, + .post_ssq_wait = ctrl->tRCD, + .data_direction = SSQ_NA, + }, + .sp_cmd_addr = { + .address = row, + .rowbits = 6, + .bank = bank, + .rank = slotrank, + }, + .addr_update = { + .inc_addr_1 = 1, + .addr_wrap = 18, + }, + }; + iosav_write_ssq(channel, &ssq); + }
- /* DRAM command ACT */ - { - const struct iosav_ssq ssq = { - .sp_cmd_ctrl = { - .command = IOSAV_ACT, - .ranksel_ap = 1, - }, - .subseq_ctrl = { - .cmd_executions = 1, - .cmd_delay_gap = MAX((ctrl->tFAW >> 2) + 1, - ctrl->tRRD), - .post_ssq_wait = ctrl->tRCD, - .data_direction = SSQ_NA, - }, - .sp_cmd_addr = { - .address = row, - .rowbits = 6, - .bank = 0, - .rank = slotrank, - }, - .addr_update = { - .inc_addr_1 = 1, - .addr_wrap = 18, - }, - }; - iosav_write_ssq(channel, &ssq); + /* + * DRAM command WR + * Writes (128 + 1) * 8 (burst length) * 8 (bus width) + * bytes. + */ + { + const struct iosav_ssq ssq = { + .sp_cmd_ctrl = { + .command = IOSAV_WR, + .ranksel_ap = 1, + }, + .subseq_ctrl = { + .cmd_executions = 129, + .cmd_delay_gap = 4, + .post_ssq_wait = ctrl->tWTR + + ctrl->CWL + 8, + .data_direction = SSQ_WR, + }, + .sp_cmd_addr = { + .address = row, + .rowbits = 0, + .bank = bank, + .rank = slotrank, + }, + .addr_update = { + .inc_addr_8 = 1, + .addr_wrap = 9, + }, + }; + iosav_write_ssq(channel, &ssq); + } + + /* + * DRAM command PRE + * Closes the row. + */ + { + const struct iosav_ssq ssq = { + .sp_cmd_ctrl = { + .command = IOSAV_PRE, + .ranksel_ap = 1, + }, + .subseq_ctrl = { + .cmd_executions = 1, + .cmd_delay_gap = 4, + .post_ssq_wait = ctrl->tRP, + .data_direction = SSQ_NA, + }, + .sp_cmd_addr = { + .address = 0, + .rowbits = 6, + .bank = bank, + .rank = slotrank, + }, + .addr_update = { + .addr_wrap = 18, + }, + }; + iosav_write_ssq(channel, &ssq); + } + + /* Execute command queue */ + iosav_run_queue(channel, 16, 0); + + wait_for_iosav(channel); } - - /* DRAM command WR */ - { - const struct iosav_ssq ssq = { - .sp_cmd_ctrl = { - .command = IOSAV_WR, - .ranksel_ap = 1, - }, - .subseq_ctrl = { - .cmd_executions = 129, - .cmd_delay_gap = 4, - .post_ssq_wait = 40, - .data_direction = SSQ_WR, - }, - .sp_cmd_addr = { - .address = row, - .rowbits = 0, - .bank = 0, - .rank = slotrank, - }, - .addr_update = { - .inc_addr_8 = 1, - .addr_wrap = 18, - }, - }; - iosav_write_ssq(channel, &ssq); - } - - /* DRAM command PRE */ - { - const struct iosav_ssq ssq = { - .sp_cmd_ctrl = { - .command = IOSAV_PRE, - .ranksel_ap = 1, - }, - .subseq_ctrl = { - .cmd_executions = 1, - .cmd_delay_gap = 3, - .post_ssq_wait = 40, - .data_direction = SSQ_NA, - }, - .sp_cmd_addr = { - .address = 1024, - .rowbits = 6, - .bank = 0, - .rank = slotrank, - }, - .addr_update = { - .addr_wrap = 18, - }, - }; - iosav_write_ssq(channel, &ssq); - } - - /* execute command queue */ - iosav_run_once(channel); - - wait_for_iosav(channel); } } }