#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bitops.h>
#include <sys/bus.h>
#include <sys/cpu_topology.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sensors.h>
#include <sys/sysctl.h>
#include <bus/pci/pcivar.h>
#include <bus/pci/pcireg.h>
#include <bus/pci/pci_cfgreg.h>
#include <bus/pci/pcib_private.h>
#include "pcib_if.h"
#include <dev/misc/dimm/dimm.h>
#include <dev/misc/ecc/e5_imc_reg.h>
#include <dev/misc/ecc/e5_imc_var.h>
#define MEMTEMP_E5_DIMM_TEMP_HIWAT 85
#define MEMTEMP_E5_DIMM_TEMP_STEP 5
struct memtemp_e5_softc;
struct memtemp_e5_dimm {
TAILQ_ENTRY(memtemp_e5_dimm) dimm_link;
struct ksensor dimm_sensor;
struct memtemp_e5_softc *dimm_parent;
int dimm_id;
int dimm_flags;
struct dimm_softc *dimm_softc;
struct sensor_task *dimm_senstask;
};
#define MEMTEMP_E5_DIMM_FLAG_CRIT 0x1
struct memtemp_e5_softc {
device_t temp_dev;
const struct e5_imc_chan *temp_chan;
int temp_node;
TAILQ_HEAD(, memtemp_e5_dimm) temp_dimm;
};
static int memtemp_e5_probe(device_t);
static int memtemp_e5_attach(device_t);
static int memtemp_e5_detach(device_t);
static int memtemp_e5_tempth_adjust(int);
static void memtemp_e5_tempth_str(int, char *, int);
static void memtemp_e5_sensor_task(void *);
#define MEMTEMP_E5_CHAN(v, imc, c, c_ext) \
{ \
.did = PCI_E5V##v##_IMC##imc##_THERMAL_CHN##c##_DID_ID, \
.slot = PCISLOT_E5V##v##_IMC##imc##_THERMAL_CHN##c, \
.func = PCIFUNC_E5V##v##_IMC##imc##_THERMAL_CHN##c, \
.desc = "Intel E5 v" #v " memory thermal sensor", \
\
E5_IMC_CHAN_FIELDS(v, imc, c, c_ext) \
}
#define MEMTEMP_E5_CHAN_V2(c) MEMTEMP_E5_CHAN(2, 0, c, c)
#define MEMTEMP_E5_CHAN_IMC0_V3(c) MEMTEMP_E5_CHAN(3, 0, c, c)
#define MEMTEMP_E5_CHAN_IMC1_V3(c, c_ext) \
MEMTEMP_E5_CHAN(3, 1, c, c_ext)
#define MEMTEMP_E5_CHAN_END E5_IMC_CHAN_END
static const struct e5_imc_chan memtemp_e5_chans[] = {
MEMTEMP_E5_CHAN_V2(0),
MEMTEMP_E5_CHAN_V2(1),
MEMTEMP_E5_CHAN_V2(2),
MEMTEMP_E5_CHAN_V2(3),
MEMTEMP_E5_CHAN_IMC0_V3(0),
MEMTEMP_E5_CHAN_IMC0_V3(1),
MEMTEMP_E5_CHAN_IMC0_V3(2),
MEMTEMP_E5_CHAN_IMC0_V3(3),
MEMTEMP_E5_CHAN_IMC1_V3(0, 2),
MEMTEMP_E5_CHAN_IMC1_V3(1, 3),
MEMTEMP_E5_CHAN_END
};
#undef MEMTEMP_E5_CHAN_END
#undef MEMTEMP_E5_CHAN_V2
#undef MEMTEMP_E5_CHAN
static device_method_t memtemp_e5_methods[] = {
DEVMETHOD(device_probe, memtemp_e5_probe),
DEVMETHOD(device_attach, memtemp_e5_attach),
DEVMETHOD(device_detach, memtemp_e5_detach),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD_END
};
static driver_t memtemp_e5_driver = {
"memtemp",
memtemp_e5_methods,
sizeof(struct memtemp_e5_softc)
};
static devclass_t memtemp_devclass;
DRIVER_MODULE(memtemp_e5, pci, memtemp_e5_driver, memtemp_devclass, NULL, NULL);
MODULE_DEPEND(memtemp_e5, pci, 1, 1, 1);
MODULE_DEPEND(memtemp_e5, dimm, 1, 1, 1);
static int
memtemp_e5_probe(device_t dev)
{
const struct e5_imc_chan *c;
uint16_t vid, did;
int slot, func;
vid = pci_get_vendor(dev);
if (vid != PCI_E5_IMC_VID_ID)
return ENXIO;
did = pci_get_device(dev);
slot = pci_get_slot(dev);
func = pci_get_function(dev);
for (c = memtemp_e5_chans; c->desc != NULL; ++c) {
if (c->did == did && c->slot == slot && c->func == func) {
struct memtemp_e5_softc *sc = device_get_softc(dev);
uint32_t cfg;
int node;
node = e5_imc_node_probe(dev, c);
if (node < 0)
break;
cfg = pci_read_config(dev,
PCI_E5_IMC_THERMAL_CHN_TEMP_CFG, 4);
if ((cfg & PCI_E5_IMC_THERMAL_CHN_TEMP_CFG_CLTT) == 0)
break;
device_set_desc(dev, c->desc);
sc->temp_chan = c;
sc->temp_node = node;
return 0;
}
}
return ENXIO;
}
static int
memtemp_e5_tempth_adjust(int temp)
{
if (temp == PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_DISABLE)
return 0;
else if (temp < PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPMIN ||
temp >= PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPMAX)
return -1;
return temp;
}
static void
memtemp_e5_tempth_str(int temp, char *temp_str, int temp_strlen)
{
if (temp < 0)
strlcpy(temp_str, "reserved", temp_strlen);
else if (temp == 0)
strlcpy(temp_str, "disabled", temp_strlen);
else
ksnprintf(temp_str, temp_strlen, "%dC", temp);
}
static int
memtemp_e5_attach(device_t dev)
{
struct memtemp_e5_softc *sc = device_get_softc(dev);
const cpu_node_t *node;
int dimm, cpuid = -1;
sc->temp_dev = dev;
TAILQ_INIT(&sc->temp_dimm);
node = get_cpu_node_by_chipid(sc->temp_node);
if (node != NULL && node->child_no > 0) {
cpuid = BSRCPUMASK(node->members);
if (bootverbose) {
device_printf(dev, "node%d chan%d -> cpu%d\n",
sc->temp_node, sc->temp_chan->chan_ext, cpuid);
}
}
for (dimm = 0; dimm < PCI_E5_IMC_CHN_DIMM_MAX; ++dimm) {
char temp_lostr[16], temp_midstr[16], temp_histr[16];
struct memtemp_e5_dimm *dimm_sc;
int temp_lo, temp_mid, temp_hi;
int temp_hiwat, temp_lowat, has_temp_thresh = 1;
uint32_t dimmmtr, temp_th;
struct ksensor *sens;
dimmmtr = IMC_CTAD_READ_4(dev, sc->temp_chan,
PCI_E5_IMC_CTAD_DIMMMTR(dimm));
if ((dimmmtr & PCI_E5_IMC_CTAD_DIMMMTR_DIMM_POP) == 0)
continue;
dimm_sc = kmalloc(sizeof(*dimm_sc), M_DEVBUF,
M_WAITOK | M_ZERO);
dimm_sc->dimm_id = dimm;
dimm_sc->dimm_parent = sc;
temp_th = pci_read_config(dev,
PCI_E5_IMC_THERMAL_DIMM_TEMP_TH(dimm), 4);
temp_lo = __SHIFTOUT(temp_th,
PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPLO);
temp_lo = memtemp_e5_tempth_adjust(temp_lo);
memtemp_e5_tempth_str(temp_lo, temp_lostr, sizeof(temp_lostr));
temp_mid = __SHIFTOUT(temp_th,
PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPMID);
temp_mid = memtemp_e5_tempth_adjust(temp_mid);
memtemp_e5_tempth_str(temp_mid, temp_midstr,
sizeof(temp_midstr));
temp_hi = __SHIFTOUT(temp_th,
PCI_E5_IMC_THERMAL_DIMM_TEMP_TH_TEMPHI);
temp_hi = memtemp_e5_tempth_adjust(temp_hi);
memtemp_e5_tempth_str(temp_hi, temp_histr, sizeof(temp_histr));
if (temp_mid <= 0) {
if (temp_hi <= 0) {
temp_hiwat = MEMTEMP_E5_DIMM_TEMP_HIWAT;
has_temp_thresh = 0;
} else {
temp_hiwat = temp_hi;
}
} else {
temp_hiwat = temp_mid;
}
if (temp_hiwat < MEMTEMP_E5_DIMM_TEMP_STEP) {
temp_hiwat = MEMTEMP_E5_DIMM_TEMP_HIWAT;
has_temp_thresh = 0;
}
temp_lowat = temp_hiwat - MEMTEMP_E5_DIMM_TEMP_STEP;
if (bootverbose) {
device_printf(dev, "DIMM%d "
"temp_hi %s, temp_mid %s, temp_lo %s\n", dimm,
temp_histr, temp_midstr, temp_lostr);
}
dimm_sc->dimm_softc = dimm_create(sc->temp_node,
sc->temp_chan->chan_ext, dimm);
if (has_temp_thresh) {
if (bootverbose) {
device_printf(dev, "DIMM%d "
"hiwat %dC, lowat %dC\n",
dimm, temp_hiwat, temp_lowat);
}
dimm_set_temp_thresh(dimm_sc->dimm_softc,
temp_hiwat, temp_lowat);
}
sens = &dimm_sc->dimm_sensor;
ksnprintf(sens->desc, sizeof(sens->desc),
"node%d chan%d DIMM%d temp",
sc->temp_node, sc->temp_chan->chan_ext, dimm);
sens->type = SENSOR_TEMP;
sensor_set_unknown(sens);
dimm_sensor_attach(dimm_sc->dimm_softc, sens);
dimm_sc->dimm_senstask = sensor_task_register2(dimm_sc,
memtemp_e5_sensor_task, 5, cpuid);
TAILQ_INSERT_TAIL(&sc->temp_dimm, dimm_sc, dimm_link);
}
return 0;
}
static int
memtemp_e5_detach(device_t dev)
{
struct memtemp_e5_softc *sc = device_get_softc(dev);
struct memtemp_e5_dimm *dimm_sc;
while ((dimm_sc = TAILQ_FIRST(&sc->temp_dimm)) != NULL) {
TAILQ_REMOVE(&sc->temp_dimm, dimm_sc, dimm_link);
sensor_task_unregister2(dimm_sc->dimm_senstask);
dimm_sensor_detach(dimm_sc->dimm_softc, &dimm_sc->dimm_sensor);
dimm_destroy(dimm_sc->dimm_softc);
kfree(dimm_sc, M_DEVBUF);
}
return 0;
}
static void
memtemp_e5_sensor_task(void *xdimm_sc)
{
struct memtemp_e5_dimm *dimm_sc = xdimm_sc;
struct ksensor *sensor = &dimm_sc->dimm_sensor;
device_t dev = dimm_sc->dimm_parent->temp_dev;
uint32_t val;
int temp, reg;
reg = PCI_E5_IMC_THERMAL_DIMMTEMPSTAT(dimm_sc->dimm_id);
val = pci_read_config(dev, reg, 4);
if (val & (PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPHI |
PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPMID |
PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPLO))
pci_write_config(dev, reg, val, 4);
temp = __SHIFTOUT(val, PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMP);
if (temp < PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPMIN ||
temp >= PCI_E5_IMC_THERMAL_DIMMTEMPSTAT_TEMPMAX) {
sensor->status = SENSOR_S_UNSPEC;
sensor->flags |= SENSOR_FINVALID;
sensor->value = 0;
return;
}
dimm_sensor_temp(dimm_sc->dimm_softc, sensor, temp);
}