root/sys/dev/misc/nmdm/nmdm.c
/*
 * (MPSAFE)
 *
 * Copyright (c) 1982, 1986, 1989, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: src/sys/dev/nmdm/nmdm.c,v 1.5.2.1 2001/08/11 00:54:14 mp Exp $
 */

/*
 * Pseudo-nulmodem Driver
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/tty.h>
#include <sys/ttydefaults.h>    /* for TTYDEF_* */
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/signalvar.h>
#include <sys/malloc.h>

MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures");

static void nmdmstart (struct tty *tp);
static void nmdmstop (struct tty *tp, int rw);
static void wakeup_other (struct tty *tp, int flag);
static void nmdminit (int n);

static  d_open_t        nmdmopen;
static  d_close_t       nmdmclose;
static  d_read_t        nmdmread;
static  d_write_t       nmdmwrite;
static  d_ioctl_t       nmdmioctl;

#define CDEV_MAJOR      18
static struct dev_ops nmdm_ops = {
        { "pts", 0, D_TTY },
        .d_open =       nmdmopen,
        .d_close =      nmdmclose,
        .d_read =       nmdmread,
        .d_write =      nmdmwrite,
        .d_ioctl =      nmdmioctl,
        .d_kqfilter =   ttykqfilter,
        .d_revoke =     ttyrevoke
};

#define BUFSIZ 100              /* Chunk size iomoved to/from user */

struct softpart {
        struct tty nm_tty;
        cdev_t  dev;
        int     modemsignals;   /* bits defined in sys/ttycom.h */
        int     gotbreak;
};

struct  nm_softc {
        int     pt_flags;
        struct softpart part1, part2;
        struct  prison *pt_prison;
};

#define PF_STOPPED      0x10            /* user told stopped */

static void
nmdm_crossover(struct nm_softc *pti,
                struct softpart *ourpart,
                struct softpart *otherpart);

#define GETPARTS(tp, ourpart, otherpart) \
do {    \
        struct nm_softc *pti = tp->t_dev->si_drv1; \
        if (tp == &pti->part1.nm_tty) { \
                ourpart = &pti->part1; \
                otherpart = &pti->part2; \
        } else { \
                ourpart = &pti->part2; \
                otherpart = &pti->part1; \
        }  \
} while (0)

/*
 * This function creates and initializes a pair of ttys.
 */
static void
nmdminit(int n)
{
        cdev_t dev1, dev2;
        struct nm_softc *pt;

        /*
         * Simplified unit number, use low 8 bits of minor number
         * (remember, the minor number mask is 0xffff00ff).
         */
        if (n & ~0x7f)
                return;

        pt = kmalloc(sizeof(*pt), M_NLMDM, M_WAITOK | M_ZERO);
        pt->part1.dev = dev1 = make_dev(&nmdm_ops, n << 1,
                                        0, 0, 0666, "nmdm%dA", n);
        pt->part2.dev = dev2 = make_dev(&nmdm_ops, (n << 1) + 1,
                                        0, 0, 0666, "nmdm%dB", n);

        dev1->si_drv1 = dev2->si_drv1 = pt;
        dev1->si_tty = &pt->part1.nm_tty;
        dev2->si_tty = &pt->part2.nm_tty;
        ttyinit(&pt->part1.nm_tty);
        ttyinit(&pt->part2.nm_tty);
        ttyregister(&pt->part1.nm_tty);
        ttyregister(&pt->part2.nm_tty);
        pt->part1.nm_tty.t_oproc = nmdmstart;
        pt->part2.nm_tty.t_oproc = nmdmstart;
        pt->part1.nm_tty.t_stop = nmdmstop;
        pt->part2.nm_tty.t_dev = dev1;
        pt->part1.nm_tty.t_dev = dev2;
        pt->part2.nm_tty.t_stop = nmdmstop;
}

/*ARGSUSED*/
static  int
nmdmopen(struct dev_open_args *ap)
{
        cdev_t dev = ap->a_head.a_dev;
        struct tty *tp, *tp2;
        int error;
        int minr;
#if 0
        cdev_t nextdev;
#endif
        struct nm_softc *pti;
        int is_b;
        int     pair;
        struct  softpart *ourpart, *otherpart;

        minr = lminor(dev);
        pair = minr >> 1;
        is_b = minr & 1;
        
#if 0
        /*
         * XXX: Gross hack for DEVFS:
         * If we openned this device, ensure we have the
         * next one too, so people can open it.
         */
        if (pair < 127) {
                nextdev = makedev(major(dev), (pair+pair) + 1);
                if (!nextdev->si_drv1) {
                        nmdminit(pair + 1);
                }
        }
#endif
        if (!dev->si_drv1)
                nmdminit(pair);

        if (!dev->si_drv1)
                return(ENXIO);  

        pti = dev->si_drv1;
        if (is_b) 
                tp = &pti->part2.nm_tty;
        else 
                tp = &pti->part1.nm_tty;
        lwkt_gettoken(&tp->t_token);
        GETPARTS(tp, ourpart, otherpart);
        tp2 = &otherpart->nm_tty;
        lwkt_gettoken(&tp2->t_token);
        ourpart->modemsignals |= TIOCM_LE;

        if ((tp->t_state & TS_ISOPEN) == 0) {
                ttychars(tp);           /* Set up default chars */
                tp->t_iflag = TTYDEF_IFLAG;
                tp->t_oflag = TTYDEF_OFLAG;
                tp->t_lflag = TTYDEF_LFLAG;
                tp->t_cflag = TTYDEF_CFLAG;
                tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
        } else if (tp->t_state & TS_XCLUDE &&
                   caps_priv_check(ap->a_cred, SYSCAP_RESTRICTEDROOT))
        {
                lwkt_reltoken(&tp2->t_token);
                lwkt_reltoken(&tp->t_token);

                return (EBUSY);
        } else if (pti->pt_prison != ap->a_cred->cr_prison) {
                lwkt_reltoken(&tp2->t_token);
                lwkt_reltoken(&tp->t_token);

                return (EBUSY);
        }

        /*
         * If the other side is open we have carrier
         */
        if (tp2->t_state & TS_ISOPEN) {
                (void)(*linesw[tp->t_line].l_modem)(tp, 1);
        }

        /*
         * And the other side gets carrier as we are now open.
         */
        (void)(*linesw[tp2->t_line].l_modem)(tp2, 1);

        /* External processing makes no sense here */
        tp->t_lflag &= ~EXTPROC;

        /* 
         * Wait here if we don't have carrier.
         */
#if 0
        while ((tp->t_state & TS_CARR_ON) == 0) {
                if (flag & FNONBLOCK)
                        break;
                error = ttysleep(tp, TSA_CARR_ON(tp), PCATCH, "nmdopn", 0);
                if (error) {
                        lwkt_reltoken(&tp2->t_token);
                        lwkt_reltoken(&tp->t_token);

                        return (error);
                }
        }
#endif

        /*
         * Give the line disciplin a chance to set this end up.
         */
        error = (*linesw[tp->t_line].l_open)(dev, tp);

        /*
         * Wake up the other side.
         * Theoretically not needed.
         */
        ourpart->modemsignals |= TIOCM_DTR;
        nmdm_crossover(pti, ourpart, otherpart);
        if (error == 0)
                wakeup_other(tp, FREAD|FWRITE); /* XXX */
        lwkt_reltoken(&tp2->t_token);
        lwkt_reltoken(&tp->t_token);

        return (error);
}

static int
nmdmclose(struct dev_close_args *ap)
{
        cdev_t dev = ap->a_head.a_dev;
        struct tty *tp, *tp2;
        int err;
        struct softpart *ourpart, *otherpart;

        /*
         * let the other end know that the game is up
         */
        tp = dev->si_tty;
        lwkt_gettoken(&tp->t_token);
        GETPARTS(tp, ourpart, otherpart);
        tp2 = &otherpart->nm_tty;
        lwkt_gettoken(&tp2->t_token);
        (void)(*linesw[tp2->t_line].l_modem)(tp2, 0);

        /*
         * XXX MDMBUF makes no sense for nmdms but would inhibit the above
         * l_modem().  CLOCAL makes sense but isn't supported.   Special
         * l_modem()s that ignore carrier drop make no sense for nmdms but
         * may be in use because other parts of the line discipline make
         * sense for nmdms.  Recover by doing everything that a normal
         * ttymodem() would have done except for sending a SIGHUP.
         */
        if (tp2->t_state & TS_ISOPEN) {
                tp2->t_state &= ~(TS_CARR_ON | TS_CONNECTED);
                tp2->t_state |= TS_ZOMBIE;
                ttyflush(tp2, FREAD | FWRITE);
        }

        err = (*linesw[tp->t_line].l_close)(tp, ap->a_fflag);
        ourpart->modemsignals &= ~TIOCM_DTR;
        nmdm_crossover(dev->si_drv1, ourpart, otherpart);
        nmdmstop(tp, FREAD|FWRITE);
        ttyclose(tp);
        lwkt_reltoken(&tp2->t_token);
        lwkt_reltoken(&tp->t_token);

        return (err);
}

static int
nmdmread(struct dev_read_args *ap)
{
        cdev_t dev = ap->a_head.a_dev;
        int error = 0;
        struct tty *tp;
#if 0
        struct tty *tp2;
        struct softpart *ourpart, *otherpart;
#endif

        tp = dev->si_tty;
        lwkt_gettoken(&tp->t_token);
#if 0
        GETPARTS(tp, ourpart, otherpart);
        tp2 = &otherpart->nm_tty;
        lwkt_gettoken(&tp2->t_token);

        if (tp2->t_state & TS_ISOPEN) {
                error = (*linesw[tp->t_line].l_read)(tp, ap->a_uio, flag);
                wakeup_other(tp, FWRITE);
        } else {
                if (flag & IO_NDELAY) {
                        lwkt_reltoken(&tp2->t_token);
                        lwkt_reltoken(&tp->t_token);
                        return (EWOULDBLOCK);
                }
                error = tsleep(TSA_PTC_READ(tp), PCATCH, "nmdout", 0);
                }
        }
        lwkt_reltoken(&tp2->t_token);
#else
        if ((error = (*linesw[tp->t_line].l_read)(tp, ap->a_uio, ap->a_ioflag)) == 0)
                wakeup_other(tp, FWRITE);
#endif
        lwkt_reltoken(&tp->t_token);

        return (error);
}

/*
 * Write to pseudo-tty.
 * Wakeups of controlling tty will happen
 * indirectly, when tty driver calls nmdmstart.
 */
static  int
nmdmwrite(struct dev_write_args *ap)
{
        cdev_t dev = ap->a_head.a_dev;
        struct uio *uio = ap->a_uio;
        u_char *cp = NULL;
        size_t cc = 0;
        u_char locbuf[BUFSIZ];
        int cnt = 0;
        int error = 0;
        struct tty *tp1, *tp;
        struct softpart *ourpart, *otherpart;

        tp1 = dev->si_tty;
        lwkt_gettoken(&tp1->t_token);
        /*
         * Get the other tty struct.
         * basically we are writing into the INPUT side of the other device.
         */
        GETPARTS(tp1, ourpart, otherpart);
        tp = &otherpart->nm_tty;
        lwkt_gettoken(&tp->t_token);

again:
        if ((tp->t_state & TS_ISOPEN) == 0) {
                lwkt_reltoken(&tp->t_token);
                lwkt_reltoken(&tp1->t_token);
                return (EIO);
        }
        while (uio->uio_resid > 0 || cc > 0) {
                /*
                 * Fill up the buffer if it's empty
                 */
                if (cc == 0) {
                        cc = szmin(uio->uio_resid, BUFSIZ);
                        cp = locbuf;
                        error = uiomove((caddr_t)cp, cc, uio);
                        if (error) {
                                lwkt_reltoken(&tp->t_token);
                                lwkt_reltoken(&tp1->t_token);
                                return (error);
                        }
                        /* check again for safety */
                        if ((tp->t_state & TS_ISOPEN) == 0) {
                                /* adjust for data copied in but not written */
                                uio->uio_resid += cc;
                                lwkt_reltoken(&tp->t_token);
                                lwkt_reltoken(&tp1->t_token);
                                return (EIO);
                        }
                }
                while (cc > 0) {
                        if (((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= (TTYHOG-2))
                        && ((tp->t_canq.c_cc > 0) || !(tp->t_iflag&ICANON))) {
                                /*
                                 * Come here to wait for space in outq,
                                 * or space in rawq, or an empty canq.
                                 */
                                wakeup(TSA_HUP_OR_INPUT(tp));
                                if ((tp->t_state & TS_CONNECTED) == 0) {
                                        /*
                                         * Data piled up because not connected.
                                         * Adjust for data copied in but
                                         * not written.
                                         */
                                        uio->uio_resid += cc;
                                        lwkt_reltoken(&tp->t_token);
                                        lwkt_reltoken(&tp1->t_token);
                                        return (EIO);
                                }
                                if (ap->a_ioflag & IO_NDELAY) {
                                        /*
                                         * Don't wait if asked not to.
                                         * Adjust for data copied in but
                                         * not written.
                                         */
                                        uio->uio_resid += cc;
                                        if (cnt == 0) {
                                                lwkt_reltoken(&tp->t_token);
                                                lwkt_reltoken(&tp1->t_token);
                                                return (EWOULDBLOCK);
                                        }
                                        lwkt_reltoken(&tp->t_token);
                                        lwkt_reltoken(&tp1->t_token);
                                        return (0);
                                }
                                error = tsleep(TSA_PTC_WRITE(tp),
                                                PCATCH, "nmdout", 0);
                                if (error) {
                                        /*
                                         * Tsleep returned (signal?).
                                         * Go find out what the user wants.
                                         * adjust for data copied in but
                                         * not written
                                         */
                                        uio->uio_resid += cc;
                                        lwkt_reltoken(&tp->t_token);
                                        lwkt_reltoken(&tp1->t_token);
                                        return (error);
                                }
                                goto again;
                        }
                        (*linesw[tp->t_line].l_rint)(*cp++, tp);
                        cnt++;
                        cc--;
                }
                cc = 0;
        }
        lwkt_reltoken(&tp->t_token);
        lwkt_reltoken(&tp1->t_token);

        return (0);
}

/*
 * Start output on pseudo-tty.
 * Wake up process selecting or sleeping for input from controlling tty.
 */
static void
nmdmstart(struct tty *tp)
{
        struct nm_softc *pti = tp->t_dev->si_drv1;

        lwkt_gettoken(&tp->t_token);
        if (tp->t_state & TS_TTSTOP) {
                lwkt_reltoken(&tp->t_token);
                return;
        }
        pti->pt_flags &= ~PF_STOPPED;
        wakeup_other(tp, FREAD);
        lwkt_reltoken(&tp->t_token);
}

/*
 * Wakes up the OTHER tty.  Caller must hold at least tp->t_token.
 */
static void
wakeup_other(struct tty *tp, int flag)
{
        struct softpart *ourpart, *otherpart;

        GETPARTS(tp, ourpart, otherpart);
        lwkt_gettoken(&otherpart->nm_tty.t_token);
        if (flag & FREAD) {
                wakeup(TSA_PTC_READ((&otherpart->nm_tty)));
                KNOTE(&otherpart->nm_tty.t_rkq.ki_note, 0);
        }
        if (flag & FWRITE) {
                wakeup(TSA_PTC_WRITE((&otherpart->nm_tty)));
                KNOTE(&otherpart->nm_tty.t_wkq.ki_note, 0);
        }
        lwkt_reltoken(&otherpart->nm_tty.t_token);
}

static  void
nmdmstop(struct tty *tp, int flush)
{
        struct nm_softc *pti = tp->t_dev->si_drv1;
        int flag;

        lwkt_gettoken(&tp->t_token);
        /* note: FLUSHREAD and FLUSHWRITE already ok */
        if (flush == 0) {
                flush = TIOCPKT_STOP;
                pti->pt_flags |= PF_STOPPED;
        } else
                pti->pt_flags &= ~PF_STOPPED;
        /* change of perspective */
        flag = 0;
        if (flush & FREAD)
                flag |= FWRITE;
        if (flush & FWRITE)
                flag |= FREAD;
        wakeup_other(tp, flag);
        lwkt_reltoken(&tp->t_token);
}

/*ARGSUSED*/
static  int
nmdmioctl(struct dev_ioctl_args *ap)
{
        cdev_t dev = ap->a_head.a_dev;
        struct tty *tp = dev->si_tty;
        struct tty *tp2;
        struct nm_softc *pti = dev->si_drv1;
        int error;
        struct softpart *ourpart, *otherpart;

        lwkt_gettoken(&tp->t_token);
        GETPARTS(tp, ourpart, otherpart);
        tp2 = &otherpart->nm_tty;
        lwkt_gettoken(&tp2->t_token);

        error = (*linesw[tp->t_line].l_ioctl)(tp, ap->a_cmd, ap->a_data,
                                              ap->a_fflag, ap->a_cred);
        if (error == ENOIOCTL)
                 error = ttioctl(tp, ap->a_cmd, ap->a_data, ap->a_fflag);
        if (error == ENOIOCTL) {
                switch (ap->a_cmd) {
                case TIOCSBRK:
                        otherpart->gotbreak = 1;
                        break;
                case TIOCCBRK:
                        break;
                case TIOCSDTR:
                        ourpart->modemsignals |= TIOCM_DTR;
                        break;
                case TIOCCDTR:
                        ourpart->modemsignals &= TIOCM_DTR;
                        break;
                case TIOCMSET:
                        ourpart->modemsignals = *(int *)ap->a_data;
                        otherpart->modemsignals = *(int *)ap->a_data;
                        break;
                case TIOCMBIS:
                        ourpart->modemsignals |= *(int *)ap->a_data;
                        break;
                case TIOCMBIC:
                        ourpart->modemsignals &= ~(*(int *)ap->a_data);
                        otherpart->modemsignals &= ~(*(int *)ap->a_data);
                        break;
                case TIOCMGET:
                        *(int *)ap->a_data = ourpart->modemsignals;
                        break;
                case TIOCMSDTRWAIT:
                        break;
                case TIOCMGDTRWAIT:
                        *(int *)ap->a_data = 0;
                        break;
                case TIOCTIMESTAMP:
                case TIOCDCDTIMESTAMP:
                default:
                        lwkt_reltoken(&tp2->t_token);
                        lwkt_reltoken(&tp->t_token);
                        error = ENOTTY;
                        return (error);
                }
                error = 0;
                nmdm_crossover(pti, ourpart, otherpart);
        }
        lwkt_reltoken(&tp2->t_token);
        lwkt_reltoken(&tp->t_token);

        return (error);
}

/*
 * Caller must hold both tty tokens
 */
static void
nmdm_crossover(struct nm_softc *pti,
                struct softpart *ourpart,
                struct softpart *otherpart)
{
        otherpart->modemsignals &= ~(TIOCM_CTS|TIOCM_CAR);
        if (ourpart->modemsignals & TIOCM_RTS)
                otherpart->modemsignals |= TIOCM_CTS;
        if (ourpart->modemsignals & TIOCM_DTR)
                otherpart->modemsignals |= TIOCM_CAR;
}



static void nmdm_drvinit (void *unused);

static void
nmdm_drvinit(void *unused)
{
        nmdminit(0);
}

SYSINIT(nmdmdev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE + CDEV_MAJOR, nmdm_drvinit,
    NULL);