The branch main has been updated by aokblast: URL: https://cgit.FreeBSD.org/src/commit/?id=2b74ff5fceb6623f6114ce39baee9f6ec5f79277
commit 2b74ff5fceb6623f6114ce39baee9f6ec5f79277 Author: ShengYi Hung <aokbl...@freebsd.org> AuthorDate: 2025-08-22 14:55:45 +0000 Commit: ShengYi Hung <aokbl...@freebsd.org> CommitDate: 2025-08-25 15:45:24 +0000 ichwd: introduce i6300esbwd watch dog driver The intel 6300ESB watchdog is a special ICH-based watchdog device with a different interface. QEMU implements this watchdog for x86 systems. This change enables watchdog mode (rather than free-running mode) and introduces 1 sysctl: - hw.i6300esbwd.0.locked: locks the watchdog register after the event is triggered, preventing it from being disabled until a hard reset. This feature has been tested on a Vultr AMD guest machine and local qemu machine. PR: 259673 Approved by: markj (mentor), lwhsu (mentor) MFC after: 2 weeks Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D52049 --- sys/conf/files.x86 | 1 + sys/dev/ichwd/i6300esbwd.c | 245 +++++++++++++++++++++++++++++++++++++++++++++ sys/dev/ichwd/i6300esbwd.h | 46 +++++++++ sys/dev/ichwd/ichwd.c | 2 +- sys/dev/ichwd/ichwd.h | 3 +- sys/modules/ichwd/Makefile | 2 +- 6 files changed, 296 insertions(+), 3 deletions(-) diff --git a/sys/conf/files.x86 b/sys/conf/files.x86 index 9976e9cfec5d..953da7dd1284 100644 --- a/sys/conf/files.x86 +++ b/sys/conf/files.x86 @@ -146,6 +146,7 @@ dev/hyperv/vmbus/vmbus_et.c optional hyperv dev/hyperv/vmbus/vmbus_if.m optional hyperv dev/hyperv/vmbus/vmbus_res.c optional hyperv dev/hyperv/vmbus/vmbus_xact.c optional hyperv +dev/ichwd/i6300esbwd.c optional ichwd dev/ichwd/ichwd.c optional ichwd dev/imcsmb/imcsmb.c optional imcsmb dev/imcsmb/imcsmb_pci.c optional imcsmb pci diff --git a/sys/dev/ichwd/i6300esbwd.c b/sys/dev/ichwd/i6300esbwd.c new file mode 100644 index 000000000000..d95aeb53c3f5 --- /dev/null +++ b/sys/dev/ichwd/i6300esbwd.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2025 The FreeBSD Foundation + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Reference: Intel 6300ESB Controller Hub Datasheet Section 16 + */ + +#include <sys/param.h> +#include <sys/eventhandler.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/sysctl.h> +#include <sys/errno.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <machine/resource.h> +#include <sys/watchdog.h> + +#include <dev/pci/pcireg.h> + +#include <dev/ichwd/ichwd.h> +#include <dev/ichwd/i6300esbwd.h> + +#include <x86/pci_cfgreg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pci_private.h> + +struct i6300esbwd_softc { + device_t dev; + int res_id; + struct resource *res; + eventhandler_tag ev_tag; + bool locked; +}; + +static const struct i6300esbwd_pci_id { + uint16_t id; + const char *name; +} i6300esbwd_pci_devices[] = { + { DEVICEID_6300ESB_2, "6300ESB Watchdog Timer" }, +}; + +static uint16_t +i6300esbwd_cfg_read(struct i6300esbwd_softc *sc) +{ + return (pci_read_config(sc->dev, WDT_CONFIG_REG, 2)); +} + +static void +i6300esbwd_cfg_write(struct i6300esbwd_softc *sc, uint16_t val) +{ + pci_write_config(sc->dev, WDT_CONFIG_REG, val, 2); +} + +static uint8_t +i6300esbwd_lock_read(struct i6300esbwd_softc *sc) +{ + return (pci_read_config(sc->dev, WDT_LOCK_REG, 1)); +} + +static void +i6300esbwd_lock_write(struct i6300esbwd_softc *sc, uint8_t val) +{ + pci_write_config(sc->dev, WDT_LOCK_REG, val, 1); +} + +/* + * According to Intel 6300ESB I/O Controller Hub Datasheet 16.5.2, + * the resource should be unlocked before modifing any registers. + * The way to unlock is by write 0x80, 0x86 to the reload register. + */ +static void +i6300esbwd_unlock_res(struct i6300esbwd_softc *sc) +{ + bus_write_2(sc->res, WDT_RELOAD_REG, WDT_UNLOCK_SEQ_1_VAL); + bus_write_2(sc->res, WDT_RELOAD_REG, WDT_UNLOCK_SEQ_2_VAL); +} + +static int +i6300esbwd_sysctl_locked(SYSCTL_HANDLER_ARGS) +{ + struct i6300esbwd_softc *sc = (struct i6300esbwd_softc *)arg1; + int error; + int result; + + result = sc->locked; + error = sysctl_handle_int(oidp, &result, 0, req); + + if (error || !req->newptr) + return (error); + + if (result == 1 && !sc->locked) { + i6300esbwd_lock_write(sc, i6300esbwd_lock_read(sc) | WDT_LOCK); + sc->locked = true; + } + + return (0); +} + +static void +i6300esbwd_event(void *arg, unsigned int cmd, int *error) +{ + struct i6300esbwd_softc *sc = arg; + uint32_t timeout; + uint16_t regval; + + cmd &= WD_INTERVAL; + if (cmd != 0 && + (cmd < WD_TO_1MS || (cmd - WD_TO_1MS) >= WDT_PRELOAD_BIT)) { + *error = EINVAL; + return; + } + timeout = 1 << (cmd - WD_TO_1MS); + + /* reset the timer to prevent timeout a timeout is about to occur */ + i6300esbwd_unlock_res(sc); + bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD); + + if (!cmd) { + /* + * when the lock is enabled, we are unable to overwrite LOCK + * register + */ + if (sc->locked) + *error = EPERM; + else + i6300esbwd_lock_write(sc, + i6300esbwd_lock_read(sc) & ~WDT_ENABLE); + return; + } + + i6300esbwd_unlock_res(sc); + bus_write_4(sc->res, WDT_PRELOAD_1_REG, timeout); + + i6300esbwd_unlock_res(sc); + bus_write_4(sc->res, WDT_PRELOAD_2_REG, timeout); + + i6300esbwd_unlock_res(sc); + bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD); + + if (!sc->locked) { + i6300esbwd_lock_write(sc, WDT_ENABLE); + regval = i6300esbwd_lock_read(sc); + sc->locked = regval & WDT_LOCK; + } +} + +static int +i6300esbwd_probe(device_t dev) +{ + const struct i6300esbwd_pci_id *pci_id; + uint16_t pci_dev_id; + int err = ENXIO; + + if (pci_get_vendor(dev) != VENDORID_INTEL) + goto end; + + pci_dev_id = pci_get_device(dev); + for (pci_id = i6300esbwd_pci_devices; + pci_id < i6300esbwd_pci_devices + nitems(i6300esbwd_pci_devices); + ++pci_id) { + if (pci_id->id == pci_dev_id) { + device_set_desc(dev, pci_id->name); + err = BUS_PROBE_DEFAULT; + break; + } + } + +end: + return (err); +} + +static int +i6300esbwd_attach(device_t dev) +{ + struct i6300esbwd_softc *sc = device_get_softc(dev); + uint16_t regval; + + sc->dev = dev; + sc->res_id = PCIR_BAR(0); + sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->res_id, + RF_ACTIVE); + if (sc->res == NULL) { + device_printf(dev, "unable to map memory region\n"); + return (ENXIO); + } + + i6300esbwd_cfg_write(sc, WDT_INT_TYPE_DISABLED_VAL); + regval = i6300esbwd_lock_read(sc); + if (regval & WDT_LOCK) + sc->locked = true; + else { + sc->locked = false; + i6300esbwd_lock_write(sc, WDT_TOUT_CNF_WT_MODE); + } + + i6300esbwd_unlock_res(sc); + bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD | WDT_TIMEOUT); + + sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, i6300esbwd_event, sc, + 0); + + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "locked", + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, + i6300esbwd_sysctl_locked, "I", + "Lock the timer so that we cannot disable it"); + + return (0); +} + +static int +i6300esbwd_detach(device_t dev) +{ + struct i6300esbwd_softc *sc = device_get_softc(dev); + + if (sc->ev_tag) + EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); + + if (sc->res) + bus_release_resource(dev, SYS_RES_MEMORY, sc->res_id, sc->res); + + return (0); +} + +static device_method_t i6300esbwd_methods[] = { + DEVMETHOD(device_probe, i6300esbwd_probe), + DEVMETHOD(device_attach, i6300esbwd_attach), + DEVMETHOD(device_detach, i6300esbwd_detach), + DEVMETHOD(device_shutdown, i6300esbwd_detach), + DEVMETHOD_END +}; + +static driver_t i6300esbwd_driver = { + "i6300esbwd", + i6300esbwd_methods, + sizeof(struct i6300esbwd_softc), +}; + +DRIVER_MODULE(i6300esbwd, pci, i6300esbwd_driver, NULL, NULL); diff --git a/sys/dev/ichwd/i6300esbwd.h b/sys/dev/ichwd/i6300esbwd.h new file mode 100644 index 000000000000..39ed5d5a84f6 --- /dev/null +++ b/sys/dev/ichwd/i6300esbwd.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 The FreeBSD Foundation + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _I6300ESBWD_H_ +#define _I6300ESBWD_H_ + +#define WDT_CONFIG_REG 0x60 +#define WDT_LOCK_REG 0x68 + +#define WDT_PRELOAD_1_REG 0x00 +#define WDT_PRELOAD_2_REG 0x04 +#define WDT_INTR_REG 0x08 +#define WDT_RELOAD_REG 0x0C + +/* For config register */ +#define WDT_OUTPUT_EN (0x1 << 5) +#define WDT_PRE_SEL (0x1 << 2) +#define WDT_INT_TYPE_BITS (0x3) +#define WDT_INT_TYPE_IRQ_VAL (0x0) +#define WDT_INT_TYPE_RES_VAL (0x1) +#define WDT_INT_TYPE_SMI_VAL (0x2) +#define WDT_INT_TYPE_DISABLED_VAL (0x3) + +/* For lock register */ +#define WDT_TOUT_CNF_WT_MODE (0x0 << 2) +#define WDT_TOUT_CNF_FR_MODE (0x1 << 2) +#define WDT_ENABLE (0x02) +#define WDT_LOCK (0x01) + +/* For preload 1/2 registers */ +#define WDT_PRELOAD_BIT 20 +#define WDT_PRELOAD_BITS ((0x1 << WDT_PRELOAD_BIT) - 1) + +/* For interrupt register */ +#define WDT_INTR_ACT (0x01 << 0) + +/* For reload register */ +#define WDT_TIMEOUT (0x01 << 9) +#define WDT_RELOAD (0x01 << 8) +#define WDT_UNLOCK_SEQ_1_VAL 0x80 +#define WDT_UNLOCK_SEQ_2_VAL 0x86 + +#endif /* _I6300ESBWD_H_ */ diff --git a/sys/dev/ichwd/ichwd.c b/sys/dev/ichwd/ichwd.c index cade2cc4fb45..5481553cc175 100644 --- a/sys/dev/ichwd/ichwd.c +++ b/sys/dev/ichwd/ichwd.c @@ -90,7 +90,7 @@ static struct ichwd_device ichwd_devices[] = { { DEVICEID_82801E, "Intel 82801E watchdog timer", 5, 1 }, { DEVICEID_82801EB, "Intel 82801EB watchdog timer", 5, 1 }, { DEVICEID_82801EBR, "Intel 82801EB/ER watchdog timer", 5, 1 }, - { DEVICEID_6300ESB, "Intel 6300ESB watchdog timer", 5, 1 }, + { DEVICEID_6300ESB_1, "Intel 6300ESB watchdog timer", 5, 1 }, { DEVICEID_82801FBR, "Intel 82801FB/FR watchdog timer", 6, 2 }, { DEVICEID_ICH6M, "Intel ICH6M watchdog timer", 6, 2 }, { DEVICEID_ICH6W, "Intel ICH6W watchdog timer", 6, 2 }, diff --git a/sys/dev/ichwd/ichwd.h b/sys/dev/ichwd/ichwd.h index 90fda08b74c1..72d0ca1cd6aa 100644 --- a/sys/dev/ichwd/ichwd.h +++ b/sys/dev/ichwd/ichwd.h @@ -151,7 +151,8 @@ struct ichwd_softc { #define DEVICEID_82801E 0x2450 #define DEVICEID_82801EB 0x24dc #define DEVICEID_82801EBR 0x24d0 -#define DEVICEID_6300ESB 0x25a1 +#define DEVICEID_6300ESB_1 0x25a1 +#define DEVICEID_6300ESB_2 0x25ab #define DEVICEID_82801FBR 0x2640 #define DEVICEID_ICH6M 0x2641 #define DEVICEID_ICH6W 0x2642 diff --git a/sys/modules/ichwd/Makefile b/sys/modules/ichwd/Makefile index 3c3bbc37eff5..27b4c38437ff 100644 --- a/sys/modules/ichwd/Makefile +++ b/sys/modules/ichwd/Makefile @@ -1,6 +1,6 @@ .PATH: ${SRCTOP}/sys/dev/ichwd KMOD= ichwd -SRCS= ichwd.c device_if.h bus_if.h pci_if.h isa_if.h +SRCS= i6300esbwd.c ichwd.c device_if.h bus_if.h pci_if.h isa_if.h .include <bsd.kmod.mk>