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