#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sensors.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <dev/misc/dimm/dimm.h>
#define DIMM_TEMP_HIWAT_DEFAULT 85
#define DIMM_TEMP_LOWAT_DEFAULT 75
#define DIMM_ECC_THRESH_DEFAULT 5
struct dimm_softc {
TAILQ_ENTRY(dimm_softc) dimm_link;
int dimm_node;
int dimm_chan;
int dimm_slot;
int dimm_temp_hiwat;
int dimm_temp_lowat;
int dimm_id;
int dimm_ref;
int dimm_ecc_cnt;
int dimm_ecc_thresh;
struct ksensordev dimm_sensdev;
uint32_t dimm_sens_taskflags;
struct sysctl_ctx_list dimm_sysctl_ctx;
struct sysctl_oid *dimm_sysctl_tree;
};
TAILQ_HEAD(dimm_softc_list, dimm_softc);
#define DIMM_SENS_TF_TEMP_CRIT 0x1
#define DIMM_SENS_TF_ECC_CRIT 0x2
static void dimm_mod_unload(void);
static void dimm_sensor_ecc(struct dimm_softc *, struct ksensor *,
boolean_t);
static struct dimm_softc_list dimm_softc_list;
static SYSCTL_NODE(_hw, OID_AUTO, dimminfo, CTLFLAG_RD, NULL,
"DIMM information");
struct dimm_softc *
dimm_create(int node, int chan, int slot)
{
struct dimm_softc *sc, *after = NULL;
int dimm_id = 0;
SYSCTL_XLOCK();
TAILQ_FOREACH(sc, &dimm_softc_list, dimm_link) {
if (sc->dimm_node == node && sc->dimm_chan == chan &&
sc->dimm_slot == slot) {
KASSERT(sc->dimm_ref > 0, ("invalid dimm reference %d",
sc->dimm_ref));
sc->dimm_ref++;
SYSCTL_XUNLOCK();
return sc;
}
if (sc->dimm_id == dimm_id) {
++dimm_id;
after = sc;
}
}
sc = kmalloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
sc->dimm_node = node;
sc->dimm_chan = chan;
sc->dimm_slot = slot;
sc->dimm_id = dimm_id;
sc->dimm_ref = 1;
sc->dimm_temp_hiwat = DIMM_TEMP_HIWAT_DEFAULT;
sc->dimm_temp_lowat = DIMM_TEMP_LOWAT_DEFAULT;
sc->dimm_ecc_thresh = DIMM_ECC_THRESH_DEFAULT;
ksnprintf(sc->dimm_sensdev.xname, sizeof(sc->dimm_sensdev.xname),
"dimm%d", sc->dimm_id);
sysctl_ctx_init(&sc->dimm_sysctl_ctx);
sc->dimm_sysctl_tree = SYSCTL_ADD_NODE(&sc->dimm_sysctl_ctx,
SYSCTL_STATIC_CHILDREN(_hw_dimminfo), OID_AUTO,
sc->dimm_sensdev.xname, CTLFLAG_RD, 0, "");
if (sc->dimm_sysctl_tree != NULL) {
SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
"node", CTLFLAG_RD, &sc->dimm_node, 0,
"CPU node of this DIMM");
SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
"chan", CTLFLAG_RD, &sc->dimm_chan, 0,
"channel of this DIMM");
SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
"slot", CTLFLAG_RD, &sc->dimm_slot, 0,
"slot of this DIMM");
SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
"temp_hiwat", CTLFLAG_RW, &sc->dimm_temp_hiwat, 0,
"Raise alarm once DIMM temperature is above this value "
"(unit: C)");
SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
"temp_lowat", CTLFLAG_RW, &sc->dimm_temp_lowat, 0,
"Cancel alarm once DIMM temperature is below this value "
"(unit: C)");
SYSCTL_ADD_INT(&sc->dimm_sysctl_ctx,
SYSCTL_CHILDREN(sc->dimm_sysctl_tree), OID_AUTO,
"ecc_thresh", CTLFLAG_RW, &sc->dimm_ecc_thresh, 0,
"Raise alarm once number ECC errors go above this value");
}
if (after == NULL) {
KKASSERT(sc->dimm_id == 0);
TAILQ_INSERT_HEAD(&dimm_softc_list, sc, dimm_link);
} else {
TAILQ_INSERT_AFTER(&dimm_softc_list, after, sc, dimm_link);
}
sensordev_install(&sc->dimm_sensdev);
SYSCTL_XUNLOCK();
return sc;
}
int
dimm_destroy(struct dimm_softc *sc)
{
SYSCTL_XLOCK();
KASSERT(sc->dimm_ref > 0, ("invalid dimm reference %d", sc->dimm_ref));
sc->dimm_ref--;
if (sc->dimm_ref > 0) {
SYSCTL_XUNLOCK();
return EAGAIN;
}
sensordev_deinstall(&sc->dimm_sensdev);
TAILQ_REMOVE(&dimm_softc_list, sc, dimm_link);
if (sc->dimm_sysctl_tree != NULL)
sysctl_ctx_free(&sc->dimm_sysctl_ctx);
kfree(sc, M_DEVBUF);
SYSCTL_XUNLOCK();
return 0;
}
void
dimm_sensor_attach(struct dimm_softc *sc, struct ksensor *sens)
{
sensor_attach(&sc->dimm_sensdev, sens);
}
void
dimm_sensor_detach(struct dimm_softc *sc, struct ksensor *sens)
{
sensor_detach(&sc->dimm_sensdev, sens);
}
void
dimm_set_temp_thresh(struct dimm_softc *sc, int hiwat, int lowat)
{
sc->dimm_temp_hiwat = hiwat;
sc->dimm_temp_lowat = lowat;
}
void
dimm_set_ecc_thresh(struct dimm_softc *sc, int thresh)
{
sc->dimm_ecc_thresh = thresh;
}
void
dimm_sensor_temp(struct dimm_softc *sc, struct ksensor *sens, int temp)
{
enum sensor_status status;
if (temp >= sc->dimm_temp_hiwat &&
(sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT) == 0) {
char temp_str[16], data[64];
ksnprintf(temp_str, sizeof(temp_str), "%d", temp);
ksnprintf(data, sizeof(data), "node=%d channel=%d dimm=%d",
sc->dimm_node, sc->dimm_chan, sc->dimm_slot);
devctl_notify("memtemp", "Thermal", temp_str, data);
kprintf("dimm%d: node%d channel%d DIMM%d "
"temperature (%dC) is too high (>= %dC)\n",
sc->dimm_id, sc->dimm_node, sc->dimm_chan, sc->dimm_slot,
temp, sc->dimm_temp_hiwat);
sc->dimm_sens_taskflags |= DIMM_SENS_TF_TEMP_CRIT;
} else if ((sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT) &&
temp < sc->dimm_temp_lowat) {
sc->dimm_sens_taskflags &= ~DIMM_SENS_TF_TEMP_CRIT;
}
if (sc->dimm_sens_taskflags & DIMM_SENS_TF_TEMP_CRIT)
status = SENSOR_S_CRIT;
else
status = SENSOR_S_OK;
sensor_set_temp_degc(sens, temp, status);
}
void
dimm_sensor_ecc_set(struct dimm_softc *sc, struct ksensor *sens,
int ecc_cnt, boolean_t crit)
{
sc->dimm_ecc_cnt = ecc_cnt;
dimm_sensor_ecc(sc, sens, crit);
}
void
dimm_sensor_ecc_add(struct dimm_softc *sc, struct ksensor *sens,
int ecc_cnt, boolean_t crit)
{
sc->dimm_ecc_cnt += ecc_cnt;
dimm_sensor_ecc(sc, sens, crit);
}
static void
dimm_sensor_ecc(struct dimm_softc *sc, struct ksensor *sens, boolean_t crit)
{
enum sensor_status status;
if (!crit && sc->dimm_ecc_cnt >= sc->dimm_ecc_thresh)
crit = TRUE;
if (crit && (sc->dimm_sens_taskflags & DIMM_SENS_TF_ECC_CRIT) == 0) {
char ecc_str[16], data[64];
ksnprintf(ecc_str, sizeof(ecc_str), "%d", sc->dimm_ecc_cnt);
ksnprintf(data, sizeof(data), "node=%d channel=%d dimm=%d",
sc->dimm_node, sc->dimm_chan, sc->dimm_slot);
devctl_notify("ecc", "ECC", ecc_str, data);
kprintf("dimm%d: node%d channel%d DIMM%d "
"too many ECC errors %d\n",
sc->dimm_id, sc->dimm_node, sc->dimm_chan, sc->dimm_slot,
sc->dimm_ecc_cnt);
sc->dimm_sens_taskflags |= DIMM_SENS_TF_ECC_CRIT;
}
if (sc->dimm_sens_taskflags & DIMM_SENS_TF_ECC_CRIT)
status = SENSOR_S_CRIT;
else
status = SENSOR_S_OK;
sensor_set(sens, sc->dimm_ecc_cnt, status);
}
static void
dimm_mod_unload(void)
{
struct dimm_softc *sc;
SYSCTL_XLOCK();
while ((sc = TAILQ_FIRST(&dimm_softc_list)) != NULL) {
int error;
error = dimm_destroy(sc);
KASSERT(!error, ("dimm%d is still referenced, ref %d",
sc->dimm_id, sc->dimm_ref));
}
SYSCTL_XUNLOCK();
}
static int
dimm_mod_event(module_t mod, int type, void *unused)
{
switch (type) {
case MOD_LOAD:
TAILQ_INIT(&dimm_softc_list);
return 0;
case MOD_UNLOAD:
dimm_mod_unload();
return 0;
default:
return 0;
}
}
static moduledata_t dimm_mod = {
"dimm",
dimm_mod_event,
0
};
DECLARE_MODULE(dimm, dimm_mod, SI_SUB_PRE_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(dimm, 1);