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, 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 extern int acpi_enter_sleepstate(s3a_t *);
143 extern int acpi_exit_sleepstate(s3a_t *);
144
145
146 int
147 _init(void)
148 {
149 int error;
150
151 if ((error = ddi_soft_state_init(&appm_statep,
152 sizeof (appm_unit), 0)) != DDI_SUCCESS) {
153 return (error);
154 }
155
156 mutex_init(&appm_lock, NULL, MUTEX_DRIVER, NULL);
157
158 if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
159 mutex_destroy(&appm_lock);
160 ddi_soft_state_fini(&appm_statep);
161 return (error);
162 }
163
164 return (error);
165 }
166
167 int
168 _fini(void)
169 {
170 int error;
171
172 if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
173 mutex_destroy(&appm_lock);
174 ddi_soft_state_fini(&appm_statep);
175 }
176
177 return (error);
178
179 }
180
181 int
182 _info(struct modinfo *modinfop)
183 {
184 return (mod_info(&modlinkage, modinfop));
185 }
186
187
188
189 /*
190 * Driver attach(9e) entry point
191 */
192 static int
193 appm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
194 {
195 char *str = "appm_attach";
196 int instance;
197 appm_unit *unitp;
198 ACPI_TABLE_FADT *fadt = NULL;
199 int rv = DDI_SUCCESS;
200
201 switch (cmd) {
202 case DDI_ATTACH:
203 break;
204 case DDI_RESUME:
205 return (DDI_SUCCESS);
206 default:
207 cmn_err(CE_WARN, "%s: cmd %d unsupported.\n", str, cmd);
208 return (DDI_FAILURE);
209 }
210
211 instance = ddi_get_instance(dip);
212 rv = ddi_soft_state_zalloc(appm_statep, instance);
213 if (rv != DDI_SUCCESS) {
214 cmn_err(CE_WARN, "%s: failed alloc for dev(%s@%s)",
215 str, ddi_binding_name(dip),
216 ddi_get_name_addr(dip) ? ddi_get_name_addr(dip) : " ");
217 return (rv);
218 }
219
220 if ((unitp = ddi_get_soft_state(appm_statep, instance)) == NULL) {
221 rv = DDI_FAILURE;
222 goto doerrs;
223 }
224
225 /*
226 * Export "ddi-kernel-ioctl" property - prepared to support
227 * kernel ioctls (driver layering).
228 * XXX is this still needed?
229 * XXXX (RSF) Not that I am aware of.
230 */
231 rv = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
232 DDI_KERNEL_IOCTL, NULL, 0);
233 if (rv != DDI_PROP_SUCCESS)
234 goto doerrs;
235
236 ddi_report_dev(dip);
237 unitp->dip = dip;
238
239 /*
240 * XXX here we would do whatever we need to to determine if the
241 * XXX platform supports ACPI, and fail the attach if not.
242 * XXX If it does, we do whatever setup is needed to get access to
243 * XXX ACPI register space.
244 */
245
246 unitp->lyropen = 0;
247
248 /*
249 * create minor node for kernel_ioctl calls
250 */
251 rv = ddi_create_minor_node(dip, "acpi-ppm", S_IFCHR, instance, 0, 0);
252 if (rv != DDI_SUCCESS)
253 goto doerrs;
254
255 /* Get the FADT */
256 if (AcpiGetTable(ACPI_SIG_FADT, 1,
257 (ACPI_TABLE_HEADER **)&fadt) != AE_OK)
258 return (rv);
259
260 /* Init the RTC offsets */
261 if (fadt != NULL)
262 pc_tod_set_rtc_offsets(fadt);
263
264 return (rv);
265
266 doerrs:
267
268 if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS |
269 DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL))
270 ddi_prop_remove_all(dip);
271
272 ddi_soft_state_free(appm_statep, instance);
273
274 return (rv);
275 }
276
277
278 /*
279 * Driver getinfo(9e) entry routine
280 */
281 /* ARGSUSED */
282 static int
283 appm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
284 {
285 appm_unit *unitp;
286 int instance;
287
288 switch (cmd) {
289 case DDI_INFO_DEVT2DEVINFO:
290 instance = getminor((dev_t)arg);
291 unitp = ddi_get_soft_state(appm_statep, instance);
292 if (unitp == NULL) {
293 return (DDI_FAILURE);
294 }
295 *result = (void *) unitp->dip;
296 return (DDI_SUCCESS);
297
298 case DDI_INFO_DEVT2INSTANCE:
299 instance = getminor((dev_t)arg);
300 *result = (void *)(uintptr_t)instance;
301 return (DDI_SUCCESS);
302
303 default:
304 return (DDI_FAILURE);
305 }
306 }
307
308
309 /*
310 * detach(9e)
311 */
312 /* ARGSUSED */
313 static int
314 appm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
315 {
316 char *str = "appm_detach";
317
318 switch (cmd) {
319 case DDI_DETACH:
320 return (DDI_FAILURE);
321 case DDI_SUSPEND:
322 return (DDI_SUCCESS);
323 default:
324 cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd);
325 return (DDI_FAILURE);
326 }
327 }
328
329
330 /* ARGSUSED */
331 static int
332 appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
333 {
334 appm_unit *unitp;
335
336 /* not intended to allow sysadmin level root process to open it */
337 if (drv_priv(cred_p) != DDI_SUCCESS)
338 return (EPERM);
339
340 if ((unitp = ddi_get_soft_state(
341 appm_statep, getminor(*dev_p))) == NULL) {
342 cmn_err(CE_WARN, "appm_open: failed to get soft state!");
343 return (DDI_FAILURE);
344 }
345
346 mutex_enter(&appm_lock);
347 if (unitp->lyropen != 0) {
348 mutex_exit(&appm_lock);
349 return (EBUSY);
350 }
351 unitp->lyropen++;
352 mutex_exit(&appm_lock);
353
354 return (DDI_SUCCESS);
355 }
356
357
358 /* ARGSUSED */
359 static int
360 appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
361 {
362 appm_unit *unitp;
363
364 if ((unitp =
365 ddi_get_soft_state(appm_statep, getminor(dev))) == NULL)
366 return (DDI_FAILURE);
367
368 mutex_enter(&appm_lock);
369 unitp->lyropen = 0;
370 mutex_exit(&appm_lock);
371
372 return (DDI_SUCCESS);
373 }
374
375
376 /*
377 * must match ppm.conf
378 */
379 #define APPMIOC ('A' << 8)
380 #define APPMIOC_ENTER_S3 (APPMIOC | 1) /* arg *s3a_t */
381 #define APPMIOC_EXIT_S3 (APPMIOC | 2) /* arg *s3a_t */
382
383 /* ARGSUSED3 */
384 static int
385 appm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
386 cred_t *cred_p, int *rval_p)
387 {
388 static boolean_t acpi_initted = B_FALSE;
389 char *str = "appm_ioctl";
390 int ret;
391 s3a_t *s3ap = (s3a_t *)arg;
392
393 PMD(PMD_SX, ("%s: called with %x\n", str, cmd))
394
395 if (drv_priv(cred_p) != 0) {
396 PMD(PMD_SX, ("%s: EPERM\n", str))
397 return (EPERM);
398 }
399
400 if (ddi_get_soft_state(appm_statep, getminor(dev)) == NULL) {
401 PMD(PMD_SX, ("%s: no soft state: EIO\n", str))
402 return (EIO);
403 }
404
405 if (!acpi_initted) {
406 PMD(PMD_SX, ("%s: !acpi_initted\n", str))
407 if (acpica_init() == 0) {
408 acpi_initted = B_TRUE;
409 } else {
410 if (rval_p != NULL) {
411 *rval_p = EINVAL;
412 }
413 PMD(PMD_SX, ("%s: EINVAL\n", str))
414 return (EINVAL);
415 }
416 }
417
418 PMD(PMD_SX, ("%s: looking for cmd %x\n", str, cmd))
419 switch (cmd) {
420 case APPMIOC_ENTER_S3:
421 /*
422 * suspend to RAM (ie S3)
423 */
424 PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
425 acpica_use_safe_delay = 1;
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 acpica_use_safe_delay = 0;
436 break;
437
438 default:
439 PMD(PMD_SX, ("%s: cmd %x unrecognized: ENOTTY\n", str, cmd))
440 return (ENOTTY);
441 }
442
443 /*
444 * upon failure return EINVAL
445 */
446 if (ret != 0) {
447 if (rval_p != NULL) {
448 *rval_p = EINVAL;
449 }
450 return (EINVAL);
451 }
452
453 return (0);
454 }