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