Michał Żygowski has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/68944 )
Change subject: soc/intel/common/block/oc_wdt: Add OC watchdog common block ......................................................................
soc/intel/common/block/oc_wdt: Add OC watchdog common block
Add new block for handling overcloking watchdog. The watchdog is present since Skylake or maybe even earlier so it is safe to use with most of the micrarchitectures utilizing intelblocks.
Some FSPs are also utilizing OC watchdog so care must be taken when initializing it. Example integration provided in subsequent patch.
The patch adds the common block for initializing and feeding the watchdog. Timeout is configurable via Kconfig and cannot be set to less than 60 seconds to avoid reset loops when full memory training is needed.
The patch also adds support for feeding watchdog in driveless mode, i.e. it utilizies periodic SMI to reload the timeout value and restart the watchdog timer. THis is optional and selectabel by Kconfig option as well. If the option is not enabled, payload and/or software must ensure to keep feeding the watchdog, otherwise the platform will reset.
TEST=Enable watchdog on MSI PRO Z690-A and see the platform resets after some time. Enable the watchdog in driverless mode and see the platform no longer resets and periodic SMI keeps feeding the watchdog.
Signed-off-by: Michał Żygowski michal.zygowski@3mdeb.com Change-Id: Ib494aa0c7581351abca8b496fc5895b2c7cbc5bc --- A src/soc/intel/common/block/include/intelblocks/oc_wdt.h A src/soc/intel/common/block/oc_wdt/Kconfig A src/soc/intel/common/block/oc_wdt/Makefile.inc A src/soc/intel/common/block/oc_wdt/oc_wdt.c M src/soc/intel/common/block/smm/smihandler.c M src/soc/intel/common/block/smm/smm.c 6 files changed, 336 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/44/68944/1
diff --git a/src/soc/intel/common/block/include/intelblocks/oc_wdt.h b/src/soc/intel/common/block/include/intelblocks/oc_wdt.h new file mode 100644 index 0000000..7b6c5c5 --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/oc_wdt.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <stdbool.h> + +void wdt_reload_and_start(uint16_t timeout); +void wdt_disable(void); +bool is_wdt_failure(void); +void wdt_allow_known_reset(void); +bool is_wdt_required(void); +bool is_wdt_enabled(void); +void wdt_reset_check(void); diff --git a/src/soc/intel/common/block/oc_wdt/Kconfig b/src/soc/intel/common/block/oc_wdt/Kconfig new file mode 100644 index 0000000..7a4d69d --- /dev/null +++ b/src/soc/intel/common/block/oc_wdt/Kconfig @@ -0,0 +1,40 @@ +config SOC_INTEL_COMMON_BLOCK_OC_WDT + bool + depends on SOC_INTEL_COMMON_BLOCK_PMC + help + Intel Processor common Overclocking Watchdog support + +config SOC_INTEL_COMMON_OC_WDT_ENABLE + bool "Enable chipset watchdog during boot" + depends on SOC_INTEL_COMMON_BLOCK_OC_WDT + select HAVE_CF9_RESET_PREPARE + help + Enables Intel chipset Overclocking Watchdog to count during system + boot. The platform will reset during lockups if watchdog is not + reloaded. Software/firmware is responsible for feeding the watchdog. + + If unsure, say N. + +config SOC_INTEL_COMMON_OC_WDT_TIMEOUT + int "Watchdog timeout in seconds" + depends on SOC_INTEL_COMMON_OC_WDT_ENABLE + default 90 + help + Intel chipset Overclocking Watchdog timeout value in seconds. + coreboot will reload the watchdog with timeout value specified + in this option. Specify a high enough value so that the platform + will have a chance to boot, at least 90 seconds or so. Especially + when platform does full memory initialization. + +config SOC_INTEL_COMMON_OC_WDT_RELOAD_IN_PERIODIC_SMI + bool "Feed the watchdog using periodic SMI" + depends on SOC_INTEL_COMMON_OC_WDT_ENABLE + depends on SOC_INTEL_COMMON_BLOCK_SMM + help + Enables Intel chipset Overclocking Watchdog reloading in the periodic + SMI handler. Allows to reload the watchdog without support of the + watchdog in the payload/software using the periodic SMI handler in + coreboot. + + If unsure, say Y. + diff --git a/src/soc/intel/common/block/oc_wdt/Makefile.inc b/src/soc/intel/common/block/oc_wdt/Makefile.inc new file mode 100644 index 0000000..1e625bf --- /dev/null +++ b/src/soc/intel/common/block/oc_wdt/Makefile.inc @@ -0,0 +1,6 @@ +bootblock-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c +verstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c +romstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c +postcar-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c +ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c +smm-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c diff --git a/src/soc/intel/common/block/oc_wdt/oc_wdt.c b/src/soc/intel/common/block/oc_wdt/oc_wdt.c new file mode 100644 index 0000000..4437755 --- /dev/null +++ b/src/soc/intel/common/block/oc_wdt/oc_wdt.c @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <acpi/acpi.h> +#include <arch/io.h> +#include <cf9_reset.h> +#include <console/console.h> +#include <halt.h> +#include <intelblocks/oc_wdt.h> +#include <intelblocks/pmclib.h> +#include <soc/iomap.h> +#include <soc/pm.h> + +#if CONFIG_SOC_INTEL_COMMON_OC_WDT_TIMEOUT < 60 +#error "Watchdog timeout too low. Set at least 60 seconds timeout" +#endif + +/* OC WDT configuration */ +#define PCH_OC_WDT_CTL (ACPI_BASE_ADDRESS + 0x54) +#define PCH_OC_WDT_CTL_RLD BIT(31) +#define PCH_OC_WDT_CTL_ICCSURV_STS BIT(25) +#define PCH_OC_WDT_CTL_NO_ICCSURV_STS BIT(24) +#define PCH_OC_WDT_CTL_FORCE_ALL BIT(15) +#define PCH_OC_WDT_CTL_EN BIT(14) +#define PCH_OC_WDT_CTL_ICCSURV BIT(13) +#define PCH_OC_WDT_CTL_LCK BIT(12) +#define PCH_OC_WDT_CTL_TOV_MASK 0x3FF +#define PCH_OC_WDT_CTL_FAILURE_STS BIT(23) +#define PCH_OC_WDT_CTL_UNXP_RESET_STS BIT(22) +#define PCH_OC_WDT_CTL_ALLOW_UNXP_RESET_STS BIT(21) +#define PCH_OC_WDT_CTL_AFTER_POST 0x1F0000 + +void wdt_reload_and_start(uint16_t timeout) +{ + uint32_t readback; + + if (!CONFIG(SOC_INTEL_COMMON_OC_WDT_ENABLE)) + return; + + printk(BIOS_SPEW, "Watchdog: reload and start timer (timeout %ds)\n", timeout); + + if (timeout > PCH_OC_WDT_CTL_TOV_MASK || timeout == 0) { + printk(BIOS_ERR, "Watchdog: invalid timeout value\n"); + return; + } + + readback = inl(PCH_OC_WDT_CTL); + readback |= (PCH_OC_WDT_CTL_EN | PCH_OC_WDT_CTL_FORCE_ALL | PCH_OC_WDT_CTL_ICCSURV); + + if (!(readback & PCH_OC_WDT_CTL_ALLOW_UNXP_RESET_STS)) + readback |= PCH_OC_WDT_CTL_UNXP_RESET_STS; + + readback &= ~PCH_OC_WDT_CTL_TOV_MASK; + readback |= ((timeout - 1) & PCH_OC_WDT_CTL_TOV_MASK); + readback |= PCH_OC_WDT_CTL_RLD; + + outl(readback, PCH_OC_WDT_CTL); +} + +void wdt_disable(void) +{ + uint32_t readback; + + printk(BIOS_DEBUG, "Watchdog: disabling OC watchdog timer\n"); + + readback = inl(PCH_OC_WDT_CTL); + readback &= ~(PCH_OC_WDT_CTL_EN | PCH_OC_WDT_CTL_FORCE_ALL | + PCH_OC_WDT_CTL_UNXP_RESET_STS); + outl(readback, PCH_OC_WDT_CTL); +} + +bool is_wdt_failure(void) +{ + uint32_t readback; + + readback = inl(PCH_OC_WDT_CTL); + + printk(BIOS_SPEW, "Watchdog: status = (%08x)\n", readback); + + if (readback & PCH_OC_WDT_CTL_FAILURE_STS) { + printk (BIOS_ERR, "Watchdog: Failure detected\n"); + return true; + } else { + return false; + } +} + +/* + * Normally, each reboot performed while watchdog runs is considered a failure. + * This function allows platform to perform expected reboots with WDT running, + * without being interpreted as failures. + */ +void wdt_allow_known_reset(void) +{ + uint32_t readback; + + if (!CONFIG(SOC_INTEL_COMMON_OC_WDT_ENABLE)) + return; + + readback = inl(PCH_OC_WDT_CTL); + readback &= ~(PCH_OC_WDT_CTL_UNXP_RESET_STS | PCH_OC_WDT_CTL_FORCE_ALL); + readback |= PCH_OC_WDT_CTL_ALLOW_UNXP_RESET_STS; + outl(readback, PCH_OC_WDT_CTL); +} + +void cf9_reset_prepare(void) +{ + wdt_allow_known_reset(); +} + +/* + * Returns information if WDT coverage for the duration of BIOS execution + * was requested by an OS application + */ +bool is_wdt_required(void) +{ + if (inl(PCH_OC_WDT_CTL) & PCH_OC_WDT_CTL_AFTER_POST) { + printk(BIOS_DEBUG, "Watchdog: OS requested WDT coverage"); + if (!CONFIG(SOC_INTEL_COMMON_OC_WDT_ENABLE)) { + printk(BIOS_DEBUG, "but WDT not enabled\n"); + return false; + } else { + printk(BIOS_DEBUG, "\n"); + return true; + } + } else { + printk(BIOS_DEBUG, "Watchdog: OS did not request WDT coverage\n"); + return false; + } +} + +bool is_wdt_enabled(void) +{ + if (inl(PCH_OC_WDT_CTL) & PCH_OC_WDT_CTL_EN) { + printk(BIOS_DEBUG, "Watchdog: WDT enabled\n"); + return true; + } else { + printk(BIOS_DEBUG, "Watchdog: WDT disabled\n"); + return false; + } +} + + +static bool is_wake_from_s3_s4(void) +{ + /* Read power state from PMC ABASE */ + if (!(inw(ACPI_BASE_ADDRESS + PM1_STS) & WAK_STS)) + return false; + + switch (acpi_sleep_from_pm1(pmc_read_pm1_control())) { + case ACPI_S3: + case ACPI_S4: + return true; + default: + return false; + } +} + +static void wdt_clear_allow_known_reset(void) +{ + uint32_t readback; + + readback = inl(PCH_OC_WDT_CTL); + readback &= ~PCH_OC_WDT_CTL_ALLOW_UNXP_RESET_STS; + readback &= ~(PCH_OC_WDT_CTL_ICCSURV_STS | PCH_OC_WDT_CTL_NO_ICCSURV_STS); + + outl(readback, PCH_OC_WDT_CTL); +} + +/* + * Check for unexpected reset. + * If there was an unexpected reset, enforces WDT expiration. + * Should be called before memory init. + */ +void wdt_reset_check(void) +{ + uint32_t readback; + + wdt_clear_allow_known_reset(); + readback = inl(PCH_OC_WDT_CTL); + + printk(BIOS_DEBUG, "Watchdog: OC WDT reset check\n"); + + /* + * If there was a WDT expiration, set Failure Status and clear timeout status bits. + * Timeout status bits are cleared by writing '1'. + */ + if (readback & (PCH_OC_WDT_CTL_ICCSURV_STS | PCH_OC_WDT_CTL_NO_ICCSURV_STS)) { + printk (BIOS_ERR, "Watchdog: timer expiration detected.\n"); + readback |= PCH_OC_WDT_CTL_FAILURE_STS; + readback |= (PCH_OC_WDT_CTL_ICCSURV_STS | PCH_OC_WDT_CTL_NO_ICCSURV_STS); + readback &= ~PCH_OC_WDT_CTL_UNXP_RESET_STS; + } else { + /* + * If there was unexpected reset but no WDT expiration and no resume from + * S3/S4, clear unexpected reset status and enforce expiration. + */ + if ((readback & PCH_OC_WDT_CTL_UNXP_RESET_STS) && !is_wake_from_s3_s4()) { + printk(BIOS_ERR, "Watchdog: unexpected reset detected\n"); + printk(BIOS_ERR, "Watchdog: enforcing WDT expiration\n"); + wdt_reload_and_start(1); + /* wait for reboot caused by WDT expiration */ + halt(); + } else { + /* No WDT expiration and no unexpected reset - clear failure status */ + printk(BIOS_DEBUG, "Watchdog: status OK.\n"); + readback &= ~PCH_OC_WDT_CTL_FAILURE_STS; + readback |= (PCH_OC_WDT_CTL_ICCSURV_STS | + PCH_OC_WDT_CTL_NO_ICCSURV_STS); + } + } + + outl(readback, PCH_OC_WDT_CTL); +} diff --git a/src/soc/intel/common/block/smm/smihandler.c b/src/soc/intel/common/block/smm/smihandler.c index 0a277f0..cde755f 100644 --- a/src/soc/intel/common/block/smm/smihandler.c +++ b/src/soc/intel/common/block/smm/smihandler.c @@ -15,6 +15,7 @@ #include <device/pci_ops.h> #include <elog.h> #include <intelblocks/fast_spi.h> +#include <intelblocks/oc_wdt.h> #include <intelblocks/pmclib.h> #include <intelblocks/smihandler.h> #include <intelblocks/tco.h> @@ -480,6 +481,9 @@ if ((reg32 & PERIODIC_EN) == 0) return; printk(BIOS_DEBUG, "Periodic SMI.\n"); + + if (CONFIG(SOC_INTEL_COMMON_OC_WDT_RELOAD_IN_PERIODIC_SMI)) + wdt_reload_and_start(CONFIG_SOC_INTEL_COMMON_OC_WDT_TIMEOUT); }
void smihandler_southbridge_gpi( diff --git a/src/soc/intel/common/block/smm/smm.c b/src/soc/intel/common/block/smm/smm.c index 2fd97da..bab84d4 100644 --- a/src/soc/intel/common/block/smm/smm.c +++ b/src/soc/intel/common/block/smm/smm.c @@ -3,9 +3,11 @@ #include <console/console.h> #include <cpu/x86/smm.h> #include <cpu/intel/smm_reloc.h> +#include <device/mmio.h> #include <intelblocks/pmclib.h> #include <intelblocks/systemagent.h> #include <soc/pm.h> +#include <soc/pmc.h>
void smm_southbridge_clear_state(void) { @@ -23,6 +25,27 @@ pmc_clear_all_gpe_status(); }
+static void configure_periodic_smi_interval(void) +{ + uint32_t gen_pmcon_a; + + if (!CONFIG(SOC_INTEL_COMMON_OC_WDT_RELOAD_IN_PERIODIC_SMI)) + return; + + gen_pmcon_a = read32p(soc_read_pmc_base() + GEN_PMCON_A); + gen_pmcon_a &= ~PER_SMI_SEL_MASK; + + /* + * Periodic SMIs have +/- 1 second error, to be safe add few seconds more. + * Also we do not allow timeouts lower than 60s, so we need to handle only + * two cases. + */ + if (CONFIG_SOC_INTEL_COMMON_OC_WDT_TIMEOUT > 70) + write32p(soc_read_pmc_base() + GEN_PMCON_A, gen_pmcon_a | SMI_RATE_64S); + else + write32p(soc_read_pmc_base() + GEN_PMCON_A, gen_pmcon_a | SMI_RATE_32S); +} + static void smm_southbridge_enable(uint16_t pm1_events) { uint32_t smi_params = ENABLE_SMI_PARAMS; @@ -48,6 +71,7 @@ * - on writes to GBL_RLS (bios commands) * - on eSPI events, unless disabled (does nothing on LPC systems) * - on TCO events (TIMEOUT, case intrusion, ...), if enabled + * - periodically, if watchdog feeding through SMI is enabled * No SMIs: * - on microcontroller writes (io 0x62/0x66) */ @@ -57,6 +81,11 @@ if (CONFIG(SOC_INTEL_COMMON_BLOCK_SMM_TCO_ENABLE)) smi_params |= TCO_SMI_EN;
+ if (CONFIG(SOC_INTEL_COMMON_OC_WDT_RELOAD_IN_PERIODIC_SMI)) { + smi_params |= PERIODIC_EN; + configure_periodic_smi_interval(); + } + /* Enable SMI generation: */ pmc_enable_smi(smi_params); }