xref: /illumos-gate/usr/src/uts/i86pc/io/ppm/acpippm.c (revision afab0816ecb604f0099a09ad8ee398f0d7b77b1c)
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 #include <sys/types.h>
28 #include <sys/conf.h>
29 #include <sys/open.h>
30 #include <sys/modctl.h>
31 #include <sys/promif.h>
32 #include <sys/stat.h>
33 #include <sys/ddi_impldefs.h>
34 #include <sys/ddi.h>
35 #include <sys/sunddi.h>
36 #include <sys/epm.h>
37 #include <sys/acpi/acpi.h>
38 #include <sys/acpica.h>
39 #include <sys/psm_types.h>
40 
41 /*
42  *	ACPI Power Management Driver
43  *
44  *	acpippm deals with those bits of ppm functionality that
45  *	must be mediated by ACPI
46  *
47  *	The routines in this driver is referenced by Platform
48  *	Power Management driver of X86 workstation systems.
49  *	acpippm driver is loaded because it is listed as a platform driver
50  *	It is initially configured as a pseudo driver.
51  */
52 extern void pc_tod_set_rtc_offsets(ACPI_TABLE_FADT *);
53 extern int acpica_use_safe_delay;
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 	ddi_quiesce_not_needed,		/* quiesce */
102 };
103 
104 extern struct mod_ops mod_driverops;
105 
106 static struct modldrv modldrv = {
107 	&mod_driverops,
108 	"ACPI ppm driver",
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 	ACPI_TABLE_FADT	*fadt = NULL;
201 	int		rv = DDI_SUCCESS;
202 
203 	switch (cmd) {
204 	case DDI_ATTACH:
205 		break;
206 	case DDI_RESUME:
207 		return (DDI_SUCCESS);
208 	default:
209 		cmn_err(CE_WARN, "%s: cmd %d unsupported.\n", str, cmd);
210 		return (DDI_FAILURE);
211 	}
212 
213 	instance = ddi_get_instance(dip);
214 	rv = ddi_soft_state_zalloc(appm_statep, instance);
215 	if (rv != DDI_SUCCESS) {
216 		cmn_err(CE_WARN, "%s: failed alloc for dev(%s@%s)",
217 		    str, ddi_binding_name(dip),
218 		    ddi_get_name_addr(dip) ? ddi_get_name_addr(dip) : " ");
219 		return (rv);
220 	}
221 
222 	if ((unitp = ddi_get_soft_state(appm_statep, instance)) == NULL) {
223 		rv = DDI_FAILURE;
224 		goto doerrs;
225 	}
226 
227 	/*
228 	 * Export "ddi-kernel-ioctl" property - prepared to support
229 	 * kernel ioctls (driver layering).
230 	 * XXX is this still needed?
231 	 * XXXX (RSF) Not that I am aware of.
232 	 */
233 	rv = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
234 	    DDI_KERNEL_IOCTL, NULL, 0);
235 	if (rv != DDI_PROP_SUCCESS)
236 		goto doerrs;
237 
238 	ddi_report_dev(dip);
239 	unitp->dip = dip;
240 
241 	/*
242 	 * XXX here we would do whatever we need to to determine if the
243 	 * XXX platform supports ACPI, and fail the attach if not.
244 	 * XXX If it does, we do whatever setup is needed to get access to
245 	 * XXX ACPI register space.
246 	 */
247 
248 	unitp->lyropen = 0;
249 
250 	/*
251 	 * create minor node for kernel_ioctl calls
252 	 */
253 	rv = ddi_create_minor_node(dip, "acpi-ppm", S_IFCHR, instance, 0, 0);
254 	if (rv != DDI_SUCCESS)
255 		goto doerrs;
256 
257 	/* Get the FADT */
258 	if (AcpiGetTable(ACPI_SIG_FADT, 1,
259 	    (ACPI_TABLE_HEADER **)&fadt) != AE_OK)
260 		return (rv);
261 
262 	/* Init the RTC offsets */
263 	if (fadt != NULL)
264 		pc_tod_set_rtc_offsets(fadt);
265 
266 	return (rv);
267 
268 doerrs:
269 
270 	if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS |
271 	    DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL))
272 		ddi_prop_remove_all(dip);
273 
274 	ddi_soft_state_free(appm_statep, instance);
275 
276 	return (rv);
277 }
278 
279 
280 /*
281  * Driver getinfo(9e) entry routine
282  */
283 /* ARGSUSED */
284 static int
285 appm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
286 {
287 	appm_unit	*unitp;
288 	int		instance;
289 
290 	switch (cmd) {
291 	case DDI_INFO_DEVT2DEVINFO:
292 		instance = getminor((dev_t)arg);
293 		unitp = ddi_get_soft_state(appm_statep, instance);
294 		if (unitp == NULL) {
295 			return (DDI_FAILURE);
296 		}
297 		*result = (void *) unitp->dip;
298 		return (DDI_SUCCESS);
299 
300 	case DDI_INFO_DEVT2INSTANCE:
301 		instance = getminor((dev_t)arg);
302 		*result = (void *)(uintptr_t)instance;
303 		return (DDI_SUCCESS);
304 
305 	default:
306 		return (DDI_FAILURE);
307 	}
308 }
309 
310 
311 /*
312  * detach(9e)
313  */
314 /* ARGSUSED */
315 static int
316 appm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
317 {
318 	char *str = "appm_detach";
319 
320 	switch (cmd) {
321 	case DDI_DETACH:
322 		return (DDI_FAILURE);
323 	case DDI_SUSPEND:
324 		return (DDI_SUCCESS);
325 	default:
326 		cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd);
327 		return (DDI_FAILURE);
328 	}
329 }
330 
331 
332 /* ARGSUSED */
333 static int
334 appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
335 {
336 	appm_unit	*unitp;
337 
338 	/* not intended to allow sysadmin level root process to open it */
339 	if (drv_priv(cred_p) != DDI_SUCCESS)
340 		return (EPERM);
341 
342 	if ((unitp = ddi_get_soft_state(
343 	    appm_statep, getminor(*dev_p))) == NULL) {
344 		cmn_err(CE_WARN, "appm_open: failed to get soft state!");
345 		return (DDI_FAILURE);
346 	}
347 
348 	mutex_enter(&appm_lock);
349 	if (unitp->lyropen != 0) {
350 		mutex_exit(&appm_lock);
351 		return (EBUSY);
352 	}
353 	unitp->lyropen++;
354 	mutex_exit(&appm_lock);
355 
356 	return (DDI_SUCCESS);
357 }
358 
359 
360 /* ARGSUSED */
361 static int
362 appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
363 {
364 	appm_unit	*unitp;
365 
366 	if ((unitp =
367 	    ddi_get_soft_state(appm_statep, getminor(dev))) == NULL)
368 		return (DDI_FAILURE);
369 
370 	mutex_enter(&appm_lock);
371 	unitp->lyropen = 0;
372 	mutex_exit(&appm_lock);
373 
374 	return (DDI_SUCCESS);
375 }
376 
377 
378 /*
379  * must match ppm.conf
380  */
381 #define	APPMIOC			('A' << 8)
382 #define	APPMIOC_ENTER_S3	(APPMIOC | 1)	/* arg *s3a_t */
383 #define	APPMIOC_EXIT_S3		(APPMIOC | 2)	/* arg *s3a_t */
384 
385 /* ARGSUSED3 */
386 static int
387 appm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
388     cred_t *cred_p, int *rval_p)
389 {
390 	static boolean_t	acpi_initted = B_FALSE;
391 	char			*str = "appm_ioctl";
392 	int			ret;
393 	s3a_t			*s3ap = (s3a_t *)arg;
394 
395 	PMD(PMD_SX, ("%s: called with %x\n", str, cmd))
396 
397 	if (drv_priv(cred_p) != 0) {
398 		PMD(PMD_SX, ("%s: EPERM\n", str))
399 		return (EPERM);
400 	}
401 
402 	if (ddi_get_soft_state(appm_statep, getminor(dev)) == NULL) {
403 		PMD(PMD_SX, ("%s: no soft state: EIO\n", str))
404 		return (EIO);
405 	}
406 
407 	if (!acpi_initted) {
408 		PMD(PMD_SX, ("%s: !acpi_initted\n", str))
409 		if (acpica_init() == 0) {
410 			acpi_initted = B_TRUE;
411 		} else {
412 			if (rval_p != NULL) {
413 				*rval_p = EINVAL;
414 			}
415 			PMD(PMD_SX, ("%s: EINVAL\n", str))
416 			return (EINVAL);
417 		}
418 	}
419 
420 	PMD(PMD_SX, ("%s: looking for cmd %x\n", str, cmd))
421 	switch (cmd) {
422 	case APPMIOC_ENTER_S3:
423 		/*
424 		 * suspend to RAM (ie S3)
425 		 */
426 		PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
427 		acpica_use_safe_delay = 1;
428 		ret = acpi_enter_sleepstate(s3ap);
429 		break;
430 
431 	case APPMIOC_EXIT_S3:
432 		/*
433 		 * return from S3
434 		 */
435 		PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
436 		ret = acpi_exit_sleepstate(s3ap);
437 		acpica_use_safe_delay = 0;
438 		break;
439 
440 	default:
441 		PMD(PMD_SX, ("%s: cmd %x unrecognized: ENOTTY\n", str, cmd))
442 		return (ENOTTY);
443 	}
444 
445 	/*
446 	 * upon failure return EINVAL
447 	 */
448 	if (ret != 0) {
449 		if (rval_p != NULL) {
450 			*rval_p = EINVAL;
451 		}
452 		return (EINVAL);
453 	}
454 
455 	return (0);
456 }
457