apm.c revision 1.14
1/*	$OpenBSD: apm.c,v 1.14 2009/02/26 17:19:47 oga Exp $	*/
2
3/*-
4 * Copyright (c) 2001 Alexander Guy.  All rights reserved.
5 * Copyright (c) 1998-2001 Michael Shalayeff. All rights reserved.
6 * Copyright (c) 1995 John T. Kohl.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the names of the authors nor the names of contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 */
33
34#include "apm.h"
35
36#if NAPM > 1
37#error only one APM emulation device may be configured
38#endif
39
40#include <sys/param.h>
41#include <sys/systm.h>
42#include <sys/kernel.h>
43#include <sys/proc.h>
44#include <sys/device.h>
45#include <sys/fcntl.h>
46#include <sys/ioctl.h>
47#include <sys/event.h>
48
49#include <machine/conf.h>
50#include <machine/cpu.h>
51#include <machine/apmvar.h>
52
53#include <dev/adb/adb.h>
54#include <macppc/dev/adbvar.h>
55#include <macppc/dev/pm_direct.h>
56
57#if defined(APMDEBUG)
58#define DPRINTF(x)	printf x
59#else
60#define	DPRINTF(x)	/**/
61#endif
62
63struct apm_softc {
64	struct device sc_dev;
65	struct klist sc_note;
66	int    sc_flags;
67};
68
69int apmmatch(struct device *, void *, void *);
70void apmattach(struct device *, struct device *, void *);
71
72struct cfattach apm_ca = {
73	sizeof(struct apm_softc), apmmatch, apmattach
74};
75
76struct cfdriver apm_cd = {
77	NULL, "apm", DV_DULL
78};
79
80#define	APMUNIT(dev)	(minor(dev)&0xf0)
81#define	APMDEV(dev)	(minor(dev)&0x0f)
82#define APMDEV_NORMAL	0
83#define APMDEV_CTL	8
84
85void filt_apmrdetach(struct knote *kn);
86int filt_apmread(struct knote *kn, long hint);
87int apmkqfilter(dev_t dev, struct knote *kn);
88
89struct filterops apmread_filtops =
90	{ 1, NULL, filt_apmrdetach, filt_apmread};
91
92/*
93 * Flags to control kernel display
94 *	SCFLAG_NOPRINT:		do not output APM power messages due to
95 *				a power change event.
96 *
97 *	SCFLAG_PCTPRINT:	do not output APM power messages due to
98 *				to a power change event unless the battery
99 *				percentage changes.
100 */
101
102#define SCFLAG_NOPRINT	0x0008000
103#define SCFLAG_PCTPRINT	0x0004000
104#define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
105
106#define	SCFLAG_OREAD 	(1 << 0)
107#define	SCFLAG_OWRITE	(1 << 1)
108#define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
109
110
111int
112apmmatch(struct device *parent, void *match, void *aux)
113{
114	struct adb_attach_args *aa = (void *)aux;
115	if (aa->origaddr != ADBADDR_APM ||
116	    aa->handler_id != ADBADDR_APM ||
117	    aa->adbaddr != ADBADDR_APM)
118		return 0;
119
120	if (adbHardware != ADB_HW_PMU)
121		return 0;
122
123	return 1;
124}
125
126void
127apmattach(struct device *parent, struct device *self, void *aux)
128{
129	struct pmu_battery_info info;
130
131	pm_battery_info(0, &info);
132
133	printf(": battery flags 0x%X, ", info.flags);
134	printf("%d%% charged\n", ((info.cur_charge * 100) / info.max_charge));
135}
136
137int
138apmopen(dev_t dev, int flag, int mode, struct proc *p)
139{
140	struct apm_softc *sc;
141	int error = 0;
142
143	/* apm0 only */
144	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
145	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
146		return ENXIO;
147
148	DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n",
149	    APMDEV(dev), p->p_pid, flag, mode));
150
151	switch (APMDEV(dev)) {
152	case APMDEV_CTL:
153		if (!(flag & FWRITE)) {
154			error = EINVAL;
155			break;
156		}
157		if (sc->sc_flags & SCFLAG_OWRITE) {
158			error = EBUSY;
159			break;
160		}
161		sc->sc_flags |= SCFLAG_OWRITE;
162		break;
163	case APMDEV_NORMAL:
164		if (!(flag & FREAD) || (flag & FWRITE)) {
165			error = EINVAL;
166			break;
167		}
168		sc->sc_flags |= SCFLAG_OREAD;
169		break;
170	default:
171		error = ENXIO;
172		break;
173	}
174	return error;
175}
176
177int
178apmclose(dev_t dev, int flag, int mode, struct proc *p)
179{
180	struct apm_softc *sc;
181
182	/* apm0 only */
183	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
184	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
185		return ENXIO;
186
187	DPRINTF(("apmclose: pid %d flag %x mode %x\n", p->p_pid, flag, mode));
188
189	switch (APMDEV(dev)) {
190	case APMDEV_CTL:
191		sc->sc_flags &= ~SCFLAG_OWRITE;
192		break;
193	case APMDEV_NORMAL:
194		sc->sc_flags &= ~SCFLAG_OREAD;
195		break;
196	}
197	return 0;
198}
199
200int
201apmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
202{
203	struct apm_softc *sc;
204	struct pmu_battery_info batt;
205	struct apm_power_info *power;
206	int error = 0;
207
208	/* apm0 only */
209	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
210	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
211		return ENXIO;
212
213	switch (cmd) {
214		/* some ioctl names from linux */
215	case APM_IOC_STANDBY:
216		if ((flag & FWRITE) == 0)
217			error = EBADF;
218		break;
219	case APM_IOC_SUSPEND:
220		if ((flag & FWRITE) == 0)
221			error = EBADF;
222		break;
223	case APM_IOC_PRN_CTL:
224		if ((flag & FWRITE) == 0)
225			error = EBADF;
226		else {
227			int flag = *(int *)data;
228			DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag ));
229			switch (flag) {
230			case APM_PRINT_ON:	/* enable printing */
231				sc->sc_flags &= ~SCFLAG_PRINT;
232				break;
233			case APM_PRINT_OFF: /* disable printing */
234				sc->sc_flags &= ~SCFLAG_PRINT;
235				sc->sc_flags |= SCFLAG_NOPRINT;
236				break;
237			case APM_PRINT_PCT: /* disable some printing */
238				sc->sc_flags &= ~SCFLAG_PRINT;
239				sc->sc_flags |= SCFLAG_PCTPRINT;
240				break;
241			default:
242				error = EINVAL;
243				break;
244			}
245		}
246		break;
247	case APM_IOC_DEV_CTL:
248		if ((flag & FWRITE) == 0)
249			error = EBADF;
250		break;
251	case APM_IOC_GETPOWER:
252	        power = (struct apm_power_info *)data;
253
254		pm_battery_info(0, &batt);
255
256		power->ac_state = ((batt.flags & PMU_PWR_AC_PRESENT) ?
257		    APM_AC_ON : APM_AC_OFF);
258		power->battery_life =
259		    ((batt.cur_charge * 100) / batt.max_charge);
260
261		/*
262		 * If the battery is charging, return the minutes left until
263		 * charging is complete. apmd knows this.
264		 */
265
266		if (!(batt.flags & PMU_PWR_BATT_PRESENT)) {
267			power->battery_state = APM_BATT_UNKNOWN;
268			power->minutes_left = 0;
269			power->battery_life = 0;
270		} else if ((power->ac_state == APM_AC_ON) &&
271			   (batt.draw > 0)) {
272			power->minutes_left =
273			    (((batt.max_charge - batt.cur_charge) * 3600) /
274			    batt.draw) / 60;
275			power->battery_state = APM_BATT_CHARGING;
276		} else {
277			power->minutes_left =
278			    ((batt.cur_charge * 3600) / (-batt.draw)) / 60;
279
280			/* XXX - Arbitrary */
281			if (power->battery_life > 60)
282				power->battery_state = APM_BATT_HIGH;
283			else if (power->battery_life < 10)
284				power->battery_state = APM_BATT_CRITICAL;
285			else
286				power->battery_state = APM_BATT_LOW;
287		}
288		break;
289	case APM_IOC_STANDBY_REQ:
290		if ((flag & FWRITE) == 0)
291			error = EBADF;
292		break;
293	case APM_IOC_SUSPEND_REQ:
294		if ((flag & FWRITE) == 0)
295			error = EBADF;
296		break;
297	default:
298		error = ENOTTY;
299	}
300
301	return error;
302}
303
304void
305filt_apmrdetach(struct knote *kn)
306{
307	struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
308
309	SLIST_REMOVE(&sc->sc_note, kn, knote, kn_selnext);
310}
311
312int
313filt_apmread(struct knote *kn, long hint)
314{
315	/* XXX weird kqueue_scan() semantics */
316	if (hint && !kn->kn_data)
317		kn->kn_data = (int)hint;
318
319	return (1);
320}
321
322int
323apmkqfilter(dev_t dev, struct knote *kn)
324{
325	struct apm_softc *sc;
326
327	/* apm0 only */
328	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
329	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
330		return ENXIO;
331
332	switch (kn->kn_filter) {
333	case EVFILT_READ:
334		kn->kn_fop = &apmread_filtops;
335		break;
336	default:
337		return (1);
338	}
339
340	kn->kn_hook = (caddr_t)sc;
341	SLIST_INSERT_HEAD(&sc->sc_note, kn, kn_selnext);
342
343	return (0);
344}
345