summaryrefslogtreecommitdiffstats
path: root/sys/dev/nmdm/nmdm.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/nmdm/nmdm.c')
-rw-r--r--sys/dev/nmdm/nmdm.c142
1 files changed, 128 insertions, 14 deletions
diff --git a/sys/dev/nmdm/nmdm.c b/sys/dev/nmdm/nmdm.c
index ccb3467..cd3b5f3 100644
--- a/sys/dev/nmdm/nmdm.c
+++ b/sys/dev/nmdm/nmdm.c
@@ -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 d_close_t nmdmclose;
static t_modem_t nmdmmodem;
-
static d_open_t nmdmopen;
-static d_close_t nmdmclose;
+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 {
@@ -141,6 +150,28 @@ nmdm_clone(void *arg, char *name, int nameen, struct cdev **dev)
}
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)
{
struct tty *tp, *otp;
@@ -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;
@@ -257,6 +296,84 @@ nmdmopen(struct cdev *dev, int flag, int devtype, struct thread *td)
}
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)
{
struct softpart *sp;
@@ -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;
OpenPOWER on IntegriCloud