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