Port-amd64 archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

tco at ichsmb



Currently tco(4) attaches at ichlpcib(4), the Intel I/O platform
controller hub's low-pin-count interface bridge, d31:f0.

On newer Intel systems, though, the registers are found via the SMBus
device, d31:f4, with some other bits to twiddle and knobs to turn at
the PMC device d31:f2 and the hidden P2SB d31:f1.  See, e.g.:

https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/100-series-chipset-datasheet-vol-2.pdf

Linux's driver iTCO_wd and FreeBSD's driver ichwd support this
configuration, and I'm trying to add it to NetBSD.

With the attached ichsmbtco.patch, I've gotten everything to attach on
my T480, and if I start wdogctl, I can read out reasonable-looking TCO
registers.  But it doesn't work: it never fires, and if I read out the
TCO RLD register, it never counts down.

Relevant fragment of output from pcictl pci0 list -n -N:

000:31:0: 0x9d4e8086 (0x06010021) [pcib0]
000:31:1: 0x9d208086 (0x05800021)
000:31:2: 0x9d218086 (0x05800021)
000:31:3: 0x9d718086 (0x04030021) [hdaudio0]
000:31:4: 0x9d238086 (0x0c050021) [ichsmb0]

(Note that d31:f1 is normally hidden -- I exposed it with `pcictl pci0
write -b 0 -d 31 -f 1 0xe0 0xfffffeff'; writing 0xffffffff the same
way will hide it again.)

The attached pmctest-v2.c dumps various registers from userland.
Caveat: It has some hard-coded addresses -- the comments say how I
found them and how you can too on your device, provided it's the same
kind of chipset (e.g., b0:d31:f0 is vendor 0x8086, product 0x9d4e, or
similar; see pcidevs for 100/200/300-series chipsets that this might
also apply to).  This is how I can see that the TCO RLD register is
set by wdogctl, but doesn't count down.

Some relevant registers after I start wdogctl:

PMC 30h: 98002033        SMI_EN, TCO_EN and GBL_SMI_EN are both set
TCO RLD   [00h]: 0x0263  countdown timer, never changes after arming
                         (was 0x0004 on boot)
TCO TDI   [02h]: 0x00
TCO TDO   [03h]: 0x00
TCO TSTS1 [04h]: 0x0000
TCO TSTS2 [06h]: 0x0000
TCO TCTL1 [08h]: 0x1000  control 1, TCO_LOCK is set and TCO_TMR_HALT is clear
TCO TCTL2 [0Ah]: 0x0008  control 2, SMB_ALERT_DISABLE is set
TCO TMSG  [0Ch]: 0x0000
TCO TWDS  [0Eh]: 0x00
TCO LE    [10h]: 0x03    LEGACY_ELIM, IRQ12_CAUSE and IRQ1_CAUSE set
TCO TTMR  [12h]: 0x0263  value loaded into RLD on each tickle, units of 0.6 sec
                         (was 0x0004 on boot)
SMBus TCOCFG [00h]: 0x00000000   IRQ Enable and IRQ Select both clear
SMBus ?      [04h]: 0x00000000
SMBus ?      [08h]: 0x00000000
SMBus GC     [0ch]: 0x00000000   No Reboot and Function Disable both clear
SMBus PCE    [10h]: 0x00000028   Sleep Enable and some undocumented bit set

I'm struggling to find anything substantively different that the Linux
and FreeBSD drivers do that might cause the timer to actually tick
there, or to find any other relevant registers in the manual that
might turn the countdown on or off.

That said, I haven't verified the thing works under Linux or FreeBSD,
so maybe the hardware just doesn't work.  It's also possible that this
is meant to be operated through an ACPI binding, but I don't see
anything watchdog-related in acpidump -dt output -- no WDRT or
anything.

Any ideas for how to get this watchdog timer ticking, and, ideally,
barking?
From e809e6257c7c1a7f17abc9263f330a594ea2ae88 Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh%NetBSD.org@localhost>
Date: Thu, 22 Sep 2022 06:28:04 +0000
Subject: [PATCH] ichsmb(4), tco(4): Add support for TCO on newer Intel
 chipsets.

TCO (`Total Cost of Ownership', Intel's bizarre name for a watchdog
timer) used to hang off the Intel I/O platform controller hub's (ICH)
low-pin-count interface bridge (LPC IB), or ichlpcib(4).  On newer
devices, it hangs off the ICH SMBus instead.

XXX kernel revbump: This breaks the module ABI -- tco(4) modules
older than the change to make ta_has_rcba into ta_version will
incorrectly attach at buses they do not understand.
---
 sys/arch/amd64/conf/GENERIC |   2 +-
 sys/arch/x86/pci/files.pci  |   1 -
 sys/arch/x86/pci/ichlpcib.c |  14 +++
 sys/arch/x86/pci/tco.c      |  44 +++++--
 sys/arch/x86/pci/tco.h      |   4 +
 sys/dev/ic/i82801lpcreg.h   |  43 +++++++
 sys/dev/pci/files.pci       |   6 +-
 sys/dev/pci/ichsmb.c        | 229 +++++++++++++++++++++++++++++++++++-
 8 files changed, 330 insertions(+), 13 deletions(-)

diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 897ddf309fcb..941f0a128b39 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -401,7 +401,7 @@ ichlpcib* at pci? dev ? function ?	# Intel ICH PCI-LPC w/ timecounter,
 					# watchdog, gpio, Speedstep and HPET
 fwhrng* at ichlpcib?		# Intel 82802 FWH Random Number Generator
 #hpet*	at ichlpcib?
-tco*	at ichlpcib?		# TCO watch dog timer
+tco*	at tcoichbus?		# TCO watch dog timer
 
 aapic*	at pci? dev ? function ?	# AMD 8131 IO apic
 
diff --git a/sys/arch/x86/pci/files.pci b/sys/arch/x86/pci/files.pci
index 948a6b47145d..b49bbf5086c4 100644
--- a/sys/arch/x86/pci/files.pci
+++ b/sys/arch/x86/pci/files.pci
@@ -59,7 +59,6 @@ file 	arch/x86/pci/rdcpcib.c 		rdcpcib
 
 define	fwhichbus {}
 define	hpetichbus {}
-define	tcoichbus {}
 device	ichlpcib: acpipmtimer, isabus, fwhichbus, hpetichbus, gpiobus, tcoichbus
 attach	ichlpcib at pci
 file 	arch/x86/pci/ichlpcib.c 	ichlpcib
diff --git a/sys/arch/x86/pci/ichlpcib.c b/sys/arch/x86/pci/ichlpcib.c
index 20051cc6b8c1..71aaa043e179 100644
--- a/sys/arch/x86/pci/ichlpcib.c
+++ b/sys/arch/x86/pci/ichlpcib.c
@@ -89,6 +89,11 @@ struct lpcib_softc {
 	bus_space_handle_t	sc_pmh;
 	bus_size_t		sc_iosize;
 
+	/* TCO variables. */
+	bus_space_tag_t		sc_tcot;
+	bus_space_handle_t	sc_tcoh;
+	bus_size_t		sc_tcosz;
+
 	/* HPET variables. */
 	uint32_t		sc_hpet_reg;
 
@@ -348,6 +353,13 @@ lpcibattach(device_t parent, device_t self, void *aux)
 		return;
 	}
 
+	if (bus_space_subregion(sc->sc_pmt, sc->sc_pmh, PMC_TCO_BASE,
+		TCO_REGSIZE, &sc->sc_tcoh)) {
+		aprint_error_dev(self, "can't map TCO space\n");
+	} else {
+		sc->sc_tcot = sc->sc_pmt;
+	}
+
 	sc->sc_pmcon_orig = pci_conf_read(sc->sc_pcib.sc_pc, sc->sc_pcib.sc_tag,
 	    LPCIB_PCI_GEN_PMCON_1);
 
@@ -644,6 +656,8 @@ tcotimer_configure(device_t self)
 	arg.ta_rcbat = sc->sc_rcbat;
 	arg.ta_rcbah = sc->sc_rcbah;
 	arg.ta_pcib = &sc->sc_pcib;
+	arg.ta_tcot = sc->sc_tcot;
+	arg.ta_tcoh = sc->sc_tcoh;
 
 	sc->sc_tco = config_found(self, &arg, NULL,
 	    CFARGS(.iattr = "tcoichbus"));
diff --git a/sys/arch/x86/pci/tco.c b/sys/arch/x86/pci/tco.c
index ff17cd2e0d6d..4866a3a25c46 100644
--- a/sys/arch/x86/pci/tco.c
+++ b/sys/arch/x86/pci/tco.c
@@ -60,8 +60,10 @@ struct tco_softc {
 	bus_space_tag_t		sc_rcbat;
 	bus_space_handle_t	sc_rcbah;
 	struct pcib_softc *	sc_pcib;
+	pci_chipset_tag_t	sc_pc;
 	bus_space_tag_t		sc_tcot;
 	bus_space_handle_t	sc_tcoh;
+	int			(*sc_set_noreboot)(device_t, bool);
 	int			sc_armed;
 	unsigned int		sc_min_t;
 	unsigned int		sc_max_t;
@@ -93,12 +95,13 @@ tco_match(device_t parent, cfdata_t match, void *aux)
 {
 	struct tco_attach_args *ta = aux;
 
-	if (ta->ta_pmt == 0)
-		return 0;
-
 	switch (ta->ta_version) {
+	case TCO_VERSION_SMBUS:
+		break;
 	case TCO_VERSION_RCBA:
 	case TCO_VERSION_PCIB:
+		if (ta->ta_pmt == 0)
+			return 0;
 		break;
 	default:
 		return 0;
@@ -125,11 +128,21 @@ tco_attach(device_t parent, device_t self, void *aux)
 	aprint_normal(": TCO (watchdog) timer configured.\n");
 	aprint_naive("\n");
 
-	sc->sc_tcot = sc->sc_pmt;
-	if (bus_space_subregion(sc->sc_pmt, sc->sc_pmh, PMC_TCO_BASE,
-		TCO_REGSIZE, &sc->sc_tcoh)) {
-		aprint_error_dev(self, "failed to map TCO registers\n");
-		return;
+	switch (sc->sc_version) {
+	case TCO_VERSION_SMBUS:
+		sc->sc_tcot = ta->ta_tcot;
+		sc->sc_tcoh = ta->ta_tcoh;
+		sc->sc_set_noreboot = ta->ta_set_noreboot;
+		break;
+	case TCO_VERSION_RCBA:
+	case TCO_VERSION_PCIB:
+		sc->sc_tcot = sc->sc_pmt;
+		if (bus_space_subregion(sc->sc_pmt, sc->sc_pmh, PMC_TCO_BASE,
+			TCO_REGSIZE, &sc->sc_tcoh)) {
+			aprint_error_dev(self, "failed to map TCO\n");
+			return;
+		}
+		break;
 	}
 
 	/* Explicitly stop the TCO timer. */
@@ -140,6 +153,7 @@ tco_attach(device_t parent, device_t self, void *aux)
 	 * work. We don't know what the SMBIOS does.
 	 */
 	ioreg = bus_space_read_4(sc->sc_pmt, sc->sc_pmh, PMC_SMI_EN);
+	aprint_debug_dev(self, "SMI_EN=0x%08x\n", ioreg);
 	ioreg &= ~PMC_SMI_EN_TCO_EN;
 
 	/*
@@ -150,7 +164,10 @@ tco_attach(device_t parent, device_t self, void *aux)
 		ioreg |= PMC_SMI_EN_TCO_EN;
 	}
 	if ((ioreg & PMC_SMI_EN_GBL_SMI_EN) != 0) {
+		aprint_debug_dev(self, "SMI_EN:=0x%08x\n", ioreg);
 		bus_space_write_4(sc->sc_pmt, sc->sc_pmh, PMC_SMI_EN, ioreg);
+		aprint_debug_dev(self, "SMI_EN=0x%08x\n",
+		    bus_space_read_4(sc->sc_pmt, sc->sc_pmh, PMC_SMI_EN));
 	}
 
 	/* Reset the watchdog status registers. */
@@ -172,6 +189,7 @@ tco_attach(device_t parent, device_t self, void *aux)
 	 *                              2secs          23secs
 	 */
 	switch (sc->sc_version) {
+	case TCO_VERSION_SMBUS:
 	case TCO_VERSION_RCBA:
 		sc->sc_max_t = TCOTIMER2_MAX_TICK;
 		sc->sc_min_t = TCOTIMER2_MIN_TICK;
@@ -256,6 +274,7 @@ tcotimer_setmode(struct sysmon_wdog *smw)
 
 		/* set the timeout, */
 		switch (sc->sc_version) {
+		case TCO_VERSION_SMBUS:
 		case TCO_VERSION_RCBA:
 			/* ICH6 or newer */
 			ich6period = bus_space_read_2(sc->sc_tcot, sc->sc_tcoh,
@@ -289,6 +308,7 @@ tcotimer_tickle(struct sysmon_wdog *smw)
 
 	/* any value is allowed */
 	switch (sc->sc_version) {
+	case TCO_VERSION_SMBUS:
 	case TCO_VERSION_RCBA:
 		bus_space_write_2(sc->sc_tcot, sc->sc_tcoh, TCO_RLD, 1);
 		break;
@@ -339,8 +359,14 @@ static int
 tcotimer_disable_noreboot(device_t self)
 {
 	struct tco_softc *sc = device_private(self);
+	int error = EINVAL;
 
 	switch (sc->sc_version) {
+	case TCO_VERSION_SMBUS:
+		error = (*sc->sc_set_noreboot)(self, false);
+		if (error)
+			goto error;
+		break;
 	case TCO_VERSION_RCBA: {
 		uint32_t status;
 
@@ -376,7 +402,7 @@ tcotimer_disable_noreboot(device_t self)
 error:
 	aprint_error_dev(self, "TCO timer reboot disabled by hardware; "
 	    "hope SMBIOS properly handles it.\n");
-	return EINVAL;
+	return error;
 }
 
 MODULE(MODULE_CLASS_DRIVER, tco, "sysmon_wdog");
diff --git a/sys/arch/x86/pci/tco.h b/sys/arch/x86/pci/tco.h
index b4302496b00d..77f0a81999e8 100644
--- a/sys/arch/x86/pci/tco.h
+++ b/sys/arch/x86/pci/tco.h
@@ -40,12 +40,16 @@ struct tco_attach_args {
 	enum {
 		TCO_VERSION_PCIB = 0,
 		TCO_VERSION_RCBA = 1,
+		TCO_VERSION_SMBUS = 2,
 	}			ta_version;
 	bus_space_tag_t		ta_pmt;
 	bus_space_handle_t	ta_pmh;
 	bus_space_tag_t		ta_rcbat;
 	bus_space_handle_t	ta_rcbah;
 	struct pcib_softc *	ta_pcib;
+	bus_space_tag_t		ta_tcot;
+	bus_space_handle_t	ta_tcoh;
+	int			(*ta_set_noreboot)(device_t, bool);
 };
 
 #endif
diff --git a/sys/dev/ic/i82801lpcreg.h b/sys/dev/ic/i82801lpcreg.h
index 9a0a4c808bb6..ae23be7ec184 100644
--- a/sys/dev/ic/i82801lpcreg.h
+++ b/sys/dev/ic/i82801lpcreg.h
@@ -171,6 +171,11 @@
 #define SMB_HOSTC_HSTEN		(1 << 0)	/* enable host controller */
 #define SMB_HOSTC_SMIEN		(1 << 1)	/* generate SMI */
 #define SMB_HOSTC_I2CEN		(1 << 2)	/* enable I2C commands */
+#define SMB_TCOBASE	0x50		/* TCO Base Address */
+#define  SMB_TCOBASE_TCOBA	__BITS(15,5)	/* TCO Base Address */
+#define  SMB_TCOBASE_IOS	__BIT(0)	/* I/O Space */
+#define SMB_TCOCTL	0x54		/* TCO Control */
+#define  SMB_TCOCTL_TCO_BASE_EN	__BIT(8)	/* TCO Base Enable */
 
 /* SMBus I/O registers */
 #define SMB_HS		0x00		/* host status */
@@ -301,4 +306,42 @@ tcotimer_second_to_tick(int ltick)
 #define TCOTIMER_MAX_TICK 	0x3f 	/* 39 seconds max */
 #define TCOTIMER2_MAX_TICK 	0x265	/* 613 seconds max */
 
+/*
+ * P2SB: Primary to Sideband Bridge, PCI configuration registers
+ */
+#define	P2SB_SBREG_BAR		0x10	/* Sideband Register Access BAR */
+#define	P2SB_SBREG_BARH		0x14	/* Sideband BAR High DWORD */
+#define	P2SB_P2SBC		0xe0	/* P2SB Control */
+#define	 P2SB_P2SBC_HIDE	__BIT(8)	/* Hide Device */
+
+/*
+ * PCH Private Configuration Space -- Sideband
+ */
+#define	SB_PORTID		__BITS(23,16)
+#define	 SB_PORTID_SMBUS		0xc6
+
+#define	SB_PORT(id)		__SHIFTIN(id, SB_PORTID)
+
+#define	SB_SMBUS_BASE		(SB_PORT(SB_PORTID_SMBUS) + 0x00)
+#define	SB_SMBUS_SIZE		0x14
+
+#define	SB_SMBUS_TCOCFG		0x00	/* TCO Configuration */
+#define	 SB_SMBUS_TCOCFG_IE	__BIT(7)	/* TCO IRQ Enable */
+#define	 SB_SMBUS_TCOCFG_IS	__BITS(2,0)	/* TCO IRQ Select */
+#define	 SB_SMBUS_TCOCFG_IS_IRQ9	0	/* maps to 8259 and APIC */
+#define	 SB_SMBUS_TCOCFG_IS_IRQ10	1	/* maps to 8259 and APIC */
+#define	 SB_SMBUS_TCOCFG_IS_IRQ11	2	/* maps to 8259 and APIC */
+#define	 SB_SMBUS_TCOCFG_IS_IRQ20	4	/* maps to APIC */
+#define	 SB_SMBUS_TCOCFG_IS_IRQ21	3	/* maps to APIC */
+#define	 SB_SMBUS_TCOCFG_IS_IRQ22	4	/* maps to APIC */
+#define	 SB_SMBUS_TCOCFG_IS_IRQ23	5	/* maps to APIC */
+#define	SB_SMBUS_GC		0x0c	/* General Control */
+#define	 SB_SMBUS_GC_NR		__BIT(1)	/* No Reboot */
+#define	 SB_SMBUS_GC_FD		__BIT(0)	/* Function Disable */
+#define	SB_SMBUS_PCE		0x10	/* Power Control Enable */
+#define	 SB_SMBUS_PCE_SE	__BIT(3)	/* Sleep Enable */
+#define	 SB_SMBUS_PCE_D3HE	__BIT(2)	/* D3-Hot Enable */
+#define	 SB_SMBUS_PCE_I3E	__BIT(1)	/* I3 Enable */
+#define	 SB_SMBUS_PCE_PMCRE	__BIT(0)	/* PMC Request Enable */
+
 #endif /*  _DEV_IC_I82801LPCREG_H_ */
diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci
index f9a97bc43b54..321bbdc72e34 100644
--- a/sys/dev/pci/files.pci
+++ b/sys/dev/pci/files.pci
@@ -978,8 +978,12 @@ device	nfsmb: i2cbus
 attach	nfsmb at nfsmbc
 file	dev/pci/nfsmb.c			nfsmbc | nfsmb
 
+# Intel ICH -- I/O or Platform Controller Hub
+# (most drivers under sys/arch/x86/pci)
+define	tcoichbus {}
+
 # Intel ICH SMBus controller
-device	ichsmb: i2cbus
+device	ichsmb: i2cbus, tcoichbus
 attach	ichsmb at pci
 file	dev/pci/ichsmb.c		ichsmb
 
diff --git a/sys/dev/pci/ichsmb.c b/sys/dev/pci/ichsmb.c
index 9d5b117d5384..ecfb650a9f06 100644
--- a/sys/dev/pci/ichsmb.c
+++ b/sys/dev/pci/ichsmb.c
@@ -42,6 +42,8 @@ __KERNEL_RCSID(0, "$NetBSD: ichsmb.c,v 1.81 2022/09/22 14:45:33 riastradh Exp $"
 
 #include <dev/i2c/i2cvar.h>
 
+#include <x86/pci/tco.h>
+
 #ifdef ICHIIC_DEBUG
 #define DPRINTF(x) printf x
 #else
@@ -75,6 +77,18 @@ struct ichsmb_softc {
 		bool         done;
 	}			sc_i2c_xfer;
 	device_t		sc_i2c_device;
+
+	bus_space_tag_t		sc_tcot;
+	bus_space_handle_t	sc_tcoh;
+	bus_size_t		sc_tcosz;
+	bus_space_tag_t		sc_sbregt;
+	bus_space_handle_t	sc_sbregh;
+	bus_size_t		sc_sbregsz;
+	bus_space_tag_t		sc_pmt;
+	bus_space_handle_t	sc_pmh;
+	bus_size_t		sc_pmsz;
+	bool			sc_tco_probed;
+	device_t		sc_tco_device;
 };
 
 static int	ichsmb_match(device_t, cfdata_t, void *);
@@ -83,6 +97,9 @@ static int	ichsmb_detach(device_t, int);
 static int	ichsmb_rescan(device_t, const char *, const int *);
 static void	ichsmb_chdet(device_t, device_t);
 
+static void	ichsmb_probe_tco(struct ichsmb_softc *,
+		    const struct pci_attach_args *);
+
 static int	ichsmb_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
 		    size_t, void *, size_t, int);
 
@@ -227,6 +244,12 @@ ichsmb_attach(device_t parent, device_t self, void *aux)
 	sc->sc_i2c_tag.ic_cookie = sc;
 	sc->sc_i2c_tag.ic_exec = ichsmb_i2c_exec;
 
+	/*
+	 * Probe to see if there's a TCO hanging here instead of the
+	 * LPCIB and map it if we can.
+	 */
+	ichsmb_probe_tco(sc, pa);
+
 	sc->sc_i2c_device = NULL;
 	ichsmb_rescan(self, NULL, NULL);
 
@@ -234,6 +257,187 @@ out:	if (!pmf_device_register(self, NULL, NULL))
 		aprint_error_dev(self, "couldn't establish power handler\n");
 }
 
+static void
+ichsmb_probe_tco(struct ichsmb_softc *sc, const struct pci_attach_args *pa)
+{
+	const device_t self = sc->sc_dev;
+	const pci_chipset_tag_t pc = sc->sc_pc;
+	const pcitag_t p2sb_tag = pci_make_tag(pc,
+	    /*bus*/0, /*dev*/0x1f, /*fn*/1);
+	const pcitag_t pmc_tag = pci_make_tag(pc,
+	    /*bus*/0, /*dev*/0x1f, /*fn*/2);
+	pcireg_t tcoctl, tcobase, p2sbc, sbreglo, sbreghi;
+	bus_addr_t sbreg, pmbase;
+	int error = EIO;
+
+	/*
+	 * Only attempt this on devices where we expect to find a TCO.
+	 */
+	switch (PCI_PRODUCT(pa->pa_id)) {
+	case PCI_PRODUCT_INTEL_100SERIES_LP_SMB:
+		break;
+	default:
+		goto fail;
+	}
+
+	/*
+	 * Verify the TCO base address register is enabled.
+	 */
+	tcoctl = pci_conf_read(pa->pa_pc, pa->pa_tag, SMB_TCOCTL);
+	aprint_debug_dev(self, "TCOCTL=0x%"PRIx32"\n", tcoctl);
+	if ((tcoctl & SMB_TCOCTL_TCO_BASE_EN) == 0) {
+		aprint_debug_dev(self, "TCO disabled\n");
+		goto fail;
+	}
+
+	/*
+	 * Verify the TCO base address register has the I/O space bit
+	 * set -- otherwise we don't know how to interpret the
+	 * register.
+	 */
+	tcobase = pci_conf_read(pa->pa_pc, pa->pa_tag, SMB_TCOBASE);
+	aprint_debug_dev(self, "TCOBASE=0x%"PRIx32"\n", tcobase);
+	if ((tcobase & SMB_TCOBASE_IOS) == 0) {
+		aprint_error_dev(self, "unrecognized TCO space\n");
+		goto fail;
+	}
+
+	/*
+	 * Map the TCO I/O space.
+	 */
+	sc->sc_tcot = sc->sc_iot;
+	error = bus_space_map(sc->sc_tcot, tcobase & SMB_TCOBASE_TCOBA,
+	    TCO_REGSIZE, 0, &sc->sc_tcoh);
+	if (error) {
+		aprint_error_dev(self, "failed to map TCO: %d\n", error);
+		goto fail;
+	}
+	sc->sc_tcosz = TCO_REGSIZE;
+
+	/*
+	 * Clear the Hide Device bit so we can map the SBREG_BAR from
+	 * the P2SB registers; then restore the Hide Device bit so
+	 * nobody else gets confused.
+	 *
+	 * XXX Hope nobody else is trying to touch the P2SB!
+	 *
+	 * XXX Should we have a way to lock PCI bus enumeration,
+	 * e.g. from concurrent drvctl rescan?
+	 *
+	 * XXX pci_mapreg_info doesn't work to get the size, somehow
+	 * comes out as 4.  Datasheet for 100-series chipset says the
+	 * size is 16 MB, unconditionally, and the type is memory.
+	 *
+	 * XXX The above XXX comment was probably a result of PEBCAK
+	 * when I tried to use 0xe4 instead of 0xe0 for P2SBC -- should
+	 * try again with pci_mapreg_info or pci_mapreg_map.
+	 */
+	p2sbc = pci_conf_read(pc, p2sb_tag, P2SB_P2SBC);
+	aprint_debug_dev(self, "P2SBC=0x%x\n", p2sbc);
+	pci_conf_write(pc, p2sb_tag, P2SB_P2SBC, p2sbc & ~P2SB_P2SBC_HIDE);
+	aprint_debug_dev(self, "P2SBC=0x%x -> 0x%x\n", p2sbc,
+	    pci_conf_read(pc, p2sb_tag, P2SB_P2SBC));
+	sbreglo = pci_conf_read(pc, p2sb_tag, P2SB_SBREG_BAR);
+	sbreghi = pci_conf_read(pc, p2sb_tag, P2SB_SBREG_BARH);
+	aprint_debug_dev(self, "SBREG_BAR=0x%08x 0x%08x\n", sbreglo, sbreghi);
+	pci_conf_write(sc->sc_pc, p2sb_tag, P2SB_P2SBC, p2sbc);
+
+	/*
+	 * Map the sideband registers so we can touch the NO_REBOOT
+	 * bit.
+	 */
+	sbreg = ((uint64_t)sbreghi << 32) | (sbreglo & ~__BITS(0,3));
+	if (((uint64_t)sbreg >> 32) != sbreghi) {
+		/* paranoia for 32-bit non-PAE */
+		aprint_error_dev(self, "can't map 64-bit SBREG\n");
+		goto fail;
+	}
+	sc->sc_sbregt = pa->pa_memt;
+	error = bus_space_map(sc->sc_sbregt, sbreg + SB_SMBUS_BASE,
+	    SB_SMBUS_SIZE, 0, &sc->sc_sbregh);
+	if (error) {
+		aprint_error_dev(self, "failed to map SMBUS sideband: %d\n",
+		    error);
+		goto fail;
+	}
+	sc->sc_sbregsz = SB_SMBUS_SIZE;
+
+	/*
+	 * Map the power management configuration controller's I/O
+	 * space.  Older manual call this PMBASE for power management;
+	 * newer manuals call it ABASE for ACPI.  The chapters
+	 * describing the registers say `power management' and I can't
+	 * find any connection to ACPI (I suppose ACPI firmware logic
+	 * probably peeks and pokes registers here?) so we say PMBASE
+	 * here.
+	 *
+	 * XXX Hope nobody else is trying to touch it!
+	 */
+	pmbase = pci_conf_read(pc, pmc_tag, LPCIB_PCI_PMBASE);
+	aprint_debug_dev(self, "PMBASE=0x%"PRIxBUSADDR"\n", pmbase);
+	if ((pmbase & 1) != 1) {	/* I/O space bit? */
+		aprint_error_dev(self, "unrecognized PMC space\n");
+		goto fail;
+	}
+	sc->sc_pmt = sc->sc_iot;
+	error = bus_space_map(sc->sc_pmt, PCI_MAPREG_IO_ADDR(pmbase),
+	    LPCIB_PCI_PM_SIZE, 0, &sc->sc_pmh);
+	if (error) {
+		aprint_error_dev(self, "failed to map PMC space: %d\n", error);
+		goto fail;
+	}
+	sc->sc_pmsz = LPCIB_PCI_PM_SIZE;
+
+	/* Success! */
+	sc->sc_tco_probed = true;
+	return;
+
+fail:	if (sc->sc_pmsz) {
+		bus_space_unmap(sc->sc_pmt, sc->sc_pmh, sc->sc_pmsz);
+		sc->sc_pmsz = 0;
+	}
+	if (sc->sc_sbregsz) {
+		bus_space_unmap(sc->sc_sbregt, sc->sc_sbregh, sc->sc_sbregsz);
+		sc->sc_sbregsz = 0;
+	}
+	if (sc->sc_tcosz) {
+		bus_space_unmap(sc->sc_tcot, sc->sc_tcoh, sc->sc_tcosz);
+		sc->sc_tcosz = 0;
+	}
+}
+
+static int
+ichsmb_tco_set_noreboot(device_t tco, bool noreboot)
+{
+	device_t self = device_parent(tco);
+	struct ichsmb_softc *sc = device_private(self);
+	uint32_t gc, gc1;
+
+	KASSERTMSG(tco == sc->sc_tco_device || sc->sc_tco_device == NULL,
+	    "tco=%p child=%p", tco, sc->sc_tco_device);
+	KASSERTMSG(device_is_a(self, "ichsmb"), "%s@%s",
+	    device_xname(tco), device_xname(self));
+
+	/*
+	 * Try to clear the No Reboot bit.
+	 */
+	gc = bus_space_read_4(sc->sc_sbregt, sc->sc_sbregh, SB_SMBUS_GC);
+	if (noreboot)
+		gc |= SB_SMBUS_GC_NR;
+	else
+		gc &= ~SB_SMBUS_GC_NR;
+	bus_space_write_4(sc->sc_sbregt, sc->sc_sbregh, SB_SMBUS_GC, gc);
+
+	/*
+	 * Check whether we could make it what we want.
+	 */
+	gc1 = bus_space_read_4(sc->sc_sbregt, sc->sc_sbregh, SB_SMBUS_GC);
+	aprint_debug_dev(self, "gc=0x%x -> 0x%x\n", gc, gc1);
+	if ((gc1 & SB_SMBUS_GC_NR) != (gc & SB_SMBUS_GC_NR))
+		return ENODEV;
+	return 0;
+}
+
 static int
 ichsmb_rescan(device_t self, const char *ifattr, const int *locators)
 {
@@ -245,7 +449,22 @@ ichsmb_rescan(device_t self, const char *ifattr, const int *locators)
 		memset(&iba, 0, sizeof(iba));
 		iba.iba_tag = &sc->sc_i2c_tag;
 		sc->sc_i2c_device = config_found(self, &iba, iicbus_print,
-		    CFARGS_NONE);
+		    CFARGS(.iattr = "i2cbus"));
+	}
+	if (sc->sc_tco_probed &&
+	    ifattr_match(ifattr, "tcoichbus") &&
+	    sc->sc_tco_device == NULL) {
+		struct tco_attach_args ta;
+
+		memset(&ta, 0, sizeof(ta));
+		ta.ta_version = TCO_VERSION_SMBUS;
+		ta.ta_pmt = sc->sc_pmt;
+		ta.ta_pmh = sc->sc_pmh;
+		ta.ta_tcot = sc->sc_tcot;
+		ta.ta_tcoh = sc->sc_tcoh;
+		ta.ta_set_noreboot = &ichsmb_tco_set_noreboot;
+		sc->sc_tco_device = config_found(self, &ta, NULL,
+		    CFARGS(.iattr = "tcoichbus"));
 	}
 
 	return 0;
@@ -273,6 +492,12 @@ ichsmb_detach(device_t self, int flags)
 		sc->sc_pihp = NULL;
 	}
 
+	if (sc->sc_pmsz != 0)
+		bus_space_unmap(sc->sc_pmt, sc->sc_pmh, sc->sc_pmsz);
+	if (sc->sc_sbregsz != 0)
+		bus_space_unmap(sc->sc_sbregt, sc->sc_sbregh, sc->sc_sbregsz);
+	if (sc->sc_tcosz != 0)
+		bus_space_unmap(sc->sc_tcot, sc->sc_tcoh, sc->sc_tcosz);
 	if (sc->sc_size != 0)
 		bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size);
 
@@ -289,6 +514,8 @@ ichsmb_chdet(device_t self, device_t child)
 
 	if (sc->sc_i2c_device == child)
 		sc->sc_i2c_device = NULL;
+	if (sc->sc_tco_device == child)
+		sc->sc_tco_device = NULL;
 }
 
 static int
#include <sys/mman.h>
#include <sys/types.h>

#include <machine/sysarch.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

static inline uint8_t
inb(uint16_t port)
{
	uint8_t x;
	asm volatile("inb %%dx,%%al"
	    : /*outputs*/"=a"(x)
	    : /*inputs*/"d"(port));
	return x;
}

static inline uint16_t
inw(uint16_t port)
{
	uint16_t x;
	asm volatile("inw %%dx,%%ax"
	    : /*outputs*/"=a"(x)
	    : /*inputs*/"d"(port));
	return x;
}

static inline uint32_t
inl(uint16_t port)
{
	uint32_t x;
	asm volatile("inl %%dx,%%eax"
	    : /*outputs*/"=a"(x)
	    : /*inputs*/"d"(port));
	return x;
}

int
main(void)
{
	const int pagesize = sysconf(_SC_PAGESIZE);
	uint32_t pmc[64];
	struct {
		uint16_t rld;
		uint8_t tdi;
		uint8_t tdo;
		uint16_t tsts1;
		uint16_t tsts2;
		uint16_t tctl1;
		uint16_t tctl2;
		uint16_t tmsg;
		uint8_t twds;
		uint8_t le;
		uint16_t ttmr;
	} tco;
	int fd;
	uint32_t *smbus;
	unsigned i;

	/*
	 * PMBASE, I/O address:
	 *
	 *	pcictl pci0 read -b 0 -d 31 -f 2 0x40
	 *
	 * Low bit should be 1 to indicate I/O space; clear it.
	 */
	const uint16_t pmbase = 0x1800;

	/*
	 * TCOBASE, I/O address:
	 *
	 *	pcictl pci0 read -b 0 -d 31 -f 4 0x50
	 *
	 * Low bit should be 1 to indicate I/O space; clear it.
	 */
	const uint16_t tcobase = 0x400;

	/*
	 * SBREG_BAR -- must first unhide P2SB (d31 f1) to get it:
	 *
	 *	pcictl pci0 write -b 0 -d 31 -f 1 0xe0 0xfffffeff
	 *	pcictl pci0 read -b 0 -d 31 -f 1 0x10
	 *	pcictl pci0 write -b 0 -d 31 -f 1 0xe0 0xffffffff
	 *
	 * Low nybble should be 4 to indicate memory space; clear it.
	 */
	const uint64_t sbreg_bar = 0xfd000000;

	/* from chipset docs */
	const uint64_t smb_portid = 0xc6;

	if (x86_64_iopl(1) == -1)
		err(1, "x86_64_iopl(1)");
	for (i = 0; i < 64; i++)
		pmc[i] = inl(pmbase + 4*i);
	tco.rld = inw(tcobase + 0x0);
	tco.tdi = inb(tcobase + 0x2);
	tco.tdo = inb(tcobase + 0x3);
	tco.tsts1 = inw(tcobase + 0x4);
	tco.tsts2 = inw(tcobase + 0x6);
	tco.tctl1 = inw(tcobase + 0x8);
	tco.tctl2 = inw(tcobase + 0xA);
	tco.tmsg = inw(tcobase + 0xC);
	tco.twds = inb(tcobase + 0xE);
	tco.le = inb(tcobase + 0x10);
	tco.ttmr = inw(tcobase + 0x12);
	if (x86_64_iopl(0) == -1)
		err(1, "x86_64_iopl(0)");

	for (i = 0; i < 64; i++)
		printf("PMC %02x: %08x\n", 4*i, pmc[i]);

	printf("TCO RLD   [00h]: 0x%04x\n", tco.rld);
	printf("TCO TDI   [02h]: 0x%02x\n", tco.tdi);
	printf("TCO TDO   [03h]: 0x%02x\n", tco.tdo);
	printf("TCO TSTS1 [04h]: 0x%04x\n", tco.tsts1);
	printf("TCO TSTS2 [06h]: 0x%04x\n", tco.tsts2);
	printf("TCO TCTL1 [08h]: 0x%04x\n", tco.tctl1);
	printf("TCO TCTL2 [0Ah]: 0x%04x\n", tco.tctl2);
	printf("TCO TMSG  [0Ch]: 0x%04x\n", tco.tmsg);
	printf("TCO TWDS  [0Eh]: 0x%02x\n", tco.twds);
	printf("TCO LE    [10h]: 0x%02x\n", tco.le);
	printf("TCO TTMR  [12h]: 0x%04x\n", tco.ttmr);

	if ((fd = open("/dev/mem", O_RDONLY)) == -1)
		err(1, "open /dev/mem");
	smbus = mmap(NULL, pagesize, PROT_READ, MAP_PRIVATE, fd,
	    (off_t)sbreg_bar + ((off_t)smb_portid << 16));
	if (smbus == MAP_FAILED)
		err(1, "mmap SMBus I/O");
	printf("SMBus TCOCFG [00h]: 0x%08x\n", smbus[0x00/4]);
	printf("SMBus ?      [04h]: 0x%08x\n", smbus[0x04/4]);
	printf("SMBus ?      [08h]: 0x%08x\n", smbus[0x08/4]);
	printf("SMBus GC     [0ch]: 0x%08x\n", smbus[0x0c/4]);
	printf("SMBus PCE    [10h]: 0x%08x\n", smbus[0x10/4]);
	if (munmap(smbus, pagesize) == -1)
		warn("munmap");
	if (close(fd) == -1)
		warn("close");

	return 0;
}


Home | Main Index | Thread Index | Old Index