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