acpi,soc/intel/common: add support for Intel Low Power Idle Table
Add support for the Intel LPIT table to support reading Low Power Idle
Residency counters by the OS. On platforms supporting S0ix sleep states
there can be two types of residencies:
* CPU package PC10 residency counter (read from MSR via FFH interface)
* PCH SLP_S0 assertion residency counter (read via memory mapped
interface)
With presence of one or both of these counters in the LPIT table, Linux
dynamically adds the corresponding attributes to the cpuidle sysfs
interface, that can be used to read the residency timers:
* /sys/devices/system/cpu/cpuidle/low_power_idle_cpu_residency_us
* /sys/devices/system/cpu/cpuidle/low_power_idle_system_residency_us
The code in src/acpi implements generic LPIT support. Each SoC or
platform has to implement `acpi_fill_lpit` to fill the table with
platform-specific LPI state entries. This is done in this change for
soc/intel/common, while being added as its own compilation unit, so SoCs
not yet using common acpi code (like Skylake) can use it, too.
Reference:
https://uefi.org/sites/default/files/resources/Intel_ACPI_Low_Power_S0_Idle.pdf
Test: Linux adds the cpuidle sysfs interface; Windows with s0ix_enable=1
boots without crashing with an INTERNAL_POWER_ERROR.
- Windows and Linux tested on google/akemi together with CB:49046
- Linux tested on clevo/cml-u, supermicro/x11ssmf together with CB:49046
Change-Id: I816888e8788e2f04c89f20d6ea1654d2f35cf18e
Tested-by: Matt DeVillier <matt.devillier@gmail.com>
Tested-by: Michael Niewöhner <foss@mniewoehner.de>
Signed-off-by: Shaunak Saha <shaunak.saha@intel.com>
Signed-off-by: Michael Niewöhner <foss@mniewoehner.de>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/49045
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
diff --git a/src/acpi/Kconfig b/src/acpi/Kconfig
index 293c194..2727889 100644
--- a/src/acpi/Kconfig
+++ b/src/acpi/Kconfig
@@ -40,3 +40,9 @@
help
This variable specifies whether a given board has ACPI table support.
It is usually set in mainboard/*/Kconfig.
+
+config ACPI_LPIT
+ bool
+ depends on HAVE_ACPI_TABLES
+ help
+ Selected by platforms that support and fill Intel Low Power Idle Table.
diff --git a/src/acpi/acpi.c b/src/acpi/acpi.c
index a16800b..ee30391 100644
--- a/src/acpi/acpi.c
+++ b/src/acpi/acpi.c
@@ -1293,6 +1293,44 @@
acpi_checksum((void *) fadt, header->length);
}
+void acpi_create_lpit(acpi_lpit_t *lpit)
+{
+ acpi_header_t *header = &(lpit->header);
+ unsigned long current = (unsigned long)lpit + sizeof(acpi_lpit_t);
+
+ memset((void *)lpit, 0, sizeof(acpi_lpit_t));
+
+ if (!header)
+ return;
+
+ /* Fill out header fields. */
+ memcpy(header->signature, "LPIT", 4);
+ memcpy(header->oem_id, OEM_ID, 6);
+ memcpy(header->oem_table_id, ACPI_TABLE_CREATOR, 8);
+ memcpy(header->asl_compiler_id, ASLC, 4);
+
+ header->asl_compiler_revision = asl_revision;
+ header->revision = get_acpi_table_revision(LPIT);
+ header->oem_revision = 42;
+ header->length = sizeof(acpi_lpit_t);
+
+ current = acpi_fill_lpit(current);
+
+ /* (Re)calculate length and checksum. */
+ header->length = current - (unsigned long)lpit;
+ header->checksum = acpi_checksum((void *)lpit, header->length);
+}
+
+unsigned long acpi_create_lpi_desc_ncst(acpi_lpi_desc_ncst_t *lpi_desc, uint16_t uid)
+{
+ memset(lpi_desc, 0, sizeof(acpi_lpi_desc_ncst_t));
+ lpi_desc->header.length = sizeof(acpi_lpi_desc_ncst_t);
+ lpi_desc->header.type = ACPI_LPI_DESC_TYPE_NATIVE_CSTATE;
+ lpi_desc->header.uid = uid;
+
+ return lpi_desc->header.length;
+}
+
unsigned long __weak fw_cfg_acpi_tables(unsigned long start)
{
return 0;
@@ -1313,6 +1351,7 @@
acpi_tcpa_t *tcpa;
acpi_tpm2_t *tpm2;
acpi_madt_t *madt;
+ acpi_lpit_t *lpit;
struct device *dev;
unsigned long fw;
size_t slic_size, dsdt_size;
@@ -1507,6 +1546,18 @@
}
}
+ if (CONFIG(ACPI_LPIT)) {
+ printk(BIOS_DEBUG, "ACPI: * LPIT\n");
+
+ lpit = (acpi_lpit_t *)current;
+ acpi_create_lpit(lpit);
+ if (lpit->header.length >= sizeof(acpi_lpit_t)) {
+ current += lpit->header.length;
+ current = acpi_align_current(current);
+ acpi_add_table(rsdp, lpit);
+ }
+ }
+
printk(BIOS_DEBUG, "ACPI: * MADT\n");
madt = (acpi_madt_t *) current;
@@ -1515,6 +1566,7 @@
current += madt->header.length;
acpi_add_table(rsdp, madt);
}
+
current = acpi_align_current(current);
printk(BIOS_DEBUG, "current = %lx\n", current);
@@ -1665,6 +1717,8 @@
return 1;
case CRAT:
return 1;
+ case LPIT: /* ACPI 5.1 up to 6.3: 0 */
+ return 0;
default:
return -1;
}
diff --git a/src/include/acpi/acpi.h b/src/include/acpi/acpi.h
index 1c364a0..e266a2d 100644
--- a/src/include/acpi/acpi.h
+++ b/src/include/acpi/acpi.h
@@ -71,7 +71,7 @@
enum acpi_tables {
/* Tables defined by ACPI and used by coreboot */
BERT, DBG2, DMAR, DSDT, FACS, FADT, HEST, HPET, IVRS, MADT, MCFG,
- RSDP, RSDT, SLIT, SRAT, SSDT, TCPA, TPM2, XSDT, ECDT,
+ RSDP, RSDT, SLIT, SRAT, SSDT, TCPA, TPM2, XSDT, ECDT, LPIT,
/* Additional proprietary tables used by coreboot */
VFCT, NHLT, SPMI, CRAT
};
@@ -257,6 +257,48 @@
u32 flags; /* Multiple APIC flags */
} __packed acpi_madt_t;
+/*
+ * LPIT (Low Power Idle Table)
+ * Conforms to "Intel Low Power S0 Idle" specification, rev 002 from July 2017.
+ */
+typedef struct acpi_lpit {
+ acpi_header_t header;
+} __packed acpi_lpit_t;
+
+/* LPIT: LPI descriptor flags */
+typedef struct acpi_lpi_flags {
+ uint32_t disabled : 1;
+ uint32_t counter_not_available : 1;
+ uint32_t reserved : 30;
+} __packed acpi_lpi_desc_flags_t;
+
+/* LPIT: LPI descriptor types */
+enum acpi_lpi_desc_type {
+ ACPI_LPI_DESC_TYPE_NATIVE_CSTATE = 0x00,
+ /* type >= 1 reserved */
+};
+
+/* LPIT: LPI descriptor header */
+typedef struct acpi_lpi_desc_hdr {
+ uint32_t type;
+ uint32_t length;
+ uint16_t uid;
+ uint16_t reserved;
+} __packed acpi_lpi_desc_hdr_t;
+
+#define ACPI_LPIT_CTR_FREQ_TSC 0
+
+/* LPIT: Native C-state instruction based LPI structure */
+typedef struct acpi_lpi_desc_ncst {
+ acpi_lpi_desc_hdr_t header;
+ acpi_lpi_desc_flags_t flags;
+ acpi_addr_t entry_trigger; /* Entry trigger C-state */
+ uint32_t min_residency; /* Minimum residency or "break-even" in microseconds */
+ uint32_t max_latency; /* Worst case exit latency in microseconds */
+ acpi_addr_t residency_counter;
+ uint64_t counter_frequency; /* Frequency in cycles per second - 0 means TSC freq */
+} __packed acpi_lpi_desc_ncst_t;
+
/* VFCT image header */
typedef struct acpi_vfct_image_hdr {
u32 PCIBus;
@@ -922,6 +964,8 @@
void update_ssdt(void *ssdt);
void update_ssdtx(void *ssdtx, int i);
+unsigned long acpi_fill_lpit(unsigned long current);
+
/* These can be used by the target port. */
u8 acpi_checksum(u8 *table, u32 length);
@@ -1025,6 +1069,9 @@
unsigned long acpi_create_hest_error_source(acpi_hest_t *hest,
acpi_hest_esd_t *esd, u16 type, void *data, u16 len);
+void acpi_create_lpit(acpi_lpit_t *lpit);
+unsigned long acpi_create_lpi_desc_ncst(acpi_lpi_desc_ncst_t *lpi_desc, uint16_t uid);
+
/* For ACPI S3 support. */
void __noreturn acpi_resume(void *wake_vec);
void mainboard_suspend_resume(void);
diff --git a/src/soc/intel/common/block/acpi/Kconfig b/src/soc/intel/common/block/acpi/Kconfig
index d9a7a87..a4f9844 100644
--- a/src/soc/intel/common/block/acpi/Kconfig
+++ b/src/soc/intel/common/block/acpi/Kconfig
@@ -5,6 +5,13 @@
help
Intel Processor common code for ACPI
+config SOC_INTEL_COMMON_BLOCK_ACPI_LPIT
+ bool
+ depends on HAVE_ACPI_TABLES
+ select ACPI_LPIT
+ help
+ Generate LPIT table with LPI state entries.
+
if SOC_INTEL_COMMON_BLOCK_ACPI
config SOC_INTEL_COMMON_BLOCK_ACPI_CPPC
diff --git a/src/soc/intel/common/block/acpi/Makefile.inc b/src/soc/intel/common/block/acpi/Makefile.inc
index c6bdac5..4e3a323 100644
--- a/src/soc/intel/common/block/acpi/Makefile.inc
+++ b/src/soc/intel/common/block/acpi/Makefile.inc
@@ -1 +1,2 @@
ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_ACPI) += acpi.c
+ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_ACPI_LPIT) += lpit.c
diff --git a/src/soc/intel/common/block/acpi/acpi.c b/src/soc/intel/common/block/acpi/acpi.c
index f4560bd..1a18cfc 100644
--- a/src/soc/intel/common/block/acpi/acpi.c
+++ b/src/soc/intel/common/block/acpi/acpi.c
@@ -10,6 +10,7 @@
#include <cf9_reset.h>
#include <console/console.h>
#include <cpu/intel/turbo.h>
+#include <cpu/intel/msr.h>
#include <cpu/intel/common/common.h>
#include <cpu/x86/smm.h>
#include <intelblocks/acpi.h>
diff --git a/src/soc/intel/common/block/acpi/lpit.c b/src/soc/intel/common/block/acpi/lpit.c
new file mode 100644
index 0000000..5b9689c
--- /dev/null
+++ b/src/soc/intel/common/block/acpi/lpit.c
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <acpi/acpi.h>
+#include <cpu/intel/msr.h>
+#include <soc/iomap.h>
+#include <soc/pmc.h>
+
+unsigned long acpi_fill_lpit(unsigned long current)
+{
+ uint16_t uid = 0;
+ acpi_lpi_desc_ncst_t *pkg_counter;
+ acpi_lpi_desc_ncst_t *sys_counter;
+
+ /*
+ * Package C10 (PC10) residency counter
+ */
+ pkg_counter = (void *)current;
+ current += acpi_create_lpi_desc_ncst((void *)current, uid++);
+
+ /* MWAIT LPI state entry trigger */
+ pkg_counter->entry_trigger.addrl = 0x60; /* MWAIT(6,0) / HW C10 */
+ pkg_counter->entry_trigger.bit_offset = ACPI_FFIXEDHW_CLASS_MWAIT;
+ pkg_counter->entry_trigger.bit_width = ACPI_FFIXEDHW_VENDOR_INTEL;
+ pkg_counter->entry_trigger.space_id = ACPI_ADDRESS_SPACE_FIXED;
+ pkg_counter->entry_trigger.access_size = ACPI_ACCESS_SIZE_UNDEFINED;
+
+ /* PC10 residency counter */
+ pkg_counter->residency_counter.addrl = MSR_PKG_C10_RESIDENCY;
+ pkg_counter->residency_counter.bit_offset = 0;
+ pkg_counter->residency_counter.bit_width = 64;
+ pkg_counter->residency_counter.space_id = ACPI_ADDRESS_SPACE_FIXED;
+ pkg_counter->residency_counter.access_size = ACPI_ACCESS_SIZE_UNDEFINED;
+ pkg_counter->counter_frequency = ACPI_LPIT_CTR_FREQ_TSC;
+
+ /* Min. residency and worst-case latency (from FSP and vendor dumps) */
+ pkg_counter->min_residency = 30000; /* break-even: 30 ms */
+ pkg_counter->max_latency = 3000; /* worst-case latency: 3 ms */
+
+ /*
+ * System (Slp_S0) residency counter
+ */
+ sys_counter = (void *)current;
+ current += acpi_create_lpi_desc_ncst((void *)current, uid++);
+
+ /* MWAIT LPI state entry trigger */
+ sys_counter->entry_trigger.addrl = 0x60; /* MWAIT(6,0) / HW C10 */
+ sys_counter->entry_trigger.bit_offset = ACPI_FFIXEDHW_CLASS_MWAIT;
+ sys_counter->entry_trigger.bit_width = ACPI_FFIXEDHW_VENDOR_INTEL;
+ sys_counter->entry_trigger.space_id = ACPI_ADDRESS_SPACE_FIXED;
+ sys_counter->entry_trigger.access_size = ACPI_ACCESS_SIZE_UNDEFINED;
+
+ /* Slp_S0 residency counter */
+ sys_counter->residency_counter.addrl = PCH_PWRM_BASE_ADDRESS + SLP_S0_RES;
+ sys_counter->residency_counter.bit_offset = 0;
+ sys_counter->residency_counter.bit_width = 32;
+ sys_counter->residency_counter.space_id = ACPI_ADDRESS_SPACE_MEMORY;
+ sys_counter->residency_counter.access_size = ACPI_ACCESS_SIZE_DWORD_ACCESS;
+ sys_counter->counter_frequency = ACPI_LPIT_CTR_FREQ_TSC;
+
+ /* Min. residency and worst-case latency (from FSP and vendor dumps) */
+ sys_counter->min_residency = 30000; /* break-even: 30 ms */
+ sys_counter->max_latency = 3000; /* worst-case latency: 3 ms */
+
+ return current;
+}