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 | 425 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 427 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..8fda156
--- /dev/null
+++ b/drivers/watchdog/wdat.c
@@ -0,0 +1,425 @@
+/*
+ * EFI Boot Guard
+ *
+ * Copyright (C) 2021 Mentor Graphics, A Siemens business
+ *
+ * Authors:
+ *  Cedric Hombourger <[email protected]>
+ *
+ * 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)
+
+/* Root System Description Pointer */
+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 */
+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 */
+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 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()
+
+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))) {
+                       if (!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;
+
+       /* do not probe when scanning PCI devices */
+       if (pci_io || pci_vendor_id || pci_device_id) {
+               return EFI_UNSUPPORTED;
+       }
+
+       /* 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;
+}
-- 
2.30.2

-- 
You received this message because you are subscribed to the Google Groups "EFI 
Boot Guard" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/efibootguard-dev/20210705082256.595-2-Cedric_Hombourger%40mentor.com.

Reply via email to