apm.c revision 1.11
1/*	$NetBSD: apm.c,v 1.11 2005/02/01 02:03:01 briggs 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 <sys/cdefs.h>
40__KERNEL_RCSID(0, "$NetBSD: apm.c,v 1.11 2005/02/01 02:03:01 briggs Exp $");
41
42#include "apm.h"
43
44#if NAPM > 1
45#error only one APM emulation device may be configured
46#endif
47
48#include <sys/param.h>
49#include <sys/systm.h>
50#include <sys/kernel.h>
51#include <sys/proc.h>
52#include <sys/device.h>
53#include <sys/fcntl.h>
54#include <sys/ioctl.h>
55#ifdef __OpenBSD__
56#include <sys/event.h>
57#endif
58#ifdef __NetBSD__
59#include <sys/select.h>
60#include <sys/poll.h>
61#include <sys/conf.h>
62#endif
63
64#ifdef __OpenBSD__
65#include <machine/conf.h>
66#endif
67#include <machine/cpu.h>
68#include <machine/apmvar.h>
69
70#include <macppc/dev/adbvar.h>
71#include <macppc/dev/pm_direct.h>
72
73#if defined(APMDEBUG)
74#define DPRINTF(x)	printf x
75#else
76#define	DPRINTF(x)	/**/
77#endif
78
79#define APM_NEVENTS 16
80
81struct apm_softc {
82	struct device sc_dev;
83	struct selinfo sc_rsel;
84#ifdef __OpenBSD__
85	struct klist sc_note;
86#endif
87	int    sc_flags;
88	int	event_count;
89	int	event_ptr;
90	struct lock sc_lock;
91	struct	apm_event_info event_list[APM_NEVENTS];
92};
93
94/*
95 * A brief note on the locking protocol: it's very simple; we
96 * assert an exclusive lock any time thread context enters the
97 * APM module.  This is both the APM thread itself, as well as
98 * user context.
99 */
100#ifdef __NetBSD__
101#define	APM_LOCK(apmsc)							\
102	(void) lockmgr(&(apmsc)->sc_lock, LK_EXCLUSIVE, NULL)
103#define	APM_UNLOCK(apmsc)						\
104	(void) lockmgr(&(apmsc)->sc_lock, LK_RELEASE, NULL)
105#else
106#define APM_LOCK(apmsc)
107#define APM_UNLOCK(apmsc)
108#endif
109
110int apmmatch(struct device *, struct cfdata *, void *);
111void apmattach(struct device *, struct device *, void *);
112
113#ifdef __NetBSD__
114#if 0
115static int	apm_record_event __P((struct apm_softc *, u_int));
116#endif
117#endif
118
119CFATTACH_DECL(apm, sizeof(struct apm_softc),
120    apmmatch, apmattach, NULL, NULL);
121
122#ifdef __OpenBSD__
123struct cfdriver apm_cd = {
124	NULL, "apm", DV_DULL
125};
126#else
127extern struct cfdriver apm_cd;
128
129dev_type_open(apmopen);
130dev_type_close(apmclose);
131dev_type_ioctl(apmioctl);
132dev_type_poll(apmpoll);
133dev_type_kqfilter(apmkqfilter);
134
135const struct cdevsw apm_cdevsw = {
136	apmopen, apmclose, noread, nowrite, apmioctl,
137	nostop, notty, apmpoll, nommap, apmkqfilter,
138};
139#endif
140
141int	apm_evindex;
142
143#define	APMUNIT(dev)	(minor(dev)&0xf0)
144#define	APMDEV(dev)	(minor(dev)&0x0f)
145#define APMDEV_NORMAL	0
146#define APMDEV_CTL	8
147
148/*
149 * Flags to control kernel display
150 *	SCFLAG_NOPRINT:		do not output APM power messages due to
151 *				a power change event.
152 *
153 *	SCFLAG_PCTPRINT:	do not output APM power messages due to
154 *				to a power change event unless the battery
155 *				percentage changes.
156 */
157
158#define SCFLAG_NOPRINT	0x0008000
159#define SCFLAG_PCTPRINT	0x0004000
160#define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
161
162#define	SCFLAG_OREAD 	(1 << 0)
163#define	SCFLAG_OWRITE	(1 << 1)
164#define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
165
166
167int
168apmmatch(parent, match, aux)
169	struct device *parent;
170	struct cfdata *match;
171	void *aux;
172{
173	struct adb_attach_args *aa = (void *)aux;
174	if (aa->origaddr != ADBADDR_APM ||
175	    aa->handler_id != ADBADDR_APM ||
176	    aa->adbaddr != ADBADDR_APM)
177		return 0;
178
179	if (adbHardware != ADB_HW_PB)
180		return 0;
181
182	return 1;
183}
184
185void
186apmattach(parent, self, aux)
187	struct device *parent, *self;
188	void *aux;
189{
190	struct apm_softc *sc = (struct apm_softc *) self;
191	struct pmu_battery_info info;
192
193	pm_battery_info(0, &info);
194
195	printf(": battery flags 0x%X, ", info.flags);
196	printf("%d%% charged\n", ((info.cur_charge * 100) / info.max_charge));
197
198	sc->sc_flags = 0;
199	sc->event_ptr = 0;
200	sc->event_count = 0;
201	lockinit(&sc->sc_lock, PWAIT, "apmlk", 0, 0);
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
434static void
435filt_apmrdetach(struct knote *kn)
436{
437	struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
438
439	APM_LOCK(sc);
440	SLIST_REMOVE(&sc->sc_rsel.sel_klist, kn, knote, kn_selnext);
441	APM_UNLOCK(sc);
442}
443
444static int
445filt_apmread(struct knote *kn, long hint)
446{
447	struct apm_softc *sc = kn->kn_hook;
448
449	kn->kn_data = sc->event_count;
450	return (kn->kn_data > 0);
451}
452
453static struct filterops apmread_filtops =
454	{ 1, NULL, filt_apmrdetach, filt_apmread};
455
456int
457apmkqfilter(dev, kn)
458	dev_t dev;
459	struct knote *kn;
460{
461	struct apm_softc *sc = apm_cd.cd_devs[APMUNIT(dev)];
462	struct klist *klist;
463
464	switch (kn->kn_filter) {
465	case EVFILT_READ:
466		klist = &sc->sc_rsel.sel_klist;
467		kn->kn_fop = &apmread_filtops;
468		break;
469	default:
470		return (1);
471	}
472
473	kn->kn_hook = sc;
474
475	APM_LOCK(sc);
476	SLIST_INSERT_HEAD(klist, kn, kn_selnext);
477	APM_UNLOCK(sc);
478
479	return (0);
480}
481