/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * 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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Battery control/ACPI interface.
 */

#pragma ident	"@(#)batstat.c	1.16	05/10/21 SMI"

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <poll.h>
#include <unistd.h>
#include <libscf.h>

#include "acpidrv.h"

#define	DEG(k)		(((k) - 2731)/10)

#define	BATSTAT_INSTANCE	"svc:/site/batstat:default"

static void
property_getval(const char *name, int *res)
{
	scf_simple_prop_t *prop;
	int64_t *vp;

	prop = scf_simple_prop_get(NULL, BATSTAT_INSTANCE, "options", name);

	if (prop == NULL)
		return;

	vp = scf_simple_prop_next_integer(prop);

	if (vp == NULL) {
		scf_simple_prop_free(prop);
		return;
	}

	*res = (int)*vp;

	scf_simple_prop_free(prop);
}

static int lowthresh = 2;
static int shutthresh = 5;
static int enabled = 0;

static struct param {
	const char *name;
	int *valp;
} params[] = {
	{ "poweroff", &lowthresh },
	{ "shutdown", &shutthresh },
	{ "enabled", &enabled },
};

#define	NPARAM	(sizeof (params) / sizeof (struct param))

static int
dumptemp(int fd)
{
	int ntz;
	int i;
	acpi_thermal_t tht[ACPI_THERMALZONE_MAX];

	if (ioctl(fd, ACPI_THERMALZONE_COUNT, &ntz) != 0) {
		perror("ioctl(ACPI_THERMALZONE_COUNT)");
		return (1);
	}
	if (ntz == 0) {
		(void) fprintf(stderr, "No thermal zones\n");
		return (1);
	}
	if (ioctl(fd, ACPI_THERMALZONE_INFO, tht) != 0) {
		perror("ioctl(ACPI_THERMALZONE_INFO)");
		return (1);
	}

	for (i = 0; i < ntz; i++) {
		(void) printf("Thermal zone: %s, temperature = %dC, "
		"critical = %dC\n", tht[i].name,
		DEG(tht[i].tmp), DEG(tht[i].crit));

		if (tht[i].nac > 0) {
			int j;

			(void) printf("\tActive Cooling:");
			for (j = 0; j < tht[i].nac; j++) {
				if (tht[i].ac[j] > 0)
					(void) printf(" %d", DEG(tht[i].ac[j]));
			}
			(void) putchar('\n');
		}
	}
	return (0);
}

static int
showwarnstat(int fd)
{
	battery_warning_t bwarn;
	if (ioctl(fd, ACPI_BATTERY_GET_WARNING, &bwarn) != 0) {
		perror("ioctl(ACPI_BATTERY_GET_WARNING)");
		return (1);
	}
	(void) printf("Battery low shutdown %s\n",
	    bwarn.bw_enabled ? "enabled" : "disabled");
	if (bwarn.bw_enabled == 0)
		return (0);
	(void) printf("\tBattery emergency power off at %d%%\n",
	    bwarn.bw_poweroff);
	(void) printf("\tBattery low ordinary shutdown at %d%%\n",
	    bwarn.bw_shutdown);
	return (0);
}

static int
setwarnstat(int fd, int warn, int pow, int shut)
{
	battery_warning_t bwarn;
	if (ioctl(fd, ACPI_BATTERY_GET_WARNING, &bwarn) != 0) {
		perror("ioctl(ACPI_BATTERY_GET_WARNING)");
		return (1);
	}
	bwarn.bw_enabled = warn;
	if (warn) {
		if (shut >= 0)
			bwarn.bw_shutdown = shut;
		if (pow >= 0)
			bwarn.bw_poweroff = pow;
	}
	if (ioctl(fd, ACPI_BATTERY_SET_WARNING, &bwarn) != 0) {
		perror("ioctl(ACPI_BATTERY_SET_WARNING)");
		return (1);
	}
	return (0);
}

static void
batwarn_init(int fd)
{
	int i;

	for (i = 0; i < NPARAM; i++)
		property_getval(params[i].name, params[i].valp);

	(void) setwarnstat(fd, enabled, lowthresh, shutthresh);
	exit(0);
}

void
usage(int interval)
{
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "	-v	dump raw output\n");
	fprintf(stderr, "	-x	xtitle output (implies looping)\n");
	fprintf(stderr, "	-l	loop\n");
	fprintf(stderr, "	-i sec	number of seconds for each loop (default is %d)\n", interval);
	fprintf(stderr, "	-p	use poll on acpidrv\n");
	fprintf(stderr, "	-d #	ACPI VIDEO switch ????\n");
	fprintf(stderr, "	-s	display battery warning status\n");
	fprintf(stderr, "	-t	display thermal status\n");
	fprintf(stderr, "	-w	enable battery warning\n");
	fprintf(stderr, "	-W	disable battery warning\n");
	fprintf(stderr, "	-I	initialize the default batstat parameters for low battery shutdown\n");
	fprintf(stderr, "	-P #	set emergency poweroff threshold\n");
	fprintf(stderr, "	-S #	set shutdown threshold\n");
}

int
main(int argc, char **argv)
{
	int fd = open("/dev/acpidrv", O_RDWR);
	acpi_bif_t *bif;
	acpi_bst_t *bst;
	int hours;
	int minutes;
	int nbat;
	int i;
	int pow;
	int verbose = 0;
	int x = 0;
	int loop = 0;
	int c;
	int interval = 60;
	int temp;
	int tverbose = 0;
	int dos = -1;
	boolean_t pollit = B_FALSE;
	struct pollfd xfd;
	int warn = -1;
	boolean_t warnstat = B_FALSE;
	int shutdown = -1;
	int poweroff = -1;

	while ((c = getopt(argc, argv, "pvxli:d:twWsP:S:I")) != EOF) {
		switch (c) {
		case 'v':
			verbose = 1;
			break;
		case 'x':
			x = 1;
			/* FALLTHROUGH */
		case 'p':
			pollit = B_TRUE;
			/* FALLTHROUGH */
		case 'l':
			loop = 1;
			break;
		case 'i':
			interval = atoi(optarg);
			break;
		case 'd':
			dos = atoi(optarg);
			break;
		case 't':
			tverbose = 1;
			break;
		case 'w':
			warn = 1;
			break;
		case 'W':
			warn = 0;
			break;
		case 's':
			warnstat = B_TRUE;
			break;
		case 'P':
			poweroff = atoi(optarg);
			break;
		case 'S':
			shutdown = atoi(optarg);
			break;
		case 'I':
			batwarn_init(fd);
			break;
		default:
			usage(interval);
			exit(1);
		}
	}

	if (fd < 0) {
		perror("/dev/apcidrv");
		(void) close(fd);
		exit(1);
	}

	if (dos != -1) {
		(void) ioctl(fd, ACPI_VIDEO_SWITCH, dos);
		exit(0);
	}

	if (warnstat)
		exit(showwarnstat(fd));

	if (warn != -1)
		exit(setwarnstat(fd, warn, poweroff, shutdown));

	if (tverbose)
		exit(dumptemp(fd));

	if (ioctl(fd, ACPI_BATTERY_COUNT, &nbat) != 0 || nbat == 0) {
		(void) fprintf(stderr, "No battery information available.\n");
		exit(1);
	}

	bif = malloc(sizeof (*bif) * nbat);
	bst = malloc(sizeof (*bst) * nbat);

loop:
	if (ioctl(fd, ACPI_BATTERY_INFO, bif) != 0) {
		perror("ACPI_BATTERY_INFO");
		exit(1);
	}

	if (ioctl(fd, ACPI_POWER_STATUS, &pow) != 0) {
		perror("ACPI_POWER_STATUS");
		exit(1);
	}

	if (ioctl(fd, ACPI_TEMPERATURE, &temp) != 0)
		temp = -1;

	if (pow && !x)
		(void) printf("System is currently on AC power\n");

	if (temp >= 0 && !x) {
		int frac;
		temp -= 2732;
		frac = temp % 10;
		if (frac == 9)
			temp++;
		(void) printf("System temperature = %d.%dC\n",
			temp/10, temp % 10);
	}
	if (verbose) {
		float	f;

		for (i = 0; i < nbat; i++) {
			(void) printf("battery %d\n", i);
			(void) printf("\tbif_unit = %x\n", bif[i].bif_unit);
			(void) printf("\tbif_design_cap = %4x   %2u.%3.3u Wh\n",
				bif[i].bif_design_cap, bif[i].bif_design_cap / 1000, bif[i].bif_design_cap % 1000);
			if (bif[i].bif_design_cap == 0)
				f = 0.0;
			else
				f = bif[i].bif_last_cap * 100.0 / bif[i].bif_design_cap;
			(void) printf("\tbif_last_cap = %4x     %2u.%3.3u Wh (%4.2f%%)\n",
			    bif[i].bif_last_cap, bif[i].bif_last_cap / 1000, bif[i].bif_last_cap % 1000, f);
			(void) printf("\tbif_tech = %x\n",
			    bif[i].bif_tech);
			(void) printf("\tbif_voltage = %4x      %2u.%3.3u V\n",
			    bif[i].bif_voltage, bif[i].bif_voltage / 1000, bif[i].bif_voltage % 1000);
			(void) printf("\tbif_warn_cap = %4x     %2u.%3.3u Wh\n",
			    bif[i].bif_warn_cap, bif[i].bif_warn_cap / 1000, bif[i].bif_warn_cap % 1000);
			(void) printf("\tbif_low_cap = %4x      %2u.%3.3u Wh\n",
			    bif[i].bif_low_cap, bif[i].bif_low_cap / 1000, bif[i].bif_low_cap % 1000);
			(void) printf("\tbif_gran1_cap = %4x    %2u.%3.3u Wh\n",
			    bif[i].bif_low_cap, bif[i].bif_low_cap / 1000, bif[i].bif_low_cap % 1000);
			(void) printf("\tbif_gran2_cap = %4x    %2u.%3.3u Wh\n",
			    bif[i].bif_low_cap, bif[i].bif_low_cap / 1000, bif[i].bif_low_cap % 1000);
			(void) printf("\tbif_model = %s\n", bif[i].bif_model);
			(void) printf("\tbif_serial = %s\n", bif[i].bif_serial);
			(void) printf("\tbif_type = %s\n", bif[i].bif_type);
			(void) printf("\tbif_oem_info = %s\n",
			    bif[i].bif_oem_info);
		}
	}

	if (ioctl(fd, ACPI_BATTERY_STATUS, bst) != 0) {
		perror("ACPI_BATTERY_STATUS");
		exit(1);
	}

	if (verbose) {
		uint32_t	j, k;

		for (i = 0; i < nbat; i++) {
			(void) printf("battery %d\n", i);
			(void) printf("\tbst_state = %x\n", bst[i].bst_state);
			if (bst[i].bst_voltage == 0) {
				j = k = 0;
			} else {
				j = bst[i].bst_rate / bst[i].bst_voltage;
				k = bst[i].bst_rate * 1000 / bst[i].bst_voltage;
			}
			(void) printf("\tbst_rate = %4x         %2u.%3.3u W  (%u.%3.3u A)\n",
			    bst[i].bst_rate, bst[i].bst_rate / 1000, bst[i].bst_rate % 1000, j, k % 1000);
			j = bst[i].bst_rem_cap;
			if (j == -1)
				j = 0;
			(void) printf("\tbst_rem_cap = %-8x  %2u.%3.3u Wh\n",
			    bst[i].bst_rem_cap, j / 1000, j % 1000);
			(void) printf("\tbst_voltage = %4x      %2u.%3.3u V\n",
			    bst[i].bst_voltage, bst[i].bst_voltage / 1000, bst[i].bst_voltage % 1000);
		}
	}

	for (i = 0; i < nbat; i++) {
		if (bst[i].bst_rate > 0) {
			if (bst[i].bst_state & ACPI_BST_CHARGING)
				minutes = bif[i].bif_last_cap -
				    bst[i].bst_rem_cap;
			else if (bst[i].bst_state & ACPI_BST_DISCHARGING)
				minutes = bst[i].bst_rem_cap;
			else
				minutes = 0;
			minutes *= 60;
			minutes /= bst[i].bst_rate;
			hours = minutes/60;
			minutes -= (hours * 60);
		} else
			hours = minutes = 0;

		if ((int)bst[i].bst_rem_cap == -1) {
			(void) printf("Battery %d not present\n", i);
			continue;
		}

		if (x) {
			(void) printf("\033]0;%.3g%%\a",
			    bst[i].bst_rem_cap * 100.0/bif[i].bif_last_cap);
			fflush(stdout);
			break;
		}
		(void) printf("Battery %d status:\n"
		    "\t\tPercentage remaining: %.3g%%\n",
		    i,
		    (bst[i].bst_rem_cap * 100.0/bif[i].bif_last_cap));
		if (bst[i].bst_state &
		    (ACPI_BST_CHARGING|ACPI_BST_DISCHARGING)) {
			(void) printf("\t\tBattery is currently %s\n",
			    bst[i].bst_state & ACPI_BST_CHARGING ?
			    "charging" : "discharging");
		} else if (bst[i].bst_rem_cap == bif[i].bif_last_cap) {
			(void) printf("\t\tBattery is fully charged\n");
		}
		if (bst[i].bst_state & ACPI_BST_CHARGING)
			(void) printf("\t\tTime remaining to full charge");
		else if (bst[i].bst_state & ACPI_BST_DISCHARGING)
			(void) printf("\t\tTime remaining");

		if (bst[i].bst_state &
		    (ACPI_BST_CHARGING|ACPI_BST_DISCHARGING)) {
			if (bst[i].bst_rate == 0)
				(void) printf(" unknown\n");
			else
				(void) printf(" %d:%02d\n", hours, minutes);
		}
	}
	if (loop) {
		if (pollit) {
			xfd.fd = fd;
			xfd.events = POLLIN|POLLRDNORM;
			poll(&xfd, 1, interval * 1000);
		} else {
			sleep(interval);
		}
		goto loop;
	}

	(void) close(fd);
	return (0);
}
