| /* |
| * 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 <arch/hlt.h> |
| #include <arch/io.h> |
| #include <console/console.h> |
| #include <cpu/x86/cache.h> |
| #include <cpu/x86/smm.h> |
| #include <device/pci_def.h> |
| #include <elog.h> |
| #include <soc/nvs.h> |
| #include <soc/pm.h> |
| #include <soc/gpio.h> |
| #include <soc/iomap.h> |
| #include <spi-generic.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #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_PMIO_BASE + 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_PMIO_BASE + 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, |
| }; |