src/acpi: Add APEI EINJ support

This adds full EINJ support with trigger action tables. The actual
error injection functionality is HW specific. Therefore, HW specific
code should call acpi_create_einj with an address where action table
resides. The default params of the action table are filled out by the
common code.  Control is then returned back to the caller to modify or
override default parameters. If no changes are needed, caller can
simply add the acpi table. At runtime, FW is responsible for filling
out the action table with the proper entries. The action table memory
is shared between FW and OS. This memory should be marked as reserved
in E820 table.

Tested on Deltalake mainboard.  Boot to OS, load the EINJ driver (
modprobe EINJ) and verify EINJ memory entries are in /proc/iomem.
Further tested by injecting errors via the APEI file nodes.  More
information on error injection can be referenced in the latest ACPI
spec.

Change-Id: I29c6a861c564ec104f2c097f3e49b3e6d38b040e
Signed-off-by: Rocky Phagura <rphagura@fb.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/49286
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Reviewed-by: Rocky Phagura
This commit is contained in:
Rocky Phagura 2021-01-10 15:42:50 -08:00 committed by David Hendricks
parent 7da1c1732a
commit eff0713dfc
3 changed files with 283 additions and 1 deletions

View File

@ -38,6 +38,12 @@ config ACPI_NO_PCAT_8259
help
Selected by platforms that don't expose a PC/AT 8259 PIC pair.
config ACPI_EINJ
def_bool n
depends on HAVE_ACPI_TABLES
help
This variable provides control for ACPI error injection table (EINJ)
config HAVE_ACPI_TABLES
bool
help

View File

@ -785,6 +785,152 @@ void acpi_create_hpet(acpi_hpet_t *hpet)
header->checksum = acpi_checksum((void *)hpet, sizeof(acpi_hpet_t));
}
/*
* This method adds the ACPI error injection capability. It fills the default information.
* HW dependent code (caller) can modify the defaults upon return. If no changes are necessary
* and the defaults are acceptable then caller can simply add the table (acpi_add_table).
* INPUTS:
* einj - ptr to the starting location of EINJ table
* actions - number of actions to trigger an error (HW dependent)
* addr - address of trigger action table. This should be ACPI reserved memory and it will be
* shared between OS and FW.
*/
void acpi_create_einj(acpi_einj_t *einj, uintptr_t addr, u8 actions)
{
int i;
acpi_header_t *header = &(einj->header);
acpi_injection_header_t *inj_header = &(einj->inj_header);
acpi_einj_smi_t *einj_smi = (acpi_einj_smi_t *)addr;
acpi_einj_trigger_table_t *tat;
if (!header)
return;
printk(BIOS_DEBUG, "%s einj_smi = %p\n", __func__, einj_smi);
memset(einj_smi, 0, sizeof(acpi_einj_smi_t));
tat = (acpi_einj_trigger_table_t *)(einj_smi + sizeof(acpi_einj_smi_t));
tat->header_size = 16;
tat->revision = 0;
tat->table_size = sizeof(acpi_einj_trigger_table_t) +
sizeof(acpi_einj_action_table_t) * actions - 1;
tat->entry_count = actions;
printk(BIOS_DEBUG, "%s trigger_action_table = %p\n", __func__, tat);
for (i = 0; i < actions; i++) {
tat->trigger_action[i].action = TRIGGER_ERROR;
tat->trigger_action[i].instruction = NO_OP;
tat->trigger_action[i].flags = FLAG_IGNORE;
tat->trigger_action[i].reg.space_id = ACPI_ADDRESS_SPACE_MEMORY;
tat->trigger_action[i].reg.bit_width = 64;
tat->trigger_action[i].reg.bit_offset = 0;
tat->trigger_action[i].reg.access_size = ACPI_ACCESS_SIZE_QWORD_ACCESS;
tat->trigger_action[i].reg.addr = 0;
tat->trigger_action[i].value = 0;
tat->trigger_action[i].mask = 0xFFFFFFFF;
}
acpi_einj_action_table_t default_actions[ACTION_COUNT] = {
[0] = {
.action = BEGIN_INJECT_OP,
.instruction = WRITE_REGISTER_VALUE,
.flags = FLAG_PRESERVE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->op_state),
.value = 0,
.mask = 0xFFFFFFFF
},
[1] = {
.action = GET_TRIGGER_ACTION_TABLE,
.instruction = READ_REGISTER,
.flags = FLAG_IGNORE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->trigger_action_table),
.value = 0,
.mask = 0xFFFFFFFFFFFFFFFF
},
[2] = {
.action = SET_ERROR_TYPE,
.instruction = WRITE_REGISTER,
.flags = FLAG_PRESERVE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->err_inject[0]),
.value = 0,
.mask = 0xFFFFFFFF
},
[3] = {
.action = GET_ERROR_TYPE,
.instruction = READ_REGISTER,
.flags = FLAG_IGNORE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->err_inj_cap),
.value = 0,
.mask = 0xFFFFFFFF
},
[4] = {
.action = END_INJECT_OP,
.instruction = WRITE_REGISTER_VALUE,
.flags = FLAG_PRESERVE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->op_state),
.value = 0,
.mask = 0xFFFFFFFF
},
[5] = {
.action = EXECUTE_INJECT_OP,
.instruction = WRITE_REGISTER_VALUE,
.flags = FLAG_PRESERVE,
.reg = EINJ_REG_IO(),
.value = 0x9a,
.mask = 0xFFFF,
},
[6] = {
.action = CHECK_BUSY_STATUS,
.instruction = READ_REGISTER_VALUE,
.flags = FLAG_IGNORE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->op_status),
.value = 1,
.mask = 1,
},
[7] = {
.action = GET_CMD_STATUS,
.instruction = READ_REGISTER,
.flags = FLAG_PRESERVE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->cmd_sts),
.value = 0,
.mask = 0x1fe,
},
[8] = {
.action = SET_ERROR_TYPE_WITH_ADDRESS,
.instruction = WRITE_REGISTER,
.flags = FLAG_PRESERVE,
.reg = EINJ_REG_MEMORY((u64)(uintptr_t)&einj_smi->setaddrtable),
.value = 1,
.mask = 0xffffffff
}
};
einj_smi->err_inj_cap = ACPI_EINJ_DEFAULT_CAP;
einj_smi->trigger_action_table = (u64) (uintptr_t)tat;
for (i = 0; i < ACTION_COUNT; i++)
printk(BIOS_DEBUG, "default_actions[%d].reg.addr is %llx\n", i,
default_actions[i].reg.addr);
memset((void *)einj, 0, sizeof(einj));
/* Fill out header fields. */
memcpy(header->signature, "EINJ", 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->length = sizeof(acpi_einj_t);
header->revision = 1;
inj_header->einj_header_size = sizeof(acpi_injection_header_t);
inj_header->entry_count = ACTION_COUNT;
printk(BIOS_DEBUG, "%s einj->action_table = %p\n",
__func__, einj->action_table);
memcpy((void *)einj->action_table, (void *)default_actions, sizeof(einj->action_table));
header->checksum = acpi_checksum((void *)einj, sizeof(einj));
}
void acpi_create_vfct(const struct device *device,
acpi_vfct_t *vfct,
unsigned long (*acpi_fill_vfct)(const struct device *device,
@ -1756,6 +1902,8 @@ int get_acpi_table_revision(enum acpi_tables table)
return 1;
case RSDP: /* ACPI 2.0 upto 6.3: 2 */
return 2;
case EINJ:
return 1;
case HEST:
return 1;
case NHLT:

View File

@ -71,7 +71,7 @@ enum coreboot_acpi_ids {
enum acpi_tables {
/* Tables defined by ACPI and used by coreboot */
BERT, DBG2, DMAR, DSDT, FACS, FADT, HEST, HPET, IVRS, MADT, MCFG,
BERT, DBG2, DMAR, DSDT, EINJ, FACS, FADT, HEST, HPET, IVRS, MADT, MCFG,
RSDP, RSDT, SLIT, SRAT, SSDT, TCPA, TPM2, XSDT, ECDT, LPIT,
/* Additional proprietary tables used by coreboot */
VFCT, NHLT, SPMI, CRAT
@ -980,6 +980,134 @@ struct acpi_spmi {
u8 reserved3;
} __packed;
/* EINJ APEI Standard Definitions */
/* EINJ Error Types
Refer to the ACPI spec, EINJ section, for more info on bit definitions
*/
#define ACPI_EINJ_CPU_CE (1 << 0)
#define ACPI_EINJ_CPU_UCE (1 << 1)
#define ACPI_EINJ_CPU_UCE_FATAL (1 << 2)
#define ACPI_EINJ_MEM_CE (1 << 3)
#define ACPI_EINJ_MEM_UCE (1 << 4)
#define ACPI_EINJ_MEM_UCE_FATAL (1 << 5)
#define ACPI_EINJ_PCIE_CE (1 << 6)
#define ACPI_EINJ_PCIE_UCE_NON_FATAL (1 << 7)
#define ACPI_EINJ_PCIE_UCE_FATAL (1 << 8)
#define ACPI_EINJ_PLATFORM_CE (1 << 9)
#define ACPI_EINJ_PLATFORM_UCE (1 << 10)
#define ACPI_EINJ_PLATFORM_UCE_FATAL (1 << 11)
#define ACPI_EINJ_VENDOR_DEFINED (1 << 31)
#define ACPI_EINJ_DEFAULT_CAP (ACPI_EINJ_MEM_CE | ACPI_EINJ_MEM_UCE | \
ACPI_EINJ_PCIE_CE | ACPI_EINJ_PCIE_UCE_FATAL)
/* EINJ actions */
#define ACTION_COUNT 9
#define BEGIN_INJECT_OP 0x00
#define GET_TRIGGER_ACTION_TABLE 0x01
#define SET_ERROR_TYPE 0x02
#define GET_ERROR_TYPE 0x03
#define END_INJECT_OP 0x04
#define EXECUTE_INJECT_OP 0x05
#define CHECK_BUSY_STATUS 0x06
#define GET_CMD_STATUS 0x07
#define SET_ERROR_TYPE_WITH_ADDRESS 0x08
#define TRIGGER_ERROR 0xFF
/* EINJ Instructions */
#define READ_REGISTER 0x00
#define READ_REGISTER_VALUE 0x01
#define WRITE_REGISTER 0x02
#define WRITE_REGISTER_VALUE 0x03
#define NO_OP 0x04
/* EINJ (Error Injection Table) */
typedef struct acpi_gen_regaddr1 {
u8 space_id; /* Address space ID */
u8 bit_width; /* Register size in bits */
u8 bit_offset; /* Register bit offset */
u8 access_size; /* Access size since ACPI 2.0c */
u64 addr; /* Register address */
} __packed acpi_addr64_t;
/* Instruction entry */
typedef struct acpi_einj_action_table {
u8 action;
u8 instruction;
u16 flags;
acpi_addr64_t reg;
u64 value;
u64 mask;
} __packed acpi_einj_action_table_t;
typedef struct acpi_injection_header {
u32 einj_header_size;
u32 flags;
u32 entry_count;
} __packed acpi_injection_header_t;
typedef struct acpi_einj_trigger_table {
u32 header_size;
u32 revision;
u32 table_size;
u32 entry_count;
acpi_einj_action_table_t trigger_action[1];
} __packed acpi_einj_trigger_table_t;
typedef struct set_error_type {
u32 errtype;
u32 vendorerrortype;
u32 flags;
u32 apicid;
u64 memaddr;
u64 memrange;
u32 pciesbdf;
} __packed set_error_type_t;
#define EINJ_PARAM_NUM 6
typedef struct acpi_einj_smi {
u64 op_state;
u64 err_inject[EINJ_PARAM_NUM];
u64 trigger_action_table;
u64 err_inj_cap;
u64 op_status;
u64 cmd_sts;
u64 einj_addr;
u64 einj_addr_msk;
set_error_type_t setaddrtable;
u64 reserved[50];
} __packed acpi_einj_smi_t;
/* EINJ Flags */
#define EINJ_DEF_TRIGGER_PORT 0xb2
#define FLAG_PRESERVE 0x01
#define FLAG_IGNORE 0x00
/* EINJ Registers */
#define EINJ_REG_MEMORY(address) \
{ \
.space_id = ACPI_ADDRESS_SPACE_MEMORY, \
.bit_width = 64, \
.bit_offset = 0, \
.access_size = ACPI_ACCESS_SIZE_QWORD_ACCESS, \
.addr = address}
#define EINJ_REG_IO() \
{ \
.space_id = ACPI_ADDRESS_SPACE_IO, \
.bit_width = 0x10, \
.bit_offset = 0, \
.access_size = ACPI_ACCESS_SIZE_WORD_ACCESS, \
.addr = EINJ_DEF_TRIGGER_PORT} /* HW dependent code can override this also */
typedef struct acpi_einj {
acpi_header_t header;
acpi_injection_header_t inj_header;
acpi_einj_action_table_t action_table[ACTION_COUNT];
} __packed acpi_einj_t;
void acpi_create_einj(acpi_einj_t *einj, uintptr_t addr, u8 actions);
unsigned long fw_cfg_acpi_tables(unsigned long start);
/* These are implemented by the target port or north/southbridge. */