/*-
 * Copyright (c) 2007 Thomas Hurst <tom@hur.st>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#define K8TEMP_VERSION "0.3.0"

/*
 * k8temp -- AMD K8 (AMD64, Opteron) on-die thermal sensor reader for FreeBSD.
 *
 * DragonFlyBSD should work fine, since it has pretty much the same /dev/pci.
 * OpenBSD should work with -DWITHOUT_PCIOCGETCONF using a kernel with USER_PCI.
 * NetBSD might work with -DWITH_LIBPCI, but it's only been vaguely build tested.
 */

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sysexits.h>
#include <unistd.h>

#include "k8temp_pci.h"
#include "k8temp.h"

static int debug = 0;

static void
usage(int exit_code)
{
	fprintf((exit_code == EX_OK ? stdout : stderr),
	        "%s\n%s\n%s\n%s\n%s\n",
	        "usage: k8temp [-nd] [cpu[:core[:sensor]] ...] | [-v | -h]",
	        "  -d    Dump debugging info",
	        "  -h    Display this help text",
	        "  -n    Only display values",
	        "  -v    Display version information");
	exit(exit_code);
}

static void
version(void)
{
	printf("k8temp v%s\nCopyright 2007 Thomas Hurst <tom@hur.st>\n", K8TEMP_VERSION);
	exit(EX_OK);
}

static void
check_cpuid(void)
{
	unsigned int vendor[3];
	unsigned int maxeid,cpuid,pwrmgt,unused;
	int i;

	asm("cpuid": "=a" (unused), "=b" (vendor[0]), "=c" (vendor[2]), "=d" (vendor[1]) : "a" (0));
	asm("cpuid": "=a" (cpuid), "=b" (unused), "=c" (unused), "=d" (unused) : "a" (1));

	if (debug)
		fprintf(stderr, "CPUID: Vendor: %.12s, Id=0x%x Model=%d Family=%d Stepping=%d\n",
		        (char *)vendor, cpuid, (cpuid >> 4) & 0xf, (cpuid >> 8) & 0xf, cpuid & 0xf);

	if (0 != memcmp((char *)&vendor, "AuthenticAMD", (size_t) 12))
		errx(EX_UNAVAILABLE, "Only AMD CPU's are supported by k8temp");

	asm("cpuid": "=a" (maxeid), "=b" (unused), "=c" (unused), "=d" (unused) : "a" (CPUID_EXTENDED));

	if (maxeid >= CPUID_POWERMGT)
	{
		asm("cpuid": "=a" (unused), "=b" (unused), "=c" (unused), "=d" (pwrmgt) : "a" (CPUID_POWERMGT));
		if (debug)
		{
			/* overkill \o/ */
			fprintf(stderr, "Advanced Power Management=0x%x\n", pwrmgt);
			i = 0;
			do
			{
				fprintf(stderr, " %20s: %s\n", advPwrMgtFlags[i],
				        (pwrmgt >> i) & 1 ? "Yes" : "No");
			}
			while (advPwrMgtFlags[++i]);
		}
		if (!(pwrmgt & 1))
			errx(EX_UNAVAILABLE, "Thermal sensor support not reported in CPUID");
	}
	else
		errx(EX_UNAVAILABLE, "CPU lacks Advanced Power Management support");

	/* Linux only checks these 3 */
	if (cpuid == 0xf40 || cpuid == 0xf50 || cpuid == 0xf51)
		errx(EX_UNAVAILABLE, "This CPU stepping does not support thermal sensors.");
}

static int
get_temp(k8_pcidev dev, int core, int sensor)
{
	static int thermtp = 0;
	uint32_t ctrl,therm;

	if (!k8_pci_read_byte(dev, THERM_REG, &ctrl))
		return(TEMP_ERR);

	if (core == 1) /* CPU0 has the bit set according to datasheet */
		ctrl &= ~SEL_CORE;
	else if (core == 0)
		ctrl |= SEL_CORE;
	else return(TEMP_ERR);

	if (sensor == 0)
		ctrl &= ~SEL_SENSOR;
	else if (sensor == 1)
		ctrl |= SEL_SENSOR;
	else return(TEMP_ERR);

	if (!k8_pci_write_byte(dev, THERM_REG, ctrl))
		return(TEMP_ERR);

	if (!k8_pci_read_word(dev, THERM_REG, &therm))
		return(TEMP_ERR);

	/* verify the selection took */
	if ((ctrl & (SEL_CORE|SEL_SENSOR)) != (therm & (SEL_CORE|SEL_SENSOR)))
		return(TEMP_ERR);

	if (THERMTRIP(therm) && !thermtp)
	{
		fprintf(stderr, "Thermal trip bit set, system overheating?\n");
		thermtp = 1;
	}

	if (debug)
		fprintf(stderr, "Thermtrip=0x%08x (CurTmp=0x%02x (%dc) TjOffset=0x%02x DiodeOffset=0x%02x (%dc))\n",
		        therm, CURTMP(therm), CURTMP(therm) + TEMP_MIN, TJOFFSET(therm),
		        DIODEOFFSET(therm), OFFSET_MAX - DIODEOFFSET(therm));

	return(CURTMP(therm) + TEMP_MIN);
}

int
main(int argc, char *argv[])
{
	k8_pcidev devs[MAX_CPU];
	unsigned int cpucount,cpu,core,sensor;
	int temp;
	int exit_code = EX_UNAVAILABLE;
	int opt;
	int value_only = 0;
	int i;
	char selections[MAX_CPU][MAX_CORE][MAX_SENSOR];
	int selcount,selected = 0;

	while ((opt = getopt(argc, argv, "dhnv")) != -1)
		switch (opt) {
		case 'd':
			debug = 1;
			break;
		case 'n':
			value_only = 1;
			break;
		case 'v':
			version();
		case 'h':
			usage(EX_OK);
		default:
			usage(EX_USAGE);
		}

	bzero(&selections, sizeof(selections));
	for (i = optind; i < argc; i++)
	{
		selcount = sscanf(argv[i], "%u:%u:%u", &cpu, &core, &sensor);
		selected += selcount;
		if (selcount > 0 && cpu >= MAX_CPU)
			errx(EX_USAGE, "CPU selector %d out of range 0-%d", cpu, MAX_CPU - 1);
		if (selcount > 1 && core >= MAX_CORE)
			errx(EX_USAGE, "Core selector %d out of range 0-%d", cpu, MAX_CORE - 1);
		if (selcount > 2 && sensor >= MAX_SENSOR)
			errx(EX_USAGE, "Sensor selector %d out of range 0-%d", cpu, MAX_SENSOR - 1);
		switch (selcount)
		{
		case 1:
			memset(selections[cpu], 1, sizeof(selections[cpu]));
			break;
		case 2:
			memset(selections[cpu][core], 1, sizeof(selections[cpu][core]));
			break;
		case 3:
			selections[cpu][core][sensor] = 1;
			break;
		case 0:
		default:
			warnx("Illegal selector: %s", argv[i]);
			usage(EX_USAGE);
		}
	}

	check_cpuid();
	k8_pci_init();

	cpu = 0;
	cpucount = k8_pci_vendor_device_list(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_K8_MISC_CTRL,
	                                     devs, MAX_CPU);
	for (cpu=0; cpu <= cpucount; cpu++)
	{
		for (core = 0; core < MAX_CORE; core++)
		{
			for (sensor = 0; sensor < MAX_SENSOR; sensor++)
			{
				if (selected > 0 && !selections[cpu][core][sensor])
					continue;

				temp = get_temp(devs[cpu], core, sensor);
				if (temp > TEMP_MIN)
				{
					if (value_only)
						printf("%d\n", temp);
					else
						printf("CPU %d Core %d Sensor %d: %dc\n", cpu, core, sensor, temp);
					exit_code = EX_OK;
				}
			}
		}
	}

	k8_pci_close();
	exit(exit_code);
}



syntax highlighted by Code2HTML, v. 0.9.1