xref: /freebsd/sys/x86/acpica/acpi_apm.c (revision 3a92d97ff0f22d21608e1c19b83104c4937523b6)
1 /*-
2  * Copyright (c) 2001 Mitsuru IWASAKI
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/param.h>
31 #include <sys/bus.h>
32 #include <sys/condvar.h>
33 #include <sys/conf.h>
34 #include <sys/fcntl.h>
35 #include <sys/kernel.h>
36 #include <sys/malloc.h>
37 #include <sys/poll.h>
38 #include <sys/uio.h>
39 
40 #include <contrib/dev/acpica/include/acpi.h>
41 
42 #include <dev/acpica/acpivar.h>
43 #include <dev/acpica/acpiio.h>
44 
45 #include <machine/apm_bios.h>
46 
47 /*
48  * APM driver emulation
49  */
50 
51 #define	APM_UNKNOWN	0xff
52 
53 static int apm_active;
54 
55 static MALLOC_DEFINE(M_APMDEV, "apmdev", "APM device emulation");
56 
57 static d_open_t		apmopen;
58 static d_write_t	apmwrite;
59 static d_ioctl_t	apmioctl;
60 static d_poll_t		apmpoll;
61 static d_kqfilter_t	apmkqfilter;
62 static void		apmreadfiltdetach(struct knote *kn);
63 static int		apmreadfilt(struct knote *kn, long hint);
64 static struct filterops	apm_readfiltops = {
65 	.f_isfd = 1,
66 	.f_detach = apmreadfiltdetach,
67 	.f_event = apmreadfilt,
68 };
69 
70 static struct cdevsw apm_cdevsw = {
71 	.d_version =	D_VERSION,
72 	.d_open =	apmopen,
73 	.d_write =	apmwrite,
74 	.d_ioctl =	apmioctl,
75 	.d_poll =	apmpoll,
76 	.d_name =	"apm",
77 	.d_kqfilter =	apmkqfilter
78 };
79 
80 static int
81 acpi_capm_convert_battstate(struct  acpi_battinfo *battp)
82 {
83 	int	state;
84 
85 	state = APM_UNKNOWN;
86 
87 	if (battp->state & ACPI_BATT_STAT_DISCHARG) {
88 		if (battp->cap >= 50)
89 			state = 0;	/* high */
90 		else
91 			state = 1;	/* low */
92 	}
93 	if (battp->state & ACPI_BATT_STAT_CRITICAL)
94 		state = 2;		/* critical */
95 	if (battp->state & ACPI_BATT_STAT_CHARGING)
96 		state = 3;		/* charging */
97 
98 	/* If still unknown, determine it based on the battery capacity. */
99 	if (state == APM_UNKNOWN) {
100 		if (battp->cap >= 50)
101 			state = 0;	/* high */
102 		else
103 			state = 1;	/* low */
104 	}
105 
106 	return (state);
107 }
108 
109 static int
110 acpi_capm_convert_battflags(struct  acpi_battinfo *battp)
111 {
112 	int	flags;
113 
114 	flags = 0;
115 
116 	if (battp->cap >= 50)
117 		flags |= APM_BATT_HIGH;
118 	else {
119 		if (battp->state & ACPI_BATT_STAT_CRITICAL)
120 			flags |= APM_BATT_CRITICAL;
121 		else
122 			flags |= APM_BATT_LOW;
123 	}
124 	if (battp->state & ACPI_BATT_STAT_CHARGING)
125 		flags |= APM_BATT_CHARGING;
126 	if (battp->state == ACPI_BATT_STAT_NOT_PRESENT)
127 		flags = APM_BATT_NOT_PRESENT;
128 
129 	return (flags);
130 }
131 
132 static int
133 acpi_capm_get_info(apm_info_t aip)
134 {
135 	int	acline;
136 	struct	acpi_battinfo batt;
137 
138 	aip->ai_infoversion = 1;
139 	aip->ai_major       = 1;
140 	aip->ai_minor       = 2;
141 	aip->ai_status      = apm_active;
142 	aip->ai_capabilities= 0xff00;	/* unknown */
143 
144 	if (acpi_acad_get_acline(&acline))
145 		aip->ai_acline = APM_UNKNOWN;	/* unknown */
146 	else
147 		aip->ai_acline = acline;	/* on/off */
148 
149 	if (acpi_battery_get_battinfo(NULL, &batt) != 0) {
150 		aip->ai_batt_stat = APM_UNKNOWN;
151 		aip->ai_batt_life = APM_UNKNOWN;
152 		aip->ai_batt_time = -1;		 /* unknown */
153 		aip->ai_batteries = ~0U;	 /* unknown */
154 	} else {
155 		aip->ai_batt_stat = acpi_capm_convert_battstate(&batt);
156 		aip->ai_batt_life = batt.cap;
157 		aip->ai_batt_time = (batt.min == -1) ? -1 : batt.min * 60;
158 		aip->ai_batteries = acpi_battery_get_units();
159 	}
160 
161 	return (0);
162 }
163 
164 static int
165 acpi_capm_get_pwstatus(apm_pwstatus_t app)
166 {
167 	device_t dev;
168 	int	acline, unit, error;
169 	struct	acpi_battinfo batt;
170 
171 	if (app->ap_device != PMDV_ALLDEV &&
172 	    (app->ap_device < PMDV_BATT0 || app->ap_device > PMDV_BATT_ALL))
173 		return (1);
174 
175 	if (app->ap_device == PMDV_ALLDEV)
176 		error = acpi_battery_get_battinfo(NULL, &batt);
177 	else {
178 		unit = app->ap_device - PMDV_BATT0;
179 		dev = devclass_get_device(devclass_find("battery"), unit);
180 		if (dev != NULL)
181 			error = acpi_battery_get_battinfo(dev, &batt);
182 		else
183 			error = ENXIO;
184 	}
185 	if (error)
186 		return (1);
187 
188 	app->ap_batt_stat = acpi_capm_convert_battstate(&batt);
189 	app->ap_batt_flag = acpi_capm_convert_battflags(&batt);
190 	app->ap_batt_life = batt.cap;
191 	app->ap_batt_time = (batt.min == -1) ? -1 : batt.min * 60;
192 
193 	if (acpi_acad_get_acline(&acline))
194 		app->ap_acline = APM_UNKNOWN;
195 	else
196 		app->ap_acline = acline;	/* on/off */
197 
198 	return (0);
199 }
200 
201 /* Create a struct for tracking per-device suspend notification. */
202 static struct apm_clone_data *
203 apm_create_clone(struct cdev *dev, struct acpi_softc *acpi_sc)
204 {
205 	struct apm_clone_data *clone;
206 
207 	clone = malloc(sizeof(*clone), M_APMDEV, M_WAITOK);
208 	clone->cdev = dev;
209 	clone->acpi_sc = acpi_sc;
210 	clone->notify_status = APM_EV_NONE;
211 	bzero(&clone->sel_read, sizeof(clone->sel_read));
212 	knlist_init_mtx(&clone->sel_read.si_note, &acpi_mutex);
213 
214 	/*
215 	 * The acpi device is always managed by devd(8) and is considered
216 	 * writable (i.e., ack is required to allow suspend to proceed.)
217 	 */
218 	if (strcmp("acpi", devtoname(dev)) == 0)
219 		clone->flags = ACPI_EVF_DEVD | ACPI_EVF_WRITE;
220 	else
221 		clone->flags = ACPI_EVF_NONE;
222 
223 	ACPI_LOCK(acpi);
224 	STAILQ_INSERT_TAIL(&acpi_sc->apm_cdevs, clone, entries);
225 	ACPI_UNLOCK(acpi);
226 	return (clone);
227 }
228 
229 static void
230 apmdtor(void *data)
231 {
232 	struct	apm_clone_data *clone;
233 	struct	acpi_softc *acpi_sc;
234 
235 	clone = data;
236 	acpi_sc = clone->acpi_sc;
237 
238 	/* We are about to lose a reference so check if suspend should occur */
239 	if (acpi_sc->acpi_next_sstate != 0 &&
240 	    clone->notify_status != APM_EV_ACKED)
241 		acpi_AckSleepState(clone, 0);
242 
243 	/* Remove this clone's data from the list and free it. */
244 	ACPI_LOCK(acpi);
245 	STAILQ_REMOVE(&acpi_sc->apm_cdevs, clone, apm_clone_data, entries);
246 	seldrain(&clone->sel_read);
247 	knlist_destroy(&clone->sel_read.si_note);
248 	ACPI_UNLOCK(acpi);
249 	free(clone, M_APMDEV);
250 }
251 
252 static int
253 apmopen(struct cdev *dev, int flag, int fmt, struct thread *td)
254 {
255 	struct	acpi_softc *acpi_sc;
256 	struct 	apm_clone_data *clone;
257 
258 	acpi_sc = devclass_get_softc(devclass_find("acpi"), 0);
259 	clone = apm_create_clone(dev, acpi_sc);
260 	devfs_set_cdevpriv(clone, apmdtor);
261 
262 	/* If the device is opened for write, record that. */
263 	if ((flag & FWRITE) != 0)
264 		clone->flags |= ACPI_EVF_WRITE;
265 
266 	return (0);
267 }
268 
269 static int
270 apmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
271 {
272 	int	error;
273 	struct	apm_clone_data *clone;
274 	struct	acpi_softc *acpi_sc;
275 	struct	apm_info info;
276 	struct 	apm_event_info *ev_info;
277 	apm_info_old_t aiop;
278 
279 	error = 0;
280 	devfs_get_cdevpriv((void **)&clone);
281 	acpi_sc = clone->acpi_sc;
282 
283 	switch (cmd) {
284 	case APMIO_SUSPEND:
285 		if ((flag & FWRITE) == 0)
286 			return (EPERM);
287 		if (acpi_sc->acpi_next_sstate == 0) {
288 			if (acpi_sc->acpi_suspend_sx != ACPI_STATE_S5) {
289 				error = acpi_ReqSleepState(acpi_sc,
290 				    acpi_sc->acpi_suspend_sx);
291 			} else {
292 				printf(
293 			"power off via apm suspend not supported\n");
294 				error = ENXIO;
295 			}
296 		} else
297 			error = acpi_AckSleepState(clone, 0);
298 		break;
299 	case APMIO_STANDBY:
300 		if ((flag & FWRITE) == 0)
301 			return (EPERM);
302 		if (acpi_sc->acpi_next_sstate == 0) {
303 			if (acpi_sc->acpi_standby_sx != ACPI_STATE_S5) {
304 				error = acpi_ReqSleepState(acpi_sc,
305 				    acpi_sc->acpi_standby_sx);
306 			} else {
307 				printf(
308 			"power off via apm standby not supported\n");
309 				error = ENXIO;
310 			}
311 		} else
312 			error = acpi_AckSleepState(clone, 0);
313 		break;
314 	case APMIO_NEXTEVENT:
315 		printf("apm nextevent start\n");
316 		ACPI_LOCK(acpi);
317 		if (acpi_sc->acpi_next_sstate != 0 && clone->notify_status ==
318 		    APM_EV_NONE) {
319 			ev_info = (struct apm_event_info *)addr;
320 			if (acpi_sc->acpi_next_sstate <= ACPI_STATE_S3)
321 				ev_info->type = PMEV_STANDBYREQ;
322 			else
323 				ev_info->type = PMEV_SUSPENDREQ;
324 			ev_info->index = 0;
325 			clone->notify_status = APM_EV_NOTIFIED;
326 			printf("apm event returning %d\n", ev_info->type);
327 		} else
328 			error = EAGAIN;
329 		ACPI_UNLOCK(acpi);
330 		break;
331 	case APMIO_GETINFO_OLD:
332 		if (acpi_capm_get_info(&info))
333 			error = ENXIO;
334 		aiop = (apm_info_old_t)addr;
335 		aiop->ai_major = info.ai_major;
336 		aiop->ai_minor = info.ai_minor;
337 		aiop->ai_acline = info.ai_acline;
338 		aiop->ai_batt_stat = info.ai_batt_stat;
339 		aiop->ai_batt_life = info.ai_batt_life;
340 		aiop->ai_status = info.ai_status;
341 		break;
342 	case APMIO_GETINFO:
343 		if (acpi_capm_get_info((apm_info_t)addr))
344 			error = ENXIO;
345 		break;
346 	case APMIO_GETPWSTATUS:
347 		if (acpi_capm_get_pwstatus((apm_pwstatus_t)addr))
348 			error = ENXIO;
349 		break;
350 	case APMIO_ENABLE:
351 		if ((flag & FWRITE) == 0)
352 			return (EPERM);
353 		apm_active = 1;
354 		break;
355 	case APMIO_DISABLE:
356 		if ((flag & FWRITE) == 0)
357 			return (EPERM);
358 		apm_active = 0;
359 		break;
360 	case APMIO_HALTCPU:
361 		break;
362 	case APMIO_NOTHALTCPU:
363 		break;
364 	case APMIO_DISPLAY:
365 		if ((flag & FWRITE) == 0)
366 			return (EPERM);
367 		break;
368 	case APMIO_BIOS:
369 		if ((flag & FWRITE) == 0)
370 			return (EPERM);
371 		bzero(addr, sizeof(struct apm_bios_arg));
372 		break;
373 	default:
374 		error = EINVAL;
375 		break;
376 	}
377 
378 	return (error);
379 }
380 
381 static int
382 apmwrite(struct cdev *dev, struct uio *uio, int ioflag)
383 {
384 	return (uio->uio_resid);
385 }
386 
387 static int
388 apmpoll(struct cdev *dev, int events, struct thread *td)
389 {
390 	struct	apm_clone_data *clone;
391 	int revents;
392 
393 	revents = 0;
394 	devfs_get_cdevpriv((void **)&clone);
395 	ACPI_LOCK(acpi);
396 	if (clone->acpi_sc->acpi_next_sstate)
397 		revents |= events & (POLLIN | POLLRDNORM);
398 	else
399 		selrecord(td, &clone->sel_read);
400 	ACPI_UNLOCK(acpi);
401 	return (revents);
402 }
403 
404 static int
405 apmkqfilter(struct cdev *dev, struct knote *kn)
406 {
407 	struct	apm_clone_data *clone;
408 
409 	devfs_get_cdevpriv((void **)&clone);
410 	ACPI_LOCK(acpi);
411 	kn->kn_hook = clone;
412 	kn->kn_fop = &apm_readfiltops;
413 	knlist_add(&clone->sel_read.si_note, kn, 0);
414 	ACPI_UNLOCK(acpi);
415 	return (0);
416 }
417 
418 static void
419 apmreadfiltdetach(struct knote *kn)
420 {
421 	struct	apm_clone_data *clone;
422 
423 	ACPI_LOCK(acpi);
424 	clone = kn->kn_hook;
425 	knlist_remove(&clone->sel_read.si_note, kn, 0);
426 	ACPI_UNLOCK(acpi);
427 }
428 
429 static int
430 apmreadfilt(struct knote *kn, long hint)
431 {
432 	struct	apm_clone_data *clone;
433 	int	sleeping;
434 
435 	ACPI_LOCK(acpi);
436 	clone = kn->kn_hook;
437 	sleeping = clone->acpi_sc->acpi_next_sstate ? 1 : 0;
438 	ACPI_UNLOCK(acpi);
439 	return (sleeping);
440 }
441 
442 void
443 acpi_apm_init(struct acpi_softc *sc)
444 {
445 
446 	/* Create a clone for /dev/acpi also. */
447 	STAILQ_INIT(&sc->apm_cdevs);
448 	sc->acpi_clone = apm_create_clone(sc->acpi_dev_t, sc);
449 
450 	make_dev(&apm_cdevsw, 0, UID_ROOT, GID_OPERATOR, 0660, "apmctl");
451 	make_dev(&apm_cdevsw, 0, UID_ROOT, GID_OPERATOR, 0664, "apm");
452 }
453