#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/bitops.h>
#include <sys/sensors.h>
#include <bus/pci/pcivar.h>
#include <bus/pci/pcireg.h>
#include <bus/pci/pci_cfgreg.h>
#include <vm/pmap.h>
#include "coremctl_if.h"
#include "pcib_if.h"
#include <dev/misc/dimm/dimm.h>
#include <dev/misc/coremctl/coremctl_reg.h>
#define ECC_E3_VER_1 1
#define ECC_E3_VER_2 2
#define ECC_E3_VER_3 3
#define ECC_E3_THRESH_DEFAULT 5
#define ECC_E3_CHAN_MAX 2
#define ECC_E3_CHAN_DIMM_MAX 2
#define ECC_E3_DIMM_RANK_MAX 2
#define ECC_E3_CHAN_RANK_MAX (ECC_E3_CHAN_DIMM_MAX * ECC_E3_DIMM_RANK_MAX)
struct ecc_e3_type {
uint16_t did;
const char *desc;
int ver;
};
struct ecc_e3_dimm {
TAILQ_ENTRY(ecc_e3_dimm) dimm_link;
struct dimm_softc *dimm_softc;
struct ksensor dimm_sensor;
};
struct ecc_e3_rank {
struct ecc_e3_dimm *rank_dimm_sc;
};
struct ecc_e3_chan {
int chan_id;
int chan_errlog0;
int chan_rank_cnt;
struct ecc_e3_rank chan_rank[ECC_E3_CHAN_RANK_MAX];
};
struct ecc_e3_softc {
device_t ecc_dev;
device_t ecc_parent;
int ecc_ver;
uint32_t ecc_flags;
struct ecc_e3_chan ecc_chan[ECC_E3_CHAN_MAX];
TAILQ_HEAD(, ecc_e3_dimm) ecc_dimm;
struct ksensordev ecc_sensdev;
struct ksensor ecc_sens;
int ecc_count;
int ecc_thresh;
};
#define ECC_E3_FLAG_SENSTASK 0x1
#define ECC_E3_FLAG_CRIT 0x2
#define ecc_printf(sc, fmt, arg...) \
device_printf((sc)->ecc_dev, fmt , ##arg)
static int ecc_e3_probe(device_t);
static int ecc_e3_attach(device_t);
static int ecc_e3_detach(device_t);
static void ecc_e3_shutdown(device_t);
static void ecc_e3_attach_ch(struct ecc_e3_softc *, struct ecc_e3_chan *,
int, uint32_t, int);
static void ecc_e3_errlog(struct ecc_e3_softc *, boolean_t);
static void ecc_e3_errlog_ch(struct ecc_e3_softc *, struct ecc_e3_chan *,
boolean_t);
static void ecc_e3_stop(struct ecc_e3_softc *);
static void ecc_e3_sensor_task(void *);
static void ecc_e3_sensor_update(struct ecc_e3_softc *, boolean_t);
static const struct ecc_e3_type ecc_e3_types[] = {
{ PCI_E3V1_MEMCTL_DID, "Intel E3 ECC", ECC_E3_VER_1 },
{ PCI_E3V2_MEMCTL_DID, "Intel E3 v2 ECC", ECC_E3_VER_2 },
{ PCI_E3V3_MEMCTL_DID, "Intel E3 v3 ECC", ECC_E3_VER_3 },
{ 0, NULL, 0 }
};
static device_method_t ecc_e3_methods[] = {
DEVMETHOD(device_probe, ecc_e3_probe),
DEVMETHOD(device_attach, ecc_e3_attach),
DEVMETHOD(device_detach, ecc_e3_detach),
DEVMETHOD(device_shutdown, ecc_e3_shutdown),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD_END
};
static driver_t ecc_e3_driver = {
"ecc",
ecc_e3_methods,
sizeof(struct ecc_e3_softc)
};
static devclass_t ecc_devclass;
DRIVER_MODULE(ecc_e3, coremctl, ecc_e3_driver, ecc_devclass, NULL, NULL);
MODULE_DEPEND(ecc_e3, pci, 1, 1, 1);
MODULE_DEPEND(ecc_e3, coremctl, 1, 1, 1);
MODULE_VERSION(ecc_e3, 1);
static __inline uint32_t
CSR_READ_4(struct ecc_e3_softc *sc, int ofs)
{
uint32_t val;
int error;
error = COREMCTL_MCH_READ(sc->ecc_parent, ofs, &val);
KASSERT(!error, ("mch read failed"));
return val;
}
static int
ecc_e3_probe(device_t dev)
{
const struct ecc_e3_type *t;
uint16_t did;
if (pci_get_vendor(dev) != PCI_CORE_MEMCTL_VID)
return ENXIO;
did = pci_get_device(dev);
for (t = ecc_e3_types; t->desc != NULL; ++t) {
if (t->did == did) {
struct ecc_e3_softc *sc = device_get_softc(dev);
device_set_desc(dev, t->desc);
sc->ecc_ver = t->ver;
return 0;
}
}
return ENXIO;
}
static int
ecc_e3_attach(device_t dev)
{
struct ecc_e3_softc *sc = device_get_softc(dev);
uint32_t val;
int error;
TAILQ_INIT(&sc->ecc_dimm);
sc->ecc_dev = dev;
error = COREMCTL_MCH_READ(device_get_parent(dev), MCH_CORE_DIMM_CH0,
&val);
if (!error)
sc->ecc_parent = device_get_parent(dev);
if (sc->ecc_parent != NULL) {
uint32_t dimm_ch0, dimm_ch1;
int ecc_active;
if (bootverbose) {
ecc_printf(sc, "LOG0_C0 %#x\n",
CSR_READ_4(sc, MCH_E3_ERRLOG0_C0));
ecc_printf(sc, "LOG0_C1 %#x\n",
CSR_READ_4(sc, MCH_E3_ERRLOG0_C1));
}
dimm_ch0 = CSR_READ_4(sc, MCH_CORE_DIMM_CH0);
dimm_ch1 = CSR_READ_4(sc, MCH_CORE_DIMM_CH1);
ecc_e3_attach_ch(sc, &sc->ecc_chan[0], 0, dimm_ch0,
MCH_E3_ERRLOG0_C0);
ecc_e3_attach_ch(sc, &sc->ecc_chan[1], 1, dimm_ch1,
MCH_E3_ERRLOG0_C1);
ecc_active = 1;
if (sc->ecc_ver == ECC_E3_VER_1 ||
sc->ecc_ver == ECC_E3_VER_2) {
if (((dimm_ch0 | dimm_ch1) & MCH_E3_DIMM_ECC) ==
MCH_E3_DIMM_ECC_NONE) {
ecc_active = 0;
ecc_printf(sc, "No ECC active\n");
}
} else {
uint32_t ecc_mode0, ecc_mode1;
ecc_mode0 = __SHIFTOUT(dimm_ch0, MCH_E3_DIMM_ECC);
ecc_mode1 = __SHIFTOUT(dimm_ch1, MCH_E3_DIMM_ECC);
if (ecc_mode0 != MCH_E3_DIMM_ECC_NONE &&
ecc_mode0 != MCH_E3_DIMM_ECC_ALL) {
ecc_active = 0;
ecc_printf(sc, "channel0, invalid ECC "
"active 0x%x\n", ecc_mode0);
}
if (ecc_mode1 != MCH_E3_DIMM_ECC_NONE &&
ecc_mode1 != MCH_E3_DIMM_ECC_ALL) {
ecc_active = 0;
ecc_printf(sc, "channel1, invalid ECC "
"active 0x%x\n", ecc_mode1);
}
if (ecc_mode0 == MCH_E3_DIMM_ECC_NONE &&
ecc_mode1 == MCH_E3_DIMM_ECC_NONE) {
ecc_active = 0;
ecc_printf(sc, "No ECC active\n");
}
}
if (!ecc_active)
return 0;
} else {
ecc_printf(sc, "MCHBAR is not enabled\n");
strlcpy(sc->ecc_sensdev.xname, device_get_nameunit(dev),
sizeof(sc->ecc_sensdev.xname));
strlcpy(sc->ecc_sens.desc, "node0 ecc",
sizeof(sc->ecc_sens.desc));
sc->ecc_sens.type = SENSOR_ECC;
sensor_set(&sc->ecc_sens, 0, SENSOR_S_OK);
sensor_attach(&sc->ecc_sensdev, &sc->ecc_sens);
sensordev_install(&sc->ecc_sensdev);
sc->ecc_thresh = ECC_E3_THRESH_DEFAULT;
SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
OID_AUTO, "thresh", CTLFLAG_RW, &sc->ecc_thresh, 0,
"Raise alarm once number of ECC errors "
"goes above this value");
}
sc->ecc_flags |= ECC_E3_FLAG_SENSTASK;
sensor_task_register(sc, ecc_e3_sensor_task, 1);
return 0;
}
static void
ecc_e3_sensor_task(void *xsc)
{
struct ecc_e3_softc *sc = xsc;
device_t dev = sc->ecc_dev;
uint16_t errsts;
errsts = pci_read_config(dev, PCI_E3_ERRSTS, 2);
if (errsts & (PCI_E3_ERRSTS_DSERR | PCI_E3_ERRSTS_DMERR)) {
boolean_t crit = FALSE;
if (errsts & PCI_E3_ERRSTS_DMERR)
crit = TRUE;
if (sc->ecc_parent != NULL)
ecc_e3_errlog(sc, crit);
else
ecc_e3_sensor_update(sc, crit);
pci_write_config(dev, PCI_E3_ERRSTS, errsts, 2);
}
}
static void
ecc_e3_attach_ch(struct ecc_e3_softc *sc, struct ecc_e3_chan *chan,
int chanid, uint32_t dimm_ch, int errlog0)
{
int dimm_size[ECC_E3_CHAN_DIMM_MAX];
uint32_t dimm_szmask[ECC_E3_CHAN_DIMM_MAX];
uint32_t dimm_dlrank[ECC_E3_CHAN_DIMM_MAX];
int rank, dimm;
dimm_szmask[0] = MCH_CORE_DIMM_A_SIZE;
dimm_dlrank[0] = MCH_CORE_DIMM_A_DUAL_RANK;
dimm_szmask[1] = MCH_CORE_DIMM_B_SIZE;
dimm_dlrank[1] = MCH_CORE_DIMM_B_DUAL_RANK;
if (dimm_ch & MCH_CORE_DIMM_A_SELECT) {
dimm_szmask[0] = MCH_CORE_DIMM_B_SIZE;
dimm_dlrank[0] = MCH_CORE_DIMM_B_DUAL_RANK;
dimm_szmask[1] = MCH_CORE_DIMM_A_SIZE;
dimm_dlrank[1] = MCH_CORE_DIMM_A_DUAL_RANK;
}
dimm_size[0] = __SHIFTOUT(dimm_ch, dimm_szmask[0]);
dimm_size[1] = __SHIFTOUT(dimm_ch, dimm_szmask[1]);
if (dimm_size[0] == 0 && dimm_size[1] == 0)
return;
if (bootverbose) {
int ecc;
ecc = __SHIFTOUT(dimm_ch, MCH_E3_DIMM_ECC);
if (ecc == MCH_E3_DIMM_ECC_NONE) {
ecc_printf(sc, "channel%d, no ECC active\n", chanid);
} else if (ecc == MCH_E3_DIMM_ECC_ALL) {
ecc_printf(sc, "channel%d, ECC active IO/logic\n",
chanid);
} else {
if (sc->ecc_ver == ECC_E3_VER_1 ||
sc->ecc_ver == ECC_E3_VER_2) {
if (ecc == MCH_E3_DIMM_ECC_IO) {
ecc_printf(sc, "channel%d, "
"ECC active IO\n", chanid);
} else {
ecc_printf(sc, "channel%d, "
"ECC active logic\n", chanid);
}
} else {
ecc_printf(sc, "channel%d, "
"invalid ECC active 0x%x\n", chanid, ecc);
}
}
}
chan->chan_id = chanid;
chan->chan_errlog0 = errlog0;
rank = 0;
for (dimm = 0; dimm < ECC_E3_CHAN_DIMM_MAX; ++dimm) {
struct ecc_e3_dimm *dimm_sc;
struct ecc_e3_rank *rk;
struct ksensor *sens;
if (dimm_size[dimm] == 0)
continue;
dimm_sc = kmalloc(sizeof(*dimm_sc), M_DEVBUF,
M_WAITOK | M_ZERO);
dimm_sc->dimm_softc = dimm_create(0, chanid, dimm);
sens = &dimm_sc->dimm_sensor;
ksnprintf(sens->desc, sizeof(sens->desc),
"node0 chan%d DIMM%d ecc", chanid, dimm);
sens->type = SENSOR_ECC;
sensor_set(sens, 0, SENSOR_S_OK);
dimm_sensor_attach(dimm_sc->dimm_softc, sens);
TAILQ_INSERT_TAIL(&sc->ecc_dimm, dimm_sc, dimm_link);
KKASSERT(rank < ECC_E3_CHAN_RANK_MAX - 1);
rk = &chan->chan_rank[rank];
rank++;
rk->rank_dimm_sc = dimm_sc;
if (dimm_ch & dimm_dlrank[dimm]) {
rk = &chan->chan_rank[rank];
rank++;
rk->rank_dimm_sc = dimm_sc;
}
}
chan->chan_rank_cnt = rank;
}
static void
ecc_e3_errlog(struct ecc_e3_softc *sc, boolean_t crit)
{
int i;
for (i = 0; i < ECC_E3_CHAN_MAX; ++i) {
struct ecc_e3_chan *chan = &sc->ecc_chan[i];
if (chan->chan_errlog0 != 0)
ecc_e3_errlog_ch(sc, chan, crit);
}
}
static void
ecc_e3_errlog_ch(struct ecc_e3_softc *sc, struct ecc_e3_chan *chan,
boolean_t crit)
{
uint32_t err0;
int rank;
err0 = CSR_READ_4(sc, chan->chan_errlog0);
if ((err0 & (MCH_E3_ERRLOG0_CERRSTS | MCH_E3_ERRLOG0_MERRSTS)) == 0)
return;
rank = __SHIFTOUT(err0, MCH_E3_ERRLOG0_ERRRANK);
if (rank >= chan->chan_rank_cnt) {
ecc_printf(sc, "channel%d rank%d %serror\n", chan->chan_id,
rank, crit ? "critical " : "");
} else {
struct ecc_e3_dimm *dimm_sc;
dimm_sc = chan->chan_rank[rank].rank_dimm_sc;
dimm_sensor_ecc_add(dimm_sc->dimm_softc, &dimm_sc->dimm_sensor,
1, crit);
}
}
static int
ecc_e3_detach(device_t dev)
{
struct ecc_e3_softc *sc = device_get_softc(dev);
ecc_e3_stop(sc);
if (sc->ecc_parent != NULL) {
struct ecc_e3_dimm *dimm_sc;
while ((dimm_sc = TAILQ_FIRST(&sc->ecc_dimm)) != NULL) {
TAILQ_REMOVE(&sc->ecc_dimm, dimm_sc, dimm_link);
dimm_sensor_detach(dimm_sc->dimm_softc,
&dimm_sc->dimm_sensor);
dimm_destroy(dimm_sc->dimm_softc);
kfree(dimm_sc, M_DEVBUF);
}
} else {
sensordev_deinstall(&sc->ecc_sensdev);
}
return 0;
}
static void
ecc_e3_shutdown(device_t dev)
{
ecc_e3_stop(device_get_softc(dev));
}
static void
ecc_e3_stop(struct ecc_e3_softc *sc)
{
if (sc->ecc_flags & ECC_E3_FLAG_SENSTASK)
sensor_task_unregister(sc);
}
static void
ecc_e3_sensor_update(struct ecc_e3_softc *sc, boolean_t crit)
{
enum sensor_status status;
sc->ecc_count++;
if (!crit && sc->ecc_count >= sc->ecc_thresh)
crit = TRUE;
if (crit && (sc->ecc_flags & ECC_E3_FLAG_CRIT) == 0) {
char ecc_str[16];
ksnprintf(ecc_str, sizeof(ecc_str), "%d", sc->ecc_count);
devctl_notify("ecc", "ECC", ecc_str, "node=0");
ecc_printf(sc, "too many ECC errors %d\n", sc->ecc_count);
sc->ecc_flags |= ECC_E3_FLAG_CRIT;
}
if (sc->ecc_flags & ECC_E3_FLAG_CRIT)
status = SENSOR_S_CRIT;
else
status = SENSOR_S_OK;
sensor_set(&sc->ecc_sens, sc->ecc_count, status);
}