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