Do it right this time. Give it a better name and place

CVSrepo deletion of the previous attempt will be requested:

--original message--
Add the 'virtual nulmodem driver'
Particularly useful for debuging kernels using vmware.

If your name is Bruce evans and you are a WIZ at tty interfaces,
then you should probably rip this to shreds and offer lots of suggestions and
patches. I've been using this since 4.0-CURRENT and it's never caused
problems but I'm sure I got something wrong. This is similar to the pty/cty
device driver except that both sides are ttys. Even minor numbers
are side A and odd minor numbers are side B.
Work needs to be done regarding what happens to the other side when you
close a node.

to use with vmware, configure vmware to redirect COM2 out to side A of one
of these and boot a kernel with teh gdb remote port set to sio1.
AFTER dropping into the gdb kernel debugger in your test kernel,
fire up gdb with it's remote port pointing at the appropriate side B.

To catch all console output, you can boot the vmware kernel with a serial
console, (COM1) similarly redirected to a nulmodem, and use 'tip' to observe it.

This is practically unaltered since pre 4.0 days except for
changes made along the way needed to make it compile, so any suggestions
or offers of total rewrites will be listenned to :-)
This commit is contained in:
Julian Elischer 2001-02-27 16:39:54 +00:00
parent 0a91bb5c52
commit 737a128622
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=73161

644
sys/dev/nmdm/nmdm.c Normal file
View File

@ -0,0 +1,644 @@
/*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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$
*/
/*
* Pseudo-nulmodem Driver
*/
#include "opt_compat.h"
#include <sys/param.h>
#include <sys/systm.h>
#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
#include <sys/ioctl_compat.h>
#endif
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/poll.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 __P((struct tty *tp));
static void nmdmstop __P((struct tty *tp, int rw));
static void wakeup_other __P((struct tty *tp, int flag));
static void nmdminit __P((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 cdevsw nmdm_cdevsw = {
/* open */ nmdmopen,
/* close */ nmdmclose,
/* read */ nmdmread,
/* write */ nmdmwrite,
/* ioctl */ nmdmioctl,
/* poll */ ttypoll,
/* mmap */ nommap,
/* strategy */ nostrategy,
/* name */ "pts",
/* maj */ CDEV_MAJOR,
/* dump */ nodump,
/* psize */ nopsize,
/* flags */ D_TTY,
/* bmaj */ -1
};
#define BUFSIZ 100 /* Chunk size iomoved to/from user */
struct softpart {
struct tty nm_tty;
dev_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(n)
int n;
{
dev_t dev1, dev2;
struct nm_softc *pt;
/* For now we only map the lower 8 bits of the minor */
if (n & ~0xff)
return;
pt = malloc(sizeof(*pt), M_NLMDM, M_WAITOK);
bzero(pt, sizeof(*pt));
pt->part1.dev = dev1 = make_dev(&nmdm_cdevsw, n+n,
0, 0, 0666, "nmdm%dA", n);
pt->part2.dev = dev2 = make_dev(&nmdm_cdevsw, n+n+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;
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(dev, flag, devtype, p)
dev_t dev;
int flag, devtype;
struct proc *p;
{
register struct tty *tp, *tp2;
int error;
int minr;
dev_t nextdev;
struct nm_softc *pti;
int is_b;
int pair;
struct softpart *ourpart, *otherpart;
/*
* XXX: Gross hack for DEVFS:
* If we openned this device, ensure we have the
* next one too, so people can open it.
*/
minr = dev2unit(dev);
pair = minr >> 1;
is_b = minr & 1;
if (pair < 127) {
nextdev = makedev(major(dev), (pair+pair) + 1);
if (!nextdev->si_drv1) {
nmdminit(pair + 1);
}
}
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;
GETPARTS(tp, ourpart, otherpart);
tp2 = &otherpart->nm_tty;
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 && suser(p)) {
return (EBUSY);
} else if (pti->pt_prison != p->p_prison) {
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), TTIPRI | PCATCH,
"nmdopn", 0);
if (error)
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 */
return (error);
}
static int
nmdmclose(dev, flag, mode, p)
dev_t dev;
int flag, mode;
struct proc *p;
{
register struct tty *tp, *tp2;
int err;
struct softpart *ourpart, *otherpart;
/*
* let the other end know that the game is up
*/
tp = dev->si_tty;
GETPARTS(tp, ourpart, otherpart);
tp2 = &otherpart->nm_tty;
(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, flag);
ourpart->modemsignals &= ~TIOCM_DTR;
nmdm_crossover(dev->si_drv1, ourpart, otherpart);
nmdmstop(tp, FREAD|FWRITE);
(void) ttyclose(tp);
return (err);
}
static int
nmdmread(dev, uio, flag)
dev_t dev;
struct uio *uio;
int flag;
{
int error = 0;
struct tty *tp, *tp2;
struct softpart *ourpart, *otherpart;
tp = dev->si_tty;
GETPARTS(tp, ourpart, otherpart);
tp2 = &otherpart->nm_tty;
#if 0
if (tp2->t_state & TS_ISOPEN) {
error = (*linesw[tp->t_line].l_read)(tp, uio, flag);
wakeup_other(tp, FWRITE);
} else {
if (flag & IO_NDELAY) {
return (EWOULDBLOCK);
}
error = tsleep(TSA_PTC_READ(tp),
TTIPRI | PCATCH, "nmdout", 0);
}
}
#else
if ((error = (*linesw[tp->t_line].l_read)(tp, uio, flag)) == 0)
wakeup_other(tp, FWRITE);
#endif
return (error);
}
/*
* Write to pseudo-tty.
* Wakeups of controlling tty will happen
* indirectly, when tty driver calls nmdmstart.
*/
static int
nmdmwrite(dev, uio, flag)
dev_t dev;
struct uio *uio;
int flag;
{
register u_char *cp = 0;
register int cc = 0;
u_char locbuf[BUFSIZ];
int cnt = 0;
int error = 0;
struct tty *tp1, *tp;
struct softpart *ourpart, *otherpart;
tp1 = dev->si_tty;
/*
* 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;
again:
if ((tp->t_state & TS_ISOPEN) == 0)
return (EIO);
while (uio->uio_resid > 0 || cc > 0) {
/*
* Fill up the buffer if it's empty
*/
if (cc == 0) {
cc = min(uio->uio_resid, BUFSIZ);
cp = locbuf;
error = uiomove((caddr_t)cp, cc, uio);
if (error)
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;
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;
return (EIO);
}
if (flag & IO_NDELAY) {
/*
* Don't wait if asked not to.
* Adjust for data copied in but
* not written.
*/
uio->uio_resid += cc;
if (cnt == 0)
return (EWOULDBLOCK);
return (0);
}
error = tsleep(TSA_PTC_WRITE(tp),
TTOPRI | 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;
return (error);
}
goto again;
}
(*linesw[tp->t_line].l_rint)(*cp++, tp);
cnt++;
cc--;
}
cc = 0;
}
return (0);
}
/*
* Start output on pseudo-tty.
* Wake up process selecting or sleeping for input from controlling tty.
*/
static void
nmdmstart(tp)
struct tty *tp;
{
register struct nm_softc *pti = tp->t_dev->si_drv1;
if (tp->t_state & TS_TTSTOP)
return;
pti->pt_flags &= ~PF_STOPPED;
wakeup_other(tp, FREAD);
}
/* Wakes up the OTHER tty;*/
static void
wakeup_other(tp, flag)
struct tty *tp;
int flag;
{
struct softpart *ourpart, *otherpart;
GETPARTS(tp, ourpart, otherpart);
if (flag & FREAD) {
selwakeup(&otherpart->nm_tty.t_rsel);
wakeup(TSA_PTC_READ((&otherpart->nm_tty)));
}
if (flag & FWRITE) {
selwakeup(&otherpart->nm_tty.t_wsel);
wakeup(TSA_PTC_WRITE((&otherpart->nm_tty)));
}
}
static void
nmdmstop(tp, flush)
register struct tty *tp;
int flush;
{
struct nm_softc *pti = tp->t_dev->si_drv1;
int flag;
/* 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);
}
static int
nmdmpoll(dev, events, p)
dev_t dev;
int events;
struct proc *p;
{
register struct tty *tp = dev->si_tty;
register struct tty *tp2;
int revents = 0;
int s;
struct softpart *ourpart, *otherpart;
GETPARTS(tp, ourpart, otherpart);
tp2 = &otherpart->nm_tty;
if ((tp->t_state & TS_CONNECTED) == 0)
return (seltrue(dev, events, p) | POLLHUP);
/*
* Need to block timeouts (ttrstart).
*/
s = spltty();
/*
* First check if there is something to report immediatly.
*/
if ((events & (POLLIN | POLLRDNORM))) {
if (tp->t_iflag & ICANON) {
if (tp->t_canq.c_cc)
revents |= events & (POLLIN | POLLRDNORM);
} else {
if (tp->t_rawq.c_cc + tp->t_canq.c_cc)
revents |= events & (POLLIN | POLLRDNORM);
}
}
/*
* check if there is room in the other tty's input buffers.
*/
if ((events & (POLLOUT | POLLWRNORM))
&& ((tp2->t_rawq.c_cc + tp2->t_canq.c_cc < TTYHOG - 2)
|| (tp2->t_canq.c_cc == 0 && (tp2->t_iflag & ICANON)))) {
revents |= events & (POLLOUT | POLLWRNORM);
}
if (events & POLLHUP)
if ((tp->t_state & TS_CARR_ON) == 0)
revents |= POLLHUP;
/*
* If nothing immediate, set us to return when something IS found.
*/
if (revents == 0) {
if (events & (POLLIN | POLLRDNORM))
selrecord(p, &tp->t_rsel);
if (events & (POLLOUT | POLLWRNORM))
selrecord(p, &tp->t_wsel);
}
splx(s);
return (revents);
}
/*ARGSUSED*/
static int
nmdmioctl(dev, cmd, data, flag, p)
dev_t dev;
u_long cmd;
caddr_t data;
int flag;
struct proc *p;
{
register struct tty *tp = dev->si_tty;
struct nm_softc *pti = dev->si_drv1;
int error, s;
register struct tty *tp2;
struct softpart *ourpart, *otherpart;
s = spltty();
GETPARTS(tp, ourpart, otherpart);
tp2 = &otherpart->nm_tty;
error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
if (error == ENOIOCTL)
error = ttioctl(tp, cmd, data, flag);
if (error == ENOIOCTL) {
switch (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 *)data;
otherpart->modemsignals = *(int *)data;
break;
case TIOCMBIS:
ourpart->modemsignals |= *(int *)data;
break;
case TIOCMBIC:
ourpart->modemsignals &= ~(*(int *)data);
otherpart->modemsignals &= ~(*(int *)data);
break;
case TIOCMGET:
*(int *)data = ourpart->modemsignals;
break;
case TIOCMSDTRWAIT:
break;
case TIOCMGDTRWAIT:
*(int *)data = 0;
break;
case TIOCTIMESTAMP:
case TIOCDCDTIMESTAMP:
default:
splx(s);
error = ENOTTY;
return (error);
}
error = 0;
nmdm_crossover(pti, ourpart, otherpart);
}
splx(s);
return (error);
}
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 __P((void *unused));
static void
nmdm_drvinit(unused)
void *unused;
{
cdevsw_add(&nmdm_cdevsw);
/* XXX: Gross hack for DEVFS */
nmdminit(0);
}
SYSINIT(nmdmdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,nmdm_drvinit,NULL)