On 02.07.21 11:02, Cedric Hombourger wrote:
> 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. This driver was tested
> on SIMATIC IPC127E.
> 
> Signed-off-by: Cedric Hombourger <[email protected]>
> ---
>  Makefile.am             |   1 +
>  drivers/watchdog/wdat.c | 425 ++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 426 insertions(+)
>  create mode 100644 drivers/watchdog/wdat.c
> 
> diff --git a/Makefile.am b/Makefile.am
> index 8fb8652..6d61cc4 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -129,6 +129,7 @@ efi_sources = \
>       drivers/watchdog/i6300esb.c \
>       drivers/watchdog/atom-quark.c \
>       drivers/watchdog/ipc4x7e_wdt.c \
> +     drivers/watchdog/wdat.c \

You should leave a comment like for the ipc4x7e_wdt.c on the required
ordering.

And I would actually restore your original prioritization of WDAT over
anything else, i.e. move this right after init_array_start.S. Or do you
see any reason we should generally prefer the specific PCI drivers over
WDAT? Quirks could still be encoded by adding some excludion to the WDAT
driver - if that should ever be needed.

>       drivers/watchdog/itco.c \
>       drivers/watchdog/init_array_end.S \
>       env/syspart.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
> + *

Again, you need to add copyright notices for the origin of these structs
with their comments. Could be linux/include/acpi/actbl.h.

> + * 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)) {

You didn't incorporate my refactoring suggestion.

> +                             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;
> +}
> 

Jan

-- 
Siemens AG, T RDA IOT
Corporate Competence Center Embedded Linux

-- 
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/c6f74585-d6b4-9457-5c60-7efae8be9f0b%40siemens.com.

Reply via email to