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:
- 9424693035
- 2a7a0e9bf7

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:
a7ae81952c

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
This commit is contained in:
Andriy Gapon 2018-10-22 14:44:44 +00:00
parent a464523e6e
commit da4e7cad13
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=339591
2 changed files with 153 additions and 16 deletions

View File

@ -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_device **id_p)
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,7 +698,59 @@ 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;
@ -642,13 +758,11 @@ ichwd_attach(device_t dev)
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);

View File

@ -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
@ -291,6 +292,14 @@ struct ichwd_softc {
#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 */
#define SMI_LEN 0x08