On Sun, Jul 21, 2024 at 9:19 AM Jan Kiszka <jan.kis...@siemens.com> wrote: > > On 17.07.24 17:15, marc.ferl...@gmail.com wrote: > > From: Marc Ferland <marc.ferl...@sonatest.com> > > > > This driver adds support for the WDT included in Advantechs's EC > > chip (EIOIS200). Unfortunatly, no documentation is available at this > > time and so the driver was inspired from the code available at: > > > > https://github.com/ADVANTECH-Corp/eio-is200-linux-kernel-driver > > Thanks for the patch. I saw you trying to contribute to that downstream > driver as well. Is anyone working on properly upstreaming all those > bits? You already found issues, and I bet the respective subsystem > maintainer will found some more. > I don't think so. Advantech tried to do it but ultimately decided it was too much effort I guess... See: https://lore.kernel.org/lkml/?q=eiois200
And yes, there are many issues with the original driver! > > > > This driver was tested on the SOM-6872. > > > > Signed-off-by: Marc Ferland <marc.ferl...@sonatest.com> > > --- > > Makefile.am | 1 + > > drivers/watchdog/eiois200_wdt.c | 442 ++++++++++++++++++++++++++++++++ > > 2 files changed, 443 insertions(+) > > create mode 100644 drivers/watchdog/eiois200_wdt.c > > > > diff --git a/Makefile.am b/Makefile.am > > index 2ea47ae..34a7ee7 100644 > > --- a/Makefile.am > > +++ b/Makefile.am > > @@ -175,6 +175,7 @@ efi_sources_watchdogs = \ > > drivers/watchdog/ipmi_wdt.c \ > > drivers/watchdog/itco.c \ > > drivers/watchdog/hpwdt.c \ > > + drivers/watchdog/eiois200_wdt.c \ > > drivers/utils/simatic.c \ > > drivers/utils/smbios.c > > else > > diff --git a/drivers/watchdog/eiois200_wdt.c > > b/drivers/watchdog/eiois200_wdt.c > > new file mode 100644 > > index 0000000..1459c2b > > --- /dev/null > > +++ b/drivers/watchdog/eiois200_wdt.c > > @@ -0,0 +1,442 @@ > > +/* > > + * EFI Boot Guard > > + * > > + * Copyright (c) Sonatest AP Inc, 2024 > > + * > > + * Authors: > > + * Marc Ferland <marc.ferl...@sonatest.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 <pci/header.h> > > +#include <sys/io.h> > > +#include <mmio.h> > > +#include "utils.h" > > + > > +/* #define EIO200IS_WDT_DEBUG */ > > + > > +#ifdef EIO200IS_WDT_DEBUG > > +#define DBG(fmt, ...) Print(fmt, ##__VA_ARGS__) > > +#else > > +#define DBG(fmt, ...) {} > > +#endif > > + > > +#define EIOIS200_MODE_ENTER 0x87 > > +#define EIOIS200_MODE_EXIT 0xaa > > +#define EIOIS200_CHIPID1 0x20 > > +#define EIOIS200_CHIPID2 0x21 > > +#define EIOIS200_200_CHIPID 0x9610 > > +#define EIOIS200_211_CHIPID 0x9620 > > +#define EIOIS200_SIOCTRL 0x23 > > +#define EIOIS200_SIOCTRL_SIOEN (1 << 0) > > +#define EIOIS200_SIOCTRL_SWRST (1 << 1) > > +#define EIOIS200_IRQCTRL 0x70 > > + > > +#define EIOIS200_PMC_STATUS_IBF (1 << 1) > > +#define EIOIS200_PMC_STATUS_OBF (1 << 0) > > +#define EIOIS200_LDAR 0x30 > > +#define EIOIS200_LDAR_LDACT (1 << 0) > > +#define EIOIS200_IOBA0H 0x60 > > +#define EIOIS200_IOBA0L 0x61 > > +#define EIOIS200_IOBA1H 0x62 > > +#define EIOIS200_IOBA1L 0x63 > > +#define EIOIS200_FLAG_PMC_READ (1 << 0) > > + > > +#define PMC_WDT_CMD_WRITE 0x2a > > +#define PMC_WDT_CMD_READ 0x2b > > +#define PMC_WDT_CTRL_START 0x01 > > +#define PMC_WDT_MIN_TIMEOUT_MS 1000 > > +#define PMC_WDT_MAX_TIMEOUT_MS 32767000 > > + > > +#define WDT_STA_AVAILABLE (1 << 0) > > +#define WDT_STA_RESET (1 << 7) > > + > > +#define WDT_REG_STATUS 0x00 > > +#define WDT_REG_CONTROL 0x02 > > +#define WDT_REG_RESET_EVT_TIME 0x14 > > + > > +/* Logical device selection */ > > +#define EIOIS200_LDN 0x07 /* index */ > > +#define EIOIS200_LDN_PMC0 0x0c /* value */ > > +#define EIOIS200_LDN_PMC1 0x0d /* value */ > > + > > +enum eiois200_port_id { > > + EIOIS200_PNP, > > + EIOIS200_PNP_COUNT > > +}; > > + > > +struct eiois200_dev_port { > > + UINT16 index_port; > > + UINT16 data_port; > > +}; > > + > > +struct pmc_port { > > + UINT16 cmd; > > + UINT16 data; > > +}; > > + > > +/* We currently only support the EIOIS200 as implemented on the > > + * SOM-6872 COM-Express from Advantech. Other boards might have > > + * different port adresses. */ > > +static const struct eiois200_dev_port pnp_port[EIOIS200_PNP_COUNT] = { > > + [EIOIS200_PNP] = { > > + .index_port = 0x0299, > > + .data_port = 0x029a, > > + }, > > +}; > > + > > +static BOOLEAN probed_before = FALSE; > > +static EFI_EVENT cmdtimer; > > + > > +static void eio200_enter(const struct eiois200_dev_port *p) > > +{ > > + UINT16 iport = p->index_port; > > + > > + /* unlock EC io port */ > > + outb(EIOIS200_MODE_ENTER, iport); > > + outb(EIOIS200_MODE_ENTER, iport); > > +} > > + > > +static void eio200_exit(const struct eiois200_dev_port *p) > > +{ > > + UINT16 iport = p->index_port; > > + > > + /* lock EC io port */ > > + outb(EIOIS200_MODE_EXIT, iport); > > +} > > Is that back and forth with enter/exit required for all the accesses, or > would it be enough to perform the enter once and then exit before > returning from init()? > I mimicked the original driver from Advantech here but I'll give it a try. Thanks for the suggestion. > > + > > +static UINT8 eio200_read(const struct eiois200_dev_port *p, UINT8 index) > > +{ > > + UINT16 iport = p->index_port; > > + UINT16 dport = p->data_port; > > + > > + outb(index, iport); > > + return inb(dport); > > +} > > + > > +static void eio200_write(const struct eiois200_dev_port *p, > > + UINT8 index, UINT8 val) > > +{ > > + UINT16 iport = p->index_port; > > + UINT16 dport = p->data_port; > > + > > + outb(index, iport); > > + outb(val, dport); > > +} > > + > > +static const struct eiois200_dev_port * eio200_find(void) > > +{ > > + const struct eiois200_dev_port *p; > > + UINT16 chipid; > > + int i; > > + > > + for (i = 0; i < EIOIS200_PNP_COUNT; ++i) { > > + p = &pnp_port[i]; > > + > > + eio200_enter(p); > > + > > + chipid = eio200_read(p, EIOIS200_CHIPID1) << 8; > > + chipid |= eio200_read(p, EIOIS200_CHIPID2); > > I don't think it is a good idea to peek on these ports on all the > hardware we run on. You should combine this with some safer platform > detection. > This part was also inspired from the original driver. I was thinking about using the DMI entries just like you did for the SIMATIC machine, but the entries are all empty. For example: Handle 0x0001, DMI type 1, 27 bytes System Information Manufacturer: Default string Product Name: Default string Version: Default string Serial Number: Default string UUID: 03000200-0400-0500-0006-000700080009 Wake-up Type: Power Switch SKU Number: Default string Family: Default string I'll try to contact the manufacturer, maybe they can update their DMI tables and make life a bit easier. > > + > > + DBG(L"ChipID: 0x%02x\n", chipid); > > + > > + eio200_exit(p); > > + > > + if (chipid != EIOIS200_200_CHIPID && > > + chipid != EIOIS200_211_CHIPID) > > + continue; > > + > > + return p; > > + } > > + > > + return NULL; > > +} > > + > > +static void eio200_enable(const struct eiois200_dev_port *p) > > +{ > > + UINT8 reg; > > + > > + eio200_enter(p); > > + > > + /* set the enable flag */ > > + reg = eio200_read(p, EIOIS200_SIOCTRL); > > + reg |= EIOIS200_SIOCTRL_SIOEN; > > + eio200_write(p, EIOIS200_SIOCTRL, reg); > > + > > + eio200_exit(p); > > +} > > + > > +static void eio200_read_pmc_ports(const struct eiois200_dev_port *p, > > + struct pmc_port *pmc_port) > > +{ > > + eio200_enter(p); > > + > > + /* switch to logical device pmc1 */ > > + eio200_write(p, EIOIS200_LDN, EIOIS200_LDN_PMC1); > > + > > + /* activate device */ > > + eio200_write(p, EIOIS200_LDAR, EIOIS200_LDAR_LDACT); > > + > > + /* read pmc cmd and data port */ > > + pmc_port->data = eio200_read(p, EIOIS200_IOBA0H) << 8; > > + pmc_port->data |= eio200_read(p, EIOIS200_IOBA0L); > > + pmc_port->cmd = eio200_read(p, EIOIS200_IOBA1H) << 8; > > + pmc_port->cmd |= eio200_read(p, EIOIS200_IOBA1L); > > + > > + /* disable irq */ > > + eio200_write(p, EIOIS200_IRQCTRL, 0); > > + > > + eio200_exit(p); > > +} > > + > > +static EFI_STATUS pmc_wait_iobf(const struct pmc_port *p, UINTN iobf) > > +{ > > + EFI_STATUS timer_status; > > + > > + BS->SetTimer(cmdtimer, TimerRelative, 50000); /* 5000 usec */ > > + > > Why do we need a timer in addition to the stall? It's not that things > are happening asynchronously here. Just count the loops you are taking. > Agreed. > > + do { > > + UINT8 status = inb(p->cmd); > > + > > + if (iobf == EIOIS200_PMC_STATUS_IBF) { > > + if (!(status & EIOIS200_PMC_STATUS_IBF)) > > + return EFI_SUCCESS; > > + } else { > > + if (status & EIOIS200_PMC_STATUS_OBF) > > + return EFI_SUCCESS; > > + } > > + > > + BS->Stall(200); /* 200 usec */ > > + > > + timer_status = BS->CheckEvent(cmdtimer); > > + } while (timer_status == EFI_NOT_READY); > > + > > + return EFI_DEVICE_ERROR; > > +} > > + > > +static EFI_STATUS pmc_outb(const struct pmc_port *p, UINT8 value, UINT16 > > port) > > +{ > > + EFI_STATUS err; > > + > > + err = pmc_wait_iobf(p, EIOIS200_PMC_STATUS_IBF); > > + if (EFI_ERROR(err)) > > + return err; > > + > > + outb(value, port); > > + > > + return EFI_SUCCESS; > > +} > > + > > +static EFI_STATUS pmc_inb(const struct pmc_port *p, UINT16 port, UINT8 > > *value) > > +{ > > + EFI_STATUS err; > > + > > + err = pmc_wait_iobf(p, EIOIS200_PMC_STATUS_OBF); > > + if (EFI_ERROR(err)) > > + return err; > > + > > + *value = inb(port); > > + > > + return EFI_SUCCESS; > > +} > > + > > +static EFI_STATUS pmc_write_data(const struct pmc_port *p, UINT8 value) > > +{ > > + return pmc_outb(p, value, p->data); > > +} > > + > > +static EFI_STATUS pmc_write_cmd(const struct pmc_port *p, UINT8 cmd) > > +{ > > + return pmc_outb(p, cmd, p->cmd); > > +} > > + > > +static EFI_STATUS pmc_read_data(const struct pmc_port *p, UINT8 *value) > > +{ > > + return pmc_inb(p, p->data, value); > > +} > > + > > +static EFI_STATUS pmc_clear(const struct pmc_port *p) > > +{ > > + UINT8 status, dummy; > > + > > + status = inb(p->cmd); > > + > > + if (!(status & EIOIS200_PMC_STATUS_IBF)) > > + return EFI_SUCCESS; > > + > > + /* read previous garbage */ > > + dummy = inb(p->data); > > + (void) dummy; > > + > > + BS->Stall(100); > > + > > + return EFI_SUCCESS; > > +} > > + > > +static EFI_STATUS pmc_cmd_exec(const struct pmc_port *p, UINT8 cmd, UINT8 > > ctl, > > + UINT8 devid, UINT8 *payload, UINT8 size) > > +{ > > + EFI_STATUS err; > > + BOOLEAN is_read; > > + UINT8 i; > > + > > + err = pmc_clear(p); > > + if (EFI_ERROR(err)) > > + goto fail; > > + > > + err = pmc_write_cmd(p, cmd); > > + if (EFI_ERROR(err)) > > + goto fail; > > + > > + err = pmc_write_data(p, ctl); > > + if (EFI_ERROR(err)) > > + goto fail; > > + > > + err = pmc_write_data(p, devid); > > + if (EFI_ERROR(err)) > > + goto fail; > > + > > + err = pmc_write_data(p, size); > > + if (EFI_ERROR(err)) > > + goto fail; > > + > > + is_read = cmd & EIOIS200_FLAG_PMC_READ; > > + > > + for (i = 0; i < size; ++i) { > > + if (is_read) > > + err = pmc_read_data(p, &payload[i]); > > + else > > + err = pmc_write_data(p, payload[i]); > > + if (EFI_ERROR(err)) > > + goto fail; > > + } > > + > > + return EFI_SUCCESS; > > + > > +fail: > > + ERROR(L"pmc err: cmd=0x%x ctl=0x%x devid=0x%x size=0x%x\n", > > + cmd, ctl, devid, size); > > + return err; > > +} > > + > > +static EFI_STATUS pmc_wdt_read(const struct pmc_port *p, UINT8 ctl, > > + UINT8 *payload, UINT8 size) > > +{ > > + return pmc_cmd_exec(p, PMC_WDT_CMD_READ, ctl, 0, payload, size); > > +} > > + > > +static EFI_STATUS pmc_wdt_write(const struct pmc_port *p, UINT8 ctl, > > + UINT8 *payload, UINT8 size) > > +{ > > + return pmc_cmd_exec(p, PMC_WDT_CMD_WRITE, ctl, 0, payload, size); > > +} > > + > > +static EFI_STATUS pmc_wdt_set_reset_timeout(const struct pmc_port *p, > > + UINT32 msec) > > +{ > > + UINT8 payload[4]; > > + > > + if (msec < PMC_WDT_MIN_TIMEOUT_MS) > > + msec = PMC_WDT_MIN_TIMEOUT_MS; > > + else if (msec > PMC_WDT_MAX_TIMEOUT_MS) > > + msec = PMC_WDT_MAX_TIMEOUT_MS; > > + > > + payload[0] = msec & 0xff; > > + payload[1] = (msec >> 8) & 0xff; > > + payload[2] = (msec >> 16) & 0xff; > > + payload[3] = (msec >> 24) & 0xff; > > + > > + return pmc_wdt_write(p, WDT_REG_RESET_EVT_TIME, > > + payload, sizeof(payload)); > > +} > > + > > +static EFI_STATUS pmc_wdt_start(const struct pmc_port *p) > > +{ > > + UINT8 payload = PMC_WDT_CTRL_START; > > + > > + return pmc_wdt_write(p, WDT_REG_CONTROL, &payload, 1); > > +} > > + > > +static EFI_STATUS pmc_wdt_status(const struct pmc_port *p, UINT8 *status) > > +{ > > + EFI_STATUS err; > > + > > + err = pmc_wdt_read(p, WDT_REG_STATUS, status, 1); > > + if (EFI_ERROR(err)) > > + return err; > > + > > + return EFI_SUCCESS; > > +} > > + > > +static EFI_STATUS init(EFI_PCI_IO __attribute__((unused)) * pci_io, > > + UINT16 __attribute__((unused)) pci_vendor_id, > > + UINT16 __attribute__((unused)) pci_device_id, > > + UINTN timeout) > > +{ > > + const struct eiois200_dev_port *eport; > > + struct pmc_port pmc; > > + UINT8 status; > > + EFI_STATUS err; > > + > > + /* We do not use PCI, and machines may have many PCI devices */ > > See above: We do need some extra detection logic to avoid reading from > or even writing to random (from the POV of other hardware) I/O ports. > I'll see what I can do about this. Thanks again for your suggestions! Marc -- 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 efibootguard-dev+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/efibootguard-dev/CAMRMzCA-CQX82b_O1-EAXyq2cHa1z8uyFBMknYHbUC-bBKS%2B3A%40mail.gmail.com.