/* $Id: libapm.c 1974 2006-09-06 09:50:27Z nick $
*
* Copyright (c) 1996, 1997 Rickard E. Faith <faith@acm.org>
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#ifndef __linux__
/* FIXME: enable BSD apm calls here */
int apm_exists(void)
{
return 0;
}
#else
#include <sys/sysmacros.h>
#include "libapm.h"
#define BACKWARD_COMPAT 1
/* If APM support of the right version exists in kernel, return zero.
* Otherwise, return 1 if no support exists, or 2 if it is the wrong
* version. *NOTE* The sense of the return value is not intuitive.
*/
int apm_exists(void)
{
apm_info i;
if (access(APM_PROC, R_OK))
return 1;
return apm_read(&i);
}
/* Read information from /proc/apm. Return 0 on success, 1 if APM not
* installed, 2 if APM installed, but old version.
*/
int apm_read(apm_info * i)
{
FILE *str;
char units[10];
char buffer[100];
int retcode = 0;
if (!(str = fopen(APM_PROC, "r")))
return 1;
fgets(buffer, sizeof(buffer) - 1, str);
buffer[sizeof(buffer) - 1] = '\0';
/* Should check for other driver versions; driver 1.9 (and some
* others) uses this format, which doesn't expose # batteries.
*/
sscanf(buffer, "%s %d.%d %x %x %x %x %d%% %d %s\n",
(char *) i->driver_version,
&i->apm_version_major,
&i->apm_version_minor,
&i->apm_flags,
&i->ac_line_status,
&i->battery_status,
&i->battery_flags,
&i->battery_percentage,
&i->battery_time,
units);
i->using_minutes = !strncmp(units, "min", 3) ? 1 : 0;
if (i->driver_version[0] == 'B')
{ /* old style. argh. */
#if !BACKWARD_COMPAT
retcode = 2;
#else
strcpy((char *) i->driver_version, "pre-0.7");
i->apm_version_major = 0;
i->apm_version_minor = 0;
i->apm_flags = 0;
i->ac_line_status = 0xff;
i->battery_status = 0xff;
i->battery_flags = 0xff;
i->battery_percentage = -1;
i->battery_time = -1;
i->using_minutes = 1;
sscanf(buffer, "BIOS version: %d.%d",
&i->apm_version_major, &i->apm_version_minor);
fgets(buffer, sizeof(buffer) - 1, str);
sscanf(buffer, "Flags: 0x%02x", &i->apm_flags);
if (i->apm_flags & APM_32_BIT_SUPPORT)
{
fgets(buffer, sizeof(buffer) - 1, str);
fgets(buffer, sizeof(buffer) - 1, str);
if (buffer[0] != 'P')
{
if (!strncmp(buffer + 4, "off line", 8))
i->ac_line_status = 0;
else if (!strncmp(buffer + 4, "on line", 7))
i->ac_line_status = 1;
else if (!strncmp(buffer + 4, "on back", 7))
i->ac_line_status = 2;
fgets(buffer, sizeof(buffer) - 1, str);
if (!strncmp(buffer + 16, "high", 4))
i->battery_status = 0;
else if (!strncmp(buffer + 16, "low", 3))
i->battery_status = 1;
else if (!strncmp(buffer + 16, "crit", 4))
i->battery_status = 2;
else if (!strncmp(buffer + 16, "charg", 5))
i->battery_status = 3;
fgets(buffer, sizeof(buffer) - 1, str);
if (strncmp(buffer + 14, "unknown", 7))
i->battery_percentage = atoi(buffer + 14);
if (i->apm_version_major >= 1 && i->apm_version_minor >= 1)
{
fgets(buffer, sizeof(buffer) - 1, str);
sscanf(buffer, "Battery flag: 0x%02x", &i->battery_flags);
fgets(buffer, sizeof(buffer) - 1, str);
if (strncmp(buffer + 14, "unknown", 7))
i->battery_time = atoi(buffer + 14);
}
}
}
#endif
}
/* Fix possible kernel bug -- percentage
* set to 0xff (==255) instead of -1.
*/
if (i->battery_percentage > 100)
i->battery_percentage = -1;
fclose(str);
return retcode;
}
/* Lookup the device number for the apm_bios device. */
dev_t apm_dev(void)
{
FILE *str;
static int cached = -1;
char buf[80];
char *pt;
apm_info i;
if (cached >= 0)
return cached;
if (access(APM_PROC, R_OK) || apm_read(&i) == 1)
return cached = -1;
if (i.driver_version[0] == '1')
return cached = makedev(10, 134);
if (!(str = fopen(APM_DEV, "r")))
return -1;
while (fgets(buf, sizeof(buf) - 1, str))
{
buf[sizeof(buf) - 1] = '\0';
for (pt = buf; *pt && isspace(*pt); ++pt); /* skip leading spaces */
for (; *pt && !isspace(*pt); ++pt); /* find next space */
if (isspace(*pt))
{
*pt++ = '\0';
pt[strlen(pt) - 1] = '\0'; /* get rid of newline */
if (!strcmp(pt, APM_NAME))
{
fclose(str);
return cached = makedev(atoi(buf), 0);
}
}
}
fclose(str);
return cached = -1;
}
/* Return a file descriptor for the apm_bios device, or -1 if there is an
* error. Is this method secure? Should we make the device in /dev
* instead of /tmp?
*
* apenwarr 2001/05/11: just throw out the weird temporary device file stuff.
* It was only for ancient kernel versions anyway.
*/
int apm_open(void)
{
int fd;
apm_info i;
if (access(APM_PROC, R_OK) || apm_read(&i) == 1)
return -1;
if (i.driver_version[0] >= '1')
{
if ((fd = open(APM_DEVICE, O_RDWR)) < 0)
{
/* Try to create it. This is reasonable
* for backward compatibility.
*/
if (mknod(APM_DEVICE, S_IFCHR | S_IRUSR | S_IWUSR, apm_dev()))
{
unlink(APM_DEVICE);
return -1;
}
fd = open(APM_DEVICE, O_RDWR);
}
return fd;
}
return -1;
}
/* Given a file descriptor for the apm_bios device, close it. */
int apm_close(int fd)
{
return close(fd);
}
/* Given a file descriptor for the apm_bios device, this routine will wait
* timeout seconds for APM events. Up to n events will be placed in the
* events queue. The return code will indicate the number of events
* stored. Since this routine uses select(2), it will return if an
* unblocked signal is caught. A timeout < 0 means to block indefinately.
*
* Note that if you read a request to standby or to suspend, the kernel
* will be waiting for you to respond to it with a call to apm_suspend()
* or to apm_standby() !
*/
int apm_get_events(int fd, int timeout, apm_event_t * events, int n)
{
int retcode;
fd_set fds;
struct timeval t;
t.tv_sec = timeout;
t.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(fd, &fds);
retcode = select(fd + 1, &fds, NULL, NULL, timeout < 0 ? NULL : &t);
if (retcode <= 0)
return 0;
return read(fd, events, n * sizeof(apm_event_t)) / sizeof(apm_event_t);
}
/* Try to set the Power State to Suspend. */
int apm_suspend(int fd)
{
sync();
return ioctl(fd, APM_IOC_SUSPEND, NULL);
}
/* Try to set the Power State to Standby. */
int apm_standby(int fd)
{
sync();
return ioctl(fd, APM_IOC_STANDBY, NULL);
}
/* Return the last error code generated by the kernel APM driver */
unsigned int apm_last_error( int fd )
{
int err = 0;
#ifdef APM_IOC_LAST_ERROR
int ierr = 0;
if ( (ierr = ioctl( fd, APM_IOC_LAST_ERROR, &err)) )
return ierr;
#endif
return err;
}
/* Define lookup table for error messages */
typedef struct lookup_t {
int key;
char * msg;
} lookup_t;
/* APM error messages, arranged by error code */
static const lookup_t error_table[] = {
/* N/A { APM_SUCCESS, "Operation succeeded" }, */
{ APM_DISABLED, "Power management disabled" },
{ APM_CONNECTED, "Real mode interface already connected" },
{ APM_NOT_CONNECTED, "Interface not connected" },
{ APM_16_CONNECTED, "16 bit interface already connected" },
/* N/A { APM_16_UNSUPPORTED, "16 bit interface not supported" }, */
{ APM_32_CONNECTED, "32 bit interface already connected" },
{ APM_32_UNSUPPORTED, "32 bit interface not supported" },
{ APM_BAD_DEVICE, "Unrecognized device ID" },
{ APM_BAD_PARAM, "Parameter out of range" },
{ APM_NOT_ENGAGED, "Interface not engaged" },
#ifdef APM_BAD_FUNCTION
{ APM_BAD_FUNCTION, "Function not supported" },
#endif
#ifdef APM_RESUME_DISABLED
{ APM_RESUME_DISABLED, "Resume timer disabled" },
#endif
{ APM_BAD_STATE, "Unable to enter requested state" },
/* N/A { APM_NO_EVENTS, "No events pending" }, */
{ APM_NOT_PRESENT, "No APM present" }
};
#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t))
/* Return character string describing error messages from APM kernel */
const char *apm_error_name( unsigned int err )
{
int i;
for(i=0; i<ERROR_COUNT; i++)
if(err == error_table[i].key) return(error_table[i].msg);
return "Unknown error";
}
int apm_reject( int fd )
{
#ifdef APM_IOC_REJECT
if ( ioctl( fd, APM_IOC_REJECT, NULL ) )
return apm_last_error( fd );
else
#endif
return 0;
}
#ifdef APM_IOC_IGNORE /* detect kernel support of IGNORE/NOIGNORE functions */
int apm_set_ignore(int fd, int mode)
/* Ignore Standby. */
{
if (mode == IGNORE)
{
printf("Telling kernel to ignore system standby/suspend mode\n");
return ioctl(fd, APM_IOC_IGNORE, NULL);
}
else
{
printf("Telling kernel not to ignore system standby/suspend mode\n");
return ioctl(fd, APM_IOC_NOIGNORE, NULL);
}
printf("NOTE: User-generated suspend/standby requests are not ignored\n");
}
#endif
/* Return a string describing the event. From p. 16 of the Intel/Microsoft
* Advanded Power Management (APM) BIOS Interface Specification, Revision
* 1.1 (September 1993). Intel Order Number: 241704-001. Microsoft Part
* Number: 781-110-X01.
*
* Updated to APM BIOS 1.2 spec (February 1996). Available on-line.
*/
const char *apm_event_name(apm_event_t event)
{
switch (event)
{
case APM_SYS_STANDBY:
return "System Standby Request";
case APM_SYS_SUSPEND:
return "System Suspend Request";
case APM_NORMAL_RESUME:
return "Normal Resume System";
case APM_CRITICAL_RESUME:
return "Critical Resume System";
case APM_LOW_BATTERY:
return "Battery Low";
case APM_POWER_STATUS_CHANGE:
return "Power Status Change";
case APM_UPDATE_TIME:
return "Update Time";
case APM_CRITICAL_SUSPEND:
return "Critical Suspend";
case APM_USER_STANDBY:
return "User System Standby Request";
case APM_USER_SUSPEND:
return "User System Suspend Request";
case APM_STANDBY_RESUME:
return "System Standby Resume";
#ifdef APM_CAPABILITY_CHANGE
case APM_CAPABILITY_CHANGE:
return "Capability Change";
#endif
}
return "Unknown";
}
/* This is a convenience function that has nothing to do with APM. It just
* formats a time nicely. If you don't like this format, then write your
* own.
*/
#define SEC_PER_DAY (60*60*24)
#define SEC_PER_HOUR (60*60)
#define SEC_PER_MIN (60)
const char *apm_delta_time(time_t then, time_t now)
{
return apm_time(now - then);
}
const char *apm_time(time_t t)
{
static char buffer[128];
unsigned long s, m, h, d;
d = t / SEC_PER_DAY;
t -= d * SEC_PER_DAY;
h = t / SEC_PER_HOUR;
t -= h * SEC_PER_HOUR;
m = t / SEC_PER_MIN;
t -= m * SEC_PER_MIN;
s = t;
if (d)
sprintf(buffer, "%lu day%s, %02lu:%02lu:%02lu",
d, d > 1 ? "s" : "", h, m, s);
else
sprintf(buffer, "%02lu:%02lu:%02lu", h, m, s);
if (t == -1)
sprintf(buffer, "unknown");
return buffer;
}
const char *apm_time_nosec(time_t t)
{
static char buffer[128];
unsigned long s, m, h, d;
d = t / SEC_PER_DAY;
t -= d * SEC_PER_DAY;
h = t / SEC_PER_HOUR;
t -= h * SEC_PER_HOUR;
m = t / SEC_PER_MIN;
t -= m * SEC_PER_MIN;
s = t;
if (s > 30)
++m;
if (d)
sprintf(buffer, "%lu day%s, %lu:%02lu",
d, d > 1 ? "s" : "", h, m);
else
sprintf(buffer, "%lu:%02lu", h, m);
if (t == -1)
sprintf(buffer, "unknown");
return buffer;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1