apm.c revision 1.4
1/*	$NetBSD: apm.c,v 1.4 2002/10/02 05:30:39 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
117CFATTACH_DECL(apm, sizeof(struct apm_softc),
118    apmmatch, apmattach, NULL, NULL);
119
120#ifdef __OpenBSD__
121struct cfdriver apm_cd = {
122	NULL, "apm", DV_DULL
123};
124#else
125extern struct cfdriver apm_cd;
126
127dev_type_open(apmopen);
128dev_type_close(apmclose);
129dev_type_ioctl(apmioctl);
130dev_type_poll(apmpoll);
131
132const struct cdevsw apm_cdevsw = {
133	apmopen, apmclose, noread, nowrite, apmioctl,
134	nostop, notty, apmpoll, nommap,
135};
136#endif
137
138int	apm_evindex;
139
140#define	APMUNIT(dev)	(minor(dev)&0xf0)
141#define	APMDEV(dev)	(minor(dev)&0x0f)
142#define APMDEV_NORMAL	0
143#define APMDEV_CTL	8
144
145#ifdef __OpenBSD__
146void filt_apmrdetach(struct knote *kn);
147int filt_apmread(struct knote *kn, long hint);
148int apmkqfilter(dev_t dev, struct knote *kn);
149
150struct filterops apmread_filtops =
151	{ 1, NULL, filt_apmrdetach, filt_apmread};
152#endif
153
154/*
155 * Flags to control kernel display
156 *	SCFLAG_NOPRINT:		do not output APM power messages due to
157 *				a power change event.
158 *
159 *	SCFLAG_PCTPRINT:	do not output APM power messages due to
160 *				to a power change event unless the battery
161 *				percentage changes.
162 */
163
164#define SCFLAG_NOPRINT	0x0008000
165#define SCFLAG_PCTPRINT	0x0004000
166#define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
167
168#define	SCFLAG_OREAD 	(1 << 0)
169#define	SCFLAG_OWRITE	(1 << 1)
170#define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
171
172
173int
174apmmatch(parent, match, aux)
175	struct device *parent;
176	struct cfdata *match;
177	void *aux;
178{
179	struct adb_attach_args *aa = (void *)aux;
180	if (aa->origaddr != ADBADDR_APM ||
181	    aa->handler_id != ADBADDR_APM ||
182	    aa->adbaddr != ADBADDR_APM)
183		return 0;
184
185	if (adbHardware != ADB_HW_PB)
186		return 0;
187
188	return 1;
189}
190
191void
192apmattach(parent, self, aux)
193	struct device *parent, *self;
194	void *aux;
195{
196	struct pmu_battery_info info;
197
198	pm_battery_info(0, &info);
199
200	printf(": battery flags 0x%X, ", info.flags);
201	printf("%d%% charged\n", ((info.cur_charge * 100) / info.max_charge));
202}
203
204int
205apmopen(dev, flag, mode, p)
206	dev_t dev;
207	int flag, mode;
208	struct proc *p;
209{
210	struct apm_softc *sc;
211	int error = 0;
212
213	/* apm0 only */
214	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
215	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
216		return ENXIO;
217
218	DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n",
219	    APMDEV(dev), p->p_pid, flag, mode));
220
221	APM_LOCK(sc);
222	switch (APMDEV(dev)) {
223	case APMDEV_CTL:
224		if (!(flag & FWRITE)) {
225			error = EINVAL;
226			break;
227		}
228		if (sc->sc_flags & SCFLAG_OWRITE) {
229			error = EBUSY;
230			break;
231		}
232		sc->sc_flags |= SCFLAG_OWRITE;
233		break;
234	case APMDEV_NORMAL:
235		if (!(flag & FREAD) || (flag & FWRITE)) {
236			error = EINVAL;
237			break;
238		}
239		sc->sc_flags |= SCFLAG_OREAD;
240		break;
241	default:
242		error = ENXIO;
243		break;
244	}
245	APM_UNLOCK(sc);
246	return error;
247}
248
249int
250apmclose(dev, flag, mode, p)
251	dev_t dev;
252	int flag, mode;
253	struct proc *p;
254{
255	struct apm_softc *sc;
256
257	/* apm0 only */
258	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
259	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
260		return ENXIO;
261
262	DPRINTF(("apmclose: pid %d flag %x mode %x\n", p->p_pid, flag, mode));
263
264	APM_LOCK(sc);
265	switch (APMDEV(dev)) {
266	case APMDEV_CTL:
267		sc->sc_flags &= ~SCFLAG_OWRITE;
268		break;
269	case APMDEV_NORMAL:
270		sc->sc_flags &= ~SCFLAG_OREAD;
271		break;
272	}
273	APM_UNLOCK(sc);
274	return 0;
275}
276
277int
278apmioctl(dev, cmd, data, flag, p)
279	dev_t dev;
280	u_long cmd;
281	caddr_t data;
282	int flag;
283	struct proc *p;
284{
285	struct apm_softc *sc;
286	struct pmu_battery_info batt;
287	struct apm_power_info *power;
288	int error = 0;
289
290	/* apm0 only */
291	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
292	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
293		return ENXIO;
294
295	APM_LOCK(sc);
296	switch (cmd) {
297		/* some ioctl names from linux */
298	case APM_IOC_STANDBY:
299		if ((flag & FWRITE) == 0)
300			error = EBADF;
301	case APM_IOC_SUSPEND:
302		if ((flag & FWRITE) == 0)
303			error = EBADF;
304		break;
305	case APM_IOC_PRN_CTL:
306		if ((flag & FWRITE) == 0)
307			error = EBADF;
308		else {
309			int flag = *(int *)data;
310			DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag ));
311			switch (flag) {
312			case APM_PRINT_ON:	/* enable printing */
313				sc->sc_flags &= ~SCFLAG_PRINT;
314				break;
315			case APM_PRINT_OFF: /* disable printing */
316				sc->sc_flags &= ~SCFLAG_PRINT;
317				sc->sc_flags |= SCFLAG_NOPRINT;
318				break;
319			case APM_PRINT_PCT: /* disable some printing */
320				sc->sc_flags &= ~SCFLAG_PRINT;
321				sc->sc_flags |= SCFLAG_PCTPRINT;
322				break;
323			default:
324				error = EINVAL;
325				break;
326			}
327		}
328		break;
329	case APM_IOC_DEV_CTL:
330		if ((flag & FWRITE) == 0)
331			error = EBADF;
332		break;
333	case APM_IOC_GETPOWER:
334	        power = (struct apm_power_info *)data;
335
336		pm_battery_info(0, &batt);
337
338		power->ac_state = ((batt.flags & PMU_PWR_AC_PRESENT) ?
339		    APM_AC_ON : APM_AC_OFF);
340		power->battery_life =
341		    ((batt.cur_charge * 100) / batt.max_charge);
342
343		/*
344		 * If the battery is charging, return the minutes left until
345		 * charging is complete. apmd knows this.
346		 */
347
348		if (!(batt.flags & PMU_PWR_BATT_PRESENT)) {
349			power->battery_state = APM_BATT_UNKNOWN;
350			power->minutes_left = 0;
351			power->battery_life = 0;
352		} else if ((power->ac_state == APM_AC_ON) &&
353			   (batt.draw > 0)) {
354			power->minutes_left =
355			    (((batt.max_charge - batt.cur_charge) * 3600) /
356			    batt.draw) / 60;
357			power->battery_state = APM_BATT_CHARGING;
358		} else {
359			power->minutes_left =
360			    ((batt.cur_charge * 3600) / (-batt.draw)) / 60;
361
362			/* XXX - Arbitrary */
363			if (power->battery_life > 60) {
364				power->battery_state = APM_BATT_HIGH;
365			} else if (power->battery_life < 10) {
366				power->battery_state = APM_BATT_CRITICAL;
367			} else {
368				power->battery_state = APM_BATT_LOW;
369			}
370		}
371
372		break;
373
374	default:
375		error = ENOTTY;
376	}
377	APM_UNLOCK(sc);
378
379	return error;
380}
381
382#ifdef __NetBSD__
383#if 0
384/*
385 * return 0 if the user will notice and handle the event,
386 * return 1 if the kernel driver should do so.
387 */
388static int
389apm_record_event(sc, event_type)
390	struct apm_softc *sc;
391	u_int event_type;
392{
393	struct apm_event_info *evp;
394
395	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
396		return 1;		/* no user waiting */
397	if (sc->event_count == APM_NEVENTS) {
398		DPRINTF(("apm_record_event: queue full!\n"));
399		return 1;			/* overflow */
400	}
401	evp = &sc->event_list[sc->event_ptr];
402	sc->event_count++;
403	sc->event_ptr++;
404	sc->event_ptr %= APM_NEVENTS;
405	evp->type = event_type;
406	evp->index = ++apm_evindex;
407	selwakeup(&sc->sc_rsel);
408	return (sc->sc_flags & SCFLAG_OWRITE) ? 0 : 1; /* user may handle */
409}
410#endif
411
412int
413apmpoll(dev, events, p)
414	dev_t dev;
415	int events;
416	struct proc *p;
417{
418	struct apm_softc *sc = apm_cd.cd_devs[APMUNIT(dev)];
419	int revents = 0;
420
421	APM_LOCK(sc);
422	if (events & (POLLIN | POLLRDNORM)) {
423		if (sc->event_count)
424			revents |= events & (POLLIN | POLLRDNORM);
425		else
426			selrecord(p, &sc->sc_rsel);
427	}
428	APM_UNLOCK(sc);
429
430	return (revents);
431}
432#endif
433
434#ifdef __OpenBSD__
435void
436filt_apmrdetach(kn)
437	struct knote *kn;
438{
439	struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
440
441	SLIST_REMOVE(&sc->sc_note, kn, knote, kn_selnext);
442}
443
444int
445filt_apmread(kn, hint)
446	struct knote *kn;
447	long hint;
448{
449	/* XXX weird kqueue_scan() semantics */
450	if (hint && !kn->kn_data)
451		kn->kn_data = (int)hint;
452
453	return (1);
454}
455
456int
457apmkqfilter(dev, kn)
458	dev_t dev;
459	struct knote *kn;
460{
461	struct apm_softc *sc;
462
463	/* apm0 only */
464	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
465	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
466		return ENXIO;
467
468	switch (kn->kn_filter) {
469	case EVFILT_READ:
470		kn->kn_fop = &apmread_filtops;
471		break;
472	default:
473		return (1);
474	}
475
476	kn->kn_hook = (caddr_t)sc;
477	SLIST_INSERT_HEAD(&sc->sc_note, kn, kn_selnext);
478
479	return (0);
480}
481#endif
482