If CDSR_OFLOW (stty dsrflow) is enabled on one or both sides of a

null-modem tty device emulate the speed settings faithfully.

The speed is emulated independently for the two directions, using
the slower of the local sides ispeed and the remote sides ospeed.

The emulated speed takes settings of bits/char, parity and stopbit
into account.

Inspired by:	The BSD-DK Editor Celebrity Deathmatch Contest
This commit is contained in:
Poul-Henning Kamp 2005-01-26 23:42:18 +00:00
parent cf9a9fe79d
commit 017a4322b5
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=140878

View File

@ -55,13 +55,15 @@ __FBSDID("$FreeBSD$");
MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures");
static void nmdmstart(struct tty *tp);
static void nmdmstop(struct tty *tp, int rw);
static void nmdminit(struct cdev *dev);
static t_modem_t nmdmmodem;
static d_open_t nmdmopen;
static d_close_t nmdmclose;
static t_modem_t nmdmmodem;
static d_open_t nmdmopen;
static t_oproc_t nmdmoproc;
static t_param_t nmdmparam;
static t_stop_t nmdmstop;
static void nmdminit(struct cdev *dev);
static struct cdevsw nmdm_cdevsw = {
.d_version = D_VERSION,
@ -82,6 +84,13 @@ struct softpart {
int nm_dcd;
struct task pt_task;
struct softpart *other;
struct callout co;
u_long quota;
u_long accumulator;
int rate;
int credits;
#define QS 8 /* Quota shift */
};
struct nm_softc {
@ -140,6 +149,28 @@ nmdm_clone(void *arg, char *name, int nameen, struct cdev **dev)
*dev = d1;
}
static void
nmdm_timeout(void *arg)
{
struct softpart *sp;
sp = arg;
if (sp->rate == 0)
return;
/*
* Do a simple Floyd-Steinberg dither here to avoid FP math.
* Wipe out unused quota from last tick.
*/
sp->accumulator += sp->credits;
sp->quota = sp->accumulator >> QS;
sp->accumulator &= ((1 << QS) - 1);
taskqueue_enqueue(taskqueue_swi_giant, &sp->pt_task);
callout_reset(&sp->co, sp->rate, nmdm_timeout, arg);
}
static void
nmdm_task_tty(void *arg, int pending __unused)
{
@ -166,14 +197,18 @@ nmdm_task_tty(void *arg, int pending __unused)
if (tp->t_state & TS_TTSTOP)
return;
while (tp->t_outq.c_cc != 0) {
if (sp->rate && !sp->quota)
return;
if (otp->t_state & TS_TBLOCK)
return;
sp->quota--;
c = getc(&tp->t_outq);
if (otp->t_state & TS_ISOPEN)
ttyld_rint(otp, c);
}
if (tp->t_outq.c_cc == 0)
ttwwakeup(tp);
}
/*
@ -199,20 +234,24 @@ nmdminit(struct cdev *dev1)
pt->part2.dev = dev2;
pt->part1.nm_tty = ttymalloc(pt->part1.nm_tty);
pt->part1.nm_tty->t_oproc = nmdmstart;
pt->part1.nm_tty->t_oproc = nmdmoproc;
pt->part1.nm_tty->t_stop = nmdmstop;
pt->part1.nm_tty->t_modem = nmdmmodem;
pt->part1.nm_tty->t_param = nmdmparam;
pt->part1.nm_tty->t_dev = dev1;
pt->part1.nm_tty->t_sc = &pt->part1;
TASK_INIT(&pt->part1.pt_task, 0, nmdm_task_tty, pt->part1.nm_tty);
callout_init(&pt->part1.co, 0);
pt->part2.nm_tty = ttymalloc(pt->part2.nm_tty);
pt->part2.nm_tty->t_oproc = nmdmstart;
pt->part2.nm_tty->t_oproc = nmdmoproc;
pt->part2.nm_tty->t_stop = nmdmstop;
pt->part2.nm_tty->t_modem = nmdmmodem;
pt->part2.nm_tty->t_param = nmdmparam;
pt->part2.nm_tty->t_dev = dev2;
pt->part2.nm_tty->t_sc = &pt->part2;
TASK_INIT(&pt->part2.pt_task, 0, nmdm_task_tty, pt->part2.nm_tty);
callout_init(&pt->part2.co, 0);
pt->part1.other = &pt->part2;
pt->part2.other = &pt->part1;
@ -256,6 +295,84 @@ nmdmopen(struct cdev *dev, int flag, int devtype, struct thread *td)
return (error);
}
static int
bits_per_char(struct termios *t)
{
int bits;
bits = 1; /* start bit */
switch (t->c_cflag & CSIZE) {
case CS5: bits += 5; break;
case CS6: bits += 6; break;
case CS7: bits += 7; break;
case CS8: bits += 8; break;
}
bits++; /* stop bit */
if (t->c_cflag & PARENB)
bits++;
if (t->c_cflag & CSTOPB)
bits++;
return (bits);
}
static int
nmdmparam(struct tty *tp, struct termios *t)
{
struct softpart *sp;
struct tty *tp2;
int bpc, rate, speed, i;
sp = tp->t_sc;
tp2 = sp->other->nm_tty;
if (!((t->c_cflag | tp2->t_cflag) & CDSR_OFLOW)) {
sp->rate = 0;
sp->other->rate = 0;
return (0);
}
/*
* DSRFLOW one either side enables rate-simulation for both
* directions.
* NB: the two directions may run at different rates.
*/
/* Find the larger of the number of bits transmitted */
bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios));
for (i = 0; i < 2; i++) {
/* Use the slower of our receive and their transmit rate */
speed = imin(tp2->t_ospeed, t->c_ispeed);
if (speed == 0) {
sp->rate = 0;
sp->other->rate = 0;
return (0);
}
speed <<= QS; /* [bit/sec, scaled] */
speed /= bpc; /* [char/sec, scaled] */
rate = (hz << QS) / speed; /* [hz per callout] */
if (rate == 0)
rate = 1;
speed *= rate;
speed /= hz; /* [(char/sec)/tick, scaled */
sp->credits = speed;
sp->rate = rate;
callout_reset(&sp->co, rate, nmdm_timeout, sp);
/*
* swap pointers for second pass so the other end gets
* updated as well.
*/
sp = sp->other;
t = &tp2->t_termios;
tp2 = tp;
}
return (0);
}
static int
nmdmmodem(struct tty *tp, int sigon, int sigoff)
{
@ -264,14 +381,11 @@ nmdmmodem(struct tty *tp, int sigon, int sigoff)
sp = tp->t_sc;
if (sigon || sigoff) {
if (sigon & SER_DTR) {
if (sigon & SER_DTR)
sp->other->nm_dcd = 1;
ttyld_modem(sp->other->nm_tty, sp->other->nm_dcd);
}
if (sigoff & SER_DTR) {
if (sigoff & SER_DTR)
sp->other->nm_dcd = 0;
ttyld_modem(sp->other->nm_tty, sp->other->nm_dcd);
}
ttyld_modem(sp->other->nm_tty, sp->other->nm_dcd);
return (0);
} else {
i = 0;
@ -291,7 +405,7 @@ nmdmclose(struct cdev *dev, int flag, int mode, struct thread *td)
}
static void
nmdmstart(struct tty *tp)
nmdmoproc(struct tty *tp)
{
struct softpart *pt;