1 /* $OpenBSD: apm.c,v 1.44 2024/05/29 06:39:13 jsg 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 #include "wsdisplay.h"
36
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/kernel.h>
40 #include <sys/proc.h>
41 #include <sys/clockintr.h>
42 #include <sys/device.h>
43 #include <sys/fcntl.h>
44 #include <sys/ioctl.h>
45 #include <sys/buf.h>
46 #include <sys/event.h>
47 #include <sys/reboot.h>
48 #include <sys/hibernate.h>
49
50 #include <machine/autoconf.h>
51 #include <machine/conf.h>
52 #include <machine/cpu.h>
53 #include <machine/apmvar.h>
54
55 #include <dev/pci/pcivar.h> /* pci_dopm */
56
57 #include <dev/wscons/wsdisplayvar.h>
58
59 #include <loongson/dev/kb3310var.h>
60
61 #if defined(APMDEBUG)
62 #define DPRINTF(x) printf x
63 #else
64 #define DPRINTF(x) /**/
65 #endif
66
67 struct apm_softc {
68 struct device sc_dev;
69 struct klist sc_note;
70 int sc_flags;
71 };
72
73 int apmmatch(struct device *, void *, void *);
74 void apmattach(struct device *, struct device *, void *);
75
76 const struct cfattach apm_ca = {
77 sizeof(struct apm_softc), apmmatch, apmattach
78 };
79
80 struct cfdriver apm_cd = {
81 NULL, "apm", DV_DULL
82 };
83
84 #define APMUNIT(dev) (minor(dev)&0xf0)
85 #define APMDEV(dev) (minor(dev)&0x0f)
86 #define APMDEV_NORMAL 0
87 #define APMDEV_CTL 8
88
89 void filt_apmrdetach(struct knote *kn);
90 int filt_apmread(struct knote *kn, long hint);
91 int apmkqfilter(dev_t dev, struct knote *kn);
92 int apm_getdefaultinfo(struct apm_power_info *);
93
94 int apm_suspend(int state);
95
96 const struct filterops apmread_filtops = {
97 .f_flags = FILTEROP_ISFD,
98 .f_attach = NULL,
99 .f_detach = filt_apmrdetach,
100 .f_event = filt_apmread,
101 };
102
103 int (*get_apminfo)(struct apm_power_info *) = apm_getdefaultinfo;
104
105 /*
106 * Flags to control kernel display
107 * SCFLAG_NOPRINT: do not output APM power messages due to
108 * a power change event.
109 *
110 * SCFLAG_PCTPRINT: do not output APM power messages due to
111 * to a power change event unless the battery
112 * percentage changes.
113 */
114
115 #define SCFLAG_NOPRINT 0x0008000
116 #define SCFLAG_PCTPRINT 0x0004000
117 #define SCFLAG_PRINT (SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
118
119 #define SCFLAG_OREAD (1 << 0)
120 #define SCFLAG_OWRITE (1 << 1)
121 #define SCFLAG_OPEN (SCFLAG_OREAD|SCFLAG_OWRITE)
122
123
124 int
apmmatch(struct device * parent,void * match,void * aux)125 apmmatch(struct device *parent, void *match, void *aux)
126 {
127 struct mainbus_attach_args *maa = aux;
128
129 /*
130 * It only makes sense to attach on a 2F system, since 2E do not
131 * feature speed throttling, and we do not support 2E-based
132 * notebooks yet (assuming there are any).
133 */
134 if (strcmp(maa->maa_name, apm_cd.cd_name) == 0 && loongson_ver == 0x2f)
135 return (1);
136 return (0);
137 }
138
139 void
apmattach(struct device * parent,struct device * self,void * aux)140 apmattach(struct device *parent, struct device *self, void *aux)
141 {
142 /* Enable PCI Power Management. */
143 pci_dopm = 1;
144
145 printf("\n");
146 }
147
148 int
apmopen(dev_t dev,int flag,int mode,struct proc * p)149 apmopen(dev_t dev, int flag, int mode, struct proc *p)
150 {
151 struct apm_softc *sc;
152 int error = 0;
153
154 /* apm0 only */
155 if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
156 !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
157 return ENXIO;
158
159 DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n",
160 APMDEV(dev), p->p_p->ps_pid, flag, mode));
161
162 switch (APMDEV(dev)) {
163 case APMDEV_CTL:
164 if (!(flag & FWRITE)) {
165 error = EINVAL;
166 break;
167 }
168 if (sc->sc_flags & SCFLAG_OWRITE) {
169 error = EBUSY;
170 break;
171 }
172 sc->sc_flags |= SCFLAG_OWRITE;
173 break;
174 case APMDEV_NORMAL:
175 if (!(flag & FREAD) || (flag & FWRITE)) {
176 error = EINVAL;
177 break;
178 }
179 sc->sc_flags |= SCFLAG_OREAD;
180 break;
181 default:
182 error = ENXIO;
183 break;
184 }
185 return error;
186 }
187
188 int
apmclose(dev_t dev,int flag,int mode,struct proc * p)189 apmclose(dev_t dev, int flag, int mode, struct proc *p)
190 {
191 struct apm_softc *sc;
192
193 /* apm0 only */
194 if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
195 !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
196 return ENXIO;
197
198 DPRINTF(("apmclose: pid %d flag %x mode %x\n",
199 p->p_p->ps_pid, flag, mode));
200
201 switch (APMDEV(dev)) {
202 case APMDEV_CTL:
203 sc->sc_flags &= ~SCFLAG_OWRITE;
204 break;
205 case APMDEV_NORMAL:
206 sc->sc_flags &= ~SCFLAG_OREAD;
207 break;
208 }
209 return 0;
210 }
211
212 int
apmioctl(dev_t dev,u_long cmd,caddr_t data,int flag,struct proc * p)213 apmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
214 {
215 struct apm_softc *sc;
216 struct apm_power_info *power;
217 int error = 0;
218
219 /* apm0 only */
220 if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
221 !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
222 return ENXIO;
223
224 switch (cmd) {
225 /* some ioctl names from linux */
226 case APM_IOC_STANDBY:
227 case APM_IOC_STANDBY_REQ:
228 case APM_IOC_SUSPEND:
229 case APM_IOC_SUSPEND_REQ:
230 if ((flag & FWRITE) == 0)
231 error = EBADF;
232 else if (sys_platform->suspend == NULL ||
233 sys_platform->resume == NULL)
234 error = EOPNOTSUPP;
235 else
236 error = apm_suspend(APM_IOC_SUSPEND);
237 break;
238 #ifdef HIBERNATE
239 case APM_IOC_HIBERNATE:
240 if ((flag & FWRITE) == 0)
241 error = EBADF;
242 else if (sys_platform->suspend == NULL ||
243 sys_platform->resume == NULL)
244 error = EOPNOTSUPP;
245 else
246 error = apm_suspend(APM_IOC_HIBERNATE);
247 break;
248 #endif
249 case APM_IOC_PRN_CTL:
250 if ((flag & FWRITE) == 0)
251 error = EBADF;
252 else {
253 int flag = *(int *)data;
254 DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag ));
255 switch (flag) {
256 case APM_PRINT_ON: /* enable printing */
257 sc->sc_flags &= ~SCFLAG_PRINT;
258 break;
259 case APM_PRINT_OFF: /* disable printing */
260 sc->sc_flags &= ~SCFLAG_PRINT;
261 sc->sc_flags |= SCFLAG_NOPRINT;
262 break;
263 case APM_PRINT_PCT: /* disable some printing */
264 sc->sc_flags &= ~SCFLAG_PRINT;
265 sc->sc_flags |= SCFLAG_PCTPRINT;
266 break;
267 default:
268 error = EINVAL;
269 break;
270 }
271 }
272 break;
273 case APM_IOC_DEV_CTL:
274 if ((flag & FWRITE) == 0)
275 error = EBADF;
276 else
277 error = EOPNOTSUPP; /* XXX */
278 break;
279 case APM_IOC_GETPOWER:
280 power = (struct apm_power_info *)data;
281 error = (*get_apminfo)(power);
282 break;
283 default:
284 error = ENOTTY;
285 }
286
287 return error;
288 }
289
290 void
filt_apmrdetach(struct knote * kn)291 filt_apmrdetach(struct knote *kn)
292 {
293 struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
294
295 klist_remove_locked(&sc->sc_note, kn);
296 }
297
298 int
filt_apmread(struct knote * kn,long hint)299 filt_apmread(struct knote *kn, long hint)
300 {
301 /* XXX weird kqueue_scan() semantics */
302 if (hint && !kn->kn_data)
303 kn->kn_data = (int)hint;
304
305 return (1);
306 }
307
308 int
apmkqfilter(dev_t dev,struct knote * kn)309 apmkqfilter(dev_t dev, struct knote *kn)
310 {
311 struct apm_softc *sc;
312
313 /* apm0 only */
314 if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
315 !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
316 return ENXIO;
317
318 switch (kn->kn_filter) {
319 case EVFILT_READ:
320 kn->kn_fop = &apmread_filtops;
321 break;
322 default:
323 return (EINVAL);
324 }
325
326 kn->kn_hook = (caddr_t)sc;
327 klist_insert_locked(&sc->sc_note, kn);
328
329 return (0);
330 }
331
332 int
apm_getdefaultinfo(struct apm_power_info * info)333 apm_getdefaultinfo(struct apm_power_info *info)
334 {
335 info->battery_state = APM_BATT_UNKNOWN;
336 info->ac_state = APM_AC_UNKNOWN;
337 info->battery_life = 0;
338 info->minutes_left = -1;
339 return (0);
340 }
341
342 void
apm_setinfohook(int (* hook)(struct apm_power_info *))343 apm_setinfohook(int (*hook)(struct apm_power_info *))
344 {
345 get_apminfo = hook;
346 }
347
348 int
apm_record_event(u_int event,const char * src,const char * msg)349 apm_record_event(u_int event, const char *src, const char *msg)
350 {
351 static int apm_evindex;
352 struct apm_softc *sc;
353
354 /* apm0 only */
355 if (apm_cd.cd_ndevs == 0 || (sc = apm_cd.cd_devs[0]) == NULL)
356 return ENXIO;
357
358 if ((sc->sc_flags & SCFLAG_NOPRINT) == 0)
359 printf("%s: %s %s\n", sc->sc_dev.dv_xname, src, msg);
360
361 /* skip if no user waiting */
362 if ((sc->sc_flags & SCFLAG_OPEN) == 0)
363 return (1);
364
365 apm_evindex++;
366 knote_locked(&sc->sc_note, APM_EVENT_COMPOSE(event, apm_evindex));
367
368 return (0);
369 }
370
371 int
apm_suspend(int state)372 apm_suspend(int state)
373 {
374 int rv;
375 int s;
376
377 #if NWSDISPLAY > 0
378 wsdisplay_suspend();
379 #endif
380
381 stop_periodic_resettodr();
382 resettodr();
383
384 config_suspend_all(DVACT_QUIESCE);
385 bufq_quiesce();
386
387 s = splhigh();
388 (void)disableintr();
389 cold = 2;
390
391 rv = config_suspend_all(DVACT_SUSPEND);
392
393 suspend_randomness();
394
395 #ifdef HIBERNATE
396 if (state == APM_IOC_HIBERNATE) {
397 uvm_pmr_zero_everything();
398 if (hibernate_suspend()) {
399 printf("apm: hibernate_suspend failed");
400 uvm_pmr_dirty_everything();
401 return (ECANCELED);
402 }
403 }
404 #endif
405
406 /* XXX
407 * Flag to disk drivers that they should "power down" the disk
408 * when we get to DVACT_POWERDOWN.
409 */
410 boothowto |= RB_POWERDOWN;
411 config_suspend_all(DVACT_POWERDOWN);
412 boothowto &= ~RB_POWERDOWN;
413
414 if (rv == 0) {
415 rv = sys_platform->suspend();
416 if (rv == 0)
417 rv = sys_platform->resume();
418 }
419
420 inittodr(gettime()); /* Move the clock forward */
421 clockintr_cpu_init(NULL);
422 clockintr_trigger();
423
424 config_suspend_all(DVACT_RESUME);
425
426 cold = 0;
427 (void)enableintr();
428 splx(s);
429
430 resume_randomness(NULL, 0); /* force RNG upper level reseed */
431 bufq_restart();
432
433 config_suspend_all(DVACT_WAKEUP);
434
435 start_periodic_resettodr();
436
437 #if NWSDISPLAY > 0
438 wsdisplay_resume();
439 #endif
440
441 apm_record_event(APM_NORMAL_RESUME, "System", "resumed from sleep");
442
443 return rv;
444 }
445