diff options
author | Raul E Rangel <rrangel@chromium.org> | 2021-05-11 11:13:38 -0600 |
---|---|---|
committer | Raul Rangel <rrangel@chromium.org> | 2021-05-19 16:26:44 +0000 |
commit | 12c0542e6fa54a3875c5786d9527bba5ffa8c45c (patch) | |
tree | 0f3bd3bd369a934470fba53f391a89f2bbeb747b /src/soc | |
parent | 224b578420a5d42e16ec6a8285971d34d8cdafac (diff) | |
download | coreboot-12c0542e6fa54a3875c5786d9527bba5ffa8c45c.tar.xz |
soc/amd/common/block/espi_util: Work around in-band reset race condition
When performing an in-band reset the host controller and the
peripheral can have mismatched IO configs.
i.e., The eSPI peripheral can be in IO-4 mode while, the
eSPI host will be in IO-1. This results in the peripheral
getting invalid packets and thus not responding. This causes the
NO_RESPONSE status bit to be set and cause eSPI init to fail.
If the peripheral is alerting when we perform an in-band
reset, there is a race condition in espi_send_command.
1) espi_send_command clears the interrupt status.
2) eSPI host controller hardware notices the alert and sends
a GET_STATUS.
3) espi_send_command writes the in-band reset command.
4) eSPI hardware enqueues the in-band reset until GET_STATUS
is complete.
5) GET_STATUS fails with NO_RESPONSE and sets the interrupt
status.
6) eSPI hardware performs in-band reset.
7) espi_send_command checks the status and sees a
NO_RESPONSE bit.
As a workaround we allow the NO_RESPONSE status code when
we perform an in-band reset.
BUG=b:186135022
TEST=suspend_stress_test and S5->S0 tests on guybrush and zork.
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
Change-Id: I71271377f20eaf29032214be98794e1645d9b70a
Reviewed-on: https://review.coreboot.org/c/coreboot/+/54070
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Felix Held <felix-coreboot@felixheld.de>
Reviewed-by: Rob Barnes <robbarnes@google.com>
Diffstat (limited to 'src/soc')
-rw-r--r-- | src/soc/amd/common/block/lpc/espi_util.c | 32 |
1 files changed, 30 insertions, 2 deletions
diff --git a/src/soc/amd/common/block/lpc/espi_util.c b/src/soc/amd/common/block/lpc/espi_util.c index 276bb3cdc3..c5aac3814a 100644 --- a/src/soc/amd/common/block/lpc/espi_util.c +++ b/src/soc/amd/common/block/lpc/espi_util.c @@ -419,6 +419,7 @@ struct espi_cmd { union espi_txhdr1 hdr1; union espi_txhdr2 hdr2; union espi_txdata data; + uint32_t expected_status_codes; } __packed; /* Wait up to ESPI_CMD_TIMEOUT_US for hardware to clear DNCMD_STATUS bit. */ @@ -512,13 +513,13 @@ static int espi_send_command(const struct espi_cmd *cmd) return -1; } - if (status & ~ESPI_STATUS_DNCMD_COMPLETE) { + if (status & ~(ESPI_STATUS_DNCMD_COMPLETE | cmd->expected_status_codes)) { espi_show_failure(cmd, "Error: unexpected eSPI status register bits set", status); return -1; } - espi_write32(ESPI_SLAVE0_INT_STS, ESPI_STATUS_DNCMD_COMPLETE); + espi_write32(ESPI_SLAVE0_INT_STS, status); return 0; } @@ -530,6 +531,33 @@ static int espi_send_reset(void) .cmd_type = CMD_TYPE_IN_BAND_RESET, .cmd_sts = 1, }, + + /* + * When performing an in-band reset the host controller and the + * peripheral can have mismatched IO configs. + * + * i.e., The eSPI peripheral can be in IO-4 mode while, the + * eSPI host will be in IO-1. This results in the peripheral + * getting invalid packets and thus not responding. + * + * If the peripheral is alerting when we perform an in-band + * reset, there is a race condition in espi_send_command. + * 1) espi_send_command clears the interrupt status. + * 2) eSPI host controller hardware notices the alert and sends + * a GET_STATUS. + * 3) espi_send_command writes the in-band reset command. + * 4) eSPI hardware enqueues the in-band reset until GET_STATUS + * is complete. + * 5) GET_STATUS fails with NO_RESPONSE and sets the interrupt + * status. + * 6) eSPI hardware performs in-band reset. + * 7) espi_send_command checks the status and sees a + * NO_RESPONSE bit. + * + * As a workaround we allow the NO_RESPONSE status code when + * we perform an in-band reset. + */ + .expected_status_codes = ESPI_STATUS_NO_RESPONSE, }; return espi_send_command(&cmd); |