summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/srn.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/common/io/srn.c')
-rwxr-xr-xusr/src/uts/common/io/srn.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/usr/src/uts/common/io/srn.c b/usr/src/uts/common/io/srn.c
new file mode 100755
index 0000000000..cb2888871d
--- /dev/null
+++ b/usr/src/uts/common/io/srn.c
@@ -0,0 +1,563 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * srn Provide apm-like interfaces to Xorg
+ */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/modctl.h>
+#include <sys/conf.h> /* driver flags and functions */
+#include <sys/open.h> /* OTYP_CHR definition */
+#include <sys/stat.h> /* S_IFCHR definition */
+#include <sys/pathname.h> /* name -> dev_info xlation */
+#include <sys/kmem.h> /* memory alloc stuff */
+#include <sys/debug.h>
+#include <sys/pm.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/epm.h>
+#include <sys/vfs.h>
+#include <sys/mode.h>
+#include <sys/mkdev.h>
+#include <sys/promif.h>
+#include <sys/consdev.h>
+#include <sys/ddi_impldefs.h>
+#include <sys/poll.h>
+#include <sys/note.h>
+#include <sys/taskq.h>
+#include <sys/policy.h>
+#include <sys/srn.h>
+
+/*
+ * Minor number is instance<<8 + clone minor from range 1-255;
+ * But only one will be allocated
+ */
+#define SRN_MINOR_TO_CLONE(minor) ((minor) & (SRN_MAX_CLONE - 1))
+#define SU 0x002
+#define SG 0x004
+
+extern kmutex_t srn_clone_lock; /* protects srn_clones array */
+extern kcondvar_t srn_clones_cv[SRN_MAX_CLONE];
+extern uint_t srn_poll_cnt[SRN_MAX_CLONE];
+
+/*
+ * The soft state of the srn driver. Since there will only be
+ * one of these, just reference it through a static struct.
+ */
+static struct srnstate {
+ dev_info_t *srn_dip; /* ptr to our dev_info node */
+ int srn_instance; /* for ddi_get_instance() */
+ uchar_t srn_clones[SRN_MAX_CLONE]; /* unique opens */
+ struct cred *srn_cred[SRN_MAX_CLONE]; /* cred for each open */
+ int srn_type[SRN_MAX_CLONE]; /* type of handshake */
+ int srn_delivered[SRN_MAX_CLONE];
+ srn_event_info_t srn_pending[SRN_MAX_CLONE];
+} srn = { NULL, -1};
+typedef struct srnstate *srn_state_t;
+
+kcondvar_t srn_clones_cv[SRN_MAX_CLONE];
+uint_t srn_poll_cnt[SRN_MAX_CLONE]; /* count of events for poll */
+int srn_apm_count;
+int srn_autosx_count;
+struct pollhead srn_pollhead[SRN_MAX_CLONE];
+
+static int srn_open(dev_t *, int, int, cred_t *);
+static int srn_close(dev_t, int, int, cred_t *);
+static int srn_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
+static int srn_chpoll(dev_t, short, int, short *, struct pollhead **);
+
+static struct cb_ops srn_cb_ops = {
+ srn_open, /* open */
+ srn_close, /* close */
+ nodev, /* strategy */
+ nodev, /* print */
+ nodev, /* dump */
+ nodev, /* read */
+ nodev, /* write */
+ srn_ioctl, /* ioctl */
+ nodev, /* devmap */
+ nodev, /* mmap */
+ nodev, /* segmap */
+ srn_chpoll, /* poll */
+ ddi_prop_op, /* prop_op */
+ NULL, /* streamtab */
+ D_NEW | D_MP /* driver compatibility flag */
+};
+
+static int srn_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
+ void **result);
+static int srn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
+static int srn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
+static void srn_notify(int type, int event);
+
+static struct dev_ops srn_ops = {
+ DEVO_REV, /* devo_rev */
+ 0, /* refcnt */
+ srn_getinfo, /* info */
+ nulldev, /* identify */
+ nulldev, /* probe */
+ srn_attach, /* attach */
+ srn_detach, /* detach */
+ nodev, /* reset */
+ &srn_cb_ops, /* driver operations */
+ NULL, /* bus operations */
+ NULL /* power */
+};
+
+static struct modldrv modldrv = {
+ &mod_driverops,
+ "srn driver v1.4",
+ &srn_ops
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, &modldrv, 0
+};
+
+/* Local functions */
+
+int
+_init(void)
+{
+ return (mod_install(&modlinkage));
+}
+
+int
+_fini(void)
+{
+ return (mod_remove(&modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+static int
+srn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ int i;
+ extern void (*srn_signal)(int, int);
+
+ switch (cmd) {
+
+ case DDI_ATTACH:
+ if (srn.srn_instance != -1) /* Only allow one instance */
+ return (DDI_FAILURE);
+ srn.srn_instance = ddi_get_instance(dip);
+ if (ddi_create_minor_node(dip, "srn", S_IFCHR,
+ (srn.srn_instance << 8) + 0, DDI_PSEUDO, 0)
+ != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+ srn.srn_dip = dip; /* srn_init and getinfo depend on it */
+
+ for (i = 0; i < SRN_MAX_CLONE; i++)
+ cv_init(&srn_clones_cv[i], NULL, CV_DEFAULT, NULL);
+
+ srn.srn_instance = ddi_get_instance(dip);
+ mutex_enter(&srn_clone_lock);
+ srn_signal = srn_notify;
+ mutex_exit(&srn_clone_lock);
+ ddi_report_dev(dip);
+ return (DDI_SUCCESS);
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+/* ARGSUSED */
+static int
+srn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ int i;
+ extern int srn_inuse;
+ extern void (*srn_signal)(int, int);
+
+ switch (cmd) {
+ case DDI_DETACH:
+
+ mutex_enter(&srn_clone_lock);
+ while (srn_inuse) {
+ mutex_exit(&srn_clone_lock);
+ delay(1);
+ mutex_enter(&srn_clone_lock);
+ }
+ srn_signal = NULL;
+ mutex_exit(&srn_clone_lock);
+
+ for (i = 0; i < SRN_MAX_CLONE; i++)
+ cv_destroy(&srn_clones_cv[i]);
+
+ ddi_remove_minor_node(dip, NULL);
+ srn.srn_instance = -1;
+ return (DDI_SUCCESS);
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+
+#ifdef DEBUG
+char *srn_cmd_string;
+int srn_cmd;
+#endif
+
+/*
+ * Returns true if permission granted by credentials
+ * XXX
+ */
+static int
+srn_perms(int perm, cred_t *cr)
+{
+ if ((perm & SU) && secpolicy_power_mgmt(cr) == 0) /* privileged? */
+ return (1);
+ if ((perm & SG) && (crgetgid(cr) == 0)) /* group 0 is ok */
+ return (1);
+ return (0);
+}
+
+static int
+srn_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
+ struct pollhead **phpp)
+{
+ extern struct pollhead srn_pollhead[]; /* common/os/sunpm.c */
+ int clone;
+
+ clone = SRN_MINOR_TO_CLONE(getminor(dev));
+ if ((events & (POLLIN | POLLRDNORM)) && srn_poll_cnt[clone]) {
+ *reventsp |= (POLLIN | POLLRDNORM);
+ } else {
+ *reventsp = 0;
+ if (!anyyet) {
+ *phpp = &srn_pollhead[clone];
+ }
+ }
+ return (0);
+}
+
+/*ARGSUSED*/
+static int
+srn_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
+{
+ dev_t dev;
+ int instance;
+
+ switch (infocmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ if (srn.srn_instance == -1)
+ return (DDI_FAILURE);
+ *result = srn.srn_dip;
+ return (DDI_SUCCESS);
+
+ case DDI_INFO_DEVT2INSTANCE:
+ dev = (dev_t)arg;
+ instance = getminor(dev) >> 8;
+ *result = (void *)(uintptr_t)instance;
+ return (DDI_SUCCESS);
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+
+/*ARGSUSED1*/
+static int
+srn_open(dev_t *devp, int flag, int otyp, cred_t *cr)
+{
+ int clone;
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ mutex_enter(&srn_clone_lock);
+ for (clone = 1; clone < SRN_MAX_CLONE - 1; clone++)
+ if (!srn.srn_clones[clone])
+ break;
+
+ if (clone == SRN_MAX_CLONE) {
+ mutex_exit(&srn_clone_lock);
+ return (ENXIO);
+ }
+ srn.srn_cred[clone] = cr;
+ ASSERT(srn_apm_count >= 0);
+ srn_apm_count++;
+ srn.srn_type[clone] = SRN_TYPE_APM;
+ crhold(cr);
+
+ *devp = makedevice(getmajor(*devp), (srn.srn_instance << 8) +
+ clone);
+ srn.srn_clones[clone] = 1;
+ srn.srn_cred[clone] = cr;
+ crhold(cr);
+ mutex_exit(&srn_clone_lock);
+ PMD(PMD_SX, ("srn open OK\n"))
+ return (0);
+}
+
+/*ARGSUSED1*/
+static int
+srn_close(dev_t dev, int flag, int otyp, cred_t *cr)
+{
+ int clone;
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ clone = SRN_MINOR_TO_CLONE(getminor(dev));
+ PMD(PMD_SX, ("srn_close: minor %x, clone %x\n", getminor(dev),
+ clone))
+ mutex_enter(&srn_clone_lock);
+ crfree(srn.srn_cred[clone]);
+ srn.srn_cred[clone] = 0;
+ srn_poll_cnt[clone] = 0;
+ if (srn.srn_pending[clone].ae_type || srn.srn_delivered[clone]) {
+ srn.srn_pending[clone].ae_type = 0;
+ srn.srn_delivered[clone] = 0;
+ cv_signal(&srn_clones_cv[clone]);
+ }
+ switch (srn.srn_type[clone]) {
+ case SRN_TYPE_AUTOSX:
+ ASSERT(srn_autosx_count);
+ srn_autosx_count--;
+ break;
+ case SRN_TYPE_APM:
+ ASSERT(srn_apm_count);
+ srn_apm_count--;
+ break;
+ default:
+ ASSERT(0);
+ return (EINVAL);
+ }
+ srn.srn_clones[clone] = 0;
+ mutex_exit(&srn_clone_lock);
+ return (0);
+}
+
+/*ARGSUSED*/
+static int
+srn_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cr, int *rval_p)
+{
+ int clone = SRN_MINOR_TO_CLONE(getminor(dev));
+
+ PMD(PMD_SX, ("ioctl: %x: begin\n", cmd))
+
+ switch (cmd) {
+ case SRN_IOC_NEXTEVENT:
+ case SRN_IOC_SUSPEND:
+ case SRN_IOC_RESUME:
+ case SRN_IOC_AUTOSX:
+ break;
+ default:
+ return (ENOTTY);
+ }
+
+ if (!srn_perms(SU | SG, srn.srn_cred[clone])) {
+ return (EPERM);
+ }
+ switch (cmd) {
+ case SRN_IOC_AUTOSX:
+ PMD(PMD_SX, ("SRN_IOC_AUTOSX entered\n"))
+ mutex_enter(&srn_clone_lock);
+ if (!srn.srn_clones[clone]) {
+ PMD(PMD_SX, (" ioctl !srn_clones--EINVAL\n"))
+ mutex_exit(&srn_clone_lock);
+ return (EINVAL);
+ }
+ if (srn.srn_pending[clone].ae_type) {
+ PMD(PMD_SX, ("AUTOSX while pending--EBUSY\n"))
+ mutex_exit(&srn_clone_lock);
+ return (EBUSY);
+ }
+ if (srn.srn_type[clone] == SRN_TYPE_AUTOSX) {
+ PMD(PMD_SX, ("AUTOSX already--EBUSY\n"))
+ mutex_exit(&srn_clone_lock);
+ return (EBUSY);
+ }
+ ASSERT(srn.srn_type[clone] == SRN_TYPE_APM);
+ srn.srn_type[clone] = SRN_TYPE_AUTOSX;
+ srn_apm_count--;
+ ASSERT(srn_apm_count >= 0);
+ ASSERT(srn_autosx_count >= 0);
+ srn_autosx_count++;
+ mutex_exit(&srn_clone_lock);
+ PMD(PMD_SX, ("SRN_IOC_AUTOSX returns success\n"))
+ return (0);
+
+ case SRN_IOC_NEXTEVENT:
+ /*
+ * return the next suspend or resume event; there should
+ * be one, cause we only get called if we've signalled a
+ * poll data completion
+ * then wake up the kernel thread sleeping for the delivery
+ */
+ PMD(PMD_SX, ("SRN_IOC_NEXTEVENT entered\n"))
+ mutex_enter(&srn_clone_lock);
+ if (srn_poll_cnt[clone] == 0) {
+ mutex_exit(&srn_clone_lock);
+ PMD(PMD_SX, ("SRN_IOC_NEXTEVENT clone %d "
+ "EWOULDBLOCK\n", clone))
+ return (EWOULDBLOCK);
+ }
+ ASSERT(srn.srn_pending[clone].ae_type);
+ if (ddi_copyout(&srn.srn_pending[clone], (void *)arg,
+ sizeof (srn_event_info_t), mode) != 0) {
+ mutex_exit(&srn_clone_lock);
+ PMD(PMD_SX, ("SRN_IOC_NEXTEVENT clone %d EFAULT\n",
+ clone))
+ return (EFAULT);
+ }
+ if (srn.srn_type[clone] == SRN_TYPE_APM)
+ srn.srn_delivered[clone] =
+ srn.srn_pending[clone].ae_type;
+ PMD(PMD_SX, ("SRN_IOC_NEXTEVENT clone %d delivered %x\n",
+ clone, srn.srn_pending[clone].ae_type))
+ srn_poll_cnt[clone] = 0;
+ mutex_exit(&srn_clone_lock);
+ return (0);
+
+ case SRN_IOC_SUSPEND:
+ /* ack suspend */
+ PMD(PMD_SX, ("SRN_IOC_SUSPEND entered clone %d\n", clone))
+ mutex_enter(&srn_clone_lock);
+ if (srn.srn_delivered[clone] != SRN_SUSPEND_REQ) {
+ mutex_exit(&srn_clone_lock);
+ PMD(PMD_SX, ("SRN_IOC_SUSPEND EINVAL\n"))
+ return (EINVAL);
+ }
+ srn.srn_delivered[clone] = 0;
+ srn.srn_pending[clone].ae_type = 0;
+ /* notify the kernel suspend thread to continue */
+ PMD(PMD_SX, ("SRN_IOC_SUSPEND clone %d ok\n", clone))
+ cv_signal(&srn_clones_cv[clone]);
+ mutex_exit(&srn_clone_lock);
+ return (0);
+
+ case SRN_IOC_RESUME:
+ /* ack resume */
+ PMD(PMD_SX, ("SRN_IOC_RESUME entered clone %d\n", clone))
+ mutex_enter(&srn_clone_lock);
+ if (srn.srn_delivered[clone] != SRN_NORMAL_RESUME) {
+ mutex_exit(&srn_clone_lock);
+ PMD(PMD_SX, ("SRN_IOC_RESUME EINVAL\n"))
+ return (EINVAL);
+ }
+ srn.srn_delivered[clone] = 0;
+ srn.srn_pending[clone].ae_type = 0;
+ /* notify the kernel resume thread to continue */
+ PMD(PMD_SX, ("SRN_IOC_RESUME ok for clone %d\n", clone))
+ cv_signal(&srn_clones_cv[clone]);
+ mutex_exit(&srn_clone_lock);
+ return (0);
+
+ default:
+ PMD(PMD_SX, ("srn_ioctl unknown cmd EINVAL\n"))
+ return (EINVAL);
+ }
+}
+/*
+ * A very simple handshake with the srn driver,
+ * only one outstanding event at a time.
+ * The OS delivers the event and depending on type,
+ * either blocks waiting for the ack, or drives on
+ */
+void
+srn_notify(int type, int event)
+{
+ int clone, count;
+ PMD(PMD_SX, ("srn_notify entered with type %d, event 0x%x\n",
+ type, event));
+ ASSERT(mutex_owned(&srn_clone_lock));
+ switch (type) {
+ case SRN_TYPE_APM:
+ if (srn_apm_count == 0) {
+ PMD(PMD_SX, ("no apm types\n"))
+ return;
+ }
+ count = srn_apm_count;
+ break;
+ case SRN_TYPE_AUTOSX:
+ if (srn_autosx_count == 0) {
+ PMD(PMD_SX, ("no autosx types\n"))
+ return;
+ }
+ count = srn_autosx_count;
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ ASSERT(count > 0);
+ PMD(PMD_SX, ("count %d\n", count))
+ for (clone = 0; clone < SRN_MAX_CLONE; clone++) {
+ if (srn.srn_type[clone] == type) {
+ if (type == SRN_TYPE_APM) {
+ ASSERT(srn.srn_pending[clone].ae_type == 0);
+ ASSERT(srn_poll_cnt[clone] == 0);
+ ASSERT(srn.srn_delivered[clone] == 0);
+ }
+ srn.srn_pending[clone].ae_type = event;
+ srn_poll_cnt[clone] = 1;
+ PMD(PMD_SX, ("pollwake %d\n", clone))
+ pollwakeup(&srn_pollhead[clone], (POLLRDNORM | POLLIN));
+ count--;
+ if (count == 0)
+ break;
+ }
+ }
+ if (type == SRN_TYPE_AUTOSX) { /* we don't wait */
+ PMD(PMD_SX, ("Not waiting for AUTOSX ack\n"))
+ return;
+ }
+ ASSERT(type == SRN_TYPE_APM);
+ /* otherwise wait for acks */
+restart:
+ /*
+ * We wait untill all of the pending events are cleared.
+ * We have to start over every time we do a cv_wait because
+ * we give up the mutex and can be re-entered
+ */
+ for (clone = 1; clone < SRN_MAX_CLONE; clone++) {
+ if (srn.srn_clones[clone] == 0 ||
+ srn.srn_type[clone] != SRN_TYPE_APM)
+ continue;
+ if (srn.srn_pending[clone].ae_type) {
+ PMD(PMD_SX, ("srn_notify waiting for ack for clone %d, "
+ "event %x\n", clone, event))
+ cv_wait(&srn_clones_cv[clone], &srn_clone_lock);
+ goto restart;
+ }
+ }
+ PMD(PMD_SX, ("srn_notify done with %x\n", event))
+}