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 dictionnary of
actions as we may just parse the tables on the go to perform our
SET_COUNTDOWN and SET_RUNNING actions. This driver was tested
on SIMATIC IPC127E.
Signed-off-by: Cedric Hombourger <
Cedric_H...@mentor.com>
---
Makefile.am | 1 +
drivers/watchdog/wdat.c | 442 ++++++++++++++++++++++++++++++++++++++++
main.c | 10 +-
3 files changed, 452 insertions(+), 1 deletion(-)
create mode 100644 drivers/watchdog/wdat.c
diff --git a/Makefile.am b/Makefile.am
index 8fb8652..9aae4b4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -130,6 +130,7 @@ efi_sources = \
drivers/watchdog/atom-quark.c \
drivers/watchdog/ipc4x7e_wdt.c \
drivers/watchdog/itco.c \
+ drivers/watchdog/wdat.c \
drivers/watchdog/init_array_end.S \
env/syspart.c \
env/fatvars.c \
diff --git a/drivers/watchdog/wdat.c b/drivers/watchdog/wdat.c
new file mode 100644
index 0000000..669c94e
--- /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 <
Cedric_H...@mentor.com>
+ *
+ * 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_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION 0x02
+
+#define ACPI_SIG_RSDP (CHAR8 *)"RSD PTR "
+#define ACPI_SIG_XSDT (CHAR8 *)"XSDT"
+#define ACPI_SIG_WDAT (CHAR8 *)"WDAT"
+
+typedef struct {
+ CHAR8 Signature[8];
+ UINT8 Checksum;
+ UINT8 OemId[6];
+ UINT8 Revision;
+ UINT32 RsdtAddress;
+ UINT32 Length;
+ UINT64 XsdtAddress;
+ UINT8 ExtendedChecksum;
+ UINT8 Reserved[3];
+} EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER;
+
+
+/*******************************************************************************
+ *
+ * XSDT is the main System Description Table.
+ *
+ * There are many kinds of SDT. An SDT may be split into two parts:
+ * A common header and a data section which is different for each table.
+ *
+ ******************************************************************************/
+
+typedef struct {
+ CHAR8 Signature[4];
+ UINT32 Length;
+ UINT8 Revision;
+ UINT8 Checksum;
+ CHAR8 OemId[6];
+ CHAR8 OemTableId[8];
+ UINT32 OemRevision;
+ UINT32 CreatorId;
+ UINT32 CreatorRevision;
+} EFI_ACPI_SDT_HEADER;
+
+#pragma pack(1)
+
+/*******************************************************************************
+ *
+ * GAS - Generic Address Structure (ACPI 2.0+)
+ *
+ * Note: Since this structure is used in the ACPI tables, it is byte aligned.
+ * If misaligned access is not supported by the hardware, accesses to the
+ * 64-bit Address field must be performed with care.
+ *
+ ******************************************************************************/
+
+#define ACPI_ADRR_SPACE_SYSTEM_MEMORY 0
+#define ACPI_ADDR_SPACE_SYSTEM_IO 1
+
+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;
+
+/* 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
+};
+
+/*******************************************************************************
+ *
+ * WDAT - Watchdog Action Table Version 1
+ *
+ * Conforms to "Hardware Watchdog Timers Design Specification",
+ * Copyright for the specification: 2006 Microsoft Corporation.
+ *
+ ******************************************************************************/
+
+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_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *rsdp, ACPI_TABLE_WDAT **wdat_table_ptr) {
+ EFI_ACPI_SDT_HEADER *xsdt, *entry;
+ UINT64 *entry_ptr;
+ UINT64 i, count;
+
+ if (wdat_table_ptr == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+ *wdat_table_ptr = NULL;
+
+ if (rsdp->Revision < EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION) {
+ return EFI_NOT_FOUND;
+ }
+
+ xsdt = (EFI_ACPI_SDT_HEADER *)(rsdp->XsdtAddress);
+ 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 (i=0; i<count; i++, 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_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *rsdp;
+ EFI_GUID acpi_table_guid = ACPI_TABLE_GUID;
+ EFI_GUID acpi2_table_guid = ACPI_20_TABLE_GUID;
+ UINTN i;
+
+ for (i=0; i<ST->NumberOfTableEntries; i++) {
+ 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_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *) 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 = readw(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 inline 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 inline 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 inline 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 i;
+
+ wdat_entry = (ACPI_WDAT_ENTRY *)(wdat_table + 1);
+ for (i=0; i<wdat_table->entries; i++, 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);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ break;
+ case ACPI_WDAT_READ_COUNTDOWN:
+ status = read_countdown(addr, value, mask, retval);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ break;
+ case ACPI_WDAT_WRITE_VALUE:
+ status = write_value(addr, value, mask, preserve);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ break;
+ case ACPI_WDAT_WRITE_COUNTDOWN:
+ status = write_value(addr, param, mask, preserve);
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ break;
+ default:
+ ERROR(L"Unsupported WDAT instruction %x!\n", flags);
+ return EFI_UNSUPPORTED;
+ }
+ /* Stop on first error */
+ if (EFI_ERROR(status)) {
+ return status;
+ }
+ }
+ if (status == EFI_UNSUPPORTED) {
+ WARNING(L"action %x isn't supported!\n", action);
+ }
+ 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 watchdog\n");
+ }
+
+ /* 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;
+ }
+
+ /* 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;
+ }
+
+ return EFI_SUCCESS;
+}
diff --git a/main.c b/main.c
index 7949218..bc4d34e 100644
--- a/main.c
+++ b/main.c
@@ -54,6 +54,14 @@ static EFI_STATUS scan_devices(EFI_LOADED_IMAGE *loaded_image, UINTN timeout)
EFI_STATUS status;
UINT32 value;
+ /* First try without PCI */
+ status = probe_watchdog(loaded_image, NULL, 0, 0, timeout);
+ if (status == EFI_SUCCESS) {
+ /* Stop if a watchdog was already found */
+ return status;
+ }
+
+ /* Check watchdog devices on the PCI bus */
status = uefi_call_wrapper(BS->LocateHandle, 5, ByProtocol,
&PciIoProtocol, NULL, &size, devices);
if (EFI_ERROR(status)) {
@@ -62,7 +70,7 @@ static EFI_STATUS scan_devices(EFI_LOADED_IMAGE *loaded_image, UINTN timeout)
count = size / sizeof(EFI_HANDLE);
if (count == 0) {
- return probe_watchdog(loaded_image, NULL, 0, 0, timeout);
+ return EFI_NOT_FOUND;
}
do {
--
2.30.2