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