/**
* $Id: k10sensor_info.c 756 2012-06-30 06:36: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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <libdevinfo.h>
#include "k10sensor.h"
#include "../configure.h"
#define AMD_VENDOR_ID 0x1022
#define AMD_0Fh_MISC_CONTROL_DEV_ID 0x1103 /* Athlon64/Opteron (early K8) */
#define AMD_10h_MISC_CONTROL_DEV_ID 0x1203 /* K10 */
#define AMD_11h_MISC_CONTROL_DEV_ID 0x1303 /* Mobiles? */
#define AMD_12h_14h_MISC_CONTROL_DEV_ID 0x1703 /* Llano, Brazos (C/E/G) */
#define AMD_15h_MISC_CONTROL_DEV_ID 0x1603 /* Bulldozer */
#ifndef LINKMOD_FILE
#define LINKMOD_FILE "/usr/lib/defsadm/linkmod/"KMODNAME"_link.so"
#endif
static const uint32_t knownDevId[] = {
AMD_0Fh_MISC_CONTROL_DEV_ID,
AMD_10h_MISC_CONTROL_DEV_ID,
AMD_11h_MISC_CONTROL_DEV_ID,
AMD_12h_14h_MISC_CONTROL_DEV_ID,
AMD_15h_MISC_CONTROL_DEV_ID
};
static boolean_t
getCPUid(uint32_t *eax, uint32_t *ebx) {
uint32_t regs[4];
int device = open("/dev/cpu/self/cpuid", O_RDONLY);
if (device == -1) {
return B_FALSE;
}
if (pread(device, regs, sizeof (regs), 0x80000001) != sizeof (regs)) {
(void) close(device);
return B_FALSE;
}
(void) close(device);
*eax = regs[0];
*ebx = regs[1];
return B_TRUE;
}
/**
* Dump CPU temperature data by directly accessing the related PCI devices
* @return number of CPUs found.
*/
static int
dumpDirect()
{
struct pci_id_match match;
struct pci_device_iterator *iter;
cpu_vars_t cv[1];
uint32_t eax, ebx;
PCI_HDL pci_hdl;
char buf[1024];
char *str;
/* since we know, that a box can't have mixed CPU families, we do
not cycle through all knownDevIds but obtain the info directly */
if (!getCPUid(&eax, &ebx)) {
perror("Couldn't obtain CPU ID infos");
return (0);
}
bzero(cv, sizeof(cv));
cv->xfamilyModel = AMD_XFAMILY_MODEL(eax);
cv->modelStepping = AMD_MODEL_STEPPING(eax);
cv->ebx = ebx;
fillCategoryIndex(cv);
/* Actually some different silicon revision CPUs can be mixed in a box
* (see Revision Guide if in doubt).
* However, to keep this easy, we assume here, all have the same. The
* kernel driver handles each CPU separately - provides the final word. */
fillSiliconRevision(cv);
fillErratum(cv);
if (cv->xfamilyModel < 0x04) {
match.device_id = AMD_0Fh_MISC_CONTROL_DEV_ID;
} else if (cv->xfamilyModel < 0x10) {
match.device_id = AMD_0Fh_MISC_CONTROL_DEV_ID;
} else if (cv->xfamilyModel < 0x20) {
match.device_id = AMD_10h_MISC_CONTROL_DEV_ID;
} else if (cv->xfamilyModel < 0x30) {
match.device_id = AMD_11h_MISC_CONTROL_DEV_ID;
} else if (cv->xfamilyModel < 0x60) {
match.device_id = AMD_12h_14h_MISC_CONTROL_DEV_ID;
} else {
match.device_id = AMD_15h_MISC_CONTROL_DEV_ID;
}
if (pci_system_init()) {
perror("Couldn't initialize PCI system");
return (0);
}
match.vendor_id = AMD_VENDOR_ID;
match.subdevice_id = PCI_MATCH_ANY;
match.subvendor_id = PCI_MATCH_ANY;
match.device_class = 0; /* PCI_CLASS_BRIDGE, but IDs are sufficient ;-) */
match.device_class_mask = 0;
iter = pci_id_match_iterator_create(&match);
eax = 0;
while ((pci_hdl = pci_device_next(iter)) != NULL) {
cv->chipId = pci_hdl->dev - 0x18;
fillThermtrip(pci_hdl, cv);
fillNBcapabilities(pci_hdl, cv);
fillHtc(pci_hdl, cv);
fillStc(pci_hdl, cv);
str = dumpCpuInfo(buf, cv);
str[0] = '\n'; str++; str[0] = '\0';
dumpTctrl(str, pci_hdl, cv);
fprintf(stdout, "%s\n", buf);
eax++;
}
pci_iterator_destroy(iter);
pci_system_cleanup();
return (eax);
}
/**
* Simple helper function to check, whether all chars of the given string
* are digits.
* @param p string to check
* @return B_FALSE if p is NULL or empty or contains a non-digit char.
*/
static boolean_t
allDigits(char *p) {
if (p == NULL || *p == '\0') {
return B_FALSE;
}
for (; *p != '\0'; p++) {
if (!isdigit(*p)) {
return B_FALSE;
}
}
return B_TRUE;
}
#pragma inline(allDigits)
static int
dumpIoctl(char *fname) {
static cpu_vars_t v[1];
static char buf[1024];
int fd, res = 0;
int32_t t[2];
if ((fd = open(fname, O_RDONLY|O_NOCTTY)) == -1) {
sprintf(buf, "Unable to open file %s", fname);
perror(buf);
return 0;
}
/* now fetch data */
bzero(v, sizeof(v));
if (ioctl(fd, K10IOC_INFO, v) == -1) {
perror("ioctl K10IOC_INFO failed");
goto done;
}
dumpCpuInfo(buf, v);
res++;
if (ioctl(fd, K10IOC_TCTRL, t) == -1) {
perror("ioctl K10IOC_TCTRL failed");
goto done;
}
strcat(buf, "\n");
if (v->flags & DC_0Fh) {
sprintf(buf + strlen(buf), "%22s:\t%2.1f\n", "T control Core 0 [C]",
t[0] == 0 ? 0 : (t[0] - v->diode_offset)/1000.0);
sprintf(buf + strlen(buf), "%22s:\t%2.1f\n", "T control Core 1 [C]",
t[1] == 0 ? 0 : (t[1] - v->diode_offset)/1000.0);
} else {
sprintf(buf + strlen(buf), "%22s:\t%2.1f\n", "T control [C]",
t[0] == 0 ? 0 : (t[0] - v->diode_offset)/1000.0);
}
done:
if (res > 0) {
fprintf(stdout, "%s\n", buf);
}
if (close(fd) != 0) {
sprintf(buf, "Unable to close file %s", fname);
perror(buf);
}
return (res);
}
static int
devfs_entry(di_node_t node, di_minor_t minor, void *list)
{
char *name = NULL;
int instance;
int *count = (int *) list;
if (di_minor_spectype(minor) != S_IFCHR) {
return (DI_WALK_CONTINUE);
}
name = di_minor_name(minor); /* di_driver_name() would work as well */
if (name == NULL || strcmp(KMODNAME, name) != 0) {
return (DI_WALK_CONTINUE);
}
name = di_devfs_minor_path(minor);
*count += dumpIoctl(++name); /* need to remove leading / of /pci... */
return (DI_WALK_CONTINUE);
}
/**
* Dump CPU temperature data by accessing the kernel modul via ioctl()s.
* @return number of CPUs found.
*/
static int
dump() {
int count;
struct stat st;
if (stat(LINKMOD_FILE, &st) != 0) {
di_node_t node;
if (chdir("/devices") != 0) {
perror("Unable to cd into /devices/");
return 0;
}
/* we could walk through 'pci[:xdigit:]+,[:xdigit:]+/' and
find all char dev files which match
'@pci1022,1[:xdigit:]03@[:xdigit:]+,3:k10sensor' but better is: */
node = di_init("/", DINFOCACHE);
di_walk_minor(node, DDI_PSEUDO, DI_CHECK_ALIAS, &count, devfs_entry);
di_fini(node);
if (count > 0) {
fprintf(stderr, "You should install "LINKMOD_FILE "\n"
"and run devfsadm -C to get access via /dev/"KMODNAME"/ !\n");
}
} else {
DIR *dirp;
struct dirent *dp;
if ((dirp = opendir("/dev/"KMODNAME)) == 0) {
perror("Unable to open /dev/"KMODNAME"/");
return 0;
}
if (fchdir(((int *)dirp)[0]) != 0) { /* S10 sucks */
perror("Unable to cd into /dev/"KMODNAME"/");
(void) closedir(dirp);
return 0;
}
while ((dp = readdir(dirp)) != NULL) {
/* some sanity checks first */
if (!allDigits(dp->d_name)) {
continue;
}
if ((stat(dp->d_name, &st) != 0) || !S_ISCHR(st.st_mode)) {
continue;
}
count += dumpIoctl(dp->d_name);
}
(void) closedir(dirp);
}
return (count);
}
/**
* Print out help info.
*/
void
usage(char* progname) {
fprintf(stderr, "Usage: %s [-h] [-d]\n\n"
"Find all AMD CPUs and print out their temperature related data.\n\n"
"Options:\n\n"
" -h .. print this help and exit.\n"
" -d .. obtain the data directly via PCI devices. Otherwise\n"
" obtain the data from the kernel driver (default).\n"
, progname);
}
int
main(int argc, char** argv) {
int c;
boolean_t viaPCI = B_FALSE;
while ((c = getopt(argc, argv, "hd")) != -1) {
switch (c) {
case 'h': usage(argv[0]); return (EXIT_SUCCESS);
case 'd': viaPCI = B_TRUE; break;
case '?': fprintf(stderr, "Unknown option -%c ignored\n", optopt);
}
}
c = (viaPCI ? dumpDirect() : dump());
if (c == 0) {
fprintf(stderr, "No AMD CPUs found!\n");
}
return (EXIT_SUCCESS);
}
/* vim: tabstop=4:shiftwidth=4:noexpandtab
*/