opnsense-src/sys/dev/amdtemp/amdtemp.c
Attilio Rao 444b91868b Make the newbus subsystem Giant free by adding the new newbus sxlock.
The newbus lock is responsible for protecting newbus internIal structures,
device states and devclass flags. It is necessary to hold it when all
such datas are accessed. For the other operations, softc locking should
ensure enough protection to avoid races.

Newbus lock is automatically held when virtual operations on the device
and bus are invoked when loading the driver or when the suspend/resume
take place. For other 'spourious' operations trying to access/modify
the newbus topology, newbus lock needs to be automatically acquired and
dropped.

For the moment Giant is also acquired in some key point (modules subsystem)
in order to avoid problems before the 8.0 release as module handlers could
make assumptions about it. This Giant locking should go just after
the release happens.

Please keep in mind that the public interface can be expanded in order
to provide more support, if there are really necessities at some point
and also some bugs could arise as long as the patch needs a bit of
further testing.

Bump __FreeBSD_version in order to reflect the newbus lock introduction.

Reviewed by:    ed, hps, jhb, imp, mav, scottl
No answer by:   ariff, thompsa, yongari
Tested by:      pho,
                G. Trematerra <giovanni dot trematerra at gmail dot com>,
                Brandon Gooch <jamesbrandongooch at gmail dot com>
Sponsored by:   Yahoo! Incorporated
Approved by:	re (ksmith)
2009-08-02 14:28:40 +00:00

379 lines
9.3 KiB
C

/*-
* Copyright (c) 2008, 2009 Rui Paulo <rpaulo@FreeBSD.org>
* Copyright (c) 2009 Norikatsu Shigemura <nork@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Driver for the AMD K8/K10/K11 thermal sensors. Initially based on the
* k8temp Linux driver.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <machine/specialreg.h>
#include <machine/cpufunc.h>
#include <machine/md_var.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
typedef enum {
SENSOR0_CORE0,
SENSOR0_CORE1,
SENSOR1_CORE0,
SENSOR1_CORE1,
CORE0,
CORE1
} amdsensor_t;
struct amdtemp_softc {
device_t sc_dev;
int sc_temps[4];
int sc_ntemps;
struct sysctl_oid *sc_oid;
struct sysctl_oid *sc_sysctl_cpu[2];
struct intr_config_hook sc_ich;
int32_t (*sc_gettemp)(device_t, amdsensor_t);
};
#define VENDORID_AMD 0x1022
#define DEVICEID_AMD_MISC0F 0x1103
#define DEVICEID_AMD_MISC10 0x1203
#define DEVICEID_AMD_MISC11 0x1303
static struct amdtemp_product {
uint16_t amdtemp_vendorid;
uint16_t amdtemp_deviceid;
} amdtemp_products[] = {
{ VENDORID_AMD, DEVICEID_AMD_MISC0F },
{ VENDORID_AMD, DEVICEID_AMD_MISC10 },
{ VENDORID_AMD, DEVICEID_AMD_MISC11 },
{ 0, 0 }
};
/*
* Register control (K8 family)
*/
#define AMDTEMP_REG0F 0xe4
#define AMDTEMP_REG_SELSENSOR 0x40
#define AMDTEMP_REG_SELCORE 0x04
/*
* Register control (K10 & K11) family
*/
#define AMDTEMP_REG 0xa4
#define TZ_ZEROC 2732
/* -49 C is the mininum temperature */
#define AMDTEMP_OFFSET0F (TZ_ZEROC-490)
#define AMDTEMP_OFFSET (TZ_ZEROC)
/*
* Device methods.
*/
static void amdtemp_identify(driver_t *driver, device_t parent);
static int amdtemp_probe(device_t dev);
static int amdtemp_attach(device_t dev);
static void amdtemp_intrhook(void *arg);
static int amdtemp_detach(device_t dev);
static int amdtemp_match(device_t dev);
static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor);
static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor);
static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS);
static device_method_t amdtemp_methods[] = {
/* Device interface */
DEVMETHOD(device_identify, amdtemp_identify),
DEVMETHOD(device_probe, amdtemp_probe),
DEVMETHOD(device_attach, amdtemp_attach),
DEVMETHOD(device_detach, amdtemp_detach),
{0, 0}
};
static driver_t amdtemp_driver = {
"amdtemp",
amdtemp_methods,
sizeof(struct amdtemp_softc),
};
static devclass_t amdtemp_devclass;
DRIVER_MODULE(amdtemp, hostb, amdtemp_driver, amdtemp_devclass, NULL, NULL);
static int
amdtemp_match(device_t dev)
{
int i;
uint16_t vendor, devid;
vendor = pci_get_vendor(dev);
devid = pci_get_device(dev);
for (i = 0; amdtemp_products[i].amdtemp_vendorid != 0; i++) {
if (vendor == amdtemp_products[i].amdtemp_vendorid &&
devid == amdtemp_products[i].amdtemp_deviceid)
return (1);
}
return (0);
}
static void
amdtemp_identify(driver_t *driver, device_t parent)
{
device_t child;
/* Make sure we're not being doubly invoked. */
if (device_find_child(parent, "amdtemp", -1) != NULL)
return;
if (amdtemp_match(parent)) {
child = device_add_child(parent, "amdtemp", -1);
if (child == NULL)
device_printf(parent, "add amdtemp child failed\n");
}
}
static int
amdtemp_probe(device_t dev)
{
uint32_t regs[4];
if (resource_disabled("amdtemp", 0))
return (ENXIO);
do_cpuid(1, regs);
switch (regs[0]) {
case 0xf40:
case 0xf50:
case 0xf51:
return (ENXIO);
}
device_set_desc(dev, "AMD K8 Thermal Sensors");
return (BUS_PROBE_GENERIC);
}
static int
amdtemp_attach(device_t dev)
{
struct amdtemp_softc *sc = device_get_softc(dev);
struct sysctl_ctx_list *sysctlctx;
struct sysctl_oid *sysctlnode;
/*
* Setup intrhook function to create dev.cpu sysctl entries. This is
* needed because the cpu driver may be loaded late on boot, after
* us.
*/
sc->sc_ich.ich_func = amdtemp_intrhook;
sc->sc_ich.ich_arg = dev;
if (config_intrhook_establish(&sc->sc_ich) != 0) {
device_printf(dev, "config_intrhook_establish "
"failed!\n");
return (ENXIO);
}
if (pci_get_device(dev) == DEVICEID_AMD_MISC0F)
sc->sc_gettemp = amdtemp_gettemp0f;
else {
sc->sc_gettemp = amdtemp_gettemp;
return (0);
}
/*
* dev.amdtemp.N tree.
*/
sysctlctx = device_get_sysctl_ctx(dev);
sysctlnode = SYSCTL_ADD_NODE(sysctlctx,
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor0",
CTLFLAG_RD, 0, "Sensor 0");
SYSCTL_ADD_PROC(sysctlctx,
SYSCTL_CHILDREN(sysctlnode),
OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD,
dev, SENSOR0_CORE0, amdtemp_sysctl, "IK",
"Sensor 0 / Core 0 temperature");
SYSCTL_ADD_PROC(sysctlctx,
SYSCTL_CHILDREN(sysctlnode),
OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD,
dev, SENSOR0_CORE1, amdtemp_sysctl, "IK",
"Sensor 0 / Core 1 temperature");
sysctlnode = SYSCTL_ADD_NODE(sysctlctx,
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor1",
CTLFLAG_RD, 0, "Sensor 1");
SYSCTL_ADD_PROC(sysctlctx,
SYSCTL_CHILDREN(sysctlnode),
OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD,
dev, SENSOR1_CORE0, amdtemp_sysctl, "IK",
"Sensor 1 / Core 0 temperature");
SYSCTL_ADD_PROC(sysctlctx,
SYSCTL_CHILDREN(sysctlnode),
OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD,
dev, SENSOR1_CORE1, amdtemp_sysctl, "IK",
"Sensor 1 / Core 1 temperature");
return (0);
}
void
amdtemp_intrhook(void *arg)
{
int i;
device_t nexus, acpi, cpu;
device_t dev = (device_t) arg;
struct amdtemp_softc *sc;
struct sysctl_ctx_list *sysctlctx;
sc = device_get_softc(dev);
/*
* dev.cpu.N.temperature.
*/
newbus_xlock();
nexus = device_find_child(root_bus, "nexus", 0);
acpi = device_find_child(nexus, "acpi", 0);
for (i = 0; i < 2; i++) {
cpu = device_find_child(acpi, "cpu",
device_get_unit(dev) * 2 + i);
if (cpu) {
sysctlctx = device_get_sysctl_ctx(cpu);
sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx,
SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)),
OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD,
dev, CORE0, amdtemp_sysctl, "IK",
"Max of sensor 0 / 1");
}
}
newbus_xunlock();
config_intrhook_disestablish(&sc->sc_ich);
}
int
amdtemp_detach(device_t dev)
{
int i;
struct amdtemp_softc *sc = device_get_softc(dev);
for (i = 0; i < 2; i++) {
if (sc->sc_sysctl_cpu[i])
sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0);
}
/* NewBus removes the dev.amdtemp.N tree by itself. */
return (0);
}
static int
amdtemp_sysctl(SYSCTL_HANDLER_ARGS)
{
device_t dev = (device_t) arg1;
struct amdtemp_softc *sc = device_get_softc(dev);
int error;
int32_t temp, auxtemp[2];
switch (arg2) {
case CORE0:
auxtemp[0] = sc->sc_gettemp(dev, SENSOR0_CORE0);
auxtemp[1] = sc->sc_gettemp(dev, SENSOR1_CORE0);
temp = imax(auxtemp[0], auxtemp[1]);
break;
case CORE1:
auxtemp[0] = sc->sc_gettemp(dev, SENSOR0_CORE1);
auxtemp[1] = sc->sc_gettemp(dev, SENSOR1_CORE1);
temp = imax(auxtemp[0], auxtemp[1]);
break;
default:
temp = sc->sc_gettemp(dev, arg2);
break;
}
error = sysctl_handle_int(oidp, &temp, 0, req);
return (error);
}
static int32_t
amdtemp_gettemp0f(device_t dev, amdsensor_t sensor)
{
uint8_t cfg;
uint32_t temp;
cfg = pci_read_config(dev, AMDTEMP_REG0F, 1);
switch (sensor) {
case SENSOR0_CORE0:
cfg &= ~(AMDTEMP_REG_SELSENSOR | AMDTEMP_REG_SELCORE);
break;
case SENSOR0_CORE1:
cfg &= ~AMDTEMP_REG_SELSENSOR;
cfg |= AMDTEMP_REG_SELCORE;
break;
case SENSOR1_CORE0:
cfg &= ~AMDTEMP_REG_SELCORE;
cfg |= AMDTEMP_REG_SELSENSOR;
break;
case SENSOR1_CORE1:
cfg |= (AMDTEMP_REG_SELSENSOR | AMDTEMP_REG_SELCORE);
break;
default:
cfg = 0;
break;
}
pci_write_config(dev, AMDTEMP_REG0F, cfg, 1);
temp = pci_read_config(dev, AMDTEMP_REG0F, 4);
temp = ((temp >> 16) & 0xff) * 10 + AMDTEMP_OFFSET0F;
return (temp);
}
static int32_t
amdtemp_gettemp(device_t dev, amdsensor_t sensor)
{
uint32_t temp;
temp = pci_read_config(dev, AMDTEMP_REG, 4);
temp = ((temp >> 21) & 0x3ff) * 10 / 8 + AMDTEMP_OFFSET;
return (temp);
}