apm.c revision 1.3
1/*	$NetBSD: apm.c,v 1.3 2002/09/27 20:33:32 thorpej Exp $	*/
2/*	$OpenBSD: apm.c,v 1.5 2002/06/07 07:13:59 miod Exp $	*/
3
4/*-
5 * Copyright (c) 2001 Alexander Guy.  All rights reserved.
6 * Copyright (c) 1998-2001 Michael Shalayeff. All rights reserved.
7 * Copyright (c) 1995 John T. Kohl.  All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 *    must display the following acknowledgement:
19 *	This product includes software developed by the University of
20 *	California, Berkeley and its contributors.
21 * 4. Neither the name of the University nor the names of its contributors
22 *    may be used to endorse or promote products derived from this software
23 *    without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 *
37 */
38
39#include "apm.h"
40
41#if NAPM > 1
42#error only one APM emulation device may be configured
43#endif
44
45#include <sys/param.h>
46#include <sys/systm.h>
47#include <sys/kernel.h>
48#include <sys/proc.h>
49#include <sys/device.h>
50#include <sys/fcntl.h>
51#include <sys/ioctl.h>
52#ifdef __OpenBSD__
53#include <sys/event.h>
54#endif
55#ifdef __NetBSD__
56#include <sys/select.h>
57#include <sys/poll.h>
58#include <sys/conf.h>
59#endif
60
61#ifdef __OpenBSD__
62#include <machine/conf.h>
63#endif
64#include <machine/cpu.h>
65#include <machine/apmvar.h>
66
67#include <macppc/dev/adbvar.h>
68#include <macppc/dev/adb_direct.h>
69#include <macppc/dev/pm_direct.h>
70
71#if defined(APMDEBUG)
72#define DPRINTF(x)	printf x
73#else
74#define	DPRINTF(x)	/**/
75#endif
76
77#define APM_NEVENTS 16
78
79struct apm_softc {
80	struct device sc_dev;
81	struct selinfo sc_rsel;
82#ifdef __OpenBSD__
83	struct klist sc_note;
84#endif
85	int    sc_flags;
86	int	event_count;
87	int	event_ptr;
88	struct lock sc_lock;
89	struct	apm_event_info event_list[APM_NEVENTS];
90};
91
92/*
93 * A brief note on the locking protocol: it's very simple; we
94 * assert an exclusive lock any time thread context enters the
95 * APM module.  This is both the APM thread itself, as well as
96 * user context.
97 */
98#ifdef __NetBSD__
99#define	APM_LOCK(apmsc)							\
100	(void) lockmgr(&(apmsc)->sc_lock, LK_EXCLUSIVE, NULL)
101#define	APM_UNLOCK(apmsc)						\
102	(void) lockmgr(&(apmsc)->sc_lock, LK_RELEASE, NULL)
103#else
104#define APM_LOCK(apmsc)
105#define APM_UNLOCK(apmsc)
106#endif
107
108int apmmatch(struct device *, struct cfdata *, void *);
109void apmattach(struct device *, struct device *, void *);
110
111#ifdef __NetBSD__
112#if 0
113static int	apm_record_event __P((struct apm_softc *, u_int));
114#endif
115#endif
116
117const struct cfattach apm_ca = {
118	sizeof(struct apm_softc), apmmatch, apmattach
119};
120
121#ifdef __OpenBSD__
122struct cfdriver apm_cd = {
123	NULL, "apm", DV_DULL
124};
125#else
126extern struct cfdriver apm_cd;
127
128dev_type_open(apmopen);
129dev_type_close(apmclose);
130dev_type_ioctl(apmioctl);
131dev_type_poll(apmpoll);
132
133const struct cdevsw apm_cdevsw = {
134	apmopen, apmclose, noread, nowrite, apmioctl,
135	nostop, notty, apmpoll, nommap,
136};
137#endif
138
139int	apm_evindex;
140
141#define	APMUNIT(dev)	(minor(dev)&0xf0)
142#define	APMDEV(dev)	(minor(dev)&0x0f)
143#define APMDEV_NORMAL	0
144#define APMDEV_CTL	8
145
146#ifdef __OpenBSD__
147void filt_apmrdetach(struct knote *kn);
148int filt_apmread(struct knote *kn, long hint);
149int apmkqfilter(dev_t dev, struct knote *kn);
150
151struct filterops apmread_filtops =
152	{ 1, NULL, filt_apmrdetach, filt_apmread};
153#endif
154
155/*
156 * Flags to control kernel display
157 *	SCFLAG_NOPRINT:		do not output APM power messages due to
158 *				a power change event.
159 *
160 *	SCFLAG_PCTPRINT:	do not output APM power messages due to
161 *				to a power change event unless the battery
162 *				percentage changes.
163 */
164
165#define SCFLAG_NOPRINT	0x0008000
166#define SCFLAG_PCTPRINT	0x0004000
167#define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
168
169#define	SCFLAG_OREAD 	(1 << 0)
170#define	SCFLAG_OWRITE	(1 << 1)
171#define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
172
173
174int
175apmmatch(parent, match, aux)
176	struct device *parent;
177	struct cfdata *match;
178	void *aux;
179{
180	struct adb_attach_args *aa = (void *)aux;
181	if (aa->origaddr != ADBADDR_APM ||
182	    aa->handler_id != ADBADDR_APM ||
183	    aa->adbaddr != ADBADDR_APM)
184		return 0;
185
186	if (adbHardware != ADB_HW_PB)
187		return 0;
188
189	return 1;
190}
191
192void
193apmattach(parent, self, aux)
194	struct device *parent, *self;
195	void *aux;
196{
197	struct pmu_battery_info info;
198
199	pm_battery_info(0, &info);
200
201	printf(": battery flags 0x%X, ", info.flags);
202	printf("%d%% charged\n", ((info.cur_charge * 100) / info.max_charge));
203}
204
205int
206apmopen(dev, flag, mode, p)
207	dev_t dev;
208	int flag, mode;
209	struct proc *p;
210{
211	struct apm_softc *sc;
212	int error = 0;
213
214	/* apm0 only */
215	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
216	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
217		return ENXIO;
218
219	DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n",
220	    APMDEV(dev), p->p_pid, flag, mode));
221
222	APM_LOCK(sc);
223	switch (APMDEV(dev)) {
224	case APMDEV_CTL:
225		if (!(flag & FWRITE)) {
226			error = EINVAL;
227			break;
228		}
229		if (sc->sc_flags & SCFLAG_OWRITE) {
230			error = EBUSY;
231			break;
232		}
233		sc->sc_flags |= SCFLAG_OWRITE;
234		break;
235	case APMDEV_NORMAL:
236		if (!(flag & FREAD) || (flag & FWRITE)) {
237			error = EINVAL;
238			break;
239		}
240		sc->sc_flags |= SCFLAG_OREAD;
241		break;
242	default:
243		error = ENXIO;
244		break;
245	}
246	APM_UNLOCK(sc);
247	return error;
248}
249
250int
251apmclose(dev, flag, mode, p)
252	dev_t dev;
253	int flag, mode;
254	struct proc *p;
255{
256	struct apm_softc *sc;
257
258	/* apm0 only */
259	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
260	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
261		return ENXIO;
262
263	DPRINTF(("apmclose: pid %d flag %x mode %x\n", p->p_pid, flag, mode));
264
265	APM_LOCK(sc);
266	switch (APMDEV(dev)) {
267	case APMDEV_CTL:
268		sc->sc_flags &= ~SCFLAG_OWRITE;
269		break;
270	case APMDEV_NORMAL:
271		sc->sc_flags &= ~SCFLAG_OREAD;
272		break;
273	}
274	APM_UNLOCK(sc);
275	return 0;
276}
277
278int
279apmioctl(dev, cmd, data, flag, p)
280	dev_t dev;
281	u_long cmd;
282	caddr_t data;
283	int flag;
284	struct proc *p;
285{
286	struct apm_softc *sc;
287	struct pmu_battery_info batt;
288	struct apm_power_info *power;
289	int error = 0;
290
291	/* apm0 only */
292	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
293	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
294		return ENXIO;
295
296	APM_LOCK(sc);
297	switch (cmd) {
298		/* some ioctl names from linux */
299	case APM_IOC_STANDBY:
300		if ((flag & FWRITE) == 0)
301			error = EBADF;
302	case APM_IOC_SUSPEND:
303		if ((flag & FWRITE) == 0)
304			error = EBADF;
305		break;
306	case APM_IOC_PRN_CTL:
307		if ((flag & FWRITE) == 0)
308			error = EBADF;
309		else {
310			int flag = *(int *)data;
311			DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag ));
312			switch (flag) {
313			case APM_PRINT_ON:	/* enable printing */
314				sc->sc_flags &= ~SCFLAG_PRINT;
315				break;
316			case APM_PRINT_OFF: /* disable printing */
317				sc->sc_flags &= ~SCFLAG_PRINT;
318				sc->sc_flags |= SCFLAG_NOPRINT;
319				break;
320			case APM_PRINT_PCT: /* disable some printing */
321				sc->sc_flags &= ~SCFLAG_PRINT;
322				sc->sc_flags |= SCFLAG_PCTPRINT;
323				break;
324			default:
325				error = EINVAL;
326				break;
327			}
328		}
329		break;
330	case APM_IOC_DEV_CTL:
331		if ((flag & FWRITE) == 0)
332			error = EBADF;
333		break;
334	case APM_IOC_GETPOWER:
335	        power = (struct apm_power_info *)data;
336
337		pm_battery_info(0, &batt);
338
339		power->ac_state = ((batt.flags & PMU_PWR_AC_PRESENT) ?
340		    APM_AC_ON : APM_AC_OFF);
341		power->battery_life =
342		    ((batt.cur_charge * 100) / batt.max_charge);
343
344		/*
345		 * If the battery is charging, return the minutes left until
346		 * charging is complete. apmd knows this.
347		 */
348
349		if (!(batt.flags & PMU_PWR_BATT_PRESENT)) {
350			power->battery_state = APM_BATT_UNKNOWN;
351			power->minutes_left = 0;
352			power->battery_life = 0;
353		} else if ((power->ac_state == APM_AC_ON) &&
354			   (batt.draw > 0)) {
355			power->minutes_left =
356			    (((batt.max_charge - batt.cur_charge) * 3600) /
357			    batt.draw) / 60;
358			power->battery_state = APM_BATT_CHARGING;
359		} else {
360			power->minutes_left =
361			    ((batt.cur_charge * 3600) / (-batt.draw)) / 60;
362
363			/* XXX - Arbitrary */
364			if (power->battery_life > 60) {
365				power->battery_state = APM_BATT_HIGH;
366			} else if (power->battery_life < 10) {
367				power->battery_state = APM_BATT_CRITICAL;
368			} else {
369				power->battery_state = APM_BATT_LOW;
370			}
371		}
372
373		break;
374
375	default:
376		error = ENOTTY;
377	}
378	APM_UNLOCK(sc);
379
380	return error;
381}
382
383#ifdef __NetBSD__
384#if 0
385/*
386 * return 0 if the user will notice and handle the event,
387 * return 1 if the kernel driver should do so.
388 */
389static int
390apm_record_event(sc, event_type)
391	struct apm_softc *sc;
392	u_int event_type;
393{
394	struct apm_event_info *evp;
395
396	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
397		return 1;		/* no user waiting */
398	if (sc->event_count == APM_NEVENTS) {
399		DPRINTF(("apm_record_event: queue full!\n"));
400		return 1;			/* overflow */
401	}
402	evp = &sc->event_list[sc->event_ptr];
403	sc->event_count++;
404	sc->event_ptr++;
405	sc->event_ptr %= APM_NEVENTS;
406	evp->type = event_type;
407	evp->index = ++apm_evindex;
408	selwakeup(&sc->sc_rsel);
409	return (sc->sc_flags & SCFLAG_OWRITE) ? 0 : 1; /* user may handle */
410}
411#endif
412
413int
414apmpoll(dev, events, p)
415	dev_t dev;
416	int events;
417	struct proc *p;
418{
419	struct apm_softc *sc = apm_cd.cd_devs[APMUNIT(dev)];
420	int revents = 0;
421
422	APM_LOCK(sc);
423	if (events & (POLLIN | POLLRDNORM)) {
424		if (sc->event_count)
425			revents |= events & (POLLIN | POLLRDNORM);
426		else
427			selrecord(p, &sc->sc_rsel);
428	}
429	APM_UNLOCK(sc);
430
431	return (revents);
432}
433#endif
434
435#ifdef __OpenBSD__
436void
437filt_apmrdetach(kn)
438	struct knote *kn;
439{
440	struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
441
442	SLIST_REMOVE(&sc->sc_note, kn, knote, kn_selnext);
443}
444
445int
446filt_apmread(kn, hint)
447	struct knote *kn;
448	long hint;
449{
450	/* XXX weird kqueue_scan() semantics */
451	if (hint && !kn->kn_data)
452		kn->kn_data = (int)hint;
453
454	return (1);
455}
456
457int
458apmkqfilter(dev, kn)
459	dev_t dev;
460	struct knote *kn;
461{
462	struct apm_softc *sc;
463
464	/* apm0 only */
465	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
466	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
467		return ENXIO;
468
469	switch (kn->kn_filter) {
470	case EVFILT_READ:
471		kn->kn_fop = &apmread_filtops;
472		break;
473	default:
474		return (1);
475	}
476
477	kn->kn_hook = (caddr_t)sc;
478	SLIST_INSERT_HEAD(&sc->sc_note, kn, kn_selnext);
479
480	return (0);
481}
482#endif
483