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
_init(void)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
_fini(void)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
_info(struct modinfo * modinfop)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
appm_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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
appm_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** result)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
appm_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
appm_open(dev_t * dev_p,int flag,int otyp,cred_t * cred_p)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
appm_close(dev_t dev,int flag,int otyp,cred_t * cred_p)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
appm_ioctl(dev_t dev,int cmd,intptr_t arg,int flag,cred_t * cred_p,int * rval_p)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