#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <sys/pci.h>
#include <sys/stddef.h>
#include <sys/cpuvar.h>
#include <sys/x86_archext.h>
#include <sys/list.h>
#include <sys/bitset.h>
#include <sys/sensors.h>
#define AMDNBTEMP_TEMPREG 0xa4
#define AMDNBTEMP_TEMPREG_CURTMP(x) BITX(x, 31, 21)
#define AMDNBTEMP_TEMPREG_TJSEL(x) BITX(x, 17, 16)
#define AMDNBTEMP_GRANULARITY 8
#define AMDNBTEMP_GSHIFT 3
#define AMDNBTEMP_TJSEL_ADJUST 0x3
#define AMDNBTEMP_TEMP_ADJUST (49 << AMDNBTEMP_GSHIFT)
#define AMDNBTEMP_FIRST_DEV 0x18
typedef enum andnbtemp_state {
AMDNBTEMP_S_CFGSPACE = 1 << 0,
AMDNBTEMP_S_MUTEX = 1 << 1,
AMDNBTMEP_S_KSENSOR = 1 << 2
} amdnbtemp_state_t;
typedef struct amdnbtemp {
amdnbtemp_state_t at_state;
dev_info_t *at_dip;
ddi_acc_handle_t at_cfgspace;
uint_t at_bus;
uint_t at_dev;
uint_t at_func;
id_t at_ksensor;
minor_t at_minor;
boolean_t at_tjsel;
kmutex_t at_mutex;
uint32_t at_raw;
int64_t at_temp;
} amdnbtemp_t;
static void *amdnbtemp_state;
static int
amdnbtemp_read(void *arg, sensor_ioctl_scalar_t *scalar)
{
amdnbtemp_t *at = arg;
mutex_enter(&at->at_mutex);
at->at_raw = pci_config_get32(at->at_cfgspace, AMDNBTEMP_TEMPREG);
if (at->at_raw == PCI_EINVAL32) {
mutex_exit(&at->at_mutex);
return (EIO);
}
at->at_temp = AMDNBTEMP_TEMPREG_CURTMP(at->at_raw);
if (at->at_tjsel &&
AMDNBTEMP_TEMPREG_TJSEL(at->at_raw) == AMDNBTEMP_TJSEL_ADJUST) {
at->at_temp -= AMDNBTEMP_TEMP_ADJUST;
}
scalar->sis_unit = SENSOR_UNIT_CELSIUS;
scalar->sis_gran = AMDNBTEMP_GRANULARITY;
scalar->sis_value = at->at_temp;
mutex_exit(&at->at_mutex);
return (0);
}
static const ksensor_ops_t amdnbtemp_temp_ops = {
.kso_kind = ksensor_kind_temperature,
.kso_scalar = amdnbtemp_read
};
static void
amdnbtemp_cleanup(amdnbtemp_t *at)
{
int inst;
inst = ddi_get_instance(at->at_dip);
if ((at->at_state & AMDNBTMEP_S_KSENSOR) != 0) {
(void) ksensor_remove(at->at_dip, KSENSOR_ALL_IDS);
at->at_state &= ~AMDNBTMEP_S_KSENSOR;
}
if ((at->at_state & AMDNBTEMP_S_MUTEX) != 0) {
mutex_destroy(&at->at_mutex);
at->at_state &= ~AMDNBTEMP_S_MUTEX;
}
if ((at->at_state & AMDNBTEMP_S_CFGSPACE) != 0) {
pci_config_teardown(&at->at_cfgspace);
at->at_state &= ~AMDNBTEMP_S_CFGSPACE;
}
ASSERT0(at->at_state);
ddi_soft_state_free(amdnbtemp_state, inst);
}
static boolean_t
amdnbtemp_erratum_319(void)
{
uint32_t socket;
if (cpuid_getfamily(CPU) != 0x10) {
return (B_FALSE);
}
socket = cpuid_getsockettype(CPU);
if (socket == X86_SOCKET_F1207 || socket == X86_SOCKET_AM2R2) {
return (B_TRUE);
}
return (B_FALSE);
}
static int
amdnbtemp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int inst, *regs, ret;
amdnbtemp_t *at;
uint_t nregs, id;
char buf[128];
switch (cmd) {
case DDI_RESUME:
return (DDI_SUCCESS);
case DDI_ATTACH:
break;
default:
return (DDI_FAILURE);
}
inst = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(amdnbtemp_state, inst) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to allocate soft state entry %d",
inst);
return (DDI_FAILURE);
}
at = ddi_get_soft_state(amdnbtemp_state, inst);
if (at == NULL) {
dev_err(dip, CE_WARN, "failed to retrieve soft state entry %d",
inst);
return (DDI_FAILURE);
}
at->at_dip = dip;
if (pci_config_setup(dip, &at->at_cfgspace) != DDI_SUCCESS) {
dev_err(dip, CE_WARN, "failed to set up PCI config space");
goto err;
}
at->at_state |= AMDNBTEMP_S_CFGSPACE;
if (amdnbtemp_erratum_319()) {
dev_err(dip, CE_WARN, "!device subject to AMD Erratum 319, "
"not attaching to unreliable sensor");
goto err;
}
mutex_init(&at->at_mutex, NULL, MUTEX_DRIVER, NULL);
at->at_state |= AMDNBTEMP_S_MUTEX;
if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, 0, "reg",
®s, &nregs) != DDI_PROP_SUCCESS) {
dev_err(dip, CE_WARN, "failed to get pci 'reg' property");
goto err;
}
if (nregs < 1) {
dev_err(dip, CE_WARN, "'reg' property missing PCI b/d/f");
ddi_prop_free(regs);
goto err;
}
at->at_bus = PCI_REG_BUS_G(regs[0]);
at->at_dev = PCI_REG_DEV_G(regs[0]);
at->at_func = PCI_REG_DEV_G(regs[0]);
ddi_prop_free(regs);
if (at->at_dev < AMDNBTEMP_FIRST_DEV) {
dev_err(dip, CE_WARN, "Invalid pci b/d/f device, found 0x%x",
at->at_dev);
goto err;
}
id = at->at_dev - AMDNBTEMP_FIRST_DEV;
if (snprintf(buf, sizeof (buf), "procnode.%u", id) >= sizeof (buf)) {
dev_err(dip, CE_WARN, "unexpected buffer name overrun "
"constructing sensor %u", id);
goto err;
}
if (cpuid_getfamily(CPU) >= 0x15) {
at->at_tjsel = B_TRUE;
}
if ((ret = ksensor_create(dip, &amdnbtemp_temp_ops, at, buf,
DDI_NT_SENSOR_TEMP_CPU, &at->at_ksensor)) != 0) {
dev_err(dip, CE_WARN, "failed to create ksensor for %s: %d",
buf, ret);
goto err;
}
at->at_state |= AMDNBTMEP_S_KSENSOR;
return (DDI_SUCCESS);
err:
amdnbtemp_cleanup(at);
return (DDI_FAILURE);
}
static int
amdnbtemp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int inst;
amdnbtemp_t *at;
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
inst = ddi_get_instance(dip);
at = ddi_get_soft_state(amdnbtemp_state, inst);
if (at == NULL) {
dev_err(dip, CE_WARN, "asked to detach instance %d, but it is "
"missing from the soft state", inst);
return (DDI_FAILURE);
}
amdnbtemp_cleanup(at);
return (DDI_SUCCESS);
}
static struct dev_ops amdnbtemp_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_getinfo = nodev,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = amdnbtemp_attach,
.devo_detach = amdnbtemp_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_needed
};
static struct modldrv amdnbtemp_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "AMD NB Temp Driver",
.drv_dev_ops = &amdnbtemp_dev_ops
};
static struct modlinkage amdnbtemp_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &amdnbtemp_modldrv, NULL }
};
int
_init(void)
{
int ret;
if (ddi_soft_state_init(&amdnbtemp_state, sizeof (amdnbtemp_t), 2) !=
DDI_SUCCESS) {
return (ENOMEM);
}
if ((ret = mod_install(&amdnbtemp_modlinkage)) != 0) {
ddi_soft_state_fini(&amdnbtemp_state);
return (ret);
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&amdnbtemp_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&amdnbtemp_modlinkage)) != 0) {
return (ret);
}
ddi_soft_state_fini(&amdnbtemp_state);
return (ret);
}