446 lines
12 KiB
C
446 lines
12 KiB
C
|
/*
|
||
|
* A not-yet-general-purpose ACPI library, by Joey Hess <joey@kitenet.net>
|
||
|
*/
|
||
|
|
||
|
#include <unistd.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <dirent.h>
|
||
|
#include <string.h>
|
||
|
#include <fnmatch.h>
|
||
|
#ifdef ACPI_APM
|
||
|
#include "apm.h"
|
||
|
#endif
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <fcntl.h>
|
||
|
|
||
|
#include "acpi.h"
|
||
|
|
||
|
#define SYSFS_PATH "/sys/class/power_supply"
|
||
|
#define ACPI_MAXITEM 8
|
||
|
|
||
|
int acpi_batt_count = 0;
|
||
|
/* Filenames of the battery info files for each system battery. */
|
||
|
char acpi_batt_info[ACPI_MAXITEM][128];
|
||
|
/* Filenames of the battery status files for each system battery. */
|
||
|
char acpi_batt_status[ACPI_MAXITEM][128];
|
||
|
/* Stores battery capacity, or 0 if the battery is absent. */
|
||
|
int acpi_batt_capacity[ACPI_MAXITEM];
|
||
|
|
||
|
int acpi_ac_count = 0;
|
||
|
char acpi_ac_adapter_info[ACPI_MAXITEM][128];
|
||
|
char acpi_ac_adapter_status[ACPI_MAXITEM][128];
|
||
|
|
||
|
char *acpi_labels[] = {
|
||
|
"uevent",
|
||
|
"status",
|
||
|
"Battery",
|
||
|
"Mains",
|
||
|
"POWER_SUPPLY_CAPACITY=",
|
||
|
"POWER_SUPPLY_??????_FULL_DESIGN=", /* CHARGE or ENERGY */
|
||
|
"POWER_SUPPLY_PRESENT=",
|
||
|
"POWER_SUPPLY_??????_NOW=",
|
||
|
"POWER_SUPPLY_CURRENT_NOW=",
|
||
|
"POWER_SUPPLY_STATUS=",
|
||
|
#if ACPI_THERMAL
|
||
|
"thermal_zone",
|
||
|
#endif
|
||
|
"POWER_SUPPLY_ONLINE=",
|
||
|
"POWER_SUPPLY_??????_FULL=",
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
#if ACPI_THERMAL
|
||
|
int acpi_thermal_count = 0;
|
||
|
char acpi_thermal_info[ACPI_MAXITEM][128];
|
||
|
char acpi_thermal_status[ACPI_MAXITEM][128];
|
||
|
#endif
|
||
|
|
||
|
/* Read in an entire ACPI proc file (well, the first 1024 bytes anyway), and
|
||
|
* return a statically allocated array containing it. */
|
||
|
inline char *get_acpi_file (const char *file) {
|
||
|
int fd;
|
||
|
int end;
|
||
|
static char buf[1024];
|
||
|
fd = open(file, O_RDONLY);
|
||
|
if (fd == -1) return NULL;
|
||
|
end = read(fd, buf, sizeof(buf));
|
||
|
buf[end-1] = '\0';
|
||
|
close(fd);
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
int strmcmp(const char *s1, const char *s2)
|
||
|
{
|
||
|
for (; (*s1 == *s2) || (*s2 == '?'); s1++, s2++) {
|
||
|
if (*s1 == '\0')
|
||
|
return 0;
|
||
|
}
|
||
|
if (*s2 == '\0')
|
||
|
return 0;
|
||
|
else
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* Given a buffer holding an acpi file, searches for the given key in it,
|
||
|
* and returns the numeric value. 0 is returned on failure. */
|
||
|
inline int scan_acpi_num (const char *buf, const char *key) {
|
||
|
char *ptr;
|
||
|
int ret = 0;
|
||
|
|
||
|
do {
|
||
|
ptr = strchr(buf, '\n');
|
||
|
if (!strmcmp(buf, key)) {
|
||
|
if ((ptr = strchr(buf, '='))) {
|
||
|
sscanf(ptr + 1, "%d", &ret);
|
||
|
return ret;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
if (ptr)
|
||
|
ptr++;
|
||
|
buf = ptr;
|
||
|
} while (buf != NULL);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Given a buffer holding an acpi file, searches for the given key in it,
|
||
|
* and returns its value in a statically allocated string. */
|
||
|
inline char *scan_acpi_value (const char *buf, const char *key) {
|
||
|
char *ptr;
|
||
|
static char ret[256];
|
||
|
|
||
|
do {
|
||
|
ptr = strchr(buf, '\n');
|
||
|
if (!strmcmp(buf, key)) {
|
||
|
if ((ptr = strchr(buf, '='))) {
|
||
|
if (sscanf(ptr + 1, "%255s", ret) == 1) {
|
||
|
return ret;
|
||
|
} else {
|
||
|
return NULL;
|
||
|
}
|
||
|
} else {
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
if (ptr)
|
||
|
ptr++;
|
||
|
buf = ptr;
|
||
|
} while (buf != NULL);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Read an ACPI proc file, pull out the requested piece of information, and
|
||
|
* return it (statically allocated string). Returns NULL on error, This is
|
||
|
* the slow, dumb way, fine for initialization or if only one value is needed
|
||
|
* from a file, slow if called many times. */
|
||
|
char *get_acpi_value (const char *file, const char *key) {
|
||
|
char *buf = get_acpi_file(file);
|
||
|
if (! buf) return NULL;
|
||
|
return scan_acpi_value(buf, key);
|
||
|
}
|
||
|
|
||
|
/* Returns the last full charge capacity of a battery.
|
||
|
*/
|
||
|
int get_acpi_batt_capacity(int battery) {
|
||
|
char *s;
|
||
|
|
||
|
s = get_acpi_value(acpi_batt_info[battery], acpi_labels[label_last_full_capacity]);
|
||
|
if (s == NULL) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
return atoi(s);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Comparison function for qsort. */
|
||
|
int _acpi_compare_strings (const void *a, const void *b) {
|
||
|
const char **pa = (const char **)a;
|
||
|
const char **pb = (const char **)b;
|
||
|
return strcasecmp((const char *)*pa, (const char *)*pb);
|
||
|
}
|
||
|
|
||
|
/* Find something (batteries, ac adpaters, etc), and set up a string array
|
||
|
* to hold the paths to info and status files of the things found.
|
||
|
* Returns the number of items found. */
|
||
|
int find_items (char *itemname, char infoarray[ACPI_MAXITEM][128],
|
||
|
char statusarray[ACPI_MAXITEM][128]) {
|
||
|
DIR *dir;
|
||
|
struct dirent *ent;
|
||
|
int num_devices=0;
|
||
|
int i;
|
||
|
char **devices = malloc(ACPI_MAXITEM * sizeof(char *));
|
||
|
|
||
|
char pathname[128];
|
||
|
|
||
|
sprintf(pathname, SYSFS_PATH);
|
||
|
|
||
|
dir = opendir(pathname);
|
||
|
if (dir == NULL)
|
||
|
return 0;
|
||
|
while ((ent = readdir(dir))) {
|
||
|
char filename[128];
|
||
|
char buf[1024];
|
||
|
|
||
|
if (!strcmp(".", ent->d_name) ||
|
||
|
!strcmp("..", ent->d_name))
|
||
|
continue;
|
||
|
|
||
|
snprintf(filename, sizeof(filename), SYSFS_PATH "/%s/type", ent->d_name);
|
||
|
int fd = open(filename, O_RDONLY);
|
||
|
if (fd != -1) {
|
||
|
int end = read(fd, buf, sizeof(buf));
|
||
|
buf[end-1] = '\0';
|
||
|
close(fd);
|
||
|
if (strstr(buf, itemname) != buf)
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
devices[num_devices]=strdup(ent->d_name);
|
||
|
num_devices++;
|
||
|
if (num_devices >= ACPI_MAXITEM)
|
||
|
break;
|
||
|
}
|
||
|
closedir(dir);
|
||
|
|
||
|
/* Sort, since readdir can return in any order. /sys/ does
|
||
|
* sometimes list BAT1 before BAT0. */
|
||
|
qsort(devices, num_devices, sizeof(char *), _acpi_compare_strings);
|
||
|
|
||
|
for (i = 0; i < num_devices; i++) {
|
||
|
snprintf(infoarray[i], sizeof(infoarray[i]), SYSFS_PATH "/%s/%s", devices[i],
|
||
|
acpi_labels[label_info]);
|
||
|
snprintf(statusarray[i], sizeof(statusarray[i]), SYSFS_PATH "/%s/%s", devices[i],
|
||
|
acpi_labels[label_status]);
|
||
|
free(devices[i]);
|
||
|
}
|
||
|
|
||
|
return num_devices;
|
||
|
}
|
||
|
|
||
|
/* Find batteries, return the number, and set acpi_batt_count to it as well. */
|
||
|
int find_batteries(void) {
|
||
|
int i;
|
||
|
acpi_batt_count = find_items(acpi_labels[label_battery], acpi_batt_info, acpi_batt_status);
|
||
|
for (i = 0; i < acpi_batt_count; i++)
|
||
|
acpi_batt_capacity[i] = get_acpi_batt_capacity(i);
|
||
|
return acpi_batt_count;
|
||
|
}
|
||
|
|
||
|
/* Find AC power adapters, return the number found, and set acpi_ac_count to it
|
||
|
* as well. */
|
||
|
int find_ac_adapters(void) {
|
||
|
acpi_ac_count = find_items(acpi_labels[label_ac_adapter], acpi_ac_adapter_info, acpi_ac_adapter_status);
|
||
|
return acpi_ac_count;
|
||
|
}
|
||
|
|
||
|
#if ACPI_THERMAL
|
||
|
/* Find thermal information sources, return the number found, and set
|
||
|
* thermal_count to it as well. */
|
||
|
int find_thermal(void) {
|
||
|
acpi_thermal_count = find_items(acpi_labels[label_thermal], acpi_thermal_info, acpi_thermal_status);
|
||
|
return acpi_thermal_count;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Returns true if the system is on ac power. Call find_ac_adapters first. */
|
||
|
int on_ac_power (void) {
|
||
|
int i;
|
||
|
for (i = 0; i < acpi_ac_count; i++) {
|
||
|
char *online=get_acpi_value(acpi_ac_adapter_info[i], acpi_labels[label_ac_state]);
|
||
|
if (online && atoi(online))
|
||
|
return 1;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* See if we have ACPI support and check version. Also find batteries and
|
||
|
* ac power adapters. */
|
||
|
int acpi_supported (void) {
|
||
|
char *version;
|
||
|
DIR *dir;
|
||
|
int num;
|
||
|
|
||
|
if (!(dir = opendir(SYSFS_PATH))) {
|
||
|
return 0;
|
||
|
}
|
||
|
closedir(dir);
|
||
|
|
||
|
/* If kernel is 2.6.21 or newer, version is in
|
||
|
/sys/module/acpi/parameters/acpica_version */
|
||
|
|
||
|
version = get_acpi_file("/sys/module/acpi/parameters/acpica_version");
|
||
|
if (version == NULL) {
|
||
|
return 0;
|
||
|
}
|
||
|
num = atoi(version);
|
||
|
if (num < ACPI_VERSION) {
|
||
|
fprintf(stderr, "ACPI subsystem %s too is old, consider upgrading to %i.\n",
|
||
|
version, ACPI_VERSION);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
find_batteries();
|
||
|
find_ac_adapters();
|
||
|
#if ACPI_THERMAL
|
||
|
find_thermal();
|
||
|
#endif
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
#ifdef ACPI_APM
|
||
|
/* Read ACPI info on a given power adapter and battery, and fill the passed
|
||
|
* apm_info struct. */
|
||
|
int acpi_read (int battery, apm_info *info) {
|
||
|
char *buf, *state;
|
||
|
|
||
|
if (acpi_batt_count == 0) {
|
||
|
info->battery_percentage = 0;
|
||
|
info->battery_time = 0;
|
||
|
info->battery_status = BATTERY_STATUS_ABSENT;
|
||
|
acpi_batt_capacity[battery] = 0;
|
||
|
/* Where else would the power come from, eh? ;-) */
|
||
|
info->ac_line_status = 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Internally it's zero indexed. */
|
||
|
battery--;
|
||
|
|
||
|
buf = get_acpi_file(acpi_batt_info[battery]);
|
||
|
if (buf == NULL) {
|
||
|
fprintf(stderr, "unable to read %s\n", acpi_batt_info[battery]);
|
||
|
perror("read");
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
info->ac_line_status = 0;
|
||
|
info->battery_flags = 0;
|
||
|
info->using_minutes = 1;
|
||
|
|
||
|
/* Work out if the battery is present, and what percentage of full
|
||
|
* it is and how much time is left. */
|
||
|
if (strcmp(scan_acpi_value(buf, acpi_labels[label_present]), "1") == 0) {
|
||
|
int pcap = scan_acpi_num(buf, acpi_labels[label_remaining_capacity]);
|
||
|
int rate = scan_acpi_num(buf, acpi_labels[label_present_rate]);
|
||
|
if (rate) {
|
||
|
/* time remaining = (current_capacity / discharge rate) */
|
||
|
info->battery_time = (float) pcap / (float) rate * 60;
|
||
|
}
|
||
|
else {
|
||
|
char *rate_s = scan_acpi_value(buf, acpi_labels[label_present_rate]);
|
||
|
if (! rate_s) {
|
||
|
/* Time remaining unknown. */
|
||
|
info->battery_time = 0;
|
||
|
}
|
||
|
else {
|
||
|
/* a zero or unknown in the file; time
|
||
|
* unknown so use a negative one to
|
||
|
* indicate this */
|
||
|
info->battery_time = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
state = scan_acpi_value(buf, acpi_labels[label_charging_state]);
|
||
|
if (state) {
|
||
|
if (state[0] == 'D') { /* discharging */
|
||
|
info->battery_status = BATTERY_STATUS_CHARGING;
|
||
|
/* Expensive ac power check used here
|
||
|
* because AC power might be on even if a
|
||
|
* battery is discharging in some cases. */
|
||
|
info->ac_line_status = on_ac_power();
|
||
|
}
|
||
|
else if (state[0] == 'C' && state[1] == 'h') { /* charging */
|
||
|
info->battery_status = BATTERY_STATUS_CHARGING;
|
||
|
info->ac_line_status = 1;
|
||
|
info->battery_flags = info->battery_flags | BATTERY_FLAGS_CHARGING;
|
||
|
if (rate)
|
||
|
info->battery_time = -1 * (float) (acpi_batt_capacity[battery] - pcap) / (float) rate * 60;
|
||
|
else
|
||
|
info->battery_time = 0;
|
||
|
if (abs(info->battery_time) < 0.5)
|
||
|
info->battery_time = 0;
|
||
|
}
|
||
|
else if (state[0] == 'F') { /* full */
|
||
|
/* charged, on ac power */
|
||
|
info->battery_status = BATTERY_STATUS_HIGH;
|
||
|
info->ac_line_status = 1;
|
||
|
}
|
||
|
else if (state[0] == 'C') { /* not charging, so must be critical */
|
||
|
info->battery_status = BATTERY_STATUS_CRITICAL;
|
||
|
/* Expensive ac power check used here
|
||
|
* because AC power might be on even if a
|
||
|
* battery is critical in some cases. */
|
||
|
info->ac_line_status = on_ac_power();
|
||
|
}
|
||
|
else if (state[0] == 'U') { /* unknown */
|
||
|
info->ac_line_status = on_ac_power();
|
||
|
int current = scan_acpi_num(buf, acpi_labels[label_present_rate]);
|
||
|
if (info->ac_line_status) {
|
||
|
if (current == 0)
|
||
|
info->battery_status = BATTERY_STATUS_HIGH;
|
||
|
else
|
||
|
info->battery_status = BATTERY_STATUS_CHARGING;
|
||
|
}
|
||
|
else {
|
||
|
info->battery_status = BATTERY_STATUS_CHARGING;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
fprintf(stderr, "unknown battery state: %s\n", state);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
/* Battery state unknown. */
|
||
|
info->battery_status = BATTERY_STATUS_ABSENT;
|
||
|
}
|
||
|
|
||
|
if (acpi_batt_capacity[battery] == 0) {
|
||
|
/* The battery was absent, and now is present.
|
||
|
* Well, it might be a different battery. So
|
||
|
* re-probe the battery. */
|
||
|
/* NOTE that this invalidates buf. No accesses of
|
||
|
* buf below this point! */
|
||
|
acpi_batt_capacity[battery] = get_acpi_batt_capacity(battery);
|
||
|
}
|
||
|
else if (pcap > acpi_batt_capacity[battery]) {
|
||
|
/* Battery is somehow charged to greater than max
|
||
|
* capacity. Rescan for a new max capacity. */
|
||
|
find_batteries();
|
||
|
}
|
||
|
|
||
|
if (pcap && acpi_batt_capacity[battery]) {
|
||
|
info->battery_percentage = 100 * pcap / acpi_batt_capacity[battery];
|
||
|
if (info->battery_percentage > 100)
|
||
|
info->battery_percentage = 100;
|
||
|
}
|
||
|
else {
|
||
|
info->battery_percentage = -1;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else {
|
||
|
info->battery_percentage = 0;
|
||
|
info->battery_time = 0;
|
||
|
info->battery_status = BATTERY_STATUS_ABSENT;
|
||
|
acpi_batt_capacity[battery] = 0;
|
||
|
if (acpi_batt_count == 0) {
|
||
|
/* Where else would the power come from, eh? ;-) */
|
||
|
info->ac_line_status = 1;
|
||
|
}
|
||
|
else {
|
||
|
/* Expensive ac power check. */
|
||
|
info->ac_line_status = on_ac_power();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|