1/*
2 * APM BIOS utility for FreeBSD
3 *
4 * Copyright (C) 1994-1996 by Tatsumi Hosokawa <hosokawa@jp.FreeBSD.org>
5 *
6 * This software may be used, modified, copied, distributed, and sold,
7 * in both source and binary form provided that the above copyright and
8 * these terms are retained. Under no circumstances is the author
9 * responsible for the proper functioning of this software, nor does
10 * the author assume any responsibility for damages incurred with its
11 * use.
12 *
13 * Sep., 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
14 */
15
16#include <sys/cdefs.h>
17#include <sys/file.h>
18#include <sys/ioctl.h>
19#include <sys/types.h>
20#include <sys/sysctl.h>
21
22#include <machine/apm_bios.h>
23
24#include <err.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <time.h>
29#include <unistd.h>
30
31#define APMDEV	"/dev/apm"
32
33#define APM_UNKNOWN	255
34
35#define xh(a)	(((a) & 0xff00) >> 8)
36#define xl(a)	((a) & 0xff)
37#define APMERR(a) xh(a)
38
39static int cmos_wall = 0; /* True when wall time is in cmos clock, else UTC */
40
41static void
42usage(void)
43{
44	fprintf(stderr,
45		"usage: apm [-ablstzZ] [-d enable ] [ -e enable ] "
46		"[ -h enable ] [-r delta]\n");
47	exit(1);
48}
49
50/*
51 * Return 1 for boolean true, and 0 for false, according to the
52 * interpretation of the string argument given.
53 */
54static int
55is_true(const char *boolean)
56{
57	char *endp;
58	long val;
59
60	val = strtoul(boolean, &endp, 0);
61	if (*endp == '\0')
62		return (val != 0 ? 1 : 0);
63	if (strcasecmp(boolean, "true") == 0 ||
64	    strcasecmp(boolean, "yes") == 0 ||
65	    strcasecmp(boolean, "enable") == 0)
66		return (1);
67	if (strcasecmp(boolean, "false") == 0 ||
68	    strcasecmp(boolean, "no") == 0 ||
69	    strcasecmp(boolean, "disable") == 0)
70		return (0);
71	/* Well, I have no idea what the user wants, so... */
72	warnx("invalid boolean argument \"%s\"", boolean);
73	usage();
74	/* NOTREACHED */
75
76	return (0);
77}
78
79static int
80int2bcd(int i)
81{
82	int retval = 0;
83	int base = 0;
84
85	if (i >= 10000)
86		return -1;
87
88	while (i) {
89		retval |= (i % 10) << base;
90		i /= 10;
91		base += 4;
92	}
93	return retval;
94}
95
96static int
97bcd2int(int bcd)
98{
99	int retval = 0;
100	int place = 1;
101
102	if (bcd > 0x9999)
103		return -1;
104
105	while (bcd) {
106		retval += (bcd & 0xf) * place;
107		bcd >>= 4;
108		place *= 10;
109	}
110	return retval;
111}
112
113static void
114apm_suspend(int fd)
115{
116	if (ioctl(fd, APMIO_SUSPEND, NULL) == -1)
117		err(1, "ioctl(APMIO_SUSPEND)");
118}
119
120static void
121apm_standby(int fd)
122{
123	if (ioctl(fd, APMIO_STANDBY, NULL) == -1)
124		err(1, "ioctl(APMIO_STANDBY)");
125}
126
127static void
128apm_getinfo(int fd, apm_info_t aip)
129{
130	if (ioctl(fd, APMIO_GETINFO, aip) == -1)
131		err(1, "ioctl(APMIO_GETINFO)");
132}
133
134static void
135apm_enable(int fd, int enable)
136{
137	if (enable) {
138		if (ioctl(fd, APMIO_ENABLE) == -1)
139			err(1, "ioctl(APMIO_ENABLE)");
140	} else {
141		if (ioctl(fd, APMIO_DISABLE) == -1)
142			err(1, "ioctl(APMIO_DISABLE)");
143	}
144}
145
146static void
147print_batt_time(int batt_time)
148{
149	printf("Remaining battery time: ");
150	if (batt_time == -1)
151		printf("unknown\n");
152	else {
153		int h, m, s;
154
155		h = batt_time;
156		s = h % 60;
157		h /= 60;
158		m = h % 60;
159		h /= 60;
160		printf("%2d:%02d:%02d\n", h, m, s);
161	}
162}
163
164static void
165print_batt_life(u_int batt_life)
166{
167	printf("Remaining battery life: ");
168	if (batt_life == APM_UNKNOWN)
169		printf("unknown\n");
170	else if (batt_life <= 100)
171		printf("%d%%\n", batt_life);
172	else
173		printf("invalid value (0x%x)\n", batt_life);
174}
175
176static void
177print_batt_stat(u_int batt_stat)
178{
179	const char *batt_msg[] = { "high", "low", "critical", "charging" };
180
181	printf("Battery Status: ");
182	if (batt_stat == APM_UNKNOWN)
183		printf("unknown\n");
184	else if (batt_stat > 3)
185		printf("invalid value (0x%x)\n", batt_stat);
186	else
187		printf("%s\n", batt_msg[batt_stat]);
188}
189
190static void
191print_all_info(int fd, apm_info_t aip, int bioscall_available)
192{
193	struct apm_bios_arg args;
194	int apmerr;
195	const char *line_msg[] = { "off-line", "on-line" , "backup power"};
196
197	printf("APM version: %d.%d\n", aip->ai_major, aip->ai_minor);
198	printf("APM Management: %s\n", aip->ai_status ? "Enabled" : "Disabled");
199	printf("AC Line status: ");
200	if (aip->ai_acline == APM_UNKNOWN)
201		printf("unknown\n");
202	else if (aip->ai_acline > 2)
203		printf("invalid value (0x%x)\n", aip->ai_acline);
204	else
205		printf("%s\n", line_msg[aip->ai_acline]);
206
207	print_batt_stat(aip->ai_batt_stat);
208	print_batt_life(aip->ai_batt_life);
209	print_batt_time(aip->ai_batt_time);
210
211	if (aip->ai_infoversion >= 1) {
212		printf("Number of batteries: ");
213		if (aip->ai_batteries == ~0U)
214			printf("unknown\n");
215		else {
216			u_int i;
217			struct apm_pwstatus aps;
218
219			printf("%d\n", aip->ai_batteries);
220			for (i = 0; i < aip->ai_batteries; ++i) {
221				bzero(&aps, sizeof(aps));
222				aps.ap_device = PMDV_BATT0 + i;
223				if (ioctl(fd, APMIO_GETPWSTATUS, &aps) == -1)
224					continue;
225				printf("Battery %d:\n", i);
226				if (aps.ap_batt_flag & APM_BATT_NOT_PRESENT) {
227					printf("not present\n");
228					continue;
229				}
230				printf("\t");
231				print_batt_stat(aps.ap_batt_stat);
232				printf("\t");
233				print_batt_life(aps.ap_batt_life);
234				printf("\t");
235				print_batt_time(aps.ap_batt_time);
236			}
237		}
238	}
239
240	if (bioscall_available) {
241		/*
242		 * try to get the suspend timer
243		 */
244		bzero(&args, sizeof(args));
245		args.eax = (APM_BIOS) << 8 | APM_RESUMETIMER;
246		args.ebx = PMDV_APMBIOS;
247		args.ecx = 0x0001;
248		if (ioctl(fd, APMIO_BIOS, &args)) {
249			printf("Resume timer: unknown\n");
250		} else {
251			apmerr = APMERR(args.eax);
252			if (apmerr == 0x0d || apmerr == 0x86)
253				printf("Resume timer: disabled\n");
254			else if (apmerr)
255				warnx(
256		"failed to get the resume timer: APM error0x%x", apmerr);
257			else {
258				/*
259				 * OK.  We have the time (all bcd).
260				 * CH - seconds
261				 * DH - hours
262				 * DL - minutes
263				 * xh(SI) - month (1-12)
264				 * xl(SI) - day of month (1-31)
265				 * DI - year
266				 */
267				struct tm tm;
268				char buf[1024];
269				time_t t;
270
271				tm.tm_sec = bcd2int(xh(args.ecx));
272				tm.tm_min = bcd2int(xl(args.edx));
273				tm.tm_hour = bcd2int(xh(args.edx));
274				tm.tm_mday = bcd2int(xl(args.esi));
275				tm.tm_mon = bcd2int(xh(args.esi)) - 1;
276				tm.tm_year = bcd2int(args.edi) - 1900;
277				if (cmos_wall)
278					t = mktime(&tm);
279				else
280					t = timegm(&tm);
281				if (t != -1) {
282					tm = *localtime(&t);
283					strftime(buf, sizeof(buf), "%c", &tm);
284					printf("Resume timer: %s\n", buf);
285				} else
286					printf("Resume timer: unknown\n");
287			}
288		}
289
290		/*
291		 * Get the ring indicator resume state
292		 */
293		bzero(&args, sizeof(args));
294		args.eax  = (APM_BIOS) << 8 | APM_RESUMEONRING;
295		args.ebx = PMDV_APMBIOS;
296		args.ecx = 0x0002;
297		if (ioctl(fd, APMIO_BIOS, &args) == 0) {
298			printf("Resume on ring indicator: %sabled\n",
299			    args.ecx ? "en" : "dis");
300		}
301	}
302
303	if (aip->ai_infoversion >= 1) {
304		if (aip->ai_capabilities == 0xff00)
305		    return;
306		printf("APM Capabilities:\n");
307		if (aip->ai_capabilities & 0x01)
308			printf("\tglobal standby state\n");
309		if (aip->ai_capabilities & 0x02)
310			printf("\tglobal suspend state\n");
311		if (aip->ai_capabilities & 0x04)
312			printf("\tresume timer from standby\n");
313		if (aip->ai_capabilities & 0x08)
314			printf("\tresume timer from suspend\n");
315		if (aip->ai_capabilities & 0x10)
316			printf("\tRI resume from standby\n");
317		if (aip->ai_capabilities & 0x20)
318			printf("\tRI resume from suspend\n");
319		if (aip->ai_capabilities & 0x40)
320			printf("\tPCMCIA RI resume from standby\n");
321		if (aip->ai_capabilities & 0x80)
322			printf("\tPCMCIA RI resume from suspend\n");
323	}
324
325}
326
327/*
328 * currently, it can turn off the display, but the display never comes
329 * back until the machine suspend/resumes :-).
330 */
331static void
332apm_display(int fd, int newstate)
333{
334	if (ioctl(fd, APMIO_DISPLAY, &newstate) == -1)
335		err(1, "ioctl(APMIO_DISPLAY)");
336}
337
338static void
339apm_haltcpu(int fd, int enable)
340{
341	if (enable) {
342		if (ioctl(fd, APMIO_HALTCPU, NULL) == -1)
343			err(1, "ioctl(APMIO_HALTCPU)");
344	} else {
345		if (ioctl(fd, APMIO_NOTHALTCPU, NULL) == -1)
346			err(1, "ioctl(APMIO_NOTHALTCPU)");
347	}
348}
349
350static void
351apm_set_timer(int fd, int delta)
352{
353	time_t tmr;
354	struct tm *tm;
355	struct apm_bios_arg args;
356
357	tmr = time(NULL) + delta;
358	if (cmos_wall)
359		tm = localtime(&tmr);
360	else
361		tm = gmtime(&tmr);
362	bzero(&args, sizeof(args));
363	args.eax = (APM_BIOS) << 8 | APM_RESUMETIMER;
364	args.ebx = PMDV_APMBIOS;
365	if (delta > 0) {
366		args.ecx = (int2bcd(tm->tm_sec) << 8) | 0x02;
367		args.edx = (int2bcd(tm->tm_hour) << 8) | int2bcd(tm->tm_min);
368		args.esi = (int2bcd(tm->tm_mon + 1) << 8) | int2bcd(tm->tm_mday);
369		args.edi = int2bcd(tm->tm_year + 1900);
370	} else {
371		args.ecx = 0x0000;
372	}
373	if (ioctl(fd, APMIO_BIOS, &args)) {
374		err(1,"set resume timer");
375	}
376}
377
378int
379main(int argc, char *argv[])
380{
381	int	c, fd;
382	int     dosleep = 0, all_info = 1, apm_status = 0, batt_status = 0;
383	int     display = -1, batt_life = 0, ac_status = 0, standby = 0;
384	int	batt_time = 0, delta = 0, enable = -1, haltcpu = -1;
385	int	bioscall_available = 0;
386	size_t	cmos_wall_len = sizeof(cmos_wall);
387
388	if (sysctlbyname("machdep.wall_cmos_clock", &cmos_wall, &cmos_wall_len,
389	    NULL, 0) == -1)
390		err(1, "sysctlbyname(machdep.wall_cmos_clock)");
391
392	while ((c = getopt(argc, argv, "abe:h:lRr:stzd:Z")) != -1) {
393		switch (c) {
394		case 'a':
395			ac_status = 1;
396			all_info = 0;
397			break;
398		case 'b':
399			batt_status = 1;
400			all_info = 0;
401			break;
402		case 'd':
403			display = is_true(optarg);
404			all_info = 0;
405			break;
406		case 'l':
407			batt_life = 1;
408			all_info = 0;
409			break;
410		case 'R':
411			delta = -1;
412			break;
413		case 'r':
414			delta = atoi(optarg);
415			break;
416		case 's':
417			apm_status = 1;
418			all_info = 0;
419			break;
420		case 'e':
421			enable = is_true(optarg);
422			all_info = 0;
423			break;
424		case 'h':
425			haltcpu = is_true(optarg);
426			all_info = 0;
427			break;
428		case 't':
429			batt_time = 1;
430			all_info = 0;
431			break;
432		case 'z':
433			dosleep = 1;
434			all_info = 0;
435			break;
436		case 'Z':
437			standby = 1;
438			all_info = 0;
439			break;
440		case '?':
441		default:
442			usage();
443		}
444		argc -= optind;
445		argv += optind;
446	}
447	if (haltcpu != -1 || enable != -1 || display != -1 || delta || dosleep
448	    || standby) {
449		fd = open(APMDEV, O_RDWR);
450		bioscall_available = 1;
451	} else if ((fd = open(APMDEV, O_RDWR)) >= 0)
452		bioscall_available = 1;
453	else
454		fd = open(APMDEV, O_RDONLY);
455	if (fd == -1)
456		err(1, "can't open %s", APMDEV);
457	if (enable != -1)
458		apm_enable(fd, enable);
459	if (haltcpu != -1)
460		apm_haltcpu(fd, haltcpu);
461	if (delta)
462		apm_set_timer(fd, delta);
463	if (dosleep)
464		apm_suspend(fd);
465	else if (standby)
466		apm_standby(fd);
467	else if (delta == 0) {
468		struct apm_info info;
469
470		apm_getinfo(fd, &info);
471		if (all_info)
472			print_all_info(fd, &info, bioscall_available);
473		if (ac_status)
474			printf("%d\n", info.ai_acline);
475		if (batt_status)
476			printf("%d\n", info.ai_batt_stat);
477		if (batt_life)
478			printf("%d\n", info.ai_batt_life);
479		if (apm_status)
480			printf("%d\n", info.ai_status);
481		if (batt_time)
482			printf("%d\n", info.ai_batt_time);
483		if (display != -1)
484			apm_display(fd, display);
485	}
486	close(fd);
487	exit(0);
488}
489