root/usr/src/uts/common/io/vcons.c
/*
 * 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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <sys/cred.h>
#include <sys/vnode.h>
#include <sys/termios.h>
#include <sys/termio.h>
#include <sys/ttold.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/tty.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/proc.h>
#include <sys/procset.h>
#include <sys/fault.h>
#include <sys/siginfo.h>
#include <sys/debug.h>
#include <sys/kd.h>
#include <sys/vt.h>
#include <sys/vtdaemon.h>
#include <sys/session.h>
#include <sys/door.h>
#include <sys/kmem.h>
#include <sys/cpuvar.h>
#include <sys/kbio.h>
#include <sys/strredir.h>
#include <sys/fs/snode.h>
#include <sys/consdev.h>
#include <sys/conf.h>
#include <sys/cmn_err.h>
#include <sys/console.h>
#include <sys/promif.h>
#include <sys/note.h>
#include <sys/polled_io.h>
#include <sys/systm.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/esunddi.h>
#include <sys/sunldi.h>
#include <sys/debug.h>
#include <sys/console.h>
#include <sys/ddi_impldefs.h>
#include <sys/policy.h>
#include <sys/tem.h>
#include <sys/wscons.h>
#include <sys/systm.h>
#include <sys/modctl.h>
#include <sys/vt_impl.h>
#include <sys/consconfig_dacf.h>

/*
 * This file belongs to wc STREAMS module which has a D_MTPERMODE
 * inner perimeter. See "Locking Policy" comment in wscons.c for
 * more information.
 */

/*
 * Minor        name            device file             Hotkeys
 *
 * 0    the system console      /dev/console            Alt + F1
 * 0:   virtual console #1      /dev/vt/0               Alt + F1
 *
 * 2:   virtual console #2      /dev/vt/2               Alt + F2
 * 3:   virtual console #3      /dev/vt/3               Alt + F3
 * ......
 * n:   virtual console #n      /dev/vt/n               Alt + Fn
 *
 * Note that vtdaemon is running on /dev/vt/1 (minor=1),
 * which is not available to end users.
 *
 */

#define VT_DAEMON_MINOR 1
#define VT_IS_DAEMON(minor)     ((minor) == VT_DAEMON_MINOR)

extern void     wc_get_size(vc_state_t *pvc);
extern boolean_t consconfig_console_is_tipline(void);


minor_t vc_last_console = VT_MINOR_INVALID;     /* the last used console */
volatile uint_t vc_target_console;              /* arg (1..n) */

static volatile minor_t vc_inuse_max_minor = 0;
static list_t vc_waitactive_list;
_NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vc_target_console))
_NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vc_last_console))
_NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vc_inuse_max_minor))
_NOTE(SCHEME_PROTECTS_DATA("D_MTPERMOD protected data", vc_waitactive_list))

static int vt_pending_vtno = -1;
kmutex_t vt_pending_vtno_lock;
_NOTE(MUTEX_PROTECTS_DATA(vt_pending_vtno_lock, vt_pending_vtno))

static int vt_activate(uint_t vt_no, cred_t *credp);
static void vt_copyout(queue_t *qp, mblk_t *mp, mblk_t *tmp, uint_t size);
static void vt_copyin(queue_t *qp, mblk_t *mp, uint_t size);
static void vt_iocnak(queue_t *qp, mblk_t *mp, int error);
static void vt_iocack(queue_t *qp, mblk_t *mp);

static uint_t vt_minor2arg(minor_t minor);
static minor_t vt_arg2minor(uint_t arg);

/*
 * If the system console is directed to tipline, consider /dev/vt/0 as
 * not being used.
 * For other VT, if it is opened and tty is initialized, consider it
 * as being used.
 */
#define VT_IS_INUSE(id)                                         \
        (((vt_minor2vc(id))->vc_flags & WCS_ISOPEN) &&          \
        ((vt_minor2vc(id))->vc_flags & WCS_INIT) &&             \
        (id != 0 || !consconfig_console_is_tipline()))

/*
 * the vt switching message is encoded as:
 *
 *   -------------------------------------------------------------
 *   |  \033  |  'Q'  |  vtno + 'A'  |  opcode  |  'z'  |  '\0'  |
 *   -------------------------------------------------------------
 */
#define VT_MSG_SWITCH(mp)                                       \
        ((int)((mp)->b_wptr - (mp)->b_rptr) >= 5 &&             \
        *((mp)->b_rptr) == '\033' &&                            \
        *((mp)->b_rptr + 1) == 'Q' &&                           \
        *((mp)->b_rptr + 4) == 'z')

#define VT_MSG_VTNO(mp)         (*((mp)->b_rptr + 2) - 'A')
#define VT_MSG_OPCODE(mp)       (*((mp)->b_rptr + 3))

#define VT_DOORCALL_MAX_RETRY   3

static void
vt_init_ttycommon(tty_common_t *pcommon)
{
        struct termios *termiosp;
        int len;

        mutex_init(&pcommon->t_excl, NULL, MUTEX_DEFAULT, NULL);
        pcommon->t_iflag = 0;

        /*
         * Get the default termios settings (cflag).
         * These are stored as a property in the
         * "options" node.
         */
        if (ddi_getlongprop(DDI_DEV_T_ANY,
            ddi_root_node(), 0, "ttymodes",
            (caddr_t)&termiosp, &len) == DDI_PROP_SUCCESS) {

                if (len == sizeof (struct termios))
                        pcommon->t_cflag = termiosp->c_cflag;
                else
                        cmn_err(CE_WARN,
                            "wc: Couldn't get ttymodes property!");

                kmem_free(termiosp, len);
        } else {
                /*
                 * Gack!  Whine about it.
                 */
                cmn_err(CE_WARN,
                    "wc: Couldn't get ttymodes property!");
        }

        pcommon->t_iocpending = NULL;
}

static int
vt_config(uint_t count)
{
        if (consmode != CONS_KFB)
                return (ENOTSUP);

        /* one for system console, one for vtdaemon */
        if (count < 2)
                return (ENXIO);

        /*
         * Shouldn't allow to shrink the max vt minor to be smaller than
         * the max in used minor.
         */
        if (count <= vc_inuse_max_minor)
                return (EBUSY);

        mutex_enter(&vc_lock);
        vt_resize(count);
        mutex_exit(&vc_lock);

        return (0);
}

void
vt_clean(queue_t *q, vc_state_t *pvc)
{
        ASSERT(MUTEX_HELD(&pvc->vc_state_lock));

        if (pvc->vc_bufcallid != 0) {
                qunbufcall(q, pvc->vc_bufcallid);
                pvc->vc_bufcallid = 0;
        }
        if (pvc->vc_timeoutid != 0) {
                (void) quntimeout(q, pvc->vc_timeoutid);
                pvc->vc_timeoutid = 0;
        }
        ttycommon_close(&pvc->vc_ttycommon);

        pvc->vc_flags &= ~WCS_INIT;
}

/*
 * Reply the VT_WAITACTIVE ioctl.
 * Argument 'close' usage:
 * B_TRUE:  the vt designated by argument 'minor' is being closed.
 * B_FALSE: the vt designated by argument 'minor' has been activated just now.
 */
static void
vc_waitactive_reply(int minor, boolean_t close)
{
        vc_waitactive_msg_t *index, *tmp;
        vc_state_t *pvc;

        index = list_head(&vc_waitactive_list);

        while (index != NULL) {
                tmp = index;
                index = list_next(&vc_waitactive_list, index);

                if ((close && tmp->wa_msg_minor == minor) ||
                    (!close && tmp->wa_wait_minor == minor)) {
                        list_remove(&vc_waitactive_list, tmp);
                        pvc = vt_minor2vc(tmp->wa_msg_minor);

                        if (close)
                                vt_iocnak(pvc->vc_wq, tmp->wa_mp, ENXIO);
                        else
                                vt_iocack(pvc->vc_wq, tmp->wa_mp);

                        kmem_free(tmp, sizeof (vc_waitactive_msg_t));
                }
        }
}

void
vt_close(queue_t *q, vc_state_t *pvc, cred_t *credp)
{
        minor_t index;

        mutex_enter(&pvc->vc_state_lock);
        vt_clean(q, pvc);
        pvc->vc_flags &= ~WCS_ISOPEN;
        mutex_exit(&pvc->vc_state_lock);

        tem_destroy(pvc->vc_tem, credp);
        pvc->vc_tem = NULL;

        index = pvc->vc_minor;
        if (index == vc_inuse_max_minor) {
                while ((--index > 0) && !VT_IS_INUSE(index))
                        ;
                vc_inuse_max_minor = index;
        }

        vc_waitactive_reply(pvc->vc_minor, B_TRUE);
}

static void
vt_init_tty(vc_state_t *pvc)
{
        ASSERT(MUTEX_HELD(&pvc->vc_state_lock));

        pvc->vc_flags |= WCS_INIT;
        vt_init_ttycommon(&pvc->vc_ttycommon);
        wc_get_size(pvc);
}

/*
 * minor 0:     /dev/vt/0       (index = 0, indicating the system console)
 * minor 1:     /dev/vt/1       (index = 1, vtdaemon special console)
 * minor 2:     /dev/vt/2       (index = 2, virtual consoles)
 * ......
 * minor n:     /dev/vt/n       (index = n)
 *
 *
 * The system console (minor 0), is opened firstly and used during console
 * configuration.  It also acts as the system hard console even when all
 * virtual consoles go off.
 *
 * In tipline case, minor 0 (/dev/vt/0) is reserved, and cannot be switched to.
 * And the system console is redirected to the tipline. During normal cases,
 * we can switch from virtual consoles to it by pressing 'Alt + F1'.
 *
 * minor 1 (/dev/vt/1) is reserved for vtdaemon special console, and it's
 * not available to end users.
 *
 * During early console configuration, consconfig_dacf opens wscons and then
 * issue a WC_OPEN_FB ioctl to kick off terminal init process. So during
 * consconfig_dacf first opening of wscons, tems (of type tem_state_t) is
 * not initialized. We do not initialize the tem_vt_state_t instance returned
 * by tem_init() for this open, since we do not have enough info to handle
 * normal terminal operation at this moment. This tem_vt_state_t instance
 * will get initialized when handling WC_OPEN_FB.
 */
int
vt_open(minor_t minor, queue_t *rq, cred_t *crp)
{
        vc_state_t *pvc;

        if (!vt_minor_valid(minor))
                return (ENXIO);

        pvc = vt_minor2vc(minor);
        if (pvc == NULL)
                return (ENXIO);

        mutex_enter(&vc_lock);
        mutex_enter(&pvc->vc_state_lock);

        if (!(pvc->vc_flags & WCS_ISOPEN)) {
                /*
                 * vc_tem might not be intialized if !tems.ts_initialized,
                 * and this only happens during console configuration.
                 */
                pvc->vc_tem = tem_init(crp, rq);
        }

        if (!(pvc->vc_flags & WCS_INIT))
                vt_init_tty(pvc);

        /*
         * In normal case, the first screen is the system console;
         * In tipline case, the first screen is the first VT that gets started.
         */
        if (vc_active_console == VT_MINOR_INVALID && minor != VT_DAEMON_MINOR)
                if (minor == 0 || consmode == CONS_KFB) {
                        boolean_t unblank = B_FALSE;

                        vc_active_console = minor;
                        vc_last_console = minor;
                        if (minor != 0) {
                                /*
                                 * If we are not opening the system console
                                 * as the first console, clear the phyical
                                 * screen.
                                 */
                                unblank = B_TRUE;
                        }

                        tem_activate(pvc->vc_tem, unblank, crp);
                }

        if ((pvc->vc_ttycommon.t_flags & TS_XCLUDE) &&
            (secpolicy_excl_open(crp) != 0)) {
                mutex_exit(&pvc->vc_state_lock);
                mutex_exit(&vc_lock);
                return (EBUSY);
        }

        if (minor > vc_inuse_max_minor)
                vc_inuse_max_minor = minor;

        pvc->vc_flags |= WCS_ISOPEN;
        pvc->vc_ttycommon.t_readq = rq;
        pvc->vc_ttycommon.t_writeq = WR(rq);

        mutex_exit(&pvc->vc_state_lock);
        mutex_exit(&vc_lock);

        rq->q_ptr = pvc;
        WR(rq)->q_ptr = pvc;
        pvc->vc_wq = WR(rq);

        qprocson(rq);
        return (0);
}

static minor_t
vt_find_prev(minor_t cur)
{
        minor_t i, t, max;

        ASSERT(vc_active_console != VT_MINOR_INVALID);

        max = VC_INSTANCES_COUNT;

        for (i = cur - 1; (t = (i + max) % max) != cur; i--)
                if (!VT_IS_DAEMON(t) && VT_IS_INUSE(t))
                        return (t);

        return (VT_MINOR_INVALID);
}

static minor_t
vt_find_next(minor_t cur)
{
        minor_t i, t, max;

        ASSERT(vc_active_console != VT_MINOR_INVALID);

        max = VC_INSTANCES_COUNT;

        for (i = cur + 1; (t = (i + max) % max) != cur; i++)
                if (!VT_IS_DAEMON(t) && VT_IS_INUSE(t))
                        return (t);

        return (VT_MINOR_INVALID);
}

/* ARGSUSED */
void
vt_send_hotkeys(void *timeout_arg)
{
        door_handle_t door;
        vt_cmd_arg_t arg;
        int error = 0;
        int retries = 0;
        door_arg_t door_arg;

        arg.vt_ev = VT_EV_HOTKEYS;

        mutex_enter(&vt_pending_vtno_lock);
        arg.vt_num = vt_pending_vtno;
        mutex_exit(&vt_pending_vtno_lock);

        /* only available in kernel context or user context */
        if (door_ki_open(VT_DAEMON_DOOR_FILE, &door) != 0) {
                mutex_enter(&vt_pending_vtno_lock);
                vt_pending_vtno = -1;
                mutex_exit(&vt_pending_vtno_lock);
                return;
        }

        door_arg.rbuf = NULL;
        door_arg.rsize = 0;
        door_arg.data_ptr = (void *)&arg;
        door_arg.data_size = sizeof (arg);
        door_arg.desc_ptr = NULL;
        door_arg.desc_num = 0;

        /*
         * Make door upcall
         */
        while ((error = door_ki_upcall(door, &door_arg)) != 0 &&
            retries < VT_DOORCALL_MAX_RETRY)
                if (error == EAGAIN || error == EINTR)
                        retries++;
                else
                        break;

        door_ki_rele(door);

        mutex_enter(&vt_pending_vtno_lock);
        vt_pending_vtno = -1;
        mutex_exit(&vt_pending_vtno_lock);
}

static boolean_t
vt_validate_hotkeys(int minor)
{
        /*
         * minor should not succeed the existing minor numbers range.
         */
        if (!vt_minor_valid(minor))
                return (B_FALSE);

        /*
         * Shouldn't switch to /dev/vt/1 or an unused vt.
         */
        if (!VT_IS_DAEMON(minor) && VT_IS_INUSE(minor))
                return (B_TRUE);

        return (B_FALSE);
}

static void
vt_trigger_hotkeys(int vtno)
{
        mutex_enter(&vt_pending_vtno_lock);

        if (vt_pending_vtno != -1) {
                mutex_exit(&vt_pending_vtno_lock);
                return;
        }

        vt_pending_vtno = vtno;
        mutex_exit(&vt_pending_vtno_lock);
        (void) timeout(vt_send_hotkeys, NULL, 1);
}

/*
 * return value:
 *    0:    non msg of vt hotkeys
 *    1:    msg of vt hotkeys
 */
int
vt_check_hotkeys(mblk_t *mp)
{
        int vtno = 0;
        minor_t minor = 0;

        /* LINTED E_PTRDIFF_OVERFLOW */
        if (!VT_MSG_SWITCH(mp))
                return (0);

        switch (VT_MSG_OPCODE(mp)) {
        case 'B':
                /* find out the previous vt */
                if (vc_active_console == VT_MINOR_INVALID)
                        return (1);

                if (VT_IS_DAEMON(vc_active_console)) {
                        minor = vt_find_prev(vt_arg2minor(vc_target_console));
                        break;
                }

                minor = vt_find_prev(vc_active_console);
                break;
        case 'F':
                /* find out the next vt */
                if (vc_active_console == VT_MINOR_INVALID)
                        return (1);

                if (VT_IS_DAEMON(vc_active_console)) {
                        minor = vt_find_next(vt_arg2minor(vc_target_console));
                        break;
                }

                minor = vt_find_next(vc_active_console);
                break;
        case 'H':
                /* find out the specified vt */
                minor = VT_MSG_VTNO(mp);

                /* check for system console, Alt + F1 */
                if (minor == 1)
                        minor = 0;
                break;
        case 'L':
                /* find out the last vt */
                if ((minor = vc_last_console) == VT_MINOR_INVALID)
                        return (1);
                break;
        default:
                return (1);
        }

        if (!vt_validate_hotkeys(minor))
                return (1);

        /*
         * for system console, the argument of vtno for
         * vt_activate is 1, though its minor is 0
         */
        if (minor == 0)
                vtno = 1;       /* for system console */
        else
                vtno = minor;

        vt_trigger_hotkeys(vtno);
        return (1);
}

static void
vt_proc_sendsig(pid_t pid, int sig)
{
        register proc_t *p;

        if (pid <= 0)
                return;

        mutex_enter(&pidlock);
        if ((p = prfind(pid)) == NULL || p->p_stat == SIDL) {
                mutex_exit(&pidlock);
                return;
        }

        psignal(p, sig);
        mutex_exit(&pidlock);
}

static int
vt_proc_exists(pid_t pid)
{
        register proc_t *p;

        if (pid <= 0)
                return (EINVAL);

        mutex_enter(&pidlock);
        if ((p = prfind(pid)) == NULL || p->p_stat == SIDL) {
                mutex_exit(&pidlock);
                return (ESRCH);
        }
        mutex_exit(&pidlock);

        return (0);
}

#define SIG_VALID(x)    (((x) > 0) && ((x) <= MAXSIG) && \
                        ((x) != SIGKILL) && ((x) != SIGSTOP))

static int
vt_setmode(vc_state_t *pvc, struct vt_mode *pmode)
{
        if ((pmode->mode != VT_PROCESS) && (pmode->mode != VT_AUTO))
                return (EINVAL);

        if (!SIG_VALID(pmode->relsig) || !SIG_VALID(pmode->acqsig))
                return (EINVAL);

        if (pmode->mode == VT_PROCESS) {
                pvc->vc_pid = curproc->p_pid;
        } else {
                pvc->vc_dispnum = 0;
                pvc->vc_login = 0;
        }

        pvc->vc_switch_mode = pmode->mode;
        pvc->vc_waitv = pmode->waitv;
        pvc->vc_relsig = pmode->relsig;
        pvc->vc_acqsig = pmode->acqsig;

        return (0);
}

static void
vt_reset(vc_state_t *pvc)
{
        pvc->vc_switch_mode = VT_AUTO;
        pvc->vc_pid = -1;
        pvc->vc_dispnum = 0;
        pvc->vc_login = 0;
        pvc->vc_switchto = VT_MINOR_INVALID;
}

/*
 * switch to vt_no from vc_active_console
 */
static int
vt_switch(uint_t vt_no, cred_t *credp)
{
        vc_state_t *pvc_active = vt_minor2vc(vc_active_console);
        vc_state_t *pvc = vt_minor2vc(vt_no);
        minor_t index;

        ASSERT(pvc_active && pvc);

        /* sanity test for the target VT and the active VT */
        if (!((pvc->vc_flags & WCS_ISOPEN) && (pvc->vc_flags & WCS_INIT)))
                return (EINVAL);

        if (!((pvc_active->vc_flags & WCS_ISOPEN) &&
            (pvc_active->vc_flags & WCS_INIT)))
                return (EINVAL);

        mutex_enter(&vc_lock);

        tem_switch(pvc_active->vc_tem, pvc->vc_tem, credp);

        if (!VT_IS_DAEMON(vc_active_console))
                vc_last_console = vc_active_console;
        else
                vc_last_console = vt_arg2minor(vc_target_console);

        vc_active_console = pvc->vc_minor;

        if (pvc->vc_switch_mode == VT_PROCESS) {
                pvc->vc_switchto = pvc->vc_minor;

                /* send it an acquired signal */
                vt_proc_sendsig(pvc->vc_pid, pvc->vc_acqsig);
        }

        vc_waitactive_reply(vc_active_console, B_FALSE);

        mutex_exit(&vc_lock);

        if (!VT_IS_DAEMON(vt_no)) {
                /*
                 * Applications that open the virtual console device may request
                 * asynchronous notification of VT switching from a previous VT
                 * to another one by setting the S_MSG flag in an I_SETSIG
                 * STREAMS ioctl. Such processes receive a SIGPOLL signal when
                 * a VT switching succeeds.
                 */
                for (index = 0; index < VC_INSTANCES_COUNT; index++) {
                        vc_state_t *tmp_pvc = vt_minor2vc(index);
                        mblk_t *mp;

                        if ((tmp_pvc->vc_flags & WCS_ISOPEN) &&
                            (tmp_pvc->vc_flags & WCS_INIT) &&
                            (mp = allocb(sizeof (unsigned char), BPRI_HI))) {
                                mp->b_datap->db_type = M_PCSIG;
                                *mp->b_wptr = SIGPOLL;
                                mp->b_wptr += sizeof (unsigned char);
                                putnext(RD(tmp_pvc->vc_wq), mp);
                        }
                }
        }

        return (0);

}

/*
 * vt_no        from 0 to n
 *
 * 0    for the vtdaemon sepcial console (only vtdaemon will use it)
 * 1    for the system console (Alt + F1, or Alt + Ctrl + F1),
 *      aka Virtual Console #1
 *
 * 2    for Virtual Console #2
 * n    for Virtual Console #n
 */
static minor_t
vt_arg2minor(uint_t arg)
{
        if (arg == 0)
                return (1);

        if (arg == 1)
                return (0);

        return (arg);
}

static uint_t
vt_minor2arg(minor_t minor)
{
        if (minor == 0)
                return (1);

        if (VT_IS_DAEMON(minor)) {
                /* here it should be the real console */
                return (vc_target_console);
        }

        return (minor);
}

static int
vt_activate(uint_t vt_no, cred_t *credp)
{
        vc_state_t *pvc;
        minor_t minor;

        minor = vt_arg2minor(vt_no);
        if (!vt_minor_valid(minor))
                return (ENXIO);
        if (minor == vc_active_console) {
                if (VT_IS_DAEMON(minor)) {
                        /*
                         * vtdaemon is reactivating itself to do locking
                         * on behalf of another console, so record current
                         * target console as the last console.
                         */
                        vc_last_console = vt_arg2minor(vc_target_console);
                }

                return (0);
        }

        /*
         * In tipline case, the system console is redirected to tipline
         * and thus is always available.
         */
        if (minor == 0 && consconfig_console_is_tipline())
                return (0);

        if (!VT_IS_INUSE(minor))
                return (ENXIO);

        pvc = vt_minor2vc(minor);
        if (pvc == NULL)
                return (ENXIO);
        if (pvc->vc_tem == NULL)
                return (ENXIO);

        pvc = vt_minor2vc(vc_active_console);
        if (pvc == NULL)
                return (ENXIO);
        if (pvc->vc_switch_mode != VT_PROCESS)
                return (vt_switch(minor, credp));

        /*
         * Validate the process, reset the
         * vt to auto mode if failed.
         */
        if (pvc->vc_pid == -1 || vt_proc_exists(pvc->vc_pid) != 0) {
                /*
                 * Xserver has not started up yet,
                 * or it dose not exist.
                 */
                vt_reset(pvc);
                return (0);
        }

        /*
         * Send the release signal to the process,
         * and wait VT_RELDISP ioctl from Xserver
         * after its leaving VT.
         */
        vt_proc_sendsig(pvc->vc_pid, pvc->vc_relsig);
        pvc->vc_switchto = minor;

        /*
         * We don't need a timeout here, for if Xserver refuses
         * or fails to respond to release signal using VT_RELDISP,
         * we cannot successfully switch to our text mode. Actually
         * users can try again. At present we don't support force
         * switch.
         */
        return (0);
}

static int
vt_reldisp(vc_state_t *pvc, int arg, cred_t *credp)
{
        minor_t target_vtno = pvc->vc_switchto;

        if ((pvc->vc_switch_mode != VT_PROCESS) ||
            (pvc->vc_minor != vc_active_console))
                return (EACCES);

        if (target_vtno == VT_MINOR_INVALID)
                return (EINVAL);

        pvc->vc_switchto = VT_MINOR_INVALID;

        if (arg == VT_ACKACQ)
                return (0);

        if (arg == 0)
                return (0); /* refuse to release */

        /* Xserver has left VT */
        return (vt_switch(target_vtno, credp));
}

void
vt_ioctl(queue_t *q, mblk_t *mp)
{
        vc_state_t *pvc = (vc_state_t *)q->q_ptr;
        struct iocblk   *iocp;
        struct vt_mode vtmode;
        struct vt_stat vtinfo;
        struct vt_dispinfo vtdisp;
        mblk_t *tmp;
        int minor;
        int arg;
        int error = 0;
        vc_waitactive_msg_t *wait_msg;

        iocp = (struct iocblk *)(void *)mp->b_rptr;
        if (consmode != CONS_KFB && iocp->ioc_cmd != VT_ENABLED) {
                vt_iocnak(q, mp, EINVAL);
                return;
        }

        switch (iocp->ioc_cmd) {
        case VT_ENABLED:
                if (!(tmp = allocb(sizeof (int), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }
                *(int *)(void *)tmp->b_rptr = consmode;
                tmp->b_wptr += sizeof (int);
                vt_copyout(q, mp, tmp, sizeof (int));
                return;

        case KDSETMODE:
                arg = *(intptr_t *)(void *)mp->b_cont->b_rptr;
                if (arg != KD_TEXT && arg != KD_GRAPHICS) {
                        error = EINVAL;
                        break;
                }
                if (tem_get_fbmode(pvc->vc_tem) == arg)
                        break;

                tem_set_fbmode(pvc->vc_tem, (uchar_t)arg, iocp->ioc_cr);

                break;

        case KDGETMODE:
                if (!(tmp = allocb(sizeof (int), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }
                *(int *)(void *)tmp->b_rptr = tem_get_fbmode(pvc->vc_tem);
                tmp->b_wptr += sizeof (int);
                vt_copyout(q, mp, tmp, sizeof (int));
                return;

        case VT_OPENQRY: /* return number of first free VT */
                if (!(tmp = allocb(sizeof (int), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }

                /* minors of 0 and 1 are not available to end users */
                for (minor = 2; vt_minor_valid(minor); minor++)
                        if (!VT_IS_INUSE(minor))
                                break;

                if (!vt_minor_valid(minor))
                        minor = -1;
                *(int *)(void *)tmp->b_rptr = minor; /* /dev/vt/minor */
                tmp->b_wptr += sizeof (int);
                vt_copyout(q, mp, tmp, sizeof (int));
                return;

        case VT_GETMODE:
                vtmode.mode = pvc->vc_switch_mode;
                vtmode.waitv = pvc->vc_waitv;
                vtmode.relsig = pvc->vc_relsig;
                vtmode.acqsig = pvc->vc_acqsig;
                vtmode.frsig = 0;
                if (!(tmp = allocb(sizeof (struct vt_mode), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }
                *(struct vt_mode *)(void *)tmp->b_rptr = vtmode;
                tmp->b_wptr += sizeof (struct vt_mode);
                vt_copyout(q, mp, tmp, sizeof (struct vt_mode));
                return;

        case VT_SETMODE:
                vt_copyin(q, mp, sizeof (struct vt_mode));
                return;

        case VT_SETDISPINFO:
                /* always enforce sys_devices privilege for setdispinfo */
                if ((error = secpolicy_console(iocp->ioc_cr)) != 0)
                        break;

                pvc->vc_dispnum = *(intptr_t *)(void *)mp->b_cont->b_rptr;
                break;

        case VT_SETDISPLOGIN:
                pvc->vc_login = *(intptr_t *)(void *)mp->b_cont->b_rptr;
                break;

        case VT_GETDISPINFO:
                vtdisp.v_pid = pvc->vc_pid;
                vtdisp.v_dispnum = pvc->vc_dispnum;
                vtdisp.v_login = pvc->vc_login;
                if (!(tmp = allocb(sizeof (struct vt_dispinfo), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }
                *(struct vt_dispinfo *)(void *)tmp->b_rptr = vtdisp;
                tmp->b_wptr += sizeof (struct vt_dispinfo);
                vt_copyout(q, mp, tmp, sizeof (struct vt_dispinfo));
                return;

        case VT_RELDISP:
                arg = *(intptr_t *)(void *)mp->b_cont->b_rptr;
                error = vt_reldisp(pvc, arg, iocp->ioc_cr);
                break;

        case VT_CONFIG:
                /* always enforce sys_devices privilege for config */
                if ((error = secpolicy_console(iocp->ioc_cr)) != 0)
                        break;

                arg = *(intptr_t *)(void *)mp->b_cont->b_rptr;
                error = vt_config(arg);
                break;

        case VT_ACTIVATE:
                /* always enforce sys_devices privilege for secure switch */
                if ((error = secpolicy_console(iocp->ioc_cr)) != 0)
                        break;

                arg = *(intptr_t *)(void *)mp->b_cont->b_rptr;
                error = vt_activate(arg, iocp->ioc_cr);
                break;

        case VT_WAITACTIVE:
                arg = *(intptr_t *)(void *)mp->b_cont->b_rptr;
                arg = vt_arg2minor(arg);
                if (!vt_minor_valid(arg)) {
                        error = ENXIO;
                        break;
                }
                if (arg == vc_active_console)
                        break;

                wait_msg = kmem_zalloc(sizeof (vc_waitactive_msg_t),
                    KM_NOSLEEP);
                if (wait_msg == NULL) {
                        error = ENXIO;
                        break;
                }

                wait_msg->wa_mp = mp;
                wait_msg->wa_msg_minor = pvc->vc_minor;
                wait_msg->wa_wait_minor = arg;
                list_insert_head(&vc_waitactive_list, wait_msg);

                return;

        case VT_GETSTATE:
                /*
                 * Here v_active is the argument for vt_activate,
                 * not minor.
                 */
                vtinfo.v_active = vt_minor2arg(vc_active_console);
                vtinfo.v_state = 3;     /* system console and vtdaemon */

                /* we only support 16 vt states since the v_state is short */
                for (minor = 2; minor < 16; minor++) {
                        pvc = vt_minor2vc(minor);
                        if (pvc == NULL)
                                break;
                        if (VT_IS_INUSE(minor))
                                vtinfo.v_state |= (1 << pvc->vc_minor);
                }

                if (!(tmp = allocb(sizeof (struct vt_stat), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }
                *(struct vt_stat *)(void *)tmp->b_rptr = vtinfo;
                tmp->b_wptr += sizeof (struct vt_stat);
                vt_copyout(q, mp, tmp, sizeof (struct vt_stat));
                return;

        case VT_SET_TARGET:
                /* always enforce sys_devices privilege */
                if ((error = secpolicy_console(iocp->ioc_cr)) != 0)
                        break;

                arg = *(intptr_t *)(void *)mp->b_cont->b_rptr;

                /* vtdaemon is doing authentication for this target console */
                vc_target_console = arg;
                break;

        case VT_GETACTIVE:      /* get real active console (minor) */
                if (!(tmp = allocb(sizeof (int), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }
                *(int *)(void *)tmp->b_rptr = vc_active_console;
                tmp->b_wptr += sizeof (int);
                vt_copyout(q, mp, tmp, sizeof (int));
                return;

        case VT_GET_CONSUSER:
                if (!(tmp = allocb(sizeof (int), BPRI_MED))) {
                        error = ENOMEM;
                        break;
                }

                if (vc_cons_user == VT_MINOR_INVALID) {
                        /*
                         * Return -1 if console user link points to
                         * /dev/console
                         */
                        *(int *)(void *)tmp->b_rptr = -1;
                } else {
                        *(int *)(void *)tmp->b_rptr = vc_cons_user;
                }

                tmp->b_wptr += sizeof (int);
                vt_copyout(q, mp, tmp, sizeof (int));
                return;

        case VT_RESET_CONSUSER:
                /* always enforce sys_devices privilege */
                if ((error = secpolicy_console(iocp->ioc_cr)) != 0)
                        break;

                /* Ensure it comes from /dev/console */
                if (pvc->vc_minor != 0) {
                        error = ENXIO;
                        break;
                }

                mutex_enter(&vc_lock);
                vc_cons_user = VT_MINOR_INVALID;
                mutex_exit(&vc_lock);
                break;

        case VT_SET_CONSUSER:
                /* always enforce sys_devices privilege */
                if ((error = secpolicy_console(iocp->ioc_cr)) != 0)
                        break;

                mutex_enter(&vc_lock);
                vc_cons_user = pvc->vc_minor;
                mutex_exit(&vc_lock);
                break;

        default:
                error = ENXIO;
                break;
        }

        if (error != 0)
                vt_iocnak(q, mp, error);
        else
                vt_iocack(q, mp);
}

void
vt_miocdata(queue_t *qp, mblk_t *mp)
{
        vc_state_t *pvc = (vc_state_t *)qp->q_ptr;
        struct copyresp *copyresp;
        struct vt_mode *pmode;
        int error = 0;

        copyresp = (struct copyresp *)(void *)mp->b_rptr;
        if (copyresp->cp_rval) {
                vt_iocnak(qp, mp, EAGAIN);
                return;
        }

        switch (copyresp->cp_cmd) {
        case VT_SETMODE:
                pmode = (struct vt_mode *)(void *)mp->b_cont->b_rptr;
                error = vt_setmode(pvc, pmode);
                break;

        case KDGETMODE:
        case VT_OPENQRY:
        case VT_GETMODE:
        case VT_GETDISPINFO:
        case VT_GETSTATE:
        case VT_ENABLED:
        case VT_GETACTIVE:
                break;

        default:
                error = ENXIO;
                break;
        }

        if (error != 0)
                vt_iocnak(qp, mp, error);
        else
                vt_iocack(qp, mp);
}

static void
vt_iocack(queue_t *qp, mblk_t *mp)
{
        struct iocblk   *iocbp = (struct iocblk *)(void *)mp->b_rptr;

        mp->b_datap->db_type = M_IOCACK;
        mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);
        iocbp->ioc_error = 0;
        iocbp->ioc_count = 0;
        iocbp->ioc_rval = 0;
        if (mp->b_cont != NULL) {
                freemsg(mp->b_cont);
                mp->b_cont = NULL;
        }
        qreply(qp, mp);
}

static void
vt_iocnak(queue_t *qp, mblk_t *mp, int error)
{
        struct iocblk *iocp = (struct iocblk *)(void *)mp->b_rptr;

        mp->b_datap->db_type = M_IOCNAK;
        iocp->ioc_rval = 0;
        iocp->ioc_count = 0;
        iocp->ioc_error = error;
        if (mp->b_cont != NULL) {
                freemsg(mp->b_cont);
                mp->b_cont = NULL;
        }
        qreply(qp, mp);
}

static void
vt_copyin(queue_t *qp, mblk_t *mp, uint_t size)
{
        struct copyreq  *cqp;

        cqp = (struct copyreq *)(void *)mp->b_rptr;
        cqp->cq_addr = *((caddr_t *)(void *)mp->b_cont->b_rptr);
        cqp->cq_size = size;
        cqp->cq_flag = 0;
        cqp->cq_private = (mblk_t *)NULL;
        mp->b_wptr = mp->b_rptr + sizeof (struct copyreq);
        mp->b_datap->db_type = M_COPYIN;
        if (mp->b_cont)
                freemsg(mp->b_cont);
        mp->b_cont = (mblk_t *)NULL;
        qreply(qp, mp);
}

static void
vt_copyout(queue_t *qp, mblk_t *mp, mblk_t *tmp, uint_t size)
{
        struct copyreq  *cqp;

        cqp = (struct copyreq *)(void *)mp->b_rptr;
        cqp->cq_size = size;
        cqp->cq_addr = *((caddr_t *)(void *)mp->b_cont->b_rptr);
        cqp->cq_flag = 0;
        cqp->cq_private = (mblk_t *)NULL;
        mp->b_wptr = mp->b_rptr + sizeof (struct copyreq);
        mp->b_datap->db_type = M_COPYOUT;
        if (mp->b_cont)
                freemsg(mp->b_cont);
        mp->b_cont = tmp;
        qreply(qp, mp);
}

/*
 * Get vc state from minor.
 * Once a caller gets a vc_state_t from this function,
 * the vc_state_t is guaranteed not being freed before
 * the caller leaves this STREAMS module by the D_MTPERMOD
 * perimeter.
 */
vc_state_t *
vt_minor2vc(minor_t minor)
{
        avl_index_t where;
        vc_state_t target;

        if (minor != VT_ACTIVE) {
                target.vc_minor = minor;
                return (avl_find(&vc_avl_root, &target, &where));
        }

        if (vc_active_console == VT_MINOR_INVALID)
                target.vc_minor = 0;
        else
                target.vc_minor = vc_active_console;

        return (avl_find(&vc_avl_root, &target, &where));
}

static void
vt_state_init(vc_state_t *vcptr, minor_t minor)
{
        mutex_init(&vcptr->vc_state_lock, NULL, MUTEX_DRIVER, NULL);

        mutex_enter(&vcptr->vc_state_lock);
        vcptr->vc_flags = 0;
        mutex_exit(&vcptr->vc_state_lock);

        vcptr->vc_pid = -1;
        vcptr->vc_dispnum = 0;
        vcptr->vc_login = 0;
        vcptr->vc_switchto = VT_MINOR_INVALID;
        vcptr->vc_switch_mode = VT_AUTO;
        vcptr->vc_relsig = SIGUSR1;
        vcptr->vc_acqsig = SIGUSR1;
        vcptr->vc_tem = NULL;
        vcptr->vc_bufcallid = 0;
        vcptr->vc_timeoutid = 0;
        vcptr->vc_wq = NULL;
        vcptr->vc_minor = minor;
}

void
vt_resize(uint_t count)
{
        uint_t vc_num, i;

        ASSERT(MUTEX_HELD(&vc_lock));

        vc_num = VC_INSTANCES_COUNT;

        if (count == vc_num)
                return;

        if (count > vc_num) {
                for (i = vc_num; i < count; i++) {
                        vc_state_t *vcptr = kmem_zalloc(sizeof (vc_state_t),
                            KM_SLEEP);
                        vt_state_init(vcptr, i);
                        avl_add(&vc_avl_root, vcptr);
                }
                return;
        }

        for (i = vc_num; i > count; i--) {
                avl_index_t where;
                vc_state_t target, *found;

                target.vc_minor = i - 1;
                found = avl_find(&vc_avl_root, &target, &where);
                ASSERT(found != NULL && found->vc_flags == 0);
                avl_remove(&vc_avl_root, found);
                kmem_free(found, sizeof (vc_state_t));
        }
}

static int
vc_avl_compare(const void *first, const void *second)
{
        const vc_state_t *vcptr1 = first;
        const vc_state_t *vcptr2 = second;

        if (vcptr1->vc_minor < vcptr2->vc_minor)
                return (-1);

        if (vcptr1->vc_minor == vcptr2->vc_minor)
                return (0);

        return (1);
}

/*
 * Only called from wc init().
 */
void
vt_init(void)
{
#ifdef  __lock_lint
        ASSERT(NO_COMPETING_THREADS);
#endif

        avl_create(&vc_avl_root, vc_avl_compare, sizeof (vc_state_t),
            offsetof(vc_state_t, vc_avl_node));

        list_create(&vc_waitactive_list, sizeof (vc_waitactive_msg_t),
            offsetof(vc_waitactive_msg_t, wa_list_node));

        mutex_init(&vc_lock, NULL, MUTEX_DRIVER, NULL);
        mutex_init(&vt_pending_vtno_lock, NULL, MUTEX_DRIVER, NULL);
}