This driver parses ACPI tables and looks for a "WDAT entry. It
then programs the watchdog using the actions/instructions specified
by the BIOS (where register addresses and values are specified).
Unlike Linux, we not need to create (allocate) a dictionary of
actions as we may just parse the tables on the go to perform our
SET_COUNTDOWN and SET_RUNNING actions. Note that this driver is
tried before any others (the BIOS is expected to expose ACPI tables
with valid information). This driver was tested on SIMATIC IPC127E.
Signed-off-by: Cedric Hombourger <[email protected]>
---
Makefile.am | 2 +
drivers/watchdog/wdat.c | 442 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 444 insertions(+)
create mode 100644 drivers/watchdog/wdat.c
diff --git a/Makefile.am b/Makefile.am
index 8fb8652..8347749 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -122,9 +122,11 @@ install-exec-hook:
#
efi_loadername = efibootguard$(MACHINE_TYPE_NAME).efi
+# NOTE: wdat.c is placed first so it is tried before any other drivers
# NOTE: ipc4x7e_wdt.c must be *before* itco.c
efi_sources = \
drivers/watchdog/init_array_start.S \
+ drivers/watchdog/wdat.c \
drivers/watchdog/amdfch_wdt.c \
drivers/watchdog/i6300esb.c \
drivers/watchdog/atom-quark.c \
diff --git a/drivers/watchdog/wdat.c b/drivers/watchdog/wdat.c
new file mode 100644
index 0000000..fe5d8a7
--- /dev/null
+++ b/drivers/watchdog/wdat.c
@@ -0,0 +1,442 @@
+/*
+ * EFI Boot Guard
+ *
+ * Copyright (C) 2021 Mentor Graphics, A Siemens business
+ *
+ * Authors:
+ * Cedric Hombourger <[email protected]>
+ *
+ * This driver includes WDAT structs from linux/include/acpi/actbl.h (GPL-2.0)
+ * ACPI structs were created from https://uefi.org/specs/ACPI/6.4/index.html
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <efi.h>
+#include <efilib.h>
+#include <mmio.h>
+#include <sys/io.h>
+#include "utils.h"
+
+#define EFI_ACPI_TABLE_GUID \
+ { 0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1,
0x4d }}
+#define EFI_ACPI_20_TABLE_GUID \
+ { 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88,
0x81 }}
+
+#define EFI_ACPI_ROOT_SDP_REVISION 0x02
+
+#define ACPI_SIG_RSDP (CHAR8 *)"RSD PTR "
+#define ACPI_SIG_XSDT (CHAR8 *)"XSDT"
+#define ACPI_SIG_WDAT (CHAR8 *)"WDAT"
+
+#pragma pack(1)
+
+/* --------------------------------------------------------------------------
+ * ACPI structures
+ * Reference: https://uefi.org/specs/ACPI/6.4/index.html
+ * --------------------------------------------------------------------------
+ */
+
+/* Root System Description Pointer (ACPI section 5.2.5.3) */
+typedef struct {
+ CHAR8 signature[8];
+ UINT8 checksum;
+ UINT8 oem_id[6];
+ UINT8 revision;
+ UINT32 rsdt_address;
+ UINT32 length;
+ UINT64 xsdt_address;
+ UINT8 extended_checksum;
+ UINT8 reserved[3];
+} EFI_ACPI_ROOT_SDP_HEADER;
+
+/* System Description Table (ACPI section 5.2.6) */
+typedef struct {
+ CHAR8 signature[4];
+ UINT32 length;
+ UINT8 revision;
+ UINT8 checksum;
+ CHAR8 oem_id[6];
+ CHAR8 oem_table_id[8];
+ UINT32 oem_revision;
+ UINT32 creator_id;
+ UINT32 creator_revision;
+} EFI_ACPI_SDT_HEADER;
+
+/* Generic Address Structure (ACPI section 5.2.3.2) */
+typedef struct {
+ UINT8 space_id; /* Address space where struct or register
exists */
+ UINT8 bit_width; /* Size in bits of given register */
+ UINT8 bit_offset; /* Bit offset within the register */
+ UINT8 access_width; /* Minimum Access size (ACPI 3.0) */
+ UINT64 address; /* 64-bit address of struct or register */
+} ACPI_ADDR;
+
+/* Values for space_id field above */
+enum acpi_spaces {
+ ACPI_ADRR_SPACE_SYSTEM_MEMORY = 0,
+ ACPI_ADDR_SPACE_SYSTEM_IO = 1
+};
+
+/* --------------------------------------------------------------------------
+ * WDAT structures
+ * Reference: linux/include/acpi/actbl.h (GPL-2.0)
+ * --------------------------------------------------------------------------
+ */
+
+/* WDAT Instruction Entries (actions) */
+typedef struct {
+ UINT8 action;
+ UINT8 instruction;
+ UINT16 reserved;
+ ACPI_ADDR register_region;
+ UINT32 value; /* Value used with Read/Write register */
+ UINT32 mask; /* Bitmask required for this register
instruction */
+} ACPI_WDAT_ENTRY;
+
+/* Values for action field above */
+enum acpi_wdat_actions {
+ ACPI_WDAT_RESET = 1,
+ ACPI_WDAT_SET_COUNTDOWN = 6,
+ ACPI_WDAT_SET_RUNNING_STATE = 9,
+ ACPI_WDAT_SET_REBOOT = 17,
+ ACPI_WDAT_GET_STATUS = 32
+};
+
+/* Values for instruction field above */
+enum acpi_wdat_instructions {
+ ACPI_WDAT_READ_VALUE = 0,
+ ACPI_WDAT_READ_COUNTDOWN = 1,
+ ACPI_WDAT_WRITE_VALUE = 2,
+ ACPI_WDAT_WRITE_COUNTDOWN = 3,
+ ACPI_WDAT_PRESERVE_REGISTER = 0x80
+};
+
+/* Watchdog Action Table */
+typedef struct {
+ EFI_ACPI_SDT_HEADER header; /* Common ACPI table header */
+ UINT32 header_length; /* Watchdog Header Length */
+ UINT16 pci_segment; /* PCI Segment number */
+ UINT8 pci_bus; /* PCI Bus number */
+ UINT8 pci_device; /* PCI Device number */
+ UINT8 pci_function; /* PCI Function number */
+ UINT8 reserved[3];
+ UINT32 timer_period; /* Period of one timer count (msec) */
+ UINT32 max_count; /* Maximum counter value supported */
+ UINT32 min_count; /* Minimum counter value */
+ UINT8 flags;
+ UINT8 reserved2[3];
+ UINT32 entries; /* Number of watchdog entries that follow */
+} ACPI_TABLE_WDAT;
+
+#pragma pack()
+
+/* --------------------------------------------------------------------------
+ * Parsing of ACPI/WDAT structures for efibootguard
+ * --------------------------------------------------------------------------
+ */
+
+static EFI_STATUS
+parse_rsdp(EFI_ACPI_ROOT_SDP_HEADER *rsdp, ACPI_TABLE_WDAT **wdat_table_ptr) {
+ EFI_ACPI_SDT_HEADER *xsdt, *entry;
+ UINT64 *entry_ptr;
+ UINT64 n, count;
+
+ *wdat_table_ptr = NULL;
+
+ if (rsdp->revision < EFI_ACPI_ROOT_SDP_REVISION) {
+ return EFI_NOT_FOUND;
+ }
+
+ xsdt = (EFI_ACPI_SDT_HEADER *)(rsdp->xsdt_address);
+ if (strncmpa(ACPI_SIG_XSDT, (CHAR8 *)(VOID *)(xsdt->signature), 4)) {
+ return EFI_INCOMPATIBLE_VERSION;
+ }
+
+ entry_ptr = (UINT64 *)(xsdt + 1);
+ count = (xsdt->length - sizeof (EFI_ACPI_SDT_HEADER)) / sizeof(UINT64);
+ for (n = 0; n < count; n++, entry_ptr++) {
+ entry = (EFI_ACPI_SDT_HEADER *)((UINTN)(*entry_ptr));
+ if (!strncmpa(ACPI_SIG_WDAT, entry->signature, 4)) {
+ *wdat_table_ptr = (ACPI_TABLE_WDAT *)entry;
+ return EFI_SUCCESS;
+ }
+ }
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS
+locate_and_parse_rsdp(ACPI_TABLE_WDAT **wdat_table_ptr) {
+ EFI_CONFIGURATION_TABLE *ect = ST->ConfigurationTable;
+ EFI_ACPI_ROOT_SDP_HEADER *rsdp;
+ EFI_GUID acpi_table_guid = ACPI_TABLE_GUID;
+ EFI_GUID acpi2_table_guid = ACPI_20_TABLE_GUID;
+ UINTN n;
+
+ for (n = 0; n < ST->NumberOfTableEntries; n++) {
+ if ((CompareGuid(&ect->VendorGuid, &acpi_table_guid) ||
+ CompareGuid(&ect->VendorGuid, &acpi2_table_guid)) &&
+ !strncmpa(ACPI_SIG_RSDP, (CHAR8 *)(ect->VendorTable), 8)) {
+ rsdp = (EFI_ACPI_ROOT_SDP_HEADER *)ect->VendorTable;
+ return parse_rsdp(rsdp, wdat_table_ptr);
+ }
+ ect++;
+ }
+ return EFI_NOT_FOUND;
+}
+
+static EFI_STATUS
+read_reg(ACPI_ADDR *addr, UINT32 *value_ptr)
+{
+ UINT32 value;
+
+ if (addr->access_width < 1 || addr->access_width > 3) {
+ ERROR(L"invalid width for WDAT read operation!\n");
+ return EFI_UNSUPPORTED;
+ }
+
+ if (addr->space_id == ACPI_ADDR_SPACE_SYSTEM_IO) {
+ switch (addr->access_width) {
+ case 1:
+ value = inb(addr->address);
+ break;
+ case 2:
+ value = inw(addr->address);
+ break;
+ case 3:
+ value = inl(addr->address);
+ break;
+ }
+ }
+ else {
+ switch (addr->access_width) {
+ case 1:
+ value = readb(addr->address);
+ break;
+ case 2:
+ value = readw(addr->address);
+ break;
+ case 3:
+ value = readl(addr->address);
+ break;
+ }
+ }
+
+ if (value_ptr) {
+ *value_ptr = value;
+ }
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS
+write_reg(ACPI_ADDR *addr, UINT32 value)
+{
+ if ((addr->access_width < 1) || (addr->access_width > 3)) {
+ ERROR(L"invalid width for WDAT write operation!\n");
+ return EFI_UNSUPPORTED;
+ }
+
+ if (addr->space_id == ACPI_ADDR_SPACE_SYSTEM_IO) {
+ switch (addr->access_width) {
+ case 1:
+ outb(value, addr->address);
+ break;
+ case 2:
+ outw(value, addr->address);
+ break;
+ case 3:
+ outl(value, addr->address);
+ break;
+ }
+ }
+ else {
+ switch (addr->access_width) {
+ case 1:
+ writeb(value, addr->address);
+ break;
+ case 2:
+ writew(value, addr->address);
+ break;
+ case 3:
+ writel(value, addr->address);
+ break;
+ }
+ }
+ return EFI_SUCCESS;
+}
+
+static EFI_STATUS
+read_value(ACPI_ADDR *addr, UINT32 value, UINT32 mask, UINT32 *retval)
+{
+ EFI_STATUS status;
+ UINT32 x;
+
+ status = read_reg(addr, &x);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ x >>= addr->bit_offset;
+ x &= mask;
+ if (retval) {
+ *retval = (x == value);
+ }
+ return status;
+}
+
+static EFI_STATUS
+read_countdown(ACPI_ADDR *addr, UINT32 value, UINT32 mask, UINT32 *retval)
+{
+ EFI_STATUS status;
+ UINT32 x;
+
+ value = value; /* unused */
+ status = read_reg(addr, &x);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ x >>= addr->bit_offset;
+ x &= mask;
+ if (retval) {
+ *retval = x;
+ }
+ return status;
+}
+
+static EFI_STATUS
+write_value(ACPI_ADDR *addr, UINT32 value, UINT32 mask, BOOLEAN preserve)
+{
+ EFI_STATUS status;
+ UINT32 x, y;
+
+ x = value & mask;
+ x <<= addr->bit_offset;
+ if (preserve) {
+ status = read_reg(addr, &y);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ y = y & ~(mask << addr->bit_offset);
+ x |= y;
+ }
+ return write_reg(addr, x);
+}
+
+static EFI_STATUS
+run_action(ACPI_TABLE_WDAT *wdat_table, UINT8 action, UINT32 param, UINT32
*retval)
+{
+ ACPI_WDAT_ENTRY *wdat_entry;
+ ACPI_ADDR *addr;
+ EFI_STATUS status = EFI_UNSUPPORTED;
+ BOOLEAN preserve;
+ UINT32 flags, value, mask;
+ UINTN n;
+
+ /* ACPI_TABLE_WDAT is immediately followed by multiple ACPI_WDAT_ENTRY
tables,
+ * the former tells us how many (via ->entries). */
+ wdat_entry = (ACPI_WDAT_ENTRY *)(wdat_table + 1);
+ for (n = 0; n < wdat_table->entries; n++, wdat_entry++) {
+ /* Check if this is the action we have requested */
+ if (wdat_entry->action != action) {
+ continue;
+ }
+
+ /* Decode the action */
+ preserve = (wdat_entry->instruction &
ACPI_WDAT_PRESERVE_REGISTER) != 0;
+ flags = wdat_entry->instruction & ~ACPI_WDAT_PRESERVE_REGISTER;
+ value = wdat_entry->value;
+ mask = wdat_entry->mask;
+ addr = &wdat_entry->register_region;
+
+ /* Operation */
+ switch (flags) {
+ case ACPI_WDAT_READ_VALUE:
+ status = read_value(addr, value, mask, retval);
+ break;
+ case ACPI_WDAT_READ_COUNTDOWN:
+ status = read_countdown(addr, value, mask, retval);
+ break;
+ case ACPI_WDAT_WRITE_VALUE:
+ status = write_value(addr, value, mask, preserve);
+ break;
+ case ACPI_WDAT_WRITE_COUNTDOWN:
+ status = write_value(addr, param, mask, preserve);
+ break;
+ default:
+ ERROR(L"Unsupported WDAT instruction %x!\n", flags);
+ return EFI_UNSUPPORTED;
+ }
+ /* Stop on first error */
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ }
+ return status;
+}
+
+static EFI_STATUS __attribute__((constructor))
+init(EFI_PCI_IO *pci_io, UINT16 pci_vendor_id, UINT16 pci_device_id,
+ UINTN timeout)
+{
+ ACPI_TABLE_WDAT *wdat_table;
+ EFI_STATUS status;
+ UINT32 boot_status;
+ UINTN n;
+
+ /* unused */
+ pci_io = pci_io; pci_vendor_id = pci_vendor_id; pci_device_id =
pci_device_id;
+
+ /* Locate WDAT in ACPI tables */
+ status = locate_and_parse_rsdp(&wdat_table);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ INFO(L"Detected WDAT watchdog\n");
+
+ /* Check if the boot was caused by the watchdog */
+ status = run_action(wdat_table, ACPI_WDAT_GET_STATUS, 0, &boot_status);
+ if ((status == EFI_SUCCESS) && (boot_status != 0)) {
+ INFO(L"Boot caused by watchdog\n");
+ }
+
+ /* Enable reboot */
+ status = run_action(wdat_table, ACPI_WDAT_SET_REBOOT, 0, NULL);
+ if (EFI_ERROR(status) && (status != EFI_UNSUPPORTED)) {
+ ERROR(L"Could not enable REBOOT for WDAT!\n");
+ return status;
+ }
+
+ /* Check period */
+ if (wdat_table->timer_period == 0) {
+ ERROR(L"Invalid WDAT period in ACPI tables!\n");
+ return EFI_INVALID_PARAMETER;
+ }
+
+ /* Compute timeout in periods */
+ n = (timeout * 1000) / wdat_table->timer_period;
+
+ /* Program countdown */
+ status = run_action(wdat_table, ACPI_WDAT_SET_COUNTDOWN, n, NULL);
+ if (EFI_ERROR(status)) {
+ ERROR(L"Could not change WDAT timeout!\n");
+ return status;
+ }
+
+ /* Initial ping with specified timeout */
+ status = run_action(wdat_table, ACPI_WDAT_RESET, n, NULL);
+ if (EFI_ERROR(status)) {
+ ERROR(L"Could not reset WDAT!\n");
+ return status;
+ }
+
+ /* Enable watchdog */
+ status = run_action(wdat_table, ACPI_WDAT_SET_RUNNING_STATE, 0, NULL);
+ if (EFI_ERROR(status)) {
+ ERROR(L"Could not change WDAT to RUNNING state!\n");
+ return status;
+ }
+
+ return EFI_SUCCESS;
+}