xref: /illumos-gate/usr/src/uts/i86pc/io/ppm/acpippm.c (revision 968633ad8faee931821fd6b656eb0d96d4b186c0)
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 
54 /*
55  * Configuration Function prototypes and data structures
56  */
57 static int	appm_attach(dev_info_t *, ddi_attach_cmd_t);
58 static int	appm_detach(dev_info_t *, ddi_detach_cmd_t);
59 static int	appm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
60 static int	appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p);
61 static int	appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p);
62 static int	appm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
63 
64 /*
65  * Configuration data structures
66  */
67 static struct cb_ops appm_cbops = {
68 	appm_open,		/* open */
69 	appm_close,		/* close */
70 	nodev,			/* strategy */
71 	nodev,			/* print */
72 	nodev,			/* dump */
73 	nodev,			/* read */
74 	nodev,			/* write */
75 	appm_ioctl,		/* ioctl */
76 	nodev,			/* devmap */
77 	nodev,			/* mmap */
78 	nodev,			/* segmap */
79 	nochpoll,		/* chpoll */
80 	ddi_prop_op,		/* prop_op */
81 	NULL,			/* stream */
82 	D_MP | D_NEW,		/* flag */
83 	CB_REV,			/* rev */
84 	nodev,			/* aread */
85 	nodev,			/* awrite */
86 };
87 
88 static struct dev_ops appm_ops = {
89 	DEVO_REV,		/* devo_rev */
90 	0,			/* refcnt */
91 	appm_getinfo,		/* getinfo */
92 	nulldev,		/* identify */
93 	nulldev,		/* probe */
94 	appm_attach,		/* attach */
95 	appm_detach,		/* detach */
96 	nodev,			/* reset */
97 	&appm_cbops,		/* cb_ops */
98 	NULL,			/* bus_ops */
99 	NULL,			/* power */
100 	ddi_quiesce_not_needed,		/* quiesce */
101 };
102 
103 extern struct mod_ops mod_driverops;
104 
105 static struct modldrv modldrv = {
106 	&mod_driverops,
107 	"ACPI ppm driver",
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 	ACPI_TABLE_FADT	*fadt = NULL;
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 	/* Get the FADT */
257 	if (AcpiGetTable(ACPI_SIG_FADT, 1,
258 	    (ACPI_TABLE_HEADER **)&fadt) != AE_OK)
259 		return (rv);
260 
261 	/* Init the RTC offsets */
262 	if (fadt != NULL)
263 		pc_tod_set_rtc_offsets(fadt);
264 
265 	return (rv);
266 
267 doerrs:
268 
269 	if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS |
270 	    DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL))
271 		ddi_prop_remove_all(dip);
272 
273 	ddi_soft_state_free(appm_statep, instance);
274 
275 	return (rv);
276 }
277 
278 
279 /*
280  * Driver getinfo(9e) entry routine
281  */
282 /* ARGSUSED */
283 static int
284 appm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
285 {
286 	appm_unit	*unitp;
287 	int		instance;
288 
289 	switch (cmd) {
290 	case DDI_INFO_DEVT2DEVINFO:
291 		instance = getminor((dev_t)arg);
292 		unitp = ddi_get_soft_state(appm_statep, instance);
293 		if (unitp == NULL) {
294 			return (DDI_FAILURE);
295 		}
296 		*result = (void *) unitp->dip;
297 		return (DDI_SUCCESS);
298 
299 	case DDI_INFO_DEVT2INSTANCE:
300 		instance = getminor((dev_t)arg);
301 		*result = (void *)(uintptr_t)instance;
302 		return (DDI_SUCCESS);
303 
304 	default:
305 		return (DDI_FAILURE);
306 	}
307 }
308 
309 
310 /*
311  * detach(9e)
312  */
313 /* ARGSUSED */
314 static int
315 appm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
316 {
317 	char *str = "appm_detach";
318 
319 	switch (cmd) {
320 	case DDI_DETACH:
321 		return (DDI_FAILURE);
322 	case DDI_SUSPEND:
323 		return (DDI_SUCCESS);
324 	default:
325 		cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd);
326 		return (DDI_FAILURE);
327 	}
328 }
329 
330 
331 /* ARGSUSED */
332 static int
333 appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
334 {
335 	appm_unit	*unitp;
336 
337 	/* not intended to allow sysadmin level root process to open it */
338 	if (drv_priv(cred_p) != DDI_SUCCESS)
339 		return (EPERM);
340 
341 	if ((unitp = ddi_get_soft_state(
342 	    appm_statep, getminor(*dev_p))) == NULL) {
343 		cmn_err(CE_WARN, "appm_open: failed to get soft state!");
344 		return (DDI_FAILURE);
345 	}
346 
347 	mutex_enter(&appm_lock);
348 	if (unitp->lyropen != 0) {
349 		mutex_exit(&appm_lock);
350 		return (EBUSY);
351 	}
352 	unitp->lyropen++;
353 	mutex_exit(&appm_lock);
354 
355 	return (DDI_SUCCESS);
356 }
357 
358 
359 /* ARGSUSED */
360 static int
361 appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
362 {
363 	appm_unit	*unitp;
364 
365 	if ((unitp =
366 	    ddi_get_soft_state(appm_statep, getminor(dev))) == NULL)
367 		return (DDI_FAILURE);
368 
369 	mutex_enter(&appm_lock);
370 	unitp->lyropen = 0;
371 	mutex_exit(&appm_lock);
372 
373 	return (DDI_SUCCESS);
374 }
375 
376 
377 /*
378  * must match ppm.conf
379  */
380 #define	APPMIOC			('A' << 8)
381 #define	APPMIOC_ENTER_S3	(APPMIOC | 1)	/* arg *s3a_t */
382 #define	APPMIOC_EXIT_S3		(APPMIOC | 2)	/* arg *s3a_t */
383 
384 /* ARGSUSED3 */
385 static int
386 appm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
387     cred_t *cred_p, int *rval_p)
388 {
389 	static boolean_t	acpi_initted = B_FALSE;
390 	char			*str = "appm_ioctl";
391 	int			ret;
392 	s3a_t			*s3ap = (s3a_t *)arg;
393 
394 	PMD(PMD_SX, ("%s: called with %x\n", str, cmd))
395 
396 	if (drv_priv(cred_p) != 0) {
397 		PMD(PMD_SX, ("%s: EPERM\n", str))
398 		return (EPERM);
399 	}
400 
401 	if (ddi_get_soft_state(appm_statep, getminor(dev)) == NULL) {
402 		PMD(PMD_SX, ("%s: no soft state: EIO\n", str))
403 		return (EIO);
404 	}
405 
406 	if (!acpi_initted) {
407 		PMD(PMD_SX, ("%s: !acpi_initted\n", str))
408 		if (acpica_init() == 0) {
409 			acpi_initted = B_TRUE;
410 		} else {
411 			if (rval_p != NULL) {
412 				*rval_p = EINVAL;
413 			}
414 			PMD(PMD_SX, ("%s: EINVAL\n", str))
415 			return (EINVAL);
416 		}
417 	}
418 
419 	PMD(PMD_SX, ("%s: looking for cmd %x\n", str, cmd))
420 	switch (cmd) {
421 	case APPMIOC_ENTER_S3:
422 		/*
423 		 * suspend to RAM (ie S3)
424 		 */
425 		PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
426 		ret = acpi_enter_sleepstate(s3ap);
427 		break;
428 
429 	case APPMIOC_EXIT_S3:
430 		/*
431 		 * return from S3
432 		 */
433 		PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
434 		ret = acpi_exit_sleepstate(s3ap);
435 		break;
436 
437 	default:
438 		PMD(PMD_SX, ("%s: cmd %x unrecognized: ENOTTY\n", str, cmd))
439 		return (ENOTTY);
440 	}
441 
442 	/*
443 	 * upon failure return EINVAL
444 	 */
445 	if (ret != 0) {
446 		if (rval_p != NULL) {
447 			*rval_p = EINVAL;
448 		}
449 		return (EINVAL);
450 	}
451 
452 	return (0);
453 }
454