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