/**
* $Id: k10sensor.c 752 2012-06-25 05:25:09Z elkner $
*
* * CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at
* http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* All Rights reserved.
* CDDL HEADER END
*/
/*
* Copyright (c) 2011,2012 by Jens Elkner,
* Otto-von-Guericke Universitaet Magdeburg. All rights reserved.
*/
#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/x86_archext.h>
#include <sys/cpuvar.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include "k10s_kstats.h"
#define DEV2INST(dev) (getminor(dev))
#define DIP2INST(dip) (ddi_get_instance(dip))
#ifdef DEBUG
#define K10DBG cmn_err
#else
#define K10DBG //
#endif
#ifndef MC_AMD_DEV_OFFSET
#define MC_AMD_DEV_OFFSET 0x18 /* node ID + offset == PCI dev num */
#endif
#define PROP_DIODE_OFFSET KMODNAME"-diode_offset"
#define PROP_T_CASE_MAX KMODNAME"-t_case_max"
static int k10sensor_attach(dev_info_t *, ddi_attach_cmd_t);
static int k10sensor_detach(dev_info_t *, ddi_detach_cmd_t);
static int k10sensor_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int k10sensor_open(dev_t *devp, int, int, cred_t *);
static int k10sensor_close(dev_t, int, int, cred_t *);
static int k10sensor_read(dev_t, struct uio *, cred_t *);
static int k10sensor_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static struct cb_ops k10sensor_cb_ops = {
k10sensor_open, /* open */
k10sensor_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
k10sensor_read, /* read */
nodev, /* write */
k10sensor_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* chpoll */
ddi_prop_op, /* prop_op */
NULL, /* streamtab */
D_MP | D_64BIT, /* flags */
CB_REV, /* revision */
nodev, /* aread */
nodev /* awrite */
};
static struct dev_ops k10sensor_dev_ops = {
DEVO_REV, /* revision */
0, /* reference count */
k10sensor_getinfo, /* getinfo */
nulldev, /* obsolete identify */
nulldev, /* probe */
k10sensor_attach, /* attach */
k10sensor_detach, /* detach */
nodev, /* reset - unsupported */
&k10sensor_cb_ops, /* cb_ops */
(struct bus_ops *) NULL, /* bus operations entry points structure */
nulldev /* power */
#ifdef __SunOS_5_11
,ddi_quiesce_not_needed /* quiesce */
#endif
};
static struct modldrv md = {
&mod_driverops,
"AMD CPU temperature sensor",
&k10sensor_dev_ops /* dev_ops */
};
static struct modlinkage ml = {
MODREV_1,
&md,
NULL
};
static void *statep;
/* Get the number of all physical CPUs in the system.
* @return a value >= 1.
*/
static int
getNumPhysCpus() {
int count = 1;
cpu_t *start = cpu_list;
int last = cpuid_get_chipid(start);
cpu_t *tcpu = start->cpu_next;
for (;tcpu != start && tcpu != NULL; tcpu = tcpu->cpu_next) {
int cid = cpuid_get_chipid(tcpu);
if (cid != last) {
count++;
last = cid;
}
}
return (count);
}
int
_init(void)
{
int pcpus = getNumPhysCpus();
K10DBG(CE_NOTE, "Inside _init %d cpus online", pcpus);
ddi_soft_state_init(&statep, sizeof(k10sensor_t), pcpus);
pcpus = mod_install(&ml);
if (pcpus != 0) {
cmn_err(CE_WARN, "_init: unable to allocate space for %d instances",
pcpus);
ddi_soft_state_fini(&statep);
}
return (pcpus);
}
int
_info(struct modinfo *modinfop)
{
K10DBG(CE_NOTE, "Inside _info");
return (mod_info(&ml, modinfop));
}
int
_fini(void)
{
int err;
K10DBG(CE_NOTE, "Inside _fini");
err = mod_remove(&ml);
if (err == 0) {
ddi_soft_state_fini(&statep);
}
return (err);
}
/* Set k10p->v->chipId to the CPU NodeID, to which this instance belongs to.
* @param k10p the per instance data record. Required members: dip, chipId
* @return DDI_FAILURE if unable to determine, DDI_SUCCESS otherwise.
*/
static int
fillChipId(k10sensor_t *k10p) {
char *unitstr = NULL;
long unitaddr;
/* unit-address = PCI_device_number,Function
which in this case is (0x18 + ChipId),3 */
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, k10p->dip, DDI_PROP_DONTPASS,
"unit-address", &unitstr) != DDI_PROP_SUCCESS)
{
return (DDI_FAILURE);
}
if (ddi_strtol(unitstr, NULL, 16, &unitaddr) != 0) {
unitaddr = 0;
}
ddi_prop_free(unitstr);
k10p->v->chipId = unitaddr - MC_AMD_DEV_OFFSET;
return (k10p->v->chipId < 0) ? (DDI_FAILURE) : (DDI_SUCCESS);
}
static void
fetchProperties(k10sensor_t *k10p) {
int val = ddi_prop_get_int(DDI_DEV_T_ANY, k10p->dip, DDI_PROP_NOTPROM,
PROP_DIODE_OFFSET, INT32_MAX);
if (val != INT32_MAX) {
k10p->v->diode_offset = val;
}
val = ddi_prop_get_int(DDI_DEV_T_ANY, k10p->dip, DDI_PROP_NOTPROM,
PROP_T_CASE_MAX, INT32_MAX);
if (val != INT32_MAX) {
k10p->v->t_case_max = val;
}
}
/* Populate k10p->v with CPU/temperature specific "static" data.
* @return DDI_FAILURE on error, DDI_SUCCESS otherwise.
*/
static int
fillChipInfo(k10sensor_t *k10p) {
struct cpuid_regs cp;
cpu_t *start_cpu, *tmp_cpu;
cpu_vars_t *v = k10p->v;
/* once CPU list is initialized, it doesn't change its structure */
tmp_cpu = start_cpu = cpu_list;
do {
int chip_id = cpuid_get_chipid(tmp_cpu);
if (chip_id == v->chipId) {
start_cpu = NULL;
break;
}
tmp_cpu = tmp_cpu->cpu_next;
} while (tmp_cpu != start_cpu && tmp_cpu != NULL);
if (start_cpu != NULL) {
return (DDI_FAILURE);
}
cp.cp_eax = 0x80000001;
cpuid_insn(tmp_cpu, &cp);
v->xfamilyModel = AMD_XFAMILY_MODEL(cp.cp_eax);
v->modelStepping = AMD_MODEL_STEPPING(cp.cp_eax);
v->ebx = cp.cp_ebx;
fillCategoryIndex(v);
fillSiliconRevision(v);
fillErratum(v);
fillNBcapabilities(k10p->pci_hdl, v);
fillThermtrip(k10p->pci_hdl, v);
fillHtc(k10p->pci_hdl, v);
fillStc(k10p->pci_hdl, v);
fetchProperties(k10p);
return (DDI_SUCCESS);
}
/* free all instance related data and ressources */
static int
destroyState(k10sensor_t *k10p, void *statep) {
if (k10p == NULL) {
return (DDI_SUCCESS);
}
k10s_kstat_destroy(k10p);
mutex_enter(k10p->lock);
ddi_remove_minor_node(k10p->dip, NULL);
if (k10p->pci_hdl) {
pci_config_teardown(&k10p->pci_hdl);
}
ddi_set_driver_private(k10p->dip, NULL);
mutex_exit(k10p->lock);
mutex_destroy(k10p->lock);
ddi_soft_state_free(statep, DIP2INST(k10p->dip));
return (DDI_SUCCESS);
}
static int
k10sensor_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
k10sensor_t *k10p;
int inst = DIP2INST(dip);
K10DBG(CE_NOTE, "Inside "KMODNAME"_attach #%d", inst);
if (x86_vendor != X86_VENDOR_AMD ) {
cmn_err(CE_NOTE, "No AMD CPU");
return (DDI_FAILURE);
}
switch (cmd) {
default:
return (DDI_FAILURE);
case DDI_RESUME:
return (DDI_SUCCESS);
case DDI_ATTACH:
break;
}
if (ddi_soft_state_zalloc(statep, inst) != DDI_SUCCESS) {
cmn_err(CE_WARN, "attach: soft state zalloc failed on #%d", inst);
return (DDI_FAILURE);
}
k10p = ddi_get_soft_state(statep, inst);
ddi_set_driver_private(dip, k10p);
k10p->dip = dip;
if (fillChipId(k10p) != DDI_SUCCESS) {
cmn_err(CE_WARN, "attach: getting chip ID failed on #%d", inst);
goto failed;
}
if (pci_config_setup(dip, &k10p->pci_hdl) != DDI_SUCCESS) {
cmn_err(CE_WARN, "attach: setup pci config failed on #%d", inst);
goto failed;
}
if (fillChipInfo(k10p) != DDI_SUCCESS) {
cmn_err(CE_WARN, "attach: getting chip info failed on #%d", inst);
goto failed;
}
if (ddi_create_minor_node(dip, KMODNAME, S_IFCHR,
inst, DDI_PSEUDO, 0) != DDI_SUCCESS)
{
cmn_err(CE_WARN, "attach: create minor node failed on #%d", inst);
goto failed;
}
k10s_kstat_create(k10p);
mutex_init(k10p->lock, NULL, MUTEX_DRIVER, NULL);
ddi_report_dev(dip);
if (k10p->v->erratum != 0) {
cmn_err(CE_WARN, "Instance #%d: Reported temperature values are "
"inaccurate (see erratum %d for family %s)", inst,
k10p->v->erratum, cat_range_map[k10p->v->catIdx].name);
}
return (DDI_SUCCESS);
failed:
destroyState(k10p, statep);
return (DDI_FAILURE);
}
static int
k10sensor_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
k10sensor_t *k10p = ddi_get_driver_private(dip);
K10DBG(CE_NOTE, "Inside "KMODNAME"_detach #%d cmd=%d", DIP2INST(dip), cmd);
switch (cmd) {
default:
return DDI_FAILURE;
case DDI_SUSPEND:
return DDI_SUCCESS;
case DDI_DETACH:
break;
}
destroyState(k10p, statep);
return DDI_SUCCESS;
}
static int
k10sensor_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp)
{
int res = DDI_FAILURE;
dev_t dev;
k10sensor_t *k10p;
K10DBG(CE_NOTE, "Inside "KMODNAME"_getinfo");
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
dev = (dev_t)arg;
k10p = ddi_get_soft_state(statep, DEV2INST(dev));
if (k10p != NULL) {
mutex_enter(k10p->lock);
*resp = k10p->dip;
mutex_exit(k10p->lock);
res = DDI_SUCCESS;
} else {
*resp = NULL;
}
break;
case DDI_INFO_DEVT2INSTANCE:
dev = (dev_t)arg;
*resp = (void *)(uintptr_t) DEV2INST(dev);
res = DDI_SUCCESS;
break;
default:
break;
}
return res;
}
/* ddi_get_lbolt(void); */
static int
k10sensor_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
K10DBG(CE_NOTE, "Inside "KMODNAME"_open #%d", DEV2INST(*devp));
return otyp != OTYP_CHR ? EINVAL : DDI_SUCCESS;
}
static int
k10sensor_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
K10DBG(CE_NOTE, "Inside "KMODNAME"_close #%d", DEV2INST(dev));
return otyp != OTYP_CHR ? EINVAL : DDI_SUCCESS;
}
static int
k10sensor_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
char buf[1024]; /* 19 lines a ~40 chars == 760 */
char *str;
int inst = DEV2INST(dev);
size_t blen, len = uiop->uio_resid;
k10sensor_t *k10p = ddi_get_soft_state(statep, inst);
K10DBG(CE_NOTE, "Inside "KMODNAME"_read #%d", inst);
if (len == 0 || k10p == NULL) {
return (0);
}
str = dumpCpuInfo(buf, k10p->v);
str[0] = '\n'; str++; str[0] = '\0';
dumpTctrl(str, k10p->pci_hdl, k10p->v);
blen = strlen(buf);
if (uiop->uio_offset < 0 || blen < uiop->uio_offset) {
return EINVAL;
}
if (len > blen - uiop->uio_offset) {
len = blen - uiop->uio_offset;
}
if (len == 0) {
return 0;
}
return (uiomove((void *)(buf + uiop->uio_offset), len, UIO_READ, uiop));
}
static int
k10sensor_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
int *rvalp)
{
cpu_vars_t *v;
int inst = DEV2INST(dev);
k10sensor_t *k10p = ddi_get_soft_state(statep, inst);
K10DBG(CE_NOTE, "Inside "KMODNAME"_ioctl #%d", inst);
if (k10p == NULL) {
return (ENXIO);
}
if (! (mode & FREAD)) {
return (EACCES);
}
v = k10p->v;
switch (cmd) {
default:
return (ENOTTY);
case K10IOC_INFO:
/* cpu_vars_t has same size for 32 and 64bit -> no conv needed */
if (ddi_copyout(v, (void *)arg, sizeof (*v), mode)) {
return (EFAULT);
}
break;
case K10IOC_TCTRL:
{
int32_t t[] = { 0, 0 };
if (v->xfamilyModel < 0x04) {
/* n/a for Athlon64/Opteron */
} else {
t[0] = getControlTemp(k10p->pci_hdl, v, 0);
if (v->flags & DC_0Fh) {
/* 0Fh and DualCore */
t[1] = getControlTemp(k10p->pci_hdl, v, 1);
}
}
if (ddi_copyout(t, (void *)arg, sizeof (t), mode)) {
return (EFAULT);
}
}
break;
case K10IOC_RHTC:
fillHtc(k10p->pci_hdl, v);
break;
case K10IOC_RSTC:
fillStc(k10p->pci_hdl, v);
break;
case K10IOC_CONF:
fetchProperties(k10p);
break;
}
/* re-read limits */
return (DDI_SUCCESS);
}