xref: /illumos-gate/usr/src/uts/i86pc/io/ppm/acpippm.c (revision 7ab4e62e3b5c454f248a38bec0d489e8f5543324)
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  * Copyright (c) 2011 Bayard G. Bell. All rights reserved.
26  */
27 
28 #include <sys/types.h>
29 #include <sys/conf.h>
30 #include <sys/open.h>
31 #include <sys/modctl.h>
32 #include <sys/promif.h>
33 #include <sys/stat.h>
34 #include <sys/ddi_impldefs.h>
35 #include <sys/ddi.h>
36 #include <sys/sunddi.h>
37 #include <sys/epm.h>
38 #include <sys/acpi/acpi.h>
39 #include <sys/acpica.h>
40 #include <sys/psm_types.h>
41 
42 /*
43  *	ACPI Power Management Driver
44  *
45  *	acpippm deals with those bits of ppm functionality that
46  *	must be mediated by ACPI
47  *
48  *	The routines in this driver is referenced by Platform
49  *	Power Management driver of X86 workstation systems.
50  *	acpippm driver is loaded because it is listed as a platform driver
51  *	It is initially configured as a pseudo driver.
52  */
53 extern void pc_tod_set_rtc_offsets(ACPI_TABLE_FADT *);
54 extern int acpica_use_safe_delay;
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 	ddi_quiesce_not_needed,		/* quiesce */
103 };
104 
105 extern struct mod_ops mod_driverops;
106 
107 static struct modldrv modldrv = {
108 	&mod_driverops,
109 	"ACPI ppm driver",
110 	&appm_ops,
111 };
112 
113 static struct modlinkage modlinkage = {
114 	MODREV_1,
115 	&modldrv,
116 	NULL
117 };
118 
119 /*
120  * Driver state structure
121  */
122 typedef struct {
123 	dev_info_t		*dip;
124 	ddi_acc_handle_t	devid_hndl;
125 	ddi_acc_handle_t	estar_hndl;
126 	int			lyropen;		/* ref count */
127 } appm_unit;
128 
129 /*
130  * Driver global variables
131  *
132  * appm_lock synchronize the access of lyr handle to each appm
133  * minor device, therefore write to tomatillo device is
134  * sequentialized.  Lyr protocol requires pairing up lyr open
135  * and close, so only a single reference is allowed per minor node.
136  */
137 static void	*appm_statep;
138 static kmutex_t  appm_lock;
139 
140 /*
141  * S3 stuff:
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 		acpica_use_safe_delay = 1;
427 		ret = acpi_enter_sleepstate(s3ap);
428 		break;
429 
430 	case APPMIOC_EXIT_S3:
431 		/*
432 		 * return from S3
433 		 */
434 		PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
435 		ret = acpi_exit_sleepstate(s3ap);
436 		acpica_use_safe_delay = 0;
437 		break;
438 
439 	default:
440 		PMD(PMD_SX, ("%s: cmd %x unrecognized: ENOTTY\n", str, cmd))
441 		return (ENOTTY);
442 	}
443 
444 	/*
445 	 * upon failure return EINVAL
446 	 */
447 	if (ret != 0) {
448 		if (rval_p != NULL) {
449 			*rval_p = EINVAL;
450 		}
451 		return (EINVAL);
452 	}
453 
454 	return (0);
455 }
456