From a86d1b8af564c291100a854cf8bb64fbe34ee952 Mon Sep 17 00:00:00 2001 From: Brandon Breitenstein Date: Thu, 8 Jun 2017 17:32:02 -0700 Subject: soc/intel/common: Add SMM common code for Intel Platforms SMI code is very similar across Intel platforms. Move this code to common/block/smi to allow it to be shared between platforms instead of duplicating the code for each platform. smihandler.h has already been made common so all it will contain is name changes and a move to the common block location. Due to moving smihandler code, APL changes are bundled here to show this change. Change-Id: I599358f23d5de7564ef1ca414bccd54cebab5a4c Signed-off-by: Brandon Breitenstein Reviewed-on: https://review.coreboot.org/19392 Tested-by: build bot (Jenkins) Reviewed-by: Aaron Durbin --- src/soc/intel/common/Makefile.inc | 2 - .../common/block/include/intelblocks/smihandler.h | 126 ++++++ .../intel/common/block/include/intelblocks/smm.h | 34 ++ src/soc/intel/common/block/smm/Kconfig | 4 + src/soc/intel/common/block/smm/Makefile.inc | 2 + src/soc/intel/common/block/smm/smihandler.c | 501 +++++++++++++++++++++ src/soc/intel/common/block/smm/smm.c | 78 ++++ src/soc/intel/common/smi.h | 113 ----- src/soc/intel/common/smihandler.c | 494 -------------------- 9 files changed, 745 insertions(+), 609 deletions(-) create mode 100644 src/soc/intel/common/block/include/intelblocks/smihandler.h create mode 100644 src/soc/intel/common/block/include/intelblocks/smm.h create mode 100644 src/soc/intel/common/block/smm/Kconfig create mode 100644 src/soc/intel/common/block/smm/Makefile.inc create mode 100644 src/soc/intel/common/block/smm/smihandler.c create mode 100644 src/soc/intel/common/block/smm/smm.c delete mode 100644 src/soc/intel/common/smi.h delete mode 100644 src/soc/intel/common/smihandler.c (limited to 'src/soc/intel/common') diff --git a/src/soc/intel/common/Makefile.inc b/src/soc/intel/common/Makefile.inc index 340e001d2d..110977585d 100644 --- a/src/soc/intel/common/Makefile.inc +++ b/src/soc/intel/common/Makefile.inc @@ -31,8 +31,6 @@ ramstage-$(CONFIG_SOC_INTEL_COMMON_GFX_OPREGION) += opregion.c ramstage-$(CONFIG_SOC_INTEL_COMMON_ACPI) += ./acpi/acpi.c ramstage-$(CONFIG_SOC_INTEL_COMMON_NHLT) += nhlt.c -smm-$(CONFIG_SOC_INTEL_COMMON_SMI) += smihandler.c - bootblock-$(CONFIG_MAINBOARD_HAS_TPM_CR50) += tpm_tis.c verstage-$(CONFIG_MAINBOARD_HAS_TPM_CR50) += tpm_tis.c romstage-$(CONFIG_MAINBOARD_HAS_TPM_CR50) += tpm_tis.c diff --git a/src/soc/intel/common/block/include/intelblocks/smihandler.h b/src/soc/intel/common/block/include/intelblocks/smihandler.h new file mode 100644 index 0000000000..b587bb136d --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/smihandler.h @@ -0,0 +1,126 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef SOC_INTEL_COMMON_BLOCK_SMI_HANDLER_H +#define SOC_INTEL_COMMON_BLOCK_SMI_HANDLER_H + +#include + +struct gpi_status; +struct global_nvs_t; + +/* + * The register value is used with get_reg and set_reg + */ +enum smm_reg { + RAX, + RBX, + RCX, + RDX, +}; + +struct smm_save_state_ops { + /* return io_misc_info from SMM Save State Area */ + uint32_t (*get_io_misc_info)(void *state); + + /* return value of the requested register from + * SMM Save State Area + */ + uint64_t (*get_reg)(void *state, enum smm_reg reg); + + void (*set_reg)(void *state, enum smm_reg reg, uint64_t val); +}; + +typedef void (*smi_handler_t)(const struct smm_save_state_ops *save_state_ops); + +/* + * SOC SMI Handler has to provide this structure which has methods to access + * the SOC specific SMM Save State Area + */ +const struct smm_save_state_ops *get_smm_save_state_ops(void); + +/* + * southbridge_smi should be defined inside SOC specific code and should have + * handlers for any SMI events that need to be handled. Default handlers + * for some SMI events are provided in soc/intel/common/block/smm/smihandler.c + */ +extern const smi_handler_t southbridge_smi[32]; + +/* + * This function should be implemented in SOC specific code to handle + * the SMI event on SLP_EN. The default functionality is provided in + * soc/intel/common/block/smm/smihandler.c + */ +void smihandler_southbridge_sleep( + const struct smm_save_state_ops *save_state_ops); + +/* + * This function should be implemented in SOC specific code to handle + * SMI_APM event. The default functionality is provided in + * soc/intel/common/block/smm/smihandler.c + */ +void smihandler_southbridge_apmc( + const struct smm_save_state_ops *save_state_ops); + +/* + * This function should be implemented in SOC specific code to handle + * SMI_PM1 event. The default functionality is provided in + * soc/intel/common/block/smm/smihandler.c + */ +void smihandler_southbridge_pm1( + const struct smm_save_state_ops *save_state_ops); + +/* + * This function should be implemented in SOC specific code to handle + * SMI_GPE0 event. The default functionality is provided in + * soc/intel/common/block/smm/smihandler.c + */ +void smihandler_southbridge_gpe0( + const struct smm_save_state_ops *save_state_ops); + +/* + * This function should be implemented in SOC specific code to handle + * SMI_TCO event. The default functionality is provided in + * soc/intel/common/block/smm/smihandler.c + */ +void smihandler_southbridge_tco( + const struct smm_save_state_ops *save_state_ops); + +/* + * This function should be implemented in SOC specific code to handle + * SMI PERIODIC_STS event. The default functionality is provided in + * soc/intel/common/block/smm/smihandler.c + */ +void smihandler_southbridge_periodic( + const struct smm_save_state_ops *save_state_ops); + +/* + * This function returns a 1 or 0 depending on whether disable_busmaster + * needs to be done for the specified device on S5 entry + */ +int smihandler_disable_busmaster(device_t dev); + +/* + * Returns gnvs pointer within SMM context + */ +struct global_nvs_t *smm_get_gnvs(void); + +/* Mainboard handler for GPI SMIs */ +void mainboard_smi_gpi_handler(const struct gpi_status *sts); + +extern const struct smm_save_state_ops em64t100_smm_ops; + +extern const struct smm_save_state_ops em64t101_smm_ops; +#endif diff --git a/src/soc/intel/common/block/include/intelblocks/smm.h b/src/soc/intel/common/block/include/intelblocks/smm.h new file mode 100644 index 0000000000..09378b9463 --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/smm.h @@ -0,0 +1,34 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 201 Intel Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef SOC_INTEL_COMMON_BLOCK_SMM_H +#define SOC_INTEL_COMMON_BLOCK_SMM_H + +/* + * This common code block relies on each specific SOC defining the macro + * ENABLE_SMI_PARAMS for the values needed for SMI enabling on the + * specific SOC + */ + +/* + * The initialization of the southbridge is split into 2 compoments. One is + * for clearing the state in the SMM registers. The other is for enabling + * SMIs. + */ +void smm_southbridge_clear_state(void); +void smm_southbridge_enable(void); + +#endif diff --git a/src/soc/intel/common/block/smm/Kconfig b/src/soc/intel/common/block/smm/Kconfig new file mode 100644 index 0000000000..003fc24cd8 --- /dev/null +++ b/src/soc/intel/common/block/smm/Kconfig @@ -0,0 +1,4 @@ +config SOC_INTEL_COMMON_BLOCK_SMM + bool + help + Intel Processor common SMM support diff --git a/src/soc/intel/common/block/smm/Makefile.inc b/src/soc/intel/common/block/smm/Makefile.inc new file mode 100644 index 0000000000..13793ee891 --- /dev/null +++ b/src/soc/intel/common/block/smm/Makefile.inc @@ -0,0 +1,2 @@ +ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMM) += smm.c +smm-$(CONFIG_SOC_INTEL_COMMON_BLOCK_SMM) += smihandler.c diff --git a/src/soc/intel/common/block/smm/smihandler.c b/src/soc/intel/common/block/smm/smihandler.c new file mode 100644 index 0000000000..b620ff9f1e --- /dev/null +++ b/src/soc/intel/common/block/smm/smihandler.c @@ -0,0 +1,501 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2013 Google Inc. + * Copyright (C) 2015-2016 Intel Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* GNVS needs to be set by coreboot initiating a software SMI. */ +static struct global_nvs_t *gnvs; + +__attribute__((weak)) int smihandler_disable_busmaster(device_t dev) +{ + return 1; +} + +static void *find_save_state(const struct smm_save_state_ops *save_state_ops, + int cmd) +{ + int node; + void *state = NULL; + uint32_t io_misc_info; + uint8_t reg_al; + + /* Check all nodes looking for the one that issued the IO */ + for (node = 0; node < CONFIG_MAX_CPUS; node++) { + state = smm_get_save_state(node); + + io_misc_info = save_state_ops->get_io_misc_info(state); + + /* Check for Synchronous IO (bit0==1) */ + if (!(io_misc_info & (1 << 0))) + continue; + /* Make sure it was a write (bit4==0) */ + if (io_misc_info & (1 << 4)) + continue; + /* Check for APMC IO port */ + if (((io_misc_info >> 16) & 0xff) != APM_CNT) + continue; + /* Check AL against the requested command */ + reg_al = save_state_ops->get_reg(state, RAX); + if (reg_al != cmd) + continue; + break; + } + return state; +} + +/* Inherited from cpu/x86/smm.h resulting in a different signature */ +void southbridge_smi_set_eos(void) +{ + enable_smi(EOS); +} + +struct global_nvs_t *smm_get_gnvs(void) +{ + return gnvs; +} + +static void busmaster_disable_on_bus(int bus) +{ + int slot, func; + unsigned int val; + unsigned char hdr; + + for (slot = 0; slot < 0x20; slot++) { + for (func = 0; func < 8; func++) { + u32 reg32; + device_t dev = PCI_DEV(bus, slot, func); + + if (!smihandler_disable_busmaster(dev)) + continue; + val = pci_read_config32(dev, PCI_VENDOR_ID); + + if (val == 0xffffffff || val == 0x00000000 || + val == 0x0000ffff || val == 0xffff0000) + continue; + + /* Disable Bus Mastering for this one device */ + reg32 = pci_read_config32(dev, PCI_COMMAND); + reg32 &= ~PCI_COMMAND_MASTER; + pci_write_config32(dev, PCI_COMMAND, reg32); + + /* If it's not a bridge, move on. */ + hdr = pci_read_config8(dev, PCI_HEADER_TYPE); + hdr &= 0x7f; + if (hdr != PCI_HEADER_TYPE_BRIDGE && + hdr != PCI_HEADER_TYPE_CARDBUS) + continue; + + /* + * If secondary bus is equal to current bus bypass + * the bridge because it's likely unconfigured and + * would cause infinite recursion. + */ + int secbus = pci_read_config8(dev, PCI_SECONDARY_BUS); + + if (secbus == bus) + continue; + + busmaster_disable_on_bus(secbus); + } + } +} + + +void smihandler_southbridge_sleep( + const struct smm_save_state_ops *save_state_ops) +{ + uint32_t reg32; + uint8_t slp_typ; + + /* First, disable further SMIs */ + disable_smi(SLP_SMI_EN); + /* Figure out SLP_TYP */ + reg32 = inl(ACPI_BASE_ADDRESS + PM1_CNT); + printk(BIOS_SPEW, "SMI#: SLP = 0x%08x\n", reg32); + slp_typ = acpi_sleep_from_pm1(reg32); + + /* Do any mainboard sleep handling */ + mainboard_smi_sleep(slp_typ); + + /* Log S3, S4, and S5 entry */ + if (slp_typ >= ACPI_S3 && IS_ENABLED(CONFIG_ELOG_GSMI)) + elog_add_event_byte(ELOG_TYPE_ACPI_ENTER, slp_typ); + + /* Clear pending GPE events */ + clear_gpe_status(); + + /* Next, do the deed. */ + + switch (slp_typ) { + case ACPI_S0: + printk(BIOS_DEBUG, "SMI#: Entering S0 (On)\n"); + break; + case ACPI_S3: + printk(BIOS_DEBUG, "SMI#: Entering S3 (Suspend-To-RAM)\n"); + + /* Invalidate the cache before going to S3 */ + wbinvd(); + break; + case ACPI_S4: + printk(BIOS_DEBUG, "SMI#: Entering S4 (Suspend-To-Disk)\n"); + break; + case ACPI_S5: + printk(BIOS_DEBUG, "SMI#: Entering S5 (Soft Power off)\n"); + + /* Disable all GPE */ + disable_all_gpe(); + /* also iterates over all bridges on bus 0 */ + busmaster_disable_on_bus(0); + break; + default: + printk(BIOS_DEBUG, "SMI#: ERROR: SLP_TYP reserved\n"); + break; + } + + /* Clear the gpio gpe0 status bits in ACPI registers */ + clear_gpi_gpe_sts(); + + /* Tri-state specific GPIOS to avoid leakage during S3/S5 */ + + /* + * Write back to the SLP register to cause the originally intended + * event again. We need to set BIT13 (SLP_EN) though to make the + * sleep happen. + */ + enable_pm1_control(SLP_EN); + + /* Make sure to stop executing code here for S3/S4/S5 */ + if (slp_typ >= ACPI_S3) + hlt(); + + /* + * In most sleep states, the code flow of this function ends at + * the line above. However, if we entered sleep state S1 and wake + * up again, we will continue to execute code in this function. + */ + reg32 = inl(ACPI_BASE_ADDRESS + PM1_CNT); + if (reg32 & SCI_EN) { + /* The OS is not an ACPI OS, so we set the state to S0 */ + disable_pm1_control(SLP_EN | SLP_TYP); + } +} + +static void southbridge_smi_gsmi( + const struct smm_save_state_ops *save_state_ops) +{ + u8 sub_command, ret; + void *io_smi = NULL; + uint32_t reg_ebx; + + io_smi = find_save_state(save_state_ops, ELOG_GSMI_APM_CNT); + if (!io_smi) + return; + /* Command and return value in EAX */ + sub_command = (save_state_ops->get_reg(io_smi, RAX) >> 8) + & 0xff; + + /* Parameter buffer in EBX */ + reg_ebx = save_state_ops->get_reg(io_smi, RBX); + + /* drivers/elog/gsmi.c */ + ret = gsmi_exec(sub_command, ®_ebx); + save_state_ops->set_reg(io_smi, RAX, ret); +} + +static void finalize(void) +{ + static int finalize_done; + + if (finalize_done) { + printk(BIOS_DEBUG, "SMM already finalized.\n"); + return; + } + finalize_done = 1; + +} + +void smihandler_southbridge_apmc( + const struct smm_save_state_ops *save_state_ops) +{ + uint8_t reg8; + void *state = NULL; + static int smm_initialized = 0; + + /* Emulate B2 register as the FADT / Linux expects it */ + + reg8 = inb(APM_CNT); + switch (reg8) { + case APM_CNT_CST_CONTROL: + /* + * Calling this function seems to cause + * some kind of race condition in Linux + * and causes a kernel oops + */ + printk(BIOS_DEBUG, "C-state control\n"); + break; + case APM_CNT_PST_CONTROL: + /* + * Calling this function seems to cause + * some kind of race condition in Linux + * and causes a kernel oops + */ + printk(BIOS_DEBUG, "P-state control\n"); + break; + case APM_CNT_ACPI_DISABLE: + disable_pm1_control(SCI_EN); + printk(BIOS_DEBUG, "SMI#: ACPI disabled.\n"); + break; + case APM_CNT_ACPI_ENABLE: + enable_pm1_control(SCI_EN); + printk(BIOS_DEBUG, "SMI#: ACPI enabled.\n"); + break; + case APM_CNT_GNVS_UPDATE: + if (smm_initialized) { + printk(BIOS_DEBUG, + "SMI#: SMM structures already initialized!\n"); + return; + } + state = find_save_state(save_state_ops, reg8); + if (state) { + /* EBX in the state save contains the GNVS pointer */ + uint32_t reg_ebx = save_state_ops->get_reg(state, RBX); + gnvs = (struct global_nvs_t *)(uintptr_t)reg_ebx; + smm_initialized = 1; + printk(BIOS_DEBUG, "SMI#: Setting GNVS to %p\n", gnvs); + } + break; + case ELOG_GSMI_APM_CNT: + if (IS_ENABLED(CONFIG_ELOG_GSMI)) + southbridge_smi_gsmi(save_state_ops); + break; + case APM_CNT_FINALIZE: + finalize(); + break; + } + + mainboard_smi_apmc(reg8); +} + +void smihandler_southbridge_pm1( + const struct smm_save_state_ops *save_state_ops) +{ + uint16_t pm1_sts = clear_pm1_status(); + + /* + * While OSPM is not active, poweroff immediately + * on a power button event. + */ + if (pm1_sts & PWRBTN_STS) { + /* power button pressed */ + if (IS_ENABLED(CONFIG_ELOG_GSMI)) + elog_add_event(ELOG_TYPE_POWER_BUTTON); + disable_pm1_control(-1UL); + enable_pm1_control(SLP_EN | (SLP_TYP_S5 << SLP_TYP_SHIFT)); + } +} + +void smihandler_southbridge_gpe0( + const struct smm_save_state_ops *save_state_ops) +{ + clear_gpe_status(); +} + +void smihandler_southbridge_tco( + const struct smm_save_state_ops *save_state_ops) +{ + uint32_t tco_sts = clear_tco_status(); + + /* Any TCO event? */ + if (!tco_sts) + return; + + if (tco_sts & TCO_TIMEOUT) { /* TIMEOUT */ + /* Handle TCO timeout */ + printk(BIOS_DEBUG, "TCO Timeout.\n"); + } +} + +void smihandler_southbridge_periodic( + const struct smm_save_state_ops *save_state_ops) +{ + uint32_t reg32; + + reg32 = get_smi_en(); + + /* Are periodic SMIs enabled? */ + if ((reg32 & PERIODIC_EN) == 0) + return; + printk(BIOS_DEBUG, "Periodic SMI.\n"); +} + +void southbridge_smi_handler(void) +{ + int i; + uint32_t smi_sts; + const struct smm_save_state_ops *save_state_ops; + + /* + * We need to clear the SMI status registers, or we won't see what's + * happening in the following calls. + */ + smi_sts = clear_smi_status(); + + save_state_ops = get_smm_save_state_ops(); + + /* Call SMI sub handler for each of the status bits */ + for (i = 0; i < ARRAY_SIZE(southbridge_smi); i++) { + if (!(smi_sts & (1 << i))) + continue; + + if (southbridge_smi[i] != NULL) { + southbridge_smi[i](save_state_ops); + } else { + printk(BIOS_DEBUG, + "SMI_STS[%d] occurred, but no " + "handler available.\n", i); + } + } +} + +static uint32_t em64t100_smm_save_state_get_io_misc_info(void *state) +{ + em64t100_smm_state_save_area_t *smm_state = state; + return smm_state->io_misc_info; +} + +static uint64_t em64t100_smm_save_state_get_reg(void *state, enum smm_reg reg) +{ + uintptr_t value = 0; + em64t100_smm_state_save_area_t *smm_state = state; + + switch (reg) { + case RAX: + value = smm_state->rax; + break; + case RBX: + value = smm_state->rbx; + break; + case RCX: + value = smm_state->rcx; + break; + case RDX: + value = smm_state->rdx; + break; + default: + break; + } + return value; +} + +static void em64t100_smm_save_state_set_reg(void *state, enum smm_reg reg, + uint64_t val) +{ + em64t100_smm_state_save_area_t *smm_state = state; + switch (reg) { + case RAX: + smm_state->rax = val; + break; + case RBX: + smm_state->rbx = val; + break; + case RCX: + smm_state->rcx = val; + break; + case RDX: + smm_state->rdx = val; + break; + default: + break; + } +} + +static uint32_t em64t101_smm_save_state_get_io_misc_info(void *state) +{ + em64t101_smm_state_save_area_t *smm_state = state; + return smm_state->io_misc_info; +} + +static uint64_t em64t101_smm_save_state_get_reg(void *state, enum smm_reg reg) +{ + uintptr_t value = 0; + em64t101_smm_state_save_area_t *smm_state = state; + + switch (reg) { + case RAX: + value = smm_state->rax; + break; + case RBX: + value = smm_state->rbx; + break; + case RCX: + value = smm_state->rcx; + break; + case RDX: + value = smm_state->rdx; + break; + default: + break; + } + return value; +} + +static void em64t101_smm_save_state_set_reg(void *state, enum smm_reg reg, + uint64_t val) +{ + em64t101_smm_state_save_area_t *smm_state = state; + switch (reg) { + case RAX: + smm_state->rax = val; + break; + case RBX: + smm_state->rbx = val; + break; + case RCX: + smm_state->rcx = val; + break; + case RDX: + smm_state->rdx = val; + break; + default: + break; + } +} + +const struct smm_save_state_ops em64t100_smm_ops = { + .get_io_misc_info = em64t100_smm_save_state_get_io_misc_info, + .get_reg = em64t100_smm_save_state_get_reg, + .set_reg = em64t100_smm_save_state_set_reg, +}; + +const struct smm_save_state_ops em64t101_smm_ops = { + .get_io_misc_info = em64t101_smm_save_state_get_io_misc_info, + .get_reg = em64t101_smm_save_state_get_reg, + .set_reg = em64t101_smm_save_state_set_reg, +}; diff --git a/src/soc/intel/common/block/smm/smm.c b/src/soc/intel/common/block/smm/smm.c new file mode 100644 index 0000000000..ab60af615d --- /dev/null +++ b/src/soc/intel/common/block/smm/smm.c @@ -0,0 +1,78 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2008-2009 coresystems GmbH + * Copyright (C) 2014 Google Inc. + * Copyright (C) 2015 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +void smm_southbridge_clear_state(void) +{ + printk(BIOS_DEBUG, "Clearing SMI status registers\n"); + + if (get_smi_en() & APMC_EN) { + printk(BIOS_INFO, "SMI# handler already enabled?\n"); + return; + } + + /* Dump and clear status registers */ + clear_smi_status(); + clear_pm1_status(); + clear_tco_status(); + clear_gpe_status(); +} + +void smm_southbridge_enable(void) +{ + printk(BIOS_DEBUG, "Enabling SMIs.\n"); + /* Configure events */ + enable_pm1(PWRBTN_EN | GBL_EN); + disable_gpe(PME_B0_EN); + + /* + * Enable SMI generation: + * - on APMC writes (io 0xb2) + * - on writes to SLP_EN (sleep states) + * - on writes to GBL_RLS (bios commands) + * - on eSPI events (does nothing on LPC systems) + * No SMIs: + * - on microcontroller writes (io 0x62/0x66) + * - on TCO events + */ + + /* Enable SMI generation: */ + enable_smi(ENABLE_SMI_PARAMS); +} + +void smm_setup_structures(void *gnvs, void *tcg, void *smi1) +{ + /* + * Issue SMI to set the gnvs pointer in SMM. + * tcg and smi1 are unused. + * + * EAX = APM_CNT_GNVS_UPDATE + * EBX = gnvs pointer + * EDX = APM_CNT + */ + asm volatile ( + "outb %%al, %%dx\n\t" + : /* ignore result */ + : "a" (APM_CNT_GNVS_UPDATE), + "b" ((u32)gnvs), + "d" (APM_CNT) + ); +} diff --git a/src/soc/intel/common/smi.h b/src/soc/intel/common/smi.h deleted file mode 100644 index 22c510435b..0000000000 --- a/src/soc/intel/common/smi.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * This file is part of the coreboot project. - * - * Copyright (C) 2016 Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef _INTEL_COMMON_SMI_H_ -#define _INTEL_COMMON_SMI_H_ - -/* - * The register value is used with get_reg and set_reg - */ -enum smm_reg { - RAX, - RBX, - RCX, - RDX, -}; - - -struct smm_save_state_ops { - /* return io_misc_info from SMM Save State Area */ - uint32_t (*get_io_misc_info)(void *state); - - /* return value of the requested register from - * SMM Save State Area - */ - uint64_t (*get_reg)(void *state, enum smm_reg reg); - - void (*set_reg)(void *state, enum smm_reg reg, uint64_t val); -}; - -typedef void (*smi_handler_t)(const struct smm_save_state_ops *save_state_ops); - -/* - * SOC SMI Handler has to provide this structure which has methods to access - * the SOC specific SMM Save State Area - */ -const struct smm_save_state_ops *get_smm_save_state_ops(void); - -/* - * southbridge_smi should be defined inside SOC specific code and should have - * handlers for any SMI events that need to be handled. Default handlers - * for some SMI events are provided in soc/intel/common/smihandler.c - */ -extern const smi_handler_t southbridge_smi[32]; - -/* - * This function should be implemented in SOC specific code to handle - * the SMI event on SLP_EN. The default functionality is provided in - * soc/intel/common/smihandler.c - */ -void southbridge_smi_sleep(const struct smm_save_state_ops *save_state_ops); - -/* - * This function should be implemented in SOC specific code to handle - * SMI_APM event. The default functionality is provided in - * soc/intel/common/smihandler.c - */ -void southbridge_smi_apmc(const struct smm_save_state_ops *save_state_ops); - -/* - * This function should be implemented in SOC specific code to handle - * SMI_PM1 event. The default functionality is provided in - * soc/intel/common/smihandler.c - */ -void southbridge_smi_pm1(const struct smm_save_state_ops *save_state_ops); - -/* - * This function should be implemented in SOC specific code to handle - * SMI_GPE0 event. The default functionality is provided in - * soc/intel/common/smihandler.c - */ -void southbridge_smi_gpe0(const struct smm_save_state_ops *save_state_ops); - -/* - * This function should be implemented in SOC specific code to handle - * SMI_TCO event. The default functionality is provided in - * soc/intel/common/smihandler.c - */ -void southbridge_smi_tco(const struct smm_save_state_ops *save_state_ops); - -/* - * This function should be implemented in SOC specific code to handle - * SMI PERIODIC_STS event. The default functionality is provided in - * soc/intel/common/smihandler.c - */ -void southbridge_smi_periodic(const struct smm_save_state_ops *save_state_ops); - -/* - * This function returns a 1 or 0 depending on whether disable_busmaster - * needs to be done for the specified device on S5 entry - */ -int smm_disable_busmaster(device_t dev); - -/* - * Returns gnvs pointer within SMM context - */ -struct global_nvs_t *smm_get_gnvs(void); - -extern const struct smm_save_state_ops em64t100_smm_ops; - -extern const struct smm_save_state_ops em64t101_smm_ops; -#endif diff --git a/src/soc/intel/common/smihandler.c b/src/soc/intel/common/smihandler.c deleted file mode 100644 index 88ea819012..0000000000 --- a/src/soc/intel/common/smihandler.c +++ /dev/null @@ -1,494 +0,0 @@ -/* - * This file is part of the coreboot project. - * - * Copyright (C) 2013 Google Inc. - * Copyright (C) 2015-2016 Intel Corp. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "smi.h" - -/* GNVS needs to be set by coreboot initiating a software SMI. */ -static struct global_nvs_t *gnvs; - -__attribute__((weak)) int smm_disable_busmaster(device_t dev) -{ - return 1; -} - -static void *find_save_state(const struct smm_save_state_ops *save_state_ops, - int cmd) -{ - int node; - void *state = NULL; - uint32_t io_misc_info; - uint8_t reg_al; - - /* Check all nodes looking for the one that issued the IO */ - for (node = 0; node < CONFIG_MAX_CPUS; node++) { - state = smm_get_save_state(node); - - io_misc_info = save_state_ops->get_io_misc_info(state); - - /* Check for Synchronous IO (bit0==1) */ - if (!(io_misc_info & (1 << 0))) - continue; - /* Make sure it was a write (bit4==0) */ - if (io_misc_info & (1 << 4)) - continue; - /* Check for APMC IO port */ - if (((io_misc_info >> 16) & 0xff) != APM_CNT) - continue; - /* Check AL against the requested command */ - reg_al = save_state_ops->get_reg(state, RAX); - if (reg_al != cmd) - continue; - break; - } - return state; -} - -void southbridge_smi_set_eos(void) -{ - enable_smi(EOS); -} - -struct global_nvs_t *smm_get_gnvs(void) -{ - return gnvs; -} - -static void busmaster_disable_on_bus(int bus) -{ - int slot, func; - unsigned int val; - unsigned char hdr; - - for (slot = 0; slot < 0x20; slot++) { - for (func = 0; func < 8; func++) { - u32 reg32; - device_t dev = PCI_DEV(bus, slot, func); - - if (!smm_disable_busmaster(dev)) - continue; - val = pci_read_config32(dev, PCI_VENDOR_ID); - - if (val == 0xffffffff || val == 0x00000000 || - val == 0x0000ffff || val == 0xffff0000) - continue; - - /* Disable Bus Mastering for this one device */ - reg32 = pci_read_config32(dev, PCI_COMMAND); - reg32 &= ~PCI_COMMAND_MASTER; - pci_write_config32(dev, PCI_COMMAND, reg32); - - /* If it's not a bridge, move on. */ - hdr = pci_read_config8(dev, PCI_HEADER_TYPE); - hdr &= 0x7f; - if (hdr != PCI_HEADER_TYPE_BRIDGE && - hdr != PCI_HEADER_TYPE_CARDBUS) - continue; - - /* - * If secondary bus is equal to current bus bypass - * the bridge because it's likely unconfigured and - * would cause infinite recursion. - */ - int secbus = pci_read_config8(dev, PCI_SECONDARY_BUS); - - if (secbus == bus) - continue; - - busmaster_disable_on_bus(secbus); - } - } -} - - -void southbridge_smi_sleep(const struct smm_save_state_ops *save_state_ops) -{ - uint32_t reg32; - uint8_t slp_typ; - - /* First, disable further SMIs */ - disable_smi(SLP_SMI_EN); - /* Figure out SLP_TYP */ - reg32 = inl(ACPI_BASE_ADDRESS + PM1_CNT); - printk(BIOS_SPEW, "SMI#: SLP = 0x%08x\n", reg32); - slp_typ = acpi_sleep_from_pm1(reg32); - - /* Do any mainboard sleep handling */ - mainboard_smi_sleep(slp_typ); - - /* Log S3, S4, and S5 entry */ - if (slp_typ >= ACPI_S3 && IS_ENABLED(CONFIG_ELOG_GSMI)) - elog_add_event_byte(ELOG_TYPE_ACPI_ENTER, slp_typ); - - /* Clear pending GPE events */ - clear_gpe_status(); - - /* Next, do the deed. */ - - switch (slp_typ) { - case ACPI_S0: - printk(BIOS_DEBUG, "SMI#: Entering S0 (On)\n"); - break; - case ACPI_S3: - printk(BIOS_DEBUG, "SMI#: Entering S3 (Suspend-To-RAM)\n"); - - /* Invalidate the cache before going to S3 */ - wbinvd(); - break; - case ACPI_S4: - printk(BIOS_DEBUG, "SMI#: Entering S4 (Suspend-To-Disk)\n"); - break; - case ACPI_S5: - printk(BIOS_DEBUG, "SMI#: Entering S5 (Soft Power off)\n"); - - /* Disable all GPE */ - disable_all_gpe(); - /* also iterates over all bridges on bus 0 */ - busmaster_disable_on_bus(0); - break; - default: - printk(BIOS_DEBUG, "SMI#: ERROR: SLP_TYP reserved\n"); - break; - } - - /* Clear the gpio gpe0 status bits in ACPI registers */ - clear_gpi_gpe_sts(); - - /* Tri-state specific GPIOS to avoid leakage during S3/S5 */ - - /* - * Write back to the SLP register to cause the originally intended - * event again. We need to set BIT13 (SLP_EN) though to make the - * sleep happen. - */ - enable_pm1_control(SLP_EN); - - /* Make sure to stop executing code here for S3/S4/S5 */ - if (slp_typ >= ACPI_S3) - hlt(); - - /* - * In most sleep states, the code flow of this function ends at - * the line above. However, if we entered sleep state S1 and wake - * up again, we will continue to execute code in this function. - */ - reg32 = inl(ACPI_BASE_ADDRESS + PM1_CNT); - if (reg32 & SCI_EN) { - /* The OS is not an ACPI OS, so we set the state to S0 */ - disable_pm1_control(SLP_EN | SLP_TYP); - } -} - -static void southbridge_smi_gsmi(const struct - smm_save_state_ops *save_state_ops) -{ - u8 sub_command, ret; - void *io_smi = NULL; - uint32_t reg_ebx; - - io_smi = find_save_state(save_state_ops, ELOG_GSMI_APM_CNT); - if (!io_smi) - return; - /* Command and return value in EAX */ - sub_command = (save_state_ops->get_reg(io_smi, RAX) >> 8) - & 0xff; - - /* Parameter buffer in EBX */ - reg_ebx = save_state_ops->get_reg(io_smi, RBX); - - /* drivers/elog/gsmi.c */ - ret = gsmi_exec(sub_command, ®_ebx); - save_state_ops->set_reg(io_smi, RAX, ret); -} - -static void finalize(void) -{ - static int finalize_done; - - if (finalize_done) { - printk(BIOS_DEBUG, "SMM already finalized.\n"); - return; - } - finalize_done = 1; - -} - -void southbridge_smi_apmc(const struct smm_save_state_ops *save_state_ops) -{ - uint8_t reg8; - void *state = NULL; - static int smm_initialized = 0; - - /* Emulate B2 register as the FADT / Linux expects it */ - - reg8 = inb(APM_CNT); - switch (reg8) { - case APM_CNT_CST_CONTROL: - /* - * Calling this function seems to cause - * some kind of race condition in Linux - * and causes a kernel oops - */ - printk(BIOS_DEBUG, "C-state control\n"); - break; - case APM_CNT_PST_CONTROL: - /* - * Calling this function seems to cause - * some kind of race condition in Linux - * and causes a kernel oops - */ - printk(BIOS_DEBUG, "P-state control\n"); - break; - case APM_CNT_ACPI_DISABLE: - disable_pm1_control(SCI_EN); - printk(BIOS_DEBUG, "SMI#: ACPI disabled.\n"); - break; - case APM_CNT_ACPI_ENABLE: - enable_pm1_control(SCI_EN); - printk(BIOS_DEBUG, "SMI#: ACPI enabled.\n"); - break; - case APM_CNT_GNVS_UPDATE: - if (smm_initialized) { - printk(BIOS_DEBUG, - "SMI#: SMM structures already initialized!\n"); - return; - } - state = find_save_state(save_state_ops, reg8); - if (state) { - /* EBX in the state save contains the GNVS pointer */ - uint32_t reg_ebx = save_state_ops->get_reg(state, RBX); - gnvs = (struct global_nvs_t *)(uintptr_t)reg_ebx; - smm_initialized = 1; - printk(BIOS_DEBUG, "SMI#: Setting GNVS to %p\n", gnvs); - } - break; - case ELOG_GSMI_APM_CNT: - if (IS_ENABLED(CONFIG_ELOG_GSMI)) - southbridge_smi_gsmi(save_state_ops); - break; - case APM_CNT_FINALIZE: - finalize(); - break; - } - - mainboard_smi_apmc(reg8); -} - -void southbridge_smi_pm1(const struct smm_save_state_ops *save_state_ops) -{ - uint16_t pm1_sts = clear_pm1_status(); - - /* - * While OSPM is not active, poweroff immediately - * on a power button event. - */ - if (pm1_sts & PWRBTN_STS) { - /* power button pressed */ - if (IS_ENABLED(CONFIG_ELOG_GSMI)) - elog_add_event(ELOG_TYPE_POWER_BUTTON); - disable_pm1_control(-1UL); - enable_pm1_control(SLP_EN | (SLP_TYP_S5 << SLP_TYP_SHIFT)); - } -} - -void southbridge_smi_gpe0(const struct smm_save_state_ops *save_state_ops) -{ - clear_gpe_status(); -} - -void southbridge_smi_tco(const struct smm_save_state_ops *save_state_ops) -{ - uint32_t tco_sts = clear_tco_status(); - - /* Any TCO event? */ - if (!tco_sts) - return; - - if (tco_sts & TCO_TIMEOUT) { /* TIMEOUT */ - /* Handle TCO timeout */ - printk(BIOS_DEBUG, "TCO Timeout.\n"); - } -} - -void southbridge_smi_periodic(const struct smm_save_state_ops *save_state_ops) -{ - uint32_t reg32; - - reg32 = get_smi_en(); - - /* Are periodic SMIs enabled? */ - if ((reg32 & PERIODIC_EN) == 0) - return; - printk(BIOS_DEBUG, "Periodic SMI.\n"); -} - -void southbridge_smi_handler(void) -{ - int i; - uint32_t smi_sts; - const struct smm_save_state_ops *save_state_ops; - - /* - * We need to clear the SMI status registers, or we won't see what's - * happening in the following calls. - */ - smi_sts = clear_smi_status(); - - save_state_ops = get_smm_save_state_ops(); - - /* Call SMI sub handler for each of the status bits */ - for (i = 0; i < ARRAY_SIZE(southbridge_smi); i++) { - if (!(smi_sts & (1 << i))) - continue; - - if (southbridge_smi[i] != NULL) { - southbridge_smi[i](save_state_ops); - } else { - printk(BIOS_DEBUG, - "SMI_STS[%d] occurred, but no " - "handler available.\n", i); - } - } -} - -static uint32_t em64t100_smm_save_state_get_io_misc_info(void *state) -{ - em64t100_smm_state_save_area_t *smm_state = state; - return smm_state->io_misc_info; -} - -static uint64_t em64t100_smm_save_state_get_reg(void *state, enum smm_reg reg) -{ - uintptr_t value = 0; - em64t100_smm_state_save_area_t *smm_state = state; - - switch (reg) { - case RAX: - value = smm_state->rax; - break; - case RBX: - value = smm_state->rbx; - break; - case RCX: - value = smm_state->rcx; - break; - case RDX: - value = smm_state->rdx; - break; - default: - break; - } - return value; -} - -static void em64t100_smm_save_state_set_reg(void *state, enum smm_reg reg, - uint64_t val) -{ - em64t100_smm_state_save_area_t *smm_state = state; - switch (reg) { - case RAX: - smm_state->rax = val; - break; - case RBX: - smm_state->rbx = val; - break; - case RCX: - smm_state->rcx = val; - break; - case RDX: - smm_state->rdx = val; - break; - default: - break; - } -} - -static uint32_t em64t101_smm_save_state_get_io_misc_info(void *state) -{ - em64t101_smm_state_save_area_t *smm_state = state; - return smm_state->io_misc_info; -} - -static uint64_t em64t101_smm_save_state_get_reg(void *state, enum smm_reg reg) -{ - uintptr_t value = 0; - em64t101_smm_state_save_area_t *smm_state = state; - - switch (reg) { - case RAX: - value = smm_state->rax; - break; - case RBX: - value = smm_state->rbx; - break; - case RCX: - value = smm_state->rcx; - break; - case RDX: - value = smm_state->rdx; - break; - default: - break; - } - return value; -} - -static void em64t101_smm_save_state_set_reg(void *state, enum smm_reg reg, - uint64_t val) -{ - em64t101_smm_state_save_area_t *smm_state = state; - switch (reg) { - case RAX: - smm_state->rax = val; - break; - case RBX: - smm_state->rbx = val; - break; - case RCX: - smm_state->rcx = val; - break; - case RDX: - smm_state->rdx = val; - break; - default: - break; - } -} - -const struct smm_save_state_ops em64t100_smm_ops = { - .get_io_misc_info = em64t100_smm_save_state_get_io_misc_info, - .get_reg = em64t100_smm_save_state_get_reg, - .set_reg = em64t100_smm_save_state_set_reg, -}; - -const struct smm_save_state_ops em64t101_smm_ops = { - .get_io_misc_info = em64t101_smm_save_state_get_io_misc_info, - .get_reg = em64t101_smm_save_state_get_reg, - .set_reg = em64t101_smm_save_state_set_reg, -}; -- cgit v1.2.3