sb/intel/common/spi: Prevent transfers across 4KiB boundaries

The ICH SPI controller fails when a single transfer spans a 4KiB
boundary. Limit data_length in spi_ctrlr_xfer() to stay within the
current 4KiB page when with_address is true, avoiding the hardware
limitation at the platform driver level.

This fixes SPI read errors observed on SandyBridge, IvyBridge, Haswell,
and Broadwell when reading option variables stored in SMMSTORE. When
scanning the store to locate a given variable, reads would often cross
into the next 4KiB page (eg, reading 60 bytes from 0x313ff0).

TEST=build/boot stumpy, link, beltino, jecht boards, verify no SPI read
errors in cbmem, CFR options work properly.

Change-Id: I73d9c0acdbbb2faf5caff1f73049bff900774156
Signed-off-by: Matt DeVillier <matt.devillier@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/90689
Reviewed-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Jérémy Compostella <jeremy.compostella@intel.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Matt DeVillier 2026-01-06 15:55:59 -06:00
commit 8bc1372f72

View file

@ -619,14 +619,28 @@ static int spi_ctrlr_xfer(const struct spi_slave *slave, const void *dout,
*/
while (trans.bytesout || trans.bytesin) {
uint32_t data_length;
uint32_t max_length;
/* SPI addresses are 24 bit only */
writel_(trans.offset & 0x00FFFFFF, cntlr.addr);
if (trans.bytesout)
data_length = MIN(trans.bytesout, cntlr.databytes);
max_length = MIN(trans.bytesout, cntlr.databytes);
else
data_length = MIN(trans.bytesin, cntlr.databytes);
max_length = MIN(trans.bytesin, cntlr.databytes);
/*
* Avoid transfers that cross 4KiB boundaries. The ICH SPI controller
* has been observed to fail on some platforms when a single transfer
* spans a 4KiB boundary (e.g., offset 0x...3ff0, len 0x3c crosses 0x...4000).
*/
if (with_address) {
uint32_t off_in_4k = trans.offset & 0xfff;
uint32_t max_to_boundary = 0x1000 - off_in_4k;
data_length = MIN(max_length, max_to_boundary);
} else {
data_length = max_length;
}
/* Program data into FDATA0 to N */
if (trans.bytesout) {