xref: /illumos-gate/usr/src/uts/i86pc/io/ppm/acpippm.c (revision da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/types.h>
30 #include <sys/conf.h>
31 #include <sys/open.h>
32 #include <sys/modctl.h>
33 #include <sys/promif.h>
34 #include <sys/stat.h>
35 #include <sys/ddi_impldefs.h>
36 #include <sys/ddi.h>
37 #include <sys/sunddi.h>
38 #include <sys/epm.h>
39 #include <sys/acpi/acpi.h>
40 #include <sys/acpica.h>
41 #include <sys/psm_types.h>
42 
43 /*
44  *	ACPI Power Management Driver
45  *
46  *	acpippm deals with those bits of ppm functionality that
47  *	must be mediated by ACPI
48  *
49  *	The routines in this driver is referenced by Platform
50  *	Power Management driver of X86 workstation systems.
51  *	acpippm driver is loaded because it is listed as a platform driver
52  *	It is initially configured as a pseudo driver.
53  */
54 
55 /*
56  * Configuration Function prototypes and data structures
57  */
58 static int	appm_attach(dev_info_t *, ddi_attach_cmd_t);
59 static int	appm_detach(dev_info_t *, ddi_detach_cmd_t);
60 static int	appm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
61 static int	appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p);
62 static int	appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p);
63 static int	appm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
64 
65 /*
66  * Configuration data structures
67  */
68 static struct cb_ops appm_cbops = {
69 	appm_open,		/* open */
70 	appm_close,		/* close */
71 	nodev,			/* strategy */
72 	nodev,			/* print */
73 	nodev,			/* dump */
74 	nodev,			/* read */
75 	nodev,			/* write */
76 	appm_ioctl,		/* ioctl */
77 	nodev,			/* devmap */
78 	nodev,			/* mmap */
79 	nodev,			/* segmap */
80 	nochpoll,		/* chpoll */
81 	ddi_prop_op,		/* prop_op */
82 	NULL,			/* stream */
83 	D_MP | D_NEW,		/* flag */
84 	CB_REV,			/* rev */
85 	nodev,			/* aread */
86 	nodev,			/* awrite */
87 };
88 
89 static struct dev_ops appm_ops = {
90 	DEVO_REV,		/* devo_rev */
91 	0,			/* refcnt */
92 	appm_getinfo,		/* getinfo */
93 	nulldev,		/* identify */
94 	nulldev,		/* probe */
95 	appm_attach,		/* attach */
96 	appm_detach,		/* detach */
97 	nodev,			/* reset */
98 	&appm_cbops,		/* cb_ops */
99 	NULL,			/* bus_ops */
100 	NULL			/* power */
101 };
102 
103 extern struct mod_ops mod_driverops;
104 
105 static struct modldrv modldrv = {
106 	&mod_driverops,
107 	"ACPI ppm driver v1.8",
108 	&appm_ops,
109 };
110 
111 static struct modlinkage modlinkage = {
112 	MODREV_1,
113 	&modldrv,
114 	NULL
115 };
116 
117 /*
118  * Driver state structure
119  */
120 typedef struct {
121 	dev_info_t		*dip;
122 	ddi_acc_handle_t	devid_hndl;
123 	ddi_acc_handle_t	estar_hndl;
124 	int			lyropen;		/* ref count */
125 } appm_unit;
126 
127 /*
128  * Driver global variables
129  *
130  * appm_lock synchronize the access of lyr handle to each appm
131  * minor device, therefore write to tomatillo device is
132  * sequentialized.  Lyr protocol requires pairing up lyr open
133  * and close, so only a single reference is allowed per minor node.
134  */
135 static void	*appm_statep;
136 static kmutex_t  appm_lock;
137 
138 /*
139  * S3 stuff:
140  */
141 char _depends_on[] = "misc/acpica";
142 
143 extern int acpi_enter_sleepstate(s3a_t *);
144 extern int acpi_exit_sleepstate(s3a_t *);
145 
146 
147 int
148 _init(void)
149 {
150 	int	error;
151 
152 	if ((error = ddi_soft_state_init(&appm_statep,
153 	    sizeof (appm_unit), 0)) != DDI_SUCCESS) {
154 		return (error);
155 	}
156 
157 	mutex_init(&appm_lock, NULL, MUTEX_DRIVER, NULL);
158 
159 	if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
160 		mutex_destroy(&appm_lock);
161 		ddi_soft_state_fini(&appm_statep);
162 		return (error);
163 	}
164 
165 	return (error);
166 }
167 
168 int
169 _fini(void)
170 {
171 	int	error;
172 
173 	if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
174 		mutex_destroy(&appm_lock);
175 		ddi_soft_state_fini(&appm_statep);
176 	}
177 
178 	return (error);
179 
180 }
181 
182 int
183 _info(struct modinfo *modinfop)
184 {
185 	return (mod_info(&modlinkage, modinfop));
186 }
187 
188 
189 
190 /*
191  * Driver attach(9e) entry point
192  */
193 static int
194 appm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
195 {
196 	char	*str = "appm_attach";
197 	int		instance;
198 	appm_unit	*unitp;
199 	int		rv = DDI_SUCCESS;
200 
201 	switch (cmd) {
202 	case DDI_ATTACH:
203 		break;
204 	case DDI_RESUME:
205 		return (DDI_SUCCESS);
206 	default:
207 		cmn_err(CE_WARN, "%s: cmd %d unsupported.\n", str, cmd);
208 		return (DDI_FAILURE);
209 	}
210 
211 	instance = ddi_get_instance(dip);
212 	rv = ddi_soft_state_zalloc(appm_statep, instance);
213 	if (rv != DDI_SUCCESS) {
214 		cmn_err(CE_WARN, "%s: failed alloc for dev(%s@%s)",
215 		    str, ddi_binding_name(dip),
216 		    ddi_get_name_addr(dip) ? ddi_get_name_addr(dip) : " ");
217 		return (rv);
218 	}
219 
220 	if ((unitp = ddi_get_soft_state(appm_statep, instance)) == NULL) {
221 		rv = DDI_FAILURE;
222 		goto doerrs;
223 	}
224 
225 	/*
226 	 * Export "ddi-kernel-ioctl" property - prepared to support
227 	 * kernel ioctls (driver layering).
228 	 * XXX is this still needed?
229 	 * XXXX (RSF) Not that I am aware of.
230 	 */
231 	rv = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
232 	    DDI_KERNEL_IOCTL, NULL, 0);
233 	if (rv != DDI_PROP_SUCCESS)
234 		goto doerrs;
235 
236 	ddi_report_dev(dip);
237 	unitp->dip = dip;
238 
239 	/*
240 	 * XXX here we would do whatever we need to to determine if the
241 	 * XXX platform supports ACPI, and fail the attach if not.
242 	 * XXX If it does, we do whatever setup is needed to get access to
243 	 * XXX ACPI register space.
244 	 */
245 
246 	unitp->lyropen = 0;
247 
248 	/*
249 	 * create minor node for kernel_ioctl calls
250 	 */
251 	rv = ddi_create_minor_node(dip, "acpi-ppm", S_IFCHR, instance, 0, 0);
252 	if (rv != DDI_SUCCESS)
253 		goto doerrs;
254 
255 	return (rv);
256 
257 doerrs:
258 
259 	if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS |
260 	    DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL))
261 		ddi_prop_remove_all(dip);
262 
263 	ddi_soft_state_free(appm_statep, instance);
264 
265 	return (rv);
266 }
267 
268 
269 /*
270  * Driver getinfo(9e) entry routine
271  */
272 /* ARGSUSED */
273 static int
274 appm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
275 {
276 	appm_unit	*unitp;
277 	int		instance;
278 
279 	switch (cmd) {
280 	case DDI_INFO_DEVT2DEVINFO:
281 		instance = getminor((dev_t)arg);
282 		unitp = ddi_get_soft_state(appm_statep, instance);
283 		if (unitp == NULL) {
284 			return (DDI_FAILURE);
285 		}
286 		*result = (void *) unitp->dip;
287 		return (DDI_SUCCESS);
288 
289 	case DDI_INFO_DEVT2INSTANCE:
290 		instance = getminor((dev_t)arg);
291 		*result = (void *)(uintptr_t)instance;
292 		return (DDI_SUCCESS);
293 
294 	default:
295 		return (DDI_FAILURE);
296 	}
297 }
298 
299 
300 /*
301  * detach(9e)
302  */
303 /* ARGSUSED */
304 static int
305 appm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
306 {
307 	char *str = "appm_detach";
308 
309 	switch (cmd) {
310 	case DDI_DETACH:
311 		return (DDI_FAILURE);
312 	case DDI_SUSPEND:
313 		return (DDI_SUCCESS);
314 	default:
315 		cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd);
316 		return (DDI_FAILURE);
317 	}
318 }
319 
320 
321 /* ARGSUSED */
322 static int
323 appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
324 {
325 	appm_unit	*unitp;
326 
327 	/* not intended to allow sysadmin level root process to open it */
328 	if (drv_priv(cred_p) != DDI_SUCCESS)
329 		return (EPERM);
330 
331 	if ((unitp = ddi_get_soft_state(
332 	    appm_statep, getminor(*dev_p))) == NULL) {
333 		cmn_err(CE_WARN, "appm_open: failed to get soft state!");
334 		return (DDI_FAILURE);
335 	}
336 
337 	mutex_enter(&appm_lock);
338 	if (unitp->lyropen != 0) {
339 		mutex_exit(&appm_lock);
340 		return (EBUSY);
341 	}
342 	unitp->lyropen++;
343 	mutex_exit(&appm_lock);
344 
345 	return (DDI_SUCCESS);
346 }
347 
348 
349 /* ARGSUSED */
350 static int
351 appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
352 {
353 	appm_unit	*unitp;
354 
355 	if ((unitp =
356 	    ddi_get_soft_state(appm_statep, getminor(dev))) == NULL)
357 		return (DDI_FAILURE);
358 
359 	mutex_enter(&appm_lock);
360 	unitp->lyropen = 0;
361 	mutex_exit(&appm_lock);
362 
363 	return (DDI_SUCCESS);
364 }
365 
366 
367 /*
368  * must match ppm.conf
369  */
370 #define	APPMIOC			('A' << 8)
371 #define	APPMIOC_ENTER_S3	(APPMIOC | 1)	/* arg *s3a_t */
372 #define	APPMIOC_EXIT_S3		(APPMIOC | 2)	/* arg *s3a_t */
373 
374 /* ARGSUSED3 */
375 static int
376 appm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
377     cred_t *cred_p, int *rval_p)
378 {
379 	static boolean_t	acpi_initted = B_FALSE;
380 	char			*str = "appm_ioctl";
381 	int			ret;
382 	s3a_t			*s3ap = (s3a_t *)arg;
383 
384 	PMD(PMD_SX, ("%s: called with %x\n", str, cmd))
385 
386 	if (drv_priv(cred_p) != 0) {
387 		PMD(PMD_SX, ("%s: EPERM\n", str))
388 		return (EPERM);
389 	}
390 
391 	if (ddi_get_soft_state(appm_statep, getminor(dev)) == NULL) {
392 		PMD(PMD_SX, ("%s: no soft state: EIO\n", str))
393 		return (EIO);
394 	}
395 
396 	if (!acpi_initted) {
397 		PMD(PMD_SX, ("%s: !acpi_initted\n", str))
398 		if (acpica_init() == 0) {
399 			acpi_initted = B_TRUE;
400 		} else {
401 			if (rval_p != NULL) {
402 				*rval_p = EINVAL;
403 			}
404 			PMD(PMD_SX, ("%s: EINVAL\n", str))
405 			return (EINVAL);
406 		}
407 	}
408 
409 	PMD(PMD_SX, ("%s: looking for cmd %x\n", str, cmd))
410 	switch (cmd) {
411 	case APPMIOC_ENTER_S3:
412 		/*
413 		 * suspend to RAM (ie S3)
414 		 */
415 		PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
416 		ret = acpi_enter_sleepstate(s3ap);
417 		break;
418 
419 	case APPMIOC_EXIT_S3:
420 		/*
421 		 * return from S3
422 		 */
423 		PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
424 		ret = acpi_exit_sleepstate(s3ap);
425 		break;
426 
427 	default:
428 		PMD(PMD_SX, ("%s: cmd %x unrecognized: ENOTTY\n", str, cmd))
429 		return (ENOTTY);
430 	}
431 
432 	/*
433 	 * upon failure return EINVAL
434 	 */
435 	if (ret != 0) {
436 		if (rval_p != NULL) {
437 			*rval_p = EINVAL;
438 		}
439 		return (EINVAL);
440 	}
441 
442 	return (0);
443 }
444