Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 22 Oct 2018 14:44:44 +0000 (UTC)
From:      Andriy Gapon <avg@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r339591 - head/sys/dev/ichwd
Message-ID:  <201810221444.w9MEiiaQ018169@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: avg
Date: Mon Oct 22 14:44:44 2018
New Revision: 339591
URL: https://svnweb.freebsd.org/changeset/base/339591

Log:
  ichwd: add support for TCO watchdog timer in Lewisburg PCH (C620)
  
  The change is based on public documents listed below as well as Linux
  changes and the code developed by Kostik.
  
  The documents:
  - Intel® C620 Series Chipset Platform Controller Hub Datasheet
  - Intel® 100 Series and Intel® C230 Series Chipset Family Platform
    Controller Hub (PCH) Datasheet - Volume 2 of 2
  
  Interesting Linux commits:
  - https://github.com/torvalds/linux/commit/9424693035a57961a8eb09e96aab315a7096535d
  - https://github.com/torvalds/linux/commit/2a7a0e9bf7b32e838d873226808ab8a6c00148f7
  
  The peculiarity of the new chipsets is that the watchdog resources are
  configured in PCI registers of SMBus controller and Power Management
  function as opposed to the LPC bridge.  I took a simplistic approach of
  querying the resources from the respective PCI devices.  ichwd is still
  a device on isa bus.  The PCI devices are found by their slot and
  function defined in the datasheets as siblings of the upstream LPC
  bridge.
  
  There are some shortcuts and missing features.
  
  First of all, I have not implemented the functionality required to clear
  the no-reboot bit.  That would require writing to a special PCI
  configuration register of a hidden / invisible PCI device after which
  the device would start responding to accesses to other registers.  The
  no-reboot bit was not set on my test hardware, so I decided to leave its
  handling for the later time.
  
  Also, I did not try to handle the case where the watchdog resources are
  not configured by the hardware as well as the case where ACPI defined
  operational region conflicts with the watchdog resources.  My test
  system did not have either of those problem, so, again, I decided to
  leave those cases until later.
  See this Linux commit for some details of the ACPI problem:
  https://github.com/torvalds/linux/commit/a7ae81952cdab56a1277bd2f9ed7284c0f575120
  
  Finally, I have added only the PCI ID found on my test system.  I think
  that more IDs can be added as the change gets tested.
  
  Tested on Dell PowerEdge R740.
  
  PR:		222079
  Reviewed by:	mav, kib
  MFC after:	3 weeks
  Relnotes:	maybe
  Sponsored by:	Panzura
  Differential Revision: https://reviews.freebsd.org/D17585

Modified:
  head/sys/dev/ichwd/ichwd.c
  head/sys/dev/ichwd/ichwd.h

Modified: head/sys/dev/ichwd/ichwd.c
==============================================================================
--- head/sys/dev/ichwd/ichwd.c	Mon Oct 22 14:01:34 2018	(r339590)
+++ head/sys/dev/ichwd/ichwd.c	Mon Oct 22 14:44:44 2018	(r339591)
@@ -289,6 +289,11 @@ static struct ichwd_device ichwd_devices[] = {
 	{ 0, NULL, 0, 0 },
 };
 
+static struct ichwd_device ichwd_smb_devices[] = {
+	{ DEVICEID_LEWISBURG_SMB, "Lewisburg watchdog timer",		10, 4 },
+	{ 0, NULL, 0, 0 },
+};
+
 static devclass_t ichwd_devclass;
 
 #define ichwd_read_tco_1(sc, off) \
@@ -374,7 +379,8 @@ ichwd_sts_reset(struct ichwd_softc *sc)
 	 * be done in two separate operations.
 	 */
 	ichwd_write_tco_2(sc, TCO2_STS, TCO_SECOND_TO_STS);
-	ichwd_write_tco_2(sc, TCO2_STS, TCO_BOOT_STS);
+	if (sc->tco_version < 4)
+		ichwd_write_tco_2(sc, TCO2_STS, TCO_BOOT_STS);
 }
 
 /*
@@ -488,6 +494,11 @@ ichwd_clear_noreboot(struct ichwd_softc *sc)
 		if (status & ICH_PMC_NO_REBOOT)
 			rc = EIO;
 		break;
+	case 4:
+		/*
+		 * TODO.  This needs access to a hidden PCI device at 31:1.
+		 */
+		break;
 	default:
 		ichwd_verbose_printf(sc->device,
 		    "Unknown TCO Version: %d, can't set NO_REBOOT.\n",
@@ -560,6 +571,36 @@ ichwd_find_ich_lpc_bridge(device_t isa, struct ichwd_d
 	return (NULL);
 }
 
+static device_t
+ichwd_find_smb_dev(device_t isa, struct ichwd_device **id_p)
+{
+	struct ichwd_device *id;
+	device_t isab, smb;
+	uint16_t devid;
+
+	/*
+	 * Check if SMBus controller provides TCO configuration.
+	 * The controller's device and function are fixed and we expect
+	 * it to be on the same bus as ISA bridge.
+	 */
+	isab = device_get_parent(isa);
+	smb = pci_find_dbsf(pci_get_domain(isab), pci_get_bus(isab), 31, 4);
+	if (smb == NULL)
+		return (NULL);
+	if (pci_get_vendor(smb) != VENDORID_INTEL)
+		return (NULL);
+	devid = pci_get_device(smb);
+	for (id = ichwd_smb_devices; id->desc != NULL; ++id) {
+		if (devid == id->device) {
+			if (id_p != NULL)
+				*id_p = id;
+			return (smb);
+		}
+	}
+
+	return (NULL);
+}
+
 /*
  * Look for an ICH LPC interface bridge.  If one is found, register an
  * ichwd device.  There can be only one.
@@ -568,14 +609,18 @@ static void
 ichwd_identify(driver_t *driver, device_t parent)
 {
 	struct ichwd_device *id_p;
-	device_t ich = NULL;
+	device_t ich, smb;
 	device_t dev;
 	uint32_t base_address;
+	uint32_t ctl;
 	int rc;
 
 	ich = ichwd_find_ich_lpc_bridge(parent, &id_p);
-	if (ich == NULL)
-		return;
+	if (ich == NULL) {
+		smb = ichwd_find_smb_dev(parent, &id_p);
+		if (smb == NULL)
+			return;
+	}
 
 	/* good, add child to bus */
 	if ((dev = device_find_child(parent, driver->name, 0)) == NULL)
@@ -609,6 +654,24 @@ ichwd_identify(driver_t *driver, device_t parent)
 			    "Can not set TCO v%d memory resource for PBASE\n",
 			    id_p->tco_version);
 		break;
+	case 4:
+		/* Get TCO base address. */
+		ctl = pci_read_config(smb, ICH_TCOCTL, 4);
+		if ((ctl & ICH_TCOCTL_TCO_BASE_EN) == 0) {
+			ichwd_verbose_printf(dev,
+			    "TCO v%d decoding is not enabled\n",
+			    id_p->tco_version);
+			break;
+		}
+		base_address = pci_read_config(smb, ICH_TCOBASE, 4);
+		rc = bus_set_resource(dev, SYS_RES_IOPORT, 0,
+		    base_address & ICH_TCOBASE_ADDRMASK, ICH_TCOBASE_SIZE);
+		if (rc != 0) {
+			ichwd_verbose_printf(dev,
+			    "Can not set TCO v%d I/O resource (err = %d)\n",
+			    id_p->tco_version, rc);
+		}
+		break;
 	default:
 		ichwd_verbose_printf(dev,
 		    "Can not set unknown TCO v%d memory resource for unknown base address\n",
@@ -626,7 +689,8 @@ ichwd_probe(device_t dev)
 	if (isa_get_logicalid(dev) != 0)
 		return (ENXIO);
 
-	if (ichwd_find_ich_lpc_bridge(device_get_parent(dev), &id_p) == NULL)
+	if (ichwd_find_ich_lpc_bridge(device_get_parent(dev), &id_p) == NULL &&
+	    ichwd_find_smb_dev(device_get_parent(dev), &id_p) == NULL)
 		return (ENXIO);
 
 	device_set_desc_copy(dev, id_p->desc);
@@ -634,21 +698,71 @@ ichwd_probe(device_t dev)
 }
 
 static int
-ichwd_attach(device_t dev)
+ichwd_smb_attach(device_t dev)
 {
 	struct ichwd_softc *sc;
 	struct ichwd_device *id_p;
+	device_t isab, pmdev;
+	device_t smb;
+	uint32_t acpi_base;
+
+	sc = device_get_softc(dev);
+	smb = ichwd_find_smb_dev(device_get_parent(dev), &id_p);
+	if (smb == NULL)
+		return (ENXIO);
+
+	sc->ich_version = id_p->ich_version;
+	sc->tco_version = id_p->tco_version;
+
+	/* Allocate TCO control I/O register space. */
+	sc->tco_rid = 0;
+	sc->tco_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->tco_rid,
+	    RF_ACTIVE | RF_SHAREABLE);
+	if (sc->tco_res == NULL) {
+		device_printf(dev, "unable to reserve TCO registers\n");
+		return (ENXIO);
+	}
+
+	/* Get ACPI base address. */
+	isab = device_get_parent(device_get_parent(dev));
+	pmdev = pci_find_dbsf(pci_get_domain(isab), pci_get_bus(isab), 31, 2);
+	if (pmdev == NULL) {
+		device_printf(dev, "unable to find Power Management device\n");
+		return (ENXIO);
+	}
+	acpi_base = pci_read_config(pmdev, ICH_PMBASE, 4) & 0xffffff00;
+	if (acpi_base == 0) {
+		device_printf(dev, "ACPI base address is not set\n");
+		return (ENXIO);
+	}
+
+	/* Allocate SMI control I/O register space. */
+	sc->smi_rid = 1;
+	sc->smi_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->smi_rid,
+	    acpi_base + SMI_BASE, acpi_base + SMI_BASE + SMI_LEN - 1, SMI_LEN,
+	    RF_ACTIVE | RF_SHAREABLE);
+	if (sc->smi_res == NULL) {
+		device_printf(dev, "unable to reserve SMI registers\n");
+		return (ENXIO);
+	}
+
+	return (0);
+}
+
+static int
+ichwd_lpc_attach(device_t dev)
+{
+	struct ichwd_softc *sc;
+	struct ichwd_device *id_p;
 	device_t ich;
 	unsigned int pmbase = 0;
 
 	sc = device_get_softc(dev);
-	sc->device = dev;
 
 	ich = ichwd_find_ich_lpc_bridge(device_get_parent(dev), &id_p);
-	if (ich == NULL) {
-		device_printf(sc->device, "Can not find ICH device.\n");
-		goto fail;
-	}
+	if (ich == NULL)
+		return (ENXIO);
+
 	sc->ich = ich;
 	sc->ich_version = id_p->ich_version;
 	sc->tco_version = id_p->tco_version;
@@ -657,7 +771,7 @@ ichwd_attach(device_t dev)
 	pmbase = pci_read_config(ich, ICH_PMBASE, 2) & ICH_PMBASE_MASK;
 	if (pmbase == 0) {
 		device_printf(dev, "ICH PMBASE register is empty\n");
-		goto fail;
+		return (ENXIO);
 	}
 
 	/* allocate I/O register space */
@@ -667,7 +781,7 @@ ichwd_attach(device_t dev)
 	    RF_ACTIVE | RF_SHAREABLE);
 	if (sc->smi_res == NULL) {
 		device_printf(dev, "unable to reserve SMI registers\n");
-		goto fail;
+		return (ENXIO);
 	}
 
 	sc->tco_rid = 1;
@@ -676,7 +790,7 @@ ichwd_attach(device_t dev)
 	    RF_ACTIVE | RF_SHAREABLE);
 	if (sc->tco_res == NULL) {
 		device_printf(dev, "unable to reserve TCO registers\n");
-		goto fail;
+		return (ENXIO);
 	}
 
 	sc->gcs_rid = 0;
@@ -685,10 +799,24 @@ ichwd_attach(device_t dev)
 		    &sc->gcs_rid, RF_ACTIVE|RF_SHAREABLE);
 		if (sc->gcs_res == NULL) {
 			device_printf(dev, "unable to reserve GCS registers\n");
-			goto fail;
+			return (ENXIO);
 		}
 	}
 
+	return (0);
+}
+
+static int
+ichwd_attach(device_t dev)
+{
+	struct ichwd_softc *sc;
+
+	sc = device_get_softc(dev);
+	sc->device = dev;
+
+	if (ichwd_lpc_attach(dev) != 0 && ichwd_smb_attach(dev) != 0)
+		goto fail;
+
 	if (ichwd_clear_noreboot(sc) != 0)
 		goto fail;
 
@@ -724,7 +852,7 @@ ichwd_attach(device_t dev)
 		bus_release_resource(dev, SYS_RES_IOPORT,
 		    sc->smi_rid, sc->smi_res);
 	if (sc->gcs_res != NULL)
-		bus_release_resource(ich, SYS_RES_MEMORY,
+		bus_release_resource(sc->ich, SYS_RES_MEMORY,
 		    sc->gcs_rid, sc->gcs_res);
 
 	return (ENXIO);

Modified: head/sys/dev/ichwd/ichwd.h
==============================================================================
--- head/sys/dev/ichwd/ichwd.h	Mon Oct 22 14:01:34 2018	(r339590)
+++ head/sys/dev/ichwd/ichwd.h	Mon Oct 22 14:44:44 2018	(r339591)
@@ -272,6 +272,7 @@ struct ichwd_softc {
 #define	DEVICEID_WCPT_LP6	0x9cc6
 #define	DEVICEID_WCPT_LP7	0x9cc7
 #define	DEVICEID_WCPT_LP9	0x9cc9
+#define	DEVICEID_LEWISBURG_SMB	0xa1a3
 
 /* ICH LPC Interface Bridge Registers (ICH5 and older) */
 #define	ICH_GEN_STA		0xd4
@@ -290,6 +291,14 @@ struct ichwd_softc {
 #define	ICH_PMC_OFFSET		0x08
 #define	ICH_PMC_SIZE		0x4
 #define	ICH_PMC_NO_REBOOT	0x10
+
+/* Lewisburg configration registers in SMBus controller. */
+#define	ICH_TCOBASE			0x50    /* TCO Base Addr */
+#define	ICH_TCOBASE_ADDRMASK		0xffe0
+#define	ICH_TCOBASE_SIZE		32
+#define	ICH_TCOCTL			0x54    /* TCO Control */
+#define	ICH_TCOCTL_TCO_BASE_EN		0x0100  /* TCO Base decoding enabled */
+#define	ICH_TCOCTL_TCO_BASE_LOCK	0x0001  /* TCOBASE is locked */
 
 /* register names and locations (relative to PMBASE) */
 #define	SMI_BASE		0x30 /* base address for SMI registers */



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201810221444.w9MEiiaQ018169>