git: 2b74ff5fceb6 - main - ichwd: introduce i6300esbwd watch dog driver

From: ShengYi Hung <aokblast_at_FreeBSD.org>
Date: Mon, 25 Aug 2025 15:45:52 UTC
The branch main has been updated by aokblast:

URL: https://cgit.FreeBSD.org/src/commit/?id=2b74ff5fceb6623f6114ce39baee9f6ec5f79277

commit 2b74ff5fceb6623f6114ce39baee9f6ec5f79277
Author:     ShengYi Hung <aokblast@FreeBSD.org>
AuthorDate: 2025-08-22 14:55:45 +0000
Commit:     ShengYi Hung <aokblast@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>