/**
* $Id: k10.c 488 2012-07-01 21:49:18Z 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 usr/src/OPENSOLARIS.LICENSE
* or 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 the appropriate
* files. 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]
*
* CDDL HEADER END
*/
/*
* Copyright 2012 Jens Elkner. All rights reserved.
* Use is subject to license terms.
*/
/*
* A GKrellm AddOns aka data-only/system plugin to monitor temperatures of
* AMD CPUs utilizing the k10sensor kernel driver's kstats.
*/
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <glib.h>
#include <gmodule.h>
#include <kstat.h>
#include <sysplugin.h>
#include <k10sensor_kstats.h>
#ifdef ENABLE_DTRACE
#include "probes.h"
#else
#define GKRELLM_STATS_DEBUG(arg0)
#define GKRELLM_STATS_DEBUG_ENABLED() (0)
#endif
#define MODULE_NAME "k10sensor"
#define STARTS_WITH(name, prefix) \
strncmp((name), (prefix), strlen((prefix))) == 0
static int dev_count = -1;
static uint data_per_dev = 1;
static kstat_ctl_t *kc = NULL;
static kstat_t **data_table = NULL;
static kstat_t **info_table = NULL;
static int *diode_offsets = NULL;
static kid_t lastCID = -1;
/**
* Automatically called when the module gets loaded.
* @param module the GModule structure corresponding to this module.
* @return NULL on success, a string describing the initialization error
* otherwise.
*/
const gchar *
g_module_check_init(GModule *module) {
if ((kc = kstat_open()) == NULL) {
return ("kstat_open failed");
}
if (kstat_chain_update(kc) == - 1) {
return ("kstat_chain_update failed");
}
return NULL;
}
/**
* Automatically called when the modul gets unloaded.
* Use it to free any resources aquired by this addon.
* @param module the GModule structure corresponding to this module.
*/
void
g_module_unload(GModule *module) {
if (kc != NULL) {
kstat_close(kc);
kc = NULL;
}
if (info_table != NULL) {
g_free(info_table);
info_table = NULL;
}
if (data_table != NULL) {
g_free(data_table);
data_table = NULL;
}
if (diode_offsets != NULL) {
g_free(diode_offsets);
diode_offsets = NULL;
}
dev_count = -1;
data_per_dev = 1;
lastCID = -1;
}
/**
* Get the number of all accessible data sources of the given device type.
* Accessible in this context means, the datasource is available/working
* and the addOn is able to read its current aka measured value.
* @return a number &gt;= 0.
*/
guint8
getCount(DeviceType type) {
kstat_t *ksp, *kspt = NULL;
int max = -1;
if (type != SENSOR) {
return 0;
}
if (dev_count >= 0) {
return dev_count;
}
if (kstat_chain_update(kc) == - 1) {
return 0;
}
if (GKRELLM_STATS_DEBUG_ENABLED()) {
gchar *msg = g_strdup_printf("kid change %d", lastCID);
GKRELLM_STATS_DEBUG(msg);
g_free(msg);
}
for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
if (strcmp(ksp->ks_module, MODULE_NAME)) {
continue;
}
if (STARTS_WITH(ksp->ks_name, K10S_KSTAT_DATA_NAME_PREFIX)) {
kspt = ksp;
} else if (STARTS_WITH(ksp->ks_name, K10S_KSTAT_INFO_NAME) == 0
&& ksp->ks_instance > max)
{
max = ksp->ks_instance;
}
}
if (kspt == NULL) {
dev_count = 0; /* Athlon64/Opteron || no AMD CPUs */
} else {
dev_count = max + 1;
data_per_dev = (strstr(kspt->ks_name, "core") != NULL) ? 2 : 1;
}
/* 0 usually causes, that update() below gets never called */
return dev_count * data_per_dev;
}
static gboolean
checkKstats() {
kstat_t *ksp;
int info_count = dev_count, data_count = dev_count * data_per_dev;
if (kstat_chain_update(kc) == - 1) {
perror("kstat_chain_update failed");
return FALSE;
}
if (lastCID == kc->kc_chain_id) {
return TRUE;
}
lastCID = kc->kc_chain_id;
if (GKRELLM_STATS_DEBUG_ENABLED()) {
gchar *msg = g_strdup_printf("kid change %d", lastCID);
GKRELLM_STATS_DEBUG(msg);
g_free(msg);
}
for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
if (strcmp(ksp->ks_module, MODULE_NAME)
|| ksp->ks_instance >= dev_count)
{
continue;
}
if (STARTS_WITH(ksp->ks_name, K10S_KSTAT_INFO_NAME)) {
info_table[ksp->ks_instance] = ksp;
info_count--;
} else if (STARTS_WITH(ksp->ks_name, K10S_KSTAT_DATA_NAME_PREFIX)) {
if (data_per_dev != 1) {
int off = ksp->ks_name[strlen(ksp->ks_name)-1] == '0' ? 0 : 1;
data_table[ksp->ks_instance * data_per_dev + off] = ksp;
} else {
data_table[ksp->ks_instance] = ksp;
}
data_count--;
}
if (data_count == 0 && info_count == 0) {
break;
}
}
return TRUE;
}
/**
* Initialize the n-th data source of the given device type and return its basic
* parameters. The ID is given by the callee, all other parameters must be
* filled in by the module. The callee creates a copy of these values on
* reception and frees all strings, so keeping a reference to these fields is
* useless and later access may run into segfaults.
* <p>
* Per contract this function gets never called as long as #getCount(DeviceType)
* returns 0.
*
* @param type The type aka category of device, the data source belongs to.
* @param id ID, which will be used by the callee to query value updates
* for the given data source. The ID is in the range of
* 0 .. #getCount(DeviceType)-1 wrt. the given device type.
* @param name The human readable name of the data source. Should be short
* (i.e. not longer than 40 characters) and sufficient to let a user
* identify the data source (so generic names are discouraged). Gets
* usually exposed to the UI as is. The string must be nul-terminated and
* might be truncated at the size of 128 chars by gkrellm.
* @param desc An optional, more detailed description of the data source.
* Use NULL if n/a. Might be exposed to the UI as tooltip or explicit text.
* The string must be nul-terminated.
* @param label The label for the sensor to be used. Usually an abbrev. of
* the <var>name</var>, which is displayed as a treenode in a GUI or chart.
* So it should not be longer than 10 characters. The string must be
* nul-terminated and might be truncated at the size of 64 chars by
* gkrellm.
* @param min The lower limit of the data source range.
* Use G_MININT32 if n/a.
* @param max The upper limit of the data source range.
* Use G_MAXINT32 if n/a.
* @param lowThreshold The low threshold a data source may have. E.g. wrt. to
* a temperature sensor an indicator when to start drinking whisky and wine.
* Use G_MININT32 if n/a.
* @param highThreshold The high threshold a data source may have. E.g. Nvidia
* GPUs have usually a high threshold (read-only), which determines when
* to slow down the GPU. Use G_MAXINT32 if n/a.
* @return TRUE on success (i.e. the callee can assume, that successive calls to
* #updateSource(DeviceType, int) can be done and return correct values.
*/
gboolean
init(DeviceType type, guint8 id, gchar **name, gchar **desc, gchar **label,
gint *min, gint *max, gint *lowThreshold, gint *highThreshold)
{
kstat_named_t *knp;
kstat_t *ksp;
int tcrit;
int dev_id = id;
/* for paranoid people */
if (type != SENSOR || id > (dev_count * data_per_dev -1)) {
return FALSE;
}
if (info_table == NULL) {
info_table = calloc(dev_count, sizeof(ksp));
if (info_table == NULL) {
perror("k10 init: info table alloc");
return FALSE;
}
data_table = calloc(dev_count * data_per_dev, sizeof(ksp));
if (data_table == NULL) {
perror("k10 init: data table init");
free(info_table);
info_table == NULL;
return FALSE;
}
diode_offsets = calloc(dev_count, sizeof(int));
if (diode_offsets == NULL) {
perror("k10 init: offset table");
free(info_table);
free(data_table);
return FALSE;
}
}
if (!checkKstats()) {
return FALSE;
}
if (data_per_dev != 1) {
dev_id = id/data_per_dev;
*name = g_strdup_printf("CPU %d Core %d Temperature", dev_id, id & 1);
*label = g_strdup_printf("C%d:%d T", dev_id, id & 1);
} else {
*name = g_strdup_printf("CPU %d Temperature", dev_id);
*label = g_strdup_printf("C%d T", dev_id);
}
*desc = "Is not a phys. temperature, i.e. a value of 70 may point to 80°C";
ksp = info_table[dev_id];
*lowThreshold = G_MININT32;
if (ksp == NULL || kstat_read(kc, ksp, NULL) == -1) {
*min = G_MININT32;
*max = G_MAXINT32;
*highThreshold = G_MAXINT32;
return FALSE;
}
knp = ksp->ks_data;
/* Athlon64/Opteron provides no Tctrl -> have to deal with 0Fh+, only */
diode_offsets[dev_id] = knp[KS_T_OFFSET].value.i32;
*max = 256000 - diode_offsets[dev_id];
*min = -diode_offsets[dev_id];
if (strcmp(knp[KS_FAMILY].value.c, "0Fh") == 0) {
max -= 49000;
min -= 49000;
/* 0Fh: HTC info inaccessible - use STC instead */
*highThreshold = knp[KS_ATC_T_HYST].value.i32;
tcrit = knp[KS_ATC_T_CRIT].value.i32;
} else {
*highThreshold = knp[KS_HTC_T_HYST].value.i32;
tcrit = knp[KS_HTC_T_CRIT].value.i32;
}
if (tcrit != G_MAXINT32) {
char buf[128];
strcpy(buf, *name);
g_free(*name);
*name = g_strdup_printf("%s (max. %d C)", buf, tcrit/1000);
}
return TRUE;
}
/**
* Update the current value of the source with the given ID and type.
* <p>
* Per contract this function gets never called as long as #getCount(DeviceType)
* returns 0.
* @param type type of the device
* @param id the ID assigned by the callee (see #initSensor())
* @param value the field, where the current value should be stored
* @return TRUE if the update was successful.
*/
gboolean
update(DeviceType type, guint8 id, int *value) {
kstat_named_t *knp;
kstat_t *ksp;
/* for paranoid people */
if (type != SENSOR || id > (dev_count * data_per_dev -1)) {
return FALSE;
}
if (!checkKstats()) {
return FALSE;
}
ksp = data_table[id];
if (ksp == NULL || kstat_read(kc, ksp, NULL) == -1) {
return FALSE;
}
knp = ksp->ks_data;
*value = knp[0].value.i32;
if (*value == 0) {
return FALSE;
}
*value -= diode_offsets[id/data_per_dev];
return TRUE;
}
#ifndef PIC
int
main(int argc, char **argv) {
uint data, i;
if (g_module_check_init(NULL)) {
exit(1);
}
data = getCount(SENSOR);
if (data == 0) {
printf("No temperature data for AMD CPUs available\n");
g_module_unload(NULL);
exit(0);
}
for (i=0; i < data; i++) {
gchar *name = NULL, *desc = NULL, *label = NULL;
gint min = 0, max = 0, low = 0, high = 0, value = 0;
if (init(SENSOR, i, &name, &desc, &label, &min, &max, &low, &high)) {
printf("%14s: %d\n", "Id", i);
printf("%14s: %s\n", "Name", name);
printf("%14s: %s\n", "Label", label);
printf("%14s: %s\n", "Description", *desc == NULL ? "null" : desc);
printf("%14s: %d..%d\n", "Min..Max", min, max);
printf("%14s: %d\n", "Low Threshold", low);
printf("%14s: %d\n", "High Threshold", high);
if (update(SENSOR, i, &value)) {
printf("%14s: %d\n", "Current Value", value);
} else {
printf("%14s: %s\n", "Current Value", "update failed");
}
} else {
printf("init for data %d failed\n", i);
}
}
g_module_unload(NULL);
exit(0);
}
#endif