apm.c revision 1.30
1/* $NetBSD: apm.c,v 1.30 2021/09/26 01:16:07 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. Neither the names of the authors nor the names of contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 */ 34 35#include <sys/cdefs.h> 36__KERNEL_RCSID(0, "$NetBSD: apm.c,v 1.30 2021/09/26 01:16:07 thorpej Exp $"); 37 38#include "apm.h" 39 40#if NAPM > 1 41#error only one APM emulation device may be configured 42#endif 43 44#include <sys/param.h> 45#include <sys/systm.h> 46#include <sys/kernel.h> 47#include <sys/proc.h> 48#include <sys/device.h> 49#include <sys/fcntl.h> 50#include <sys/ioctl.h> 51#include <sys/mutex.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/pm_direct.h> 69 70#if defined(APMDEBUG) 71#define DPRINTF(x) printf x 72#else 73#define DPRINTF(x) /**/ 74#endif 75 76#define APM_NEVENTS 16 77 78struct apm_softc { 79 struct selinfo sc_rsel; 80#ifdef __OpenBSD__ 81 struct klist sc_note; 82#endif 83 int sc_flags; 84 int event_count; 85 int event_ptr; 86 kmutex_t sc_lock; 87 struct apm_event_info event_list[APM_NEVENTS]; 88}; 89 90/* 91 * A brief note on the locking protocol: it's very simple; we 92 * assert an exclusive lock any time thread context enters the 93 * APM module. This is both the APM thread itself, as well as 94 * user context. 95 */ 96#ifdef __NetBSD__ 97#define APM_LOCK(apmsc) mutex_enter(&(apmsc)->sc_lock) 98#define APM_UNLOCK(apmsc) mutex_exit(&(apmsc)->sc_lock) 99#else 100#define APM_LOCK(apmsc) 101#define APM_UNLOCK(apmsc) 102#endif 103 104int apmmatch(device_t, cfdata_t, void *); 105void apmattach(device_t, device_t, void *); 106 107#ifdef __NetBSD__ 108#if 0 109static int apm_record_event(struct apm_softc *, u_int); 110#endif 111#endif 112 113CFATTACH_DECL_NEW(apm, sizeof(struct apm_softc), 114 apmmatch, apmattach, NULL, NULL); 115 116#ifdef __OpenBSD__ 117struct cfdriver apm_cd = { 118 NULL, "apm", DV_DULL 119}; 120#else 121extern struct cfdriver apm_cd; 122 123dev_type_open(apmopen); 124dev_type_close(apmclose); 125dev_type_ioctl(apmioctl); 126dev_type_poll(apmpoll); 127dev_type_kqfilter(apmkqfilter); 128 129const struct cdevsw apm_cdevsw = { 130 .d_open = apmopen, 131 .d_close = apmclose, 132 .d_read = noread, 133 .d_write = nowrite, 134 .d_ioctl = apmioctl, 135 .d_stop = nostop, 136 .d_tty = notty, 137 .d_poll = apmpoll, 138 .d_mmap = nommap, 139 .d_kqfilter = apmkqfilter, 140 .d_discard = nodiscard, 141 .d_flag = 0 142}; 143#endif 144 145int apm_evindex; 146 147#define APMUNIT(dev) (minor(dev)&0xf0) 148#define APMDEV(dev) (minor(dev)&0x0f) 149#define APMDEV_NORMAL 0 150#define APMDEV_CTL 8 151 152/* 153 * Flags to control kernel display 154 * SCFLAG_NOPRINT: do not output APM power messages due to 155 * a power change event. 156 * 157 * SCFLAG_PCTPRINT: do not output APM power messages due to 158 * to a power change event unless the battery 159 * percentage changes. 160 */ 161 162#define SCFLAG_NOPRINT 0x0008000 163#define SCFLAG_PCTPRINT 0x0004000 164#define SCFLAG_PRINT (SCFLAG_NOPRINT|SCFLAG_PCTPRINT) 165 166#define SCFLAG_OREAD (1 << 0) 167#define SCFLAG_OWRITE (1 << 1) 168#define SCFLAG_OPEN (SCFLAG_OREAD|SCFLAG_OWRITE) 169 170 171int 172apmmatch(device_t parent, cfdata_t match, void *aux) 173{ 174 struct adb_attach_args *aa = (void *)aux; 175 if (aa->origaddr != ADBADDR_APM || 176 aa->handler_id != ADBADDR_APM || 177 aa->adbaddr != ADBADDR_APM) 178 return 0; 179 180 if (adbHardware != ADB_HW_PMU) 181 return 0; 182 183 return 1; 184} 185 186void 187apmattach(device_t parent, device_t self, void *aux) 188{ 189 struct apm_softc *sc = device_private(self); 190 struct pmu_battery_info info; 191 192 pm_battery_info(0, &info); 193 194 printf(": battery flags 0x%X, ", info.flags); 195 printf("%d%% charged\n", ((info.cur_charge * 100) / info.max_charge)); 196 197 sc->sc_flags = 0; 198 sc->event_ptr = 0; 199 sc->event_count = 0; 200 mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); 201 selinit(&sc->sc_rsel); 202} 203 204int 205apmopen(dev_t dev, int flag, int mode, struct lwp *l) 206{ 207 struct apm_softc *sc; 208 int error = 0; 209 210 /* apm0 only */ 211 sc = device_lookup_private(&apm_cd, APMUNIT(dev)); 212 if (sc == NULL) 213 return ENXIO; 214 215 DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n", 216 APMDEV(dev), l->l_proc->p_pid, flag, mode)); 217 218 APM_LOCK(sc); 219 switch (APMDEV(dev)) { 220 case APMDEV_CTL: 221 if (!(flag & FWRITE)) { 222 error = EINVAL; 223 break; 224 } 225 if (sc->sc_flags & SCFLAG_OWRITE) { 226 error = EBUSY; 227 break; 228 } 229 sc->sc_flags |= SCFLAG_OWRITE; 230 break; 231 case APMDEV_NORMAL: 232 if (!(flag & FREAD) || (flag & FWRITE)) { 233 error = EINVAL; 234 break; 235 } 236 sc->sc_flags |= SCFLAG_OREAD; 237 break; 238 default: 239 error = ENXIO; 240 break; 241 } 242 APM_UNLOCK(sc); 243 return error; 244} 245 246int 247apmclose(dev_t dev, int flag, int mode, struct lwp *l) 248{ 249 struct apm_softc *sc; 250 251 /* apm0 only */ 252 sc = device_lookup_private(&apm_cd, APMUNIT(dev)); 253 if (sc == NULL) 254 return ENXIO; 255 256 DPRINTF(("apmclose: pid %d flag %x mode %x\n", l->l_proc->p_pid, flag, mode)); 257 258 APM_LOCK(sc); 259 switch (APMDEV(dev)) { 260 case APMDEV_CTL: 261 sc->sc_flags &= ~SCFLAG_OWRITE; 262 break; 263 case APMDEV_NORMAL: 264 sc->sc_flags &= ~SCFLAG_OREAD; 265 break; 266 } 267 APM_UNLOCK(sc); 268 return 0; 269} 270 271int 272apmioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) 273{ 274 struct apm_softc *sc; 275 struct pmu_battery_info batt; 276 struct apm_power_info *power; 277 int error = 0; 278 279 /* apm0 only */ 280 sc = device_lookup_private(&apm_cd, APMUNIT(dev)); 281 if (sc == NULL) 282 return ENXIO; 283 284 APM_LOCK(sc); 285 switch (cmd) { 286 /* some ioctl names from linux */ 287 case APM_IOC_STANDBY: 288 if ((flag & FWRITE) == 0) 289 error = EBADF; 290 case APM_IOC_SUSPEND: 291 if ((flag & FWRITE) == 0) 292 error = EBADF; 293 break; 294 case APM_IOC_PRN_CTL: 295 if ((flag & FWRITE) == 0) 296 error = EBADF; 297 else { 298 int op = *(int *)data; 299 DPRINTF(( "APM_IOC_PRN_CTL: %d\n", op )); 300 switch (op) { 301 case APM_PRINT_ON: /* enable printing */ 302 sc->sc_flags &= ~SCFLAG_PRINT; 303 break; 304 case APM_PRINT_OFF: /* disable printing */ 305 sc->sc_flags &= ~SCFLAG_PRINT; 306 sc->sc_flags |= SCFLAG_NOPRINT; 307 break; 308 case APM_PRINT_PCT: /* disable some printing */ 309 sc->sc_flags &= ~SCFLAG_PRINT; 310 sc->sc_flags |= SCFLAG_PCTPRINT; 311 break; 312 default: 313 error = EINVAL; 314 break; 315 } 316 } 317 break; 318 case APM_IOC_DEV_CTL: 319 if ((flag & FWRITE) == 0) 320 error = EBADF; 321 break; 322 case APM_IOC_GETPOWER: 323 power = (struct apm_power_info *)data; 324 325 pm_battery_info(0, &batt); 326 327 power->ac_state = ((batt.flags & PMU_PWR_AC_PRESENT) ? 328 APM_AC_ON : APM_AC_OFF); 329 power->battery_life = 330 ((batt.cur_charge * 100) / batt.max_charge); 331 332 /* 333 * If the battery is charging, return the minutes left until 334 * charging is complete. apmd knows this. 335 */ 336 337 if (!(batt.flags & PMU_PWR_BATT_PRESENT)) { 338 power->battery_state = APM_BATT_UNKNOWN; 339 power->minutes_left = 0; 340 power->battery_life = 0; 341 } else if ((power->ac_state == APM_AC_ON) && 342 (batt.draw > 0)) { 343 power->minutes_left = batt.secs_remaining / 60; 344 power->battery_state = APM_BATT_CHARGING; 345 } else { 346 power->minutes_left = batt.secs_remaining / 60; 347 348 /* XXX - Arbitrary */ 349 if (power->battery_life > 60) { 350 power->battery_state = APM_BATT_HIGH; 351 } else if (power->battery_life < 10) { 352 power->battery_state = APM_BATT_CRITICAL; 353 } else { 354 power->battery_state = APM_BATT_LOW; 355 } 356 } 357 358 break; 359 360 default: 361 error = ENOTTY; 362 } 363 APM_UNLOCK(sc); 364 365 return error; 366} 367 368#ifdef __NetBSD__ 369#if 0 370/* 371 * return 0 if the user will notice and handle the event, 372 * return 1 if the kernel driver should do so. 373 */ 374static int 375apm_record_event(struct apm_softc *sc, u_int event_type) 376{ 377 struct apm_event_info *evp; 378 379 if ((sc->sc_flags & SCFLAG_OPEN) == 0) 380 return 1; /* no user waiting */ 381 if (sc->event_count == APM_NEVENTS) { 382 DPRINTF(("apm_record_event: queue full!\n")); 383 return 1; /* overflow */ 384 } 385 evp = &sc->event_list[sc->event_ptr]; 386 sc->event_count++; 387 sc->event_ptr++; 388 sc->event_ptr %= APM_NEVENTS; 389 evp->type = event_type; 390 evp->index = ++apm_evindex; 391 selnotify(&sc->sc_rsel, 0, 0); 392 return (sc->sc_flags & SCFLAG_OWRITE) ? 0 : 1; /* user may handle */ 393} 394#endif 395 396int 397apmpoll(dev_t dev, int events, struct lwp *l) 398{ 399 struct apm_softc *sc = device_lookup_private(&apm_cd,APMUNIT(dev)); 400 int revents = 0; 401 402 APM_LOCK(sc); 403 if (events & (POLLIN | POLLRDNORM)) { 404 if (sc->event_count) 405 revents |= events & (POLLIN | POLLRDNORM); 406 else 407 selrecord(l, &sc->sc_rsel); 408 } 409 APM_UNLOCK(sc); 410 411 return (revents); 412} 413#endif 414 415static void 416filt_apmrdetach(struct knote *kn) 417{ 418 struct apm_softc *sc = (struct apm_softc *)kn->kn_hook; 419 420 APM_LOCK(sc); 421 selremove_knote(&sc->sc_rsel, kn); 422 APM_UNLOCK(sc); 423} 424 425static int 426filt_apmread(struct knote *kn, long hint) 427{ 428 struct apm_softc *sc = kn->kn_hook; 429 430 kn->kn_data = sc->event_count; 431 return (kn->kn_data > 0); 432} 433 434static struct filterops apmread_filtops = { 435 .f_flags = FILTEROP_ISFD, 436 .f_attach = NULL, 437 .f_detach = filt_apmrdetach, 438 .f_event = filt_apmread, 439}; 440 441int 442apmkqfilter(dev_t dev, struct knote *kn) 443{ 444 struct apm_softc *sc = device_lookup_private(&apm_cd,APMUNIT(dev)); 445 446 switch (kn->kn_filter) { 447 case EVFILT_READ: 448 kn->kn_fop = &apmread_filtops; 449 break; 450 default: 451 return (1); 452 } 453 454 kn->kn_hook = sc; 455 456 APM_LOCK(sc); 457 selrecord_knote(&sc->sc_rsel, kn); 458 APM_UNLOCK(sc); 459 460 return (0); 461} 462