summaryrefslogtreecommitdiffstats
path: root/sys/dev/digi/digi.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/digi/digi.c')
-rw-r--r--sys/dev/digi/digi.c1963
1 files changed, 1963 insertions, 0 deletions
diff --git a/sys/dev/digi/digi.c b/sys/dev/digi/digi.c
new file mode 100644
index 0000000..57f6022
--- /dev/null
+++ b/sys/dev/digi/digi.c
@@ -0,0 +1,1963 @@
+/*-
+ * Copyright (c) 2001 Brian Somers <brian@Awfulhak.org>
+ * based on work by Slawa Olhovchenkov
+ * John Prince <johnp@knight-trosoft.com>
+ * Eric Hernes
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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$
+ */
+
+/*-
+ * TODO:
+ * Figure out what the con bios stuff is supposed to do
+ * Test with *LOTS* more cards - I only have a PCI8r and an ISA Xem.
+ */
+
+#include "opt_compat.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/proc.h>
+#include <sys/conf.h>
+#include <sys/linker.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/malloc.h>
+#include <sys/tty.h>
+#include <sys/syslog.h>
+#include <sys/fcntl.h>
+#include <sys/bus.h>
+#include <sys/bus.h>
+#include <machine/resource.h>
+
+#include <sys/digiio.h>
+#include <dev/digi/digireg.h>
+#include <dev/digi/digi.h>
+#include <dev/digi/digi_mod.h>
+#include <dev/digi/digi_pci.h>
+
+#define CDEV_MAJOR 162
+
+#define CTRL_DEV 0x800000
+#define CALLOUT_MASK 0x400000
+#define CONTROL_INIT_STATE 0x100000
+#define CONTROL_LOCK_STATE 0x200000
+#define CONTROL_MASK (CTRL_DEV|CONTROL_INIT_STATE|CONTROL_LOCK_STATE)
+#define UNIT_MASK 0x030000
+#define PORT_MASK 0x0000FF
+#define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev)))
+#define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK)
+#define MINOR_TO_UNIT(mynor) (((mynor) & UNIT_MASK)>>16)
+#define MINOR_TO_PORT(mynor) ((mynor) & PORT_MASK)
+
+static d_open_t digiopen;
+static d_close_t digiclose;
+static d_read_t digiread;
+static d_write_t digiwrite;
+static d_ioctl_t digiioctl;
+
+static void digistop(struct tty *tp, int rw);
+static int digimctl(struct digi_p *port, int bits, int how);
+static void digi_poll(void *ptr);
+static void digi_freemoduledata(struct digi_softc *);
+static void fepcmd(struct digi_p *port, int cmd, int op, int ncmds);
+static void digistart(struct tty *tp);
+static int digiparam(struct tty *tp, struct termios *t);
+static void digihardclose(struct digi_p *port);
+static void digi_intr(void *);
+static int digi_init(struct digi_softc *_sc);
+static int digi_loadmoduledata(struct digi_softc *);
+static int digi_inuse(struct digi_softc *);
+static void digi_free_state(struct digi_softc *);
+
+#define fepcmd_b(port, cmd, op1, op2, ncmds) \
+ fepcmd(port, cmd, (op2 << 8) | op1, ncmds)
+#define fepcmd_w fepcmd
+
+
+static speed_t digidefaultrate = TTYDEF_SPEED;
+
+struct con_bios {
+ struct con_bios *next;
+ u_char *bios;
+ size_t size;
+};
+
+static struct con_bios *con_bios_list;
+devclass_t digi_devclass;
+static char driver_name[] = "digi";
+unsigned digi_debug = 0;
+
+static struct speedtab digispeedtab[] = {
+ { 0, 0}, /* old (sysV-like) Bx codes */
+ { 50, 1},
+ { 75, 2},
+ { 110, 3},
+ { 134, 4},
+ { 150, 5},
+ { 200, 6},
+ { 300, 7},
+ { 600, 8},
+ { 1200, 9},
+ { 1800, 10},
+ { 2400, 11},
+ { 4800, 12},
+ { 9600, 13},
+ { 19200, 14},
+ { 38400, 15},
+ { 57600, (02000 | 1)},
+ { 76800, (02000 | 2)},
+ { 115200, (02000 | 3)},
+ { 230400, (02000 | 6)},
+ { -1, -1}
+};
+
+const struct digi_control_signals digi_xixe_signals = {
+ 0x02, 0x08, 0x10, 0x20, 0x40, 0x80
+};
+
+const struct digi_control_signals digi_normal_signals = {
+ 0x02, 0x80, 0x20, 0x10, 0x40, 0x01
+};
+
+static struct cdevsw digi_sw = {
+ digiopen, /* open */
+ digiclose, /* close */
+ digiread, /* read */
+ digiwrite, /* write */
+ digiioctl, /* ioctl */
+ ttypoll, /* poll */
+ nommap, /* mmap */
+ nostrategy, /* strategy */
+ driver_name, /* name */
+ CDEV_MAJOR, /* maj */
+ nodump, /* dump */
+ nopsize, /* psize */
+ D_TTY | D_KQFILTER, /* flags */
+ ttykqfilter /* bmaj */
+};
+
+static void
+digi_poll(void *ptr)
+{
+ struct digi_softc *sc;
+
+ sc = (struct digi_softc *)ptr;
+ callout_handle_init(&sc->callout);
+ digi_intr(sc);
+ sc->callout = timeout(digi_poll, sc, (hz >= 200) ? hz / 100 : 1);
+}
+
+static void
+digi_int_test(void *v)
+{
+ struct digi_softc *sc = v;
+
+ callout_handle_init(&sc->inttest);
+#ifdef DIGI_INTERRUPT
+ if (sc->intr_timestamp.tv_sec || sc->intr_timestamp.tv_usec) {
+ /* interrupt OK! */
+ return;
+ }
+ log(LOG_ERR, "digi%d: Interrupt didn't work, use polled mode\n", unit);
+#endif
+ sc->callout = timeout(digi_poll, sc, (hz >= 200) ? hz / 100 : 1);
+}
+
+static void
+digi_freemoduledata(struct digi_softc *sc)
+{
+ if (sc->fep.data != NULL) {
+ free(sc->fep.data, M_TTYS);
+ sc->fep.data = NULL;
+ }
+ if (sc->link.data != NULL) {
+ free(sc->link.data, M_TTYS);
+ sc->link.data = NULL;
+ }
+ if (sc->bios.data != NULL) {
+ free(sc->bios.data, M_TTYS);
+ sc->bios.data = NULL;
+ }
+}
+
+static int
+digi_bcopy(const void *vfrom, void *vto, size_t sz)
+{
+ volatile const char *from = (volatile const char *)vfrom;
+ volatile char *to = (volatile char *)vto;
+ size_t i;
+
+ for (i = 0; i < sz; i++)
+ *to++ = *from++;
+
+ from = (const volatile char *)vfrom;
+ to = (volatile char *)vto;
+ for (i = 0; i < sz; i++)
+ if (*to++ != *from++)
+ return (0);
+ return (1);
+}
+
+void
+digi_delay(struct digi_softc *sc, const char *txt, u_long timo)
+{
+ if (cold)
+ DELAY(timo * 1000000 / hz);
+ else
+ tsleep(sc, PUSER | PCATCH, txt, timo);
+}
+
+static int
+digi_init(struct digi_softc *sc)
+{
+ int i, cnt, resp;
+ u_char *ptr;
+ int lowwater;
+ struct digi_p *port;
+ volatile struct board_chan *bc;
+
+ ptr = NULL;
+
+ if (sc->status == DIGI_STATUS_DISABLED) {
+ log(LOG_ERR, "digi%d: Cannot init a disabled card\n",
+ sc->res.unit);
+ return (EIO);
+ }
+ if (sc->bios.data == NULL) {
+ log(LOG_ERR, "digi%d: Cannot init without BIOS\n",
+ sc->res.unit);
+ return (EIO);
+ }
+#if 0
+ if (sc->link.data == NULL && sc->model >= PCCX) {
+ log(LOG_ERR, "digi%d: Cannot init without link info\n",
+ sc->res.unit);
+ return (EIO);
+ }
+#endif
+ if (sc->fep.data == NULL) {
+ log(LOG_ERR, "digi%d: Cannot init without fep code\n",
+ sc->res.unit);
+ return (EIO);
+ }
+ sc->status = DIGI_STATUS_NOTINIT;
+
+ if (sc->numports) {
+ /*
+ * We're re-initialising - maybe because someone's attached
+ * another port module. For now, we just re-initialise
+ * everything.
+ */
+ if (digi_inuse(sc))
+ return (EBUSY);
+
+ digi_free_state(sc);
+ }
+
+ ptr = sc->setwin(sc, MISCGLOBAL);
+ for (i = 0; i < 16; i += 2)
+ vW(ptr + i) = 0;
+
+ switch (sc->model) {
+ case PCXEVE:
+ outb(sc->wport, 0xff); /* window 7 */
+ ptr = sc->vmem + (BIOSCODE & 0x1fff);
+
+ if (!digi_bcopy(sc->bios.data, ptr, sc->bios.size)) {
+ device_printf(sc->dev, "BIOS upload failed\n");
+ return (EIO);
+ }
+
+ outb(sc->port, FEPCLR);
+ break;
+
+ case PCXE:
+ case PCXI:
+ case PCCX:
+ ptr = sc->setwin(sc, BIOSCODE + ((0xf000 - sc->mem_seg) << 4));
+ if (!digi_bcopy(sc->bios.data, ptr, sc->bios.size)) {
+ device_printf(sc->dev, "BIOS upload failed\n");
+ return (EIO);
+ }
+ break;
+
+ case PCXEM:
+ case PCIEPCX:
+ case PCIXR:
+ if (sc->pcibus)
+ PCIPORT = FEPRST;
+ else
+ outb(sc->port, FEPRST | FEPMEM);
+
+ for (i = 0; ((sc->pcibus ? PCIPORT : inb(sc->port)) &
+ FEPMASK) != FEPRST; i++) {
+ if (i > hz) {
+ log(LOG_ERR, "digi%d: %s init reset failed\n",
+ sc->res.unit, sc->name);
+ return (EIO);
+ }
+ digi_delay(sc, "digiinit0", 5);
+ }
+ DLOG(DIGIDB_INIT, (sc->dev, "Got init reset after %d us\n", i));
+
+ /* Now upload the BIOS */
+ cnt = (sc->bios.size < sc->win_size - BIOSOFFSET) ?
+ sc->bios.size : sc->win_size - BIOSOFFSET;
+
+ ptr = sc->setwin(sc, BIOSOFFSET);
+ if (!digi_bcopy(sc->bios.data, ptr, cnt)) {
+ device_printf(sc->dev, "BIOS upload (1) failed\n");
+ return (EIO);
+ }
+
+ if (cnt != sc->bios.size) {
+ /* and the second part */
+ ptr = sc->setwin(sc, sc->win_size);
+ if (!digi_bcopy(sc->bios.data + cnt, ptr,
+ sc->bios.size - cnt)) {
+ device_printf(sc->dev, "BIOS upload failed\n");
+ return (EIO);
+ }
+ }
+
+ ptr = sc->setwin(sc, 0);
+ vW(ptr + 0) = 0x0401;
+ vW(ptr + 2) = 0x0bf0;
+ vW(ptr + 4) = 0x0000;
+ vW(ptr + 6) = 0x0000;
+
+ break;
+ }
+
+ DLOG(DIGIDB_INIT, (sc->dev, "BIOS uploaded\n"));
+
+ ptr = sc->setwin(sc, MISCGLOBAL);
+ W(ptr) = 0;
+
+ if (sc->pcibus) {
+ PCIPORT = FEPCLR;
+ resp = FEPRST;
+ } else if (sc->model == PCXEVE) {
+ outb(sc->port, FEPCLR);
+ resp = FEPRST;
+ } else {
+ outb(sc->port, FEPCLR | FEPMEM);
+ resp = FEPRST | FEPMEM;
+ }
+
+ for (i = 0; ((sc->pcibus ? PCIPORT : inb(sc->port)) & FEPMASK)
+ == resp; i++) {
+ if (i > hz) {
+ log(LOG_ERR, "digi%d: BIOS start failed\n",
+ sc->res.unit);
+ return (EIO);
+ }
+ digi_delay(sc, "digibios0", 5);
+ }
+
+ DLOG(DIGIDB_INIT, (sc->dev, "BIOS started after %d us\n", i));
+
+ for (i = 0; vW(ptr) != *(u_short *)"GD"; i++) {
+ if (i > 2*hz) {
+ log(LOG_ERR, "digi%d: BIOS boot failed "
+ "(0x%02x != 0x%02x)\n",
+ sc->res.unit, vW(ptr), *(u_short *)"GD");
+ return (EIO);
+ }
+ digi_delay(sc, "digibios1", 5);
+ }
+
+ DLOG(DIGIDB_INIT, (sc->dev, "BIOS booted after %d iterations\n", i));
+
+ if (sc->link.data != NULL) {
+ DLOG(DIGIDB_INIT, (sc->dev, "Loading link data\n"));
+ ptr = sc->setwin(sc, 0xcd0);
+ digi_bcopy(sc->link.data, ptr, 21); /* XXX 21 ? */
+ }
+
+ /* load FEP/OS */
+
+ switch (sc->model) {
+ case PCXE:
+ case PCXEVE:
+ case PCXI:
+ ptr = sc->setwin(sc, sc->model == PCXI ? 0x2000 : 0x0);
+ digi_bcopy(sc->fep.data, ptr, sc->fep.size);
+
+ /* A BIOS request to move our data to 0x2000 */
+ ptr = sc->setwin(sc, MBOX);
+ vW(ptr + 0) = 2;
+ vW(ptr + 2) = sc->mem_seg + FEPCODESEG;
+ vW(ptr + 4) = 0;
+ vW(ptr + 6) = FEPCODESEG;
+ vW(ptr + 8) = 0;
+ vW(ptr + 10) = sc->fep.size;
+
+ /* Run the BIOS request */
+ outb(sc->port, FEPREQ | FEPMEM);
+ outb(sc->port, FEPCLR | FEPMEM);
+
+ for (i = 0; W(ptr); i++) {
+ if (i > hz) {
+ log(LOG_ERR, "digi%d: FEP/OS move failed\n",
+ sc->res.unit);
+ sc->hidewin(sc);
+ return (EIO);
+ }
+ digi_delay(sc, "digifep0", 5);
+ }
+ DLOG(DIGIDB_INIT,
+ (sc->dev, "FEP/OS moved after %d iterations\n", i));
+
+ /* Clear the confirm word */
+ ptr = sc->setwin(sc, FEPSTAT);
+ vW(ptr + 0) = 0;
+
+ /* A BIOS request to execute the FEP/OS */
+ ptr = sc->setwin(sc, MBOX);
+ vW(ptr + 0) = 0x01;
+ vW(ptr + 2) = FEPCODESEG;
+ vW(ptr + 4) = 0x04;
+
+ /* Run the BIOS request */
+ outb(sc->port, FEPREQ);
+ outb(sc->port, FEPCLR);
+
+ ptr = sc->setwin(sc, FEPSTAT);
+
+ break;
+
+ case PCXEM:
+ case PCIEPCX:
+ case PCIXR:
+ DLOG(DIGIDB_INIT, (sc->dev, "Loading FEP/OS\n"));
+
+ cnt = (sc->fep.size < sc->win_size - BIOSOFFSET) ?
+ sc->fep.size : sc->win_size - BIOSOFFSET;
+
+ ptr = sc->setwin(sc, BIOSOFFSET);
+ digi_bcopy(sc->fep.data, ptr, cnt);
+
+ if (cnt != sc->fep.size) {
+ ptr = sc->setwin(sc, BIOSOFFSET + cnt);
+ digi_bcopy(sc->fep.data + cnt, ptr,
+ sc->fep.size - cnt);
+ }
+
+ DLOG(DIGIDB_INIT, (sc->dev, "FEP/OS loaded\n"));
+
+ ptr = sc->setwin(sc, 0xc30);
+ W(ptr + 4) = 0x1004;
+ W(ptr + 6) = 0xbfc0;
+ W(ptr + 0) = 0x03;
+ W(ptr + 2) = 0x00;
+
+ /* Clear the confirm word */
+ ptr = sc->setwin(sc, FEPSTAT);
+ W(ptr + 0) = 0;
+
+ if (sc->port)
+ outb(sc->port, 0); /* XXX necessary ? */
+
+ break;
+
+ case PCCX:
+ ptr = sc->setwin(sc, 0xd000);
+ digi_bcopy(sc->fep.data, ptr, sc->fep.size);
+
+ /* A BIOS request to execute the FEP/OS */
+ ptr = sc->setwin(sc, 0xc40);
+ W(ptr + 0) = 1;
+ W(ptr + 2) = FEPCODE >> 4;
+ W(ptr + 4) = 4;
+
+ /* Clear the confirm word */
+ ptr = sc->setwin(sc, FEPSTAT);
+ W(ptr + 0) = 0;
+
+ /* Run the BIOS request */
+ outb(sc->port, FEPREQ | FEPMEM); /* send interrupt to BIOS */
+ outb(sc->port, FEPCLR | FEPMEM);
+ break;
+ }
+
+ /* Now wait 'till the FEP/OS has booted */
+ for (i = 0; vW(ptr) != *(u_short *)"OS"; i++) {
+ if (i > 2*hz) {
+ log(LOG_ERR, "digi%d: FEP/OS start failed "
+ "(0x%02x != 0x%02x)\n",
+ sc->res.unit, vW(ptr), *(u_short *)"OS");
+ sc->hidewin(sc);
+ return (EIO);
+ }
+ digi_delay(sc, "digifep1", 5);
+ }
+
+ DLOG(DIGIDB_INIT, (sc->dev, "FEP/OS started after %d iterations\n", i));
+
+ if (sc->model >= PCXEM) {
+ ptr = sc->setwin(sc, 0xe04);
+ vW(ptr) = 2;
+ ptr = sc->setwin(sc, 0xc02);
+ sc->numports = vW(ptr);
+ } else {
+ ptr = sc->setwin(sc, 0xc22);
+ sc->numports = vW(ptr);
+ }
+
+ if (sc->numports == 0) {
+ device_printf(sc->dev, "%s, 0 ports found\n", sc->name);
+ sc->hidewin(sc);
+ return (0);
+ }
+
+ if (sc->numports > 256) {
+ /* Our minor numbering scheme is broken for more than 256 */
+ device_printf(sc->dev, "%s, 256 ports (%d ports found)\n",
+ sc->name, sc->numports);
+ sc->numports = 256;
+ } else
+ device_printf(sc->dev, "%s, %d ports found\n", sc->name,
+ sc->numports);
+
+ if (sc->ports)
+ free(sc->ports, M_TTYS);
+ sc->ports = malloc(sizeof(struct digi_p) * sc->numports,
+ M_TTYS, M_WAITOK | M_ZERO);
+
+ if (sc->ttys)
+ free(sc->ttys, M_TTYS);
+ sc->ttys = malloc(sizeof(struct tty) * sc->numports,
+ M_TTYS, M_WAITOK | M_ZERO);
+
+ /*
+ * XXX Should read port 0xc90 for an array of 2byte values, 1 per
+ * port. If the value is 0, the port is broken....
+ */
+
+ ptr = sc->setwin(sc, 0);
+
+ /* We should now init per-port structures */
+ bc = (volatile struct board_chan *)(ptr + CHANSTRUCT);
+ sc->gdata = (volatile struct global_data *)(ptr + FEP_GLOBAL);
+
+ sc->memcmd = ptr + sc->gdata->cstart;
+ sc->memevent = ptr + sc->gdata->istart;
+
+ for (i = 0; i < sc->numports; i++, bc++) {
+ port = sc->ports + i;
+ port->pnum = i;
+ port->sc = sc;
+ port->status = ENABLED;
+ port->tp = sc->ttys + i;
+ port->bc = bc;
+
+ if (sc->model == PCXEVE) {
+ port->txbuf = ptr +
+ (((bc->tseg - sc->mem_seg) << 4) & 0x1fff);
+ port->rxbuf = ptr +
+ (((bc->rseg - sc->mem_seg) << 4) & 0x1fff);
+ port->txwin = FEPWIN | ((bc->tseg - sc->mem_seg) >> 9);
+ port->rxwin = FEPWIN | ((bc->rseg - sc->mem_seg) >> 9);
+ } else if (sc->model == PCXI || sc->model == PCXE) {
+ port->txbuf = ptr + ((bc->tseg - sc->mem_seg) << 4);
+ port->rxbuf = ptr + ((bc->rseg - sc->mem_seg) << 4);
+ port->txwin = port->rxwin = 0;
+ } else {
+ port->txbuf = ptr +
+ (((bc->tseg - sc->mem_seg) << 4) % sc->win_size);
+ port->rxbuf = ptr +
+ (((bc->rseg - sc->mem_seg) << 4) % sc->win_size);
+ port->txwin = FEPWIN |
+ (((bc->tseg - sc->mem_seg) << 4) / sc->win_size);
+ port->rxwin = FEPWIN |
+ (((bc->rseg - sc->mem_seg) << 4) / sc->win_size);
+ }
+ port->txbufsize = bc->tmax + 1;
+ port->rxbufsize = bc->rmax + 1;
+
+ lowwater = port->txbufsize >> 2;
+ if (lowwater > 1024)
+ lowwater = 1024;
+ sc->setwin(sc, 0);
+ fepcmd_w(port, STXLWATER, lowwater, 10);
+ fepcmd_w(port, SRXLWATER, port->rxbufsize >> 2, 10);
+ fepcmd_w(port, SRXHWATER, (3 * port->rxbufsize) >> 2, 10);
+
+ bc->edelay = 100;
+ port->dtr_wait = 3 * hz;
+
+ /*
+ * We don't use all the flags from <sys/ttydefaults.h> since
+ * they are only relevant for logins. It's important to have
+ * echo off initially so that the line doesn't start blathering
+ * before the echo flag can be turned off.
+ */
+ port->it_in.c_iflag = 0;
+ port->it_in.c_oflag = 0;
+ port->it_in.c_cflag = TTYDEF_CFLAG;
+ port->it_in.c_lflag = 0;
+ termioschars(&port->it_in);
+ port->it_in.c_ispeed = port->it_in.c_ospeed = digidefaultrate;
+ port->it_out = port->it_in;
+ port->send_ring = 1; /* Default action on signal RI */
+
+ port->dev[0] = make_dev(&digi_sw, (sc->res.unit << 16) + i,
+ UID_ROOT, GID_WHEEL, 0600, "ttyD%d.%d", sc->res.unit, i);
+ port->dev[1] = make_dev(&digi_sw, ((sc->res.unit << 16) + i) |
+ CONTROL_INIT_STATE, UID_ROOT, GID_WHEEL,
+ 0600, "ttyiD%d.%d", sc->res.unit, i);
+ port->dev[2] = make_dev(&digi_sw, ((sc->res.unit << 16) + i) |
+ CONTROL_LOCK_STATE, UID_ROOT, GID_WHEEL,
+ 0600, "ttylD%d.%d", sc->res.unit, i);
+ port->dev[3] = make_dev(&digi_sw, ((sc->res.unit << 16) + i) |
+ CALLOUT_MASK, UID_UUCP, GID_DIALER,
+ 0660, "cuaD%d.%d", sc->res.unit, i);
+ port->dev[4] = make_dev(&digi_sw, ((sc->res.unit << 16) + i) |
+ CALLOUT_MASK | CONTROL_INIT_STATE, UID_UUCP, GID_DIALER,
+ 0660, "cuaiD%d.%d", sc->res.unit, i);
+ port->dev[5] = make_dev(&digi_sw, ((sc->res.unit << 16) + i) |
+ CALLOUT_MASK | CONTROL_LOCK_STATE, UID_UUCP, GID_DIALER,
+ 0660, "cualD%d.%d", sc->res.unit, i);
+ }
+
+ sc->hidewin(sc);
+ sc->inttest = timeout(digi_int_test, sc, hz);
+ /* fepcmd_w(&sc->ports[0], 0xff, 0, 0); */
+ sc->status = DIGI_STATUS_ENABLED;
+
+ return (0);
+}
+
+static int
+digimctl(struct digi_p *port, int bits, int how)
+{
+ int mstat;
+
+ if (how == DMGET) {
+ port->sc->setwin(port->sc, 0);
+ mstat = port->bc->mstat;
+ port->sc->hidewin(port->sc);
+ bits = TIOCM_LE;
+ if (mstat & port->sc->csigs->rts)
+ bits |= TIOCM_RTS;
+ if (mstat & port->cd)
+ bits |= TIOCM_CD;
+ if (mstat & port->dsr)
+ bits |= TIOCM_DSR;
+ if (mstat & port->sc->csigs->cts)
+ bits |= TIOCM_CTS;
+ if (mstat & port->sc->csigs->ri)
+ bits |= TIOCM_RI;
+ if (mstat & port->sc->csigs->dtr)
+ bits |= TIOCM_DTR;
+ return (bits);
+ }
+
+ /* Only DTR and RTS may be set */
+ mstat = 0;
+ if (bits & TIOCM_DTR)
+ mstat |= port->sc->csigs->dtr;
+ if (bits & TIOCM_RTS)
+ mstat |= port->sc->csigs->rts;
+
+ switch (how) {
+ case DMSET:
+ fepcmd_b(port, SETMODEM, mstat, ~mstat, 0);
+ break;
+ case DMBIS:
+ fepcmd_b(port, SETMODEM, mstat, 0, 0);
+ break;
+ case DMBIC:
+ fepcmd_b(port, SETMODEM, 0, mstat, 0);
+ break;
+ }
+
+ return (0);
+}
+
+static void
+digi_disc_optim(struct tty *tp, struct termios *t, struct digi_p *port)
+{
+ if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP)) &&
+ (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) &&
+ (!(t->c_iflag & PARMRK) ||
+ (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) &&
+ !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) &&
+ linesw[tp->t_line].l_rint == ttyinput)
+ tp->t_state |= TS_CAN_BYPASS_L_RINT;
+ else
+ tp->t_state &= ~TS_CAN_BYPASS_L_RINT;
+}
+
+static int
+digiopen(dev_t dev, int flag, int mode, struct thread *td)
+{
+ struct digi_softc *sc;
+ struct tty *tp;
+ int unit;
+ int pnum;
+ struct digi_p *port;
+ int s;
+ int error, mynor;
+ volatile struct board_chan *bc;
+
+ error = 0;
+ mynor = minor(dev);
+ unit = MINOR_TO_UNIT(minor(dev));
+ pnum = MINOR_TO_PORT(minor(dev));
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ if (!sc)
+ return (ENXIO);
+
+ if (sc->status != DIGI_STATUS_ENABLED) {
+ DLOG(DIGIDB_OPEN, (sc->dev, "Cannot open a disabled card\n"));
+ return (ENXIO);
+ }
+ if (pnum >= sc->numports) {
+ DLOG(DIGIDB_OPEN, (sc->dev, "port%d: Doesn't exist\n", pnum));
+ return (ENXIO);
+ }
+ if (mynor & (CTRL_DEV | CONTROL_MASK)) {
+ sc->opencnt++;
+ return (0);
+ }
+ port = &sc->ports[pnum];
+ tp = dev->si_tty = port->tp;
+ bc = port->bc;
+
+ s = spltty();
+
+open_top:
+ while (port->status & DIGI_DTR_OFF) {
+ port->wopeners++;
+ error = tsleep(&port->dtr_wait, TTIPRI | PCATCH, "digidtr", 0);
+ port->wopeners--;
+ if (error)
+ goto out;
+ }
+
+ if (tp->t_state & TS_ISOPEN) {
+ /*
+ * The device is open, so everything has been initialized.
+ * Handle conflicts.
+ */
+ if (mynor & CALLOUT_MASK) {
+ if (!port->active_out) {
+ error = EBUSY;
+ DLOG(DIGIDB_OPEN, (sc->dev, "port %d:"
+ " BUSY error = %d\n", pnum, error));
+ goto out;
+ }
+ } else if (port->active_out) {
+ if (flag & O_NONBLOCK) {
+ error = EBUSY;
+ DLOG(DIGIDB_OPEN, (sc->dev,
+ "port %d: BUSY error = %d\n", pnum, error));
+ goto out;
+ }
+ port->wopeners++;
+ error = tsleep(&port->active_out, TTIPRI | PCATCH,
+ "digibi", 0);
+ port->wopeners--;
+ if (error != 0) {
+ DLOG(DIGIDB_OPEN, (sc->dev,
+ "port %d: tsleep(digibi) error = %d\n",
+ pnum, error));
+ goto out;
+ }
+ goto open_top;
+ }
+ if (tp->t_state & TS_XCLUDE && suser(td) != 0) {
+ error = EBUSY;
+ goto out;
+ }
+ } else {
+ /*
+ * The device isn't open, so there are no conflicts.
+ * Initialize it. Initialization is done twice in many
+ * cases: to preempt sleeping callin opens if we are callout,
+ * and to complete a callin open after DCD rises.
+ */
+ tp->t_oproc = digistart;
+ tp->t_param = digiparam;
+ tp->t_stop = digistop;
+ tp->t_dev = dev;
+ tp->t_termios = (mynor & CALLOUT_MASK) ?
+ port->it_out : port->it_in;
+ sc->setwin(sc, 0);
+
+ bc->rout = bc->rin; /* clear input queue */
+ bc->idata = 1;
+ bc->iempty = 1;
+ bc->ilow = 1;
+ bc->mint = port->cd | port->sc->csigs->ri;
+ bc->tin = bc->tout;
+ if (port->ialtpin) {
+ port->cd = sc->csigs->dsr;
+ port->dsr = sc->csigs->cd;
+ } else {
+ port->cd = sc->csigs->cd;
+ port->dsr = sc->csigs->dsr;
+ }
+ port->wopeners++; /* XXX required ? */
+ error = digiparam(tp, &tp->t_termios);
+ port->wopeners--;
+
+ if (error != 0) {
+ DLOG(DIGIDB_OPEN, (sc->dev,
+ "port %d: cxpparam error = %d\n", pnum, error));
+ goto out;
+ }
+ ttsetwater(tp);
+
+ /* handle fake and initial DCD for callout devices */
+
+ if (bc->mstat & port->cd || mynor & CALLOUT_MASK)
+ linesw[tp->t_line].l_modem(tp, 1);
+ }
+
+ /* Wait for DCD if necessary */
+ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) &&
+ !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) {
+ port->wopeners++;
+ error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "digidcd", 0);
+ port->wopeners--;
+ if (error != 0) {
+ DLOG(DIGIDB_OPEN, (sc->dev,
+ "port %d: tsleep(digidcd) error = %d\n",
+ pnum, error));
+ goto out;
+ }
+ goto open_top;
+ }
+ error = linesw[tp->t_line].l_open(dev, tp);
+ DLOG(DIGIDB_OPEN, (sc->dev, "port %d: l_open error = %d\n",
+ pnum, error));
+
+ digi_disc_optim(tp, &tp->t_termios, port);
+
+ if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK)
+ port->active_out = TRUE;
+
+ if (tp->t_state & TS_ISOPEN)
+ sc->opencnt++;
+out:
+ splx(s);
+
+ if (!(tp->t_state & TS_ISOPEN))
+ digihardclose(port);
+
+ DLOG(DIGIDB_OPEN, (sc->dev, "port %d: open() returns %d\n",
+ pnum, error));
+
+ return (error);
+}
+
+static int
+digiclose(dev_t dev, int flag, int mode, struct thread *td)
+{
+ int mynor;
+ struct tty *tp;
+ int unit, pnum;
+ struct digi_softc *sc;
+ struct digi_p *port;
+ int s;
+
+ mynor = minor(dev);
+ unit = MINOR_TO_UNIT(mynor);
+ pnum = MINOR_TO_PORT(mynor);
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ KASSERT(sc, ("digi%d: softc not allocated in digiclose\n", unit));
+
+ if (mynor & (CTRL_DEV | CONTROL_MASK)) {
+ sc->opencnt--;
+ return (0);
+ }
+
+ port = sc->ports + pnum;
+ tp = port->tp;
+
+ DLOG(DIGIDB_CLOSE, (sc->dev, "port %d: closing\n", pnum));
+
+ s = spltty();
+ linesw[tp->t_line].l_close(tp, flag);
+ digi_disc_optim(tp, &tp->t_termios, port);
+ digistop(tp, FREAD | FWRITE);
+ digihardclose(port);
+ ttyclose(tp);
+ if (--sc->opencnt == 0)
+ splx(s);
+ return (0);
+}
+
+static void
+digidtrwakeup(void *chan)
+{
+ struct digi_p *port = chan;
+
+ port->status &= ~DIGI_DTR_OFF;
+ wakeup(&port->dtr_wait);
+ port->wopeners--;
+}
+
+static void
+digihardclose(struct digi_p *port)
+{
+ volatile struct board_chan *bc;
+ int s;
+
+ bc = port->bc;
+
+ s = spltty();
+ port->sc->setwin(port->sc, 0);
+ bc->idata = 0;
+ bc->iempty = 0;
+ bc->ilow = 0;
+ bc->mint = 0;
+ if ((port->tp->t_cflag & HUPCL) ||
+ (!port->active_out && !(bc->mstat & port->cd) &&
+ !(port->it_in.c_cflag & CLOCAL)) ||
+ !(port->tp->t_state & TS_ISOPEN)) {
+ digimctl(port, TIOCM_DTR | TIOCM_RTS, DMBIC);
+ if (port->dtr_wait != 0) {
+ /* Schedule a wakeup of any callin devices */
+ port->wopeners++;
+ timeout(&digidtrwakeup, port, port->dtr_wait);
+ port->status |= DIGI_DTR_OFF;
+ }
+ }
+ port->active_out = FALSE;
+ wakeup(&port->active_out);
+ wakeup(TSA_CARR_ON(port->tp));
+ splx(s);
+}
+
+static int
+digiread(dev_t dev, struct uio *uio, int flag)
+{
+ int mynor;
+ struct tty *tp;
+ int error, unit, pnum;
+ struct digi_softc *sc;
+
+ mynor = minor(dev);
+ if (mynor & CONTROL_MASK)
+ return (ENODEV);
+
+ unit = MINOR_TO_UNIT(mynor);
+ pnum = MINOR_TO_PORT(mynor);
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ KASSERT(sc, ("digi%d: softc not allocated in digiclose\n", unit));
+ tp = &sc->ttys[pnum];
+
+ error = linesw[tp->t_line].l_read(tp, uio, flag);
+ DLOG(DIGIDB_READ, (sc->dev, "port %d: read() returns %d\n",
+ pnum, error));
+
+ return (error);
+}
+
+static int
+digiwrite(dev_t dev, struct uio *uio, int flag)
+{
+ int mynor;
+ struct tty *tp;
+ int error, unit, pnum;
+ struct digi_softc *sc;
+
+ mynor = minor(dev);
+ if (mynor & CONTROL_MASK)
+ return (ENODEV);
+
+ unit = MINOR_TO_UNIT(mynor);
+ pnum = MINOR_TO_PORT(mynor);
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ KASSERT(sc, ("digi%d: softc not allocated in digiclose\n", unit));
+ tp = &sc->ttys[pnum];
+
+ error = linesw[tp->t_line].l_write(tp, uio, flag);
+ DLOG(DIGIDB_WRITE, (sc->dev, "port %d: write() returns %d\n",
+ pnum, error));
+
+ return (error);
+}
+
+/*
+ * Load module "digi_<mod>.ko" and look for a symbol called digi_mod_<mod>.
+ *
+ * Populate sc->bios, sc->fep, and sc->link from this data.
+ *
+ * sc->fep.data, sc->bios.data and sc->link.data are malloc()d according
+ * to their respective sizes.
+ *
+ * The module is unloaded when we're done.
+ */
+static int
+digi_loadmoduledata(struct digi_softc *sc)
+{
+ struct digi_mod *digi_mod;
+ linker_file_t lf;
+ char *modfile, *sym;
+ caddr_t symptr;
+ int modlen, res;
+
+ KASSERT(sc->bios.data == NULL, ("Uninitialised BIOS variable"));
+ KASSERT(sc->fep.data == NULL, ("Uninitialised FEP variable"));
+ KASSERT(sc->link.data == NULL, ("Uninitialised LINK variable"));
+ KASSERT(sc->module != NULL, ("Uninitialised module name"));
+
+ modlen = strlen(sc->module);
+ modfile = malloc(modlen + 6, M_TEMP, M_WAITOK);
+ snprintf(modfile, modlen + 6, "digi_%s", sc->module);
+ if ((res = linker_reference_module(modfile, NULL, &lf)) != 0) {
+ if (res == ENOENT && rootdev == NODEV)
+ printf("%s: Failed to autoload module: No filesystem\n",
+ modfile);
+ else
+ printf("%s: Failed %d to autoload module\n", modfile,
+ res);
+ }
+ free(modfile, M_TEMP);
+ if (res != 0)
+ return (res);
+
+ sym = malloc(modlen + 10, M_TEMP, M_WAITOK);
+ snprintf(sym, modlen + 10, "digi_mod_%s", sc->module);
+ if ((symptr = linker_file_lookup_symbol(lf, sym, 0)) == NULL)
+ printf("digi_%s.ko: Symbol `%s' not found\n", sc->module, sym);
+ free(sym, M_TEMP);
+
+ digi_mod = (struct digi_mod *)symptr;
+ if (digi_mod->dm_version != DIGI_MOD_VERSION) {
+ printf("digi_%s.ko: Invalid version %d (need %d)\n",
+ sc->module, digi_mod->dm_version, DIGI_MOD_VERSION);
+ linker_file_unload(lf);
+ return (EINVAL);
+ }
+
+ sc->bios.size = digi_mod->dm_bios.size;
+ if (sc->bios.size != 0 && digi_mod->dm_bios.data != NULL) {
+ sc->bios.data = malloc(sc->bios.size, M_TTYS, M_WAITOK);
+ bcopy(digi_mod->dm_bios.data, sc->bios.data, sc->bios.size);
+ }
+
+ sc->fep.size = digi_mod->dm_fep.size;
+ if (sc->fep.size != 0 && digi_mod->dm_fep.data != NULL) {
+ sc->fep.data = malloc(sc->fep.size, M_TTYS, M_WAITOK);
+ bcopy(digi_mod->dm_fep.data, sc->fep.data, sc->fep.size);
+ }
+
+ sc->link.size = digi_mod->dm_link.size;
+ if (sc->link.size != 0 && digi_mod->dm_link.data != NULL) {
+ sc->link.data = malloc(sc->link.size, M_TTYS, M_WAITOK);
+ bcopy(digi_mod->dm_link.data, sc->link.data, sc->link.size);
+ }
+
+ linker_file_unload(lf);
+
+ return (0);
+}
+
+static int
+digiioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td)
+{
+ int unit, pnum, mynor, error, s;
+ struct digi_softc *sc;
+ struct digi_p *port;
+ struct tty *tp;
+#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
+ int oldcmd;
+ struct termios term;
+#endif
+
+ mynor = minor(dev);
+ unit = MINOR_TO_UNIT(mynor);
+ pnum = MINOR_TO_PORT(mynor);
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ KASSERT(sc, ("digi%d: softc not allocated in digiioctl\n", unit));
+
+ if (sc->status == DIGI_STATUS_DISABLED)
+ return (ENXIO);
+
+ if (mynor & CTRL_DEV) {
+ switch (cmd) {
+ case DIGIIO_DEBUG:
+#ifdef DEBUG
+ digi_debug = *(int *)data;
+ return (0);
+#else
+ device_printf(sc->dev, "DEBUG not defined\n");
+ return (ENXIO);
+#endif
+ case DIGIIO_REINIT:
+ digi_loadmoduledata(sc);
+ error = digi_init(sc);
+ digi_freemoduledata(sc);
+ return (error);
+
+ case DIGIIO_MODEL:
+ *(enum digi_model *)data = sc->model;
+ return (0);
+
+ case DIGIIO_IDENT:
+ return (copyout(sc->name, *(char **)data,
+ strlen(sc->name) + 1));
+ }
+ }
+
+ if (pnum >= sc->numports)
+ return (ENXIO);
+
+ port = sc->ports + pnum;
+ if (!(port->status & ENABLED))
+ return (ENXIO);
+
+ tp = port->tp;
+
+ if (mynor & CONTROL_MASK) {
+ struct termios *ct;
+
+ switch (mynor & CONTROL_MASK) {
+ case CONTROL_INIT_STATE:
+ ct = (mynor & CALLOUT_MASK) ?
+ &port->it_out : &port->it_in;
+ break;
+ case CONTROL_LOCK_STATE:
+ ct = (mynor & CALLOUT_MASK) ?
+ &port->lt_out : &port->lt_in;
+ break;
+ default:
+ return (ENODEV); /* /dev/nodev */
+ }
+
+ switch (cmd) {
+ case TIOCSETA:
+ error = suser(td);
+ if (error != 0)
+ return (error);
+ *ct = *(struct termios *)data;
+ return (0);
+
+ case TIOCGETA:
+ *(struct termios *)data = *ct;
+ return (0);
+
+ case TIOCGETD:
+ *(int *)data = TTYDISC;
+ return (0);
+
+ case TIOCGWINSZ:
+ bzero(data, sizeof(struct winsize));
+ return (0);
+
+ case DIGIIO_GETALTPIN:
+ switch (mynor & CONTROL_MASK) {
+ case CONTROL_INIT_STATE:
+ *(int *)data = port->ialtpin;
+ break;
+
+ case CONTROL_LOCK_STATE:
+ *(int *)data = port->laltpin;
+ break;
+
+ default:
+ panic("Confusion when re-testing minor");
+ return (ENODEV);
+ }
+ return (0);
+
+ case DIGIIO_SETALTPIN:
+ switch (mynor & CONTROL_MASK) {
+ case CONTROL_INIT_STATE:
+ if (!port->laltpin) {
+ port->ialtpin = !!*(int *)data;
+ DLOG(DIGIDB_SET, (sc->dev,
+ "port%d: initial ALTPIN %s\n", pnum,
+ port->ialtpin ? "set" : "cleared"));
+ }
+ break;
+
+ case CONTROL_LOCK_STATE:
+ port->laltpin = !!*(int *)data;
+ DLOG(DIGIDB_SET, (sc->dev,
+ "port%d: ALTPIN %slocked\n",
+ pnum, port->laltpin ? "" : "un"));
+ break;
+
+ default:
+ panic("Confusion when re-testing minor");
+ return (ENODEV);
+ }
+ return (0);
+
+ default:
+ return (ENOTTY);
+ }
+ }
+
+ switch (cmd) {
+ case DIGIIO_GETALTPIN:
+ *(int *)data = !!(port->dsr == sc->csigs->cd);
+ return (0);
+
+ case DIGIIO_SETALTPIN:
+ if (!port->laltpin) {
+ if (*(int *)data) {
+ DLOG(DIGIDB_SET, (sc->dev,
+ "port%d: ALTPIN set\n", pnum));
+ port->cd = sc->csigs->dsr;
+ port->dsr = sc->csigs->cd;
+ } else {
+ DLOG(DIGIDB_SET, (sc->dev,
+ "port%d: ALTPIN cleared\n", pnum));
+ port->cd = sc->csigs->cd;
+ port->dsr = sc->csigs->dsr;
+ }
+ }
+ return (0);
+ }
+
+ tp = port->tp;
+#if defined(COMPAT_43) || defined(COMPAT_SUNOS)
+ term = tp->t_termios;
+ oldcmd = cmd;
+ error = ttsetcompat(tp, &cmd, data, &term);
+ if (error != 0)
+ return (error);
+ if (cmd != oldcmd)
+ data = (caddr_t) & term;
+#endif
+ if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) {
+ int cc;
+ struct termios *dt;
+ struct termios *lt;
+
+ dt = (struct termios *)data;
+ lt = (mynor & CALLOUT_MASK) ? &port->lt_out : &port->lt_in;
+
+ dt->c_iflag =
+ (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag);
+ dt->c_oflag =
+ (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag);
+ dt->c_cflag =
+ (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag);
+ dt->c_lflag =
+ (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag);
+ port->c_iflag = dt->c_iflag & (IXOFF | IXON | IXANY);
+ dt->c_iflag &= ~(IXOFF | IXON | IXANY);
+ for (cc = 0; cc < NCCS; ++cc)
+ if (lt->c_cc[cc] != 0)
+ dt->c_cc[cc] = tp->t_cc[cc];
+ if (lt->c_ispeed != 0)
+ dt->c_ispeed = tp->t_ispeed;
+ if (lt->c_ospeed != 0)
+ dt->c_ospeed = tp->t_ospeed;
+ }
+ error = linesw[tp->t_line].l_ioctl(tp, cmd, data, flag, td);
+ if (error == 0 && cmd == TIOCGETA)
+ ((struct termios *)data)->c_iflag |= port->c_iflag;
+
+ if (error >= 0 && error != ENOIOCTL)
+ return (error);
+ s = spltty();
+ error = ttioctl(tp, cmd, data, flag);
+ if (error == 0 && cmd == TIOCGETA)
+ ((struct termios *)data)->c_iflag |= port->c_iflag;
+
+ digi_disc_optim(tp, &tp->t_termios, port);
+ if (error >= 0 && error != ENOIOCTL) {
+ splx(s);
+ return (error);
+ }
+ sc->setwin(sc, 0);
+ switch (cmd) {
+ case DIGIIO_RING:
+ port->send_ring = *(u_char *)data;
+ break;
+ case TIOCSBRK:
+ /*
+ * now it sends 400 millisecond break because I don't know
+ * how to send an infinite break
+ */
+ fepcmd_w(port, SENDBREAK, 400, 10);
+ break;
+ case TIOCCBRK:
+ /* now it's empty */
+ break;
+ case TIOCSDTR:
+ digimctl(port, TIOCM_DTR, DMBIS);
+ break;
+ case TIOCCDTR:
+ digimctl(port, TIOCM_DTR, DMBIC);
+ break;
+ case TIOCMSET:
+ digimctl(port, *(int *)data, DMSET);
+ break;
+ case TIOCMBIS:
+ digimctl(port, *(int *)data, DMBIS);
+ break;
+ case TIOCMBIC:
+ digimctl(port, *(int *)data, DMBIC);
+ break;
+ case TIOCMGET:
+ *(int *)data = digimctl(port, 0, DMGET);
+ break;
+ case TIOCMSDTRWAIT:
+ error = suser(td);
+ if (error != 0) {
+ splx(s);
+ return (error);
+ }
+ port->dtr_wait = *(int *)data *hz / 100;
+
+ break;
+ case TIOCMGDTRWAIT:
+ *(int *)data = port->dtr_wait * 100 / hz;
+ break;
+#ifdef DIGI_INTERRUPT
+ case TIOCTIMESTAMP:
+ *(struct timeval *)data = sc->intr_timestamp;
+
+ break;
+#endif
+ default:
+ splx(s);
+ return (ENOTTY);
+ }
+ splx(s);
+ return (0);
+}
+
+static int
+digiparam(struct tty *tp, struct termios *t)
+{
+ int mynor;
+ int unit;
+ int pnum;
+ struct digi_softc *sc;
+ struct digi_p *port;
+ int cflag;
+ int iflag;
+ int hflow;
+ int s;
+ int window;
+
+ mynor = minor(tp->t_dev);
+ unit = MINOR_TO_UNIT(mynor);
+ pnum = MINOR_TO_PORT(mynor);
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ KASSERT(sc, ("digi%d: softc not allocated in digiparam\n", unit));
+
+ port = &sc->ports[pnum];
+
+ DLOG(DIGIDB_SET, (sc->dev, "port%d: setting parameters\n", pnum));
+
+ if (t->c_ispeed == 0)
+ t->c_ispeed = t->c_ospeed;
+
+ cflag = ttspeedtab(t->c_ospeed, digispeedtab);
+
+ if (cflag < 0 || (cflag > 0 && t->c_ispeed != t->c_ospeed))
+ return (EINVAL);
+
+ s = splclock();
+
+ window = sc->window;
+ sc->setwin(sc, 0);
+
+ if (cflag == 0) { /* hangup */
+ DLOG(DIGIDB_SET, (sc->dev, "port%d: hangup\n", pnum));
+ digimctl(port, TIOCM_DTR | TIOCM_RTS, DMBIC);
+ } else {
+ digimctl(port, TIOCM_DTR | TIOCM_RTS, DMBIS);
+
+ DLOG(DIGIDB_SET, (sc->dev, "port%d: CBAUD = %d\n", pnum,
+ cflag));
+
+#if 0
+ /* convert flags to sysV-style values */
+ if (t->c_cflag & PARODD)
+ cflag |= 0x0200;
+ if (t->c_cflag & PARENB)
+ cflag |= 0x0100;
+ if (t->c_cflag & CSTOPB)
+ cflag |= 0x0080;
+#else
+ /* convert flags to sysV-style values */
+ if (t->c_cflag & PARODD)
+ cflag |= FEP_PARODD;
+ if (t->c_cflag & PARENB)
+ cflag |= FEP_PARENB;
+ if (t->c_cflag & CSTOPB)
+ cflag |= FEP_CSTOPB;
+ if (t->c_cflag & CLOCAL)
+ cflag |= FEP_CLOCAL;
+#endif
+
+ cflag |= (t->c_cflag & CSIZE) >> 4;
+ DLOG(DIGIDB_SET, (sc->dev, "port%d: CFLAG = 0x%x\n", pnum,
+ cflag));
+ fepcmd_w(port, SETCFLAGS, (unsigned)cflag, 0);
+ }
+
+ iflag =
+ t->c_iflag & (IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP);
+ if (port->c_iflag & IXON)
+ iflag |= 0x400;
+ if (port->c_iflag & IXANY)
+ iflag |= 0x800;
+ if (port->c_iflag & IXOFF)
+ iflag |= 0x1000;
+
+ DLOG(DIGIDB_SET, (sc->dev, "port%d: set iflag = 0x%x\n", pnum, iflag));
+ fepcmd_w(port, SETIFLAGS, (unsigned)iflag, 0);
+
+ hflow = 0;
+ if (t->c_cflag & CDTR_IFLOW)
+ hflow |= sc->csigs->dtr;
+ if (t->c_cflag & CRTS_IFLOW)
+ hflow |= sc->csigs->rts;
+ if (t->c_cflag & CCTS_OFLOW)
+ hflow |= sc->csigs->cts;
+ if (t->c_cflag & CDSR_OFLOW)
+ hflow |= port->dsr;
+ if (t->c_cflag & CCAR_OFLOW)
+ hflow |= port->cd;
+
+ DLOG(DIGIDB_SET, (sc->dev, "port%d: set hflow = 0x%x\n", pnum, hflow));
+ fepcmd_w(port, SETHFLOW, 0xff00 | (unsigned)hflow, 0);
+
+ DLOG(DIGIDB_SET, (sc->dev, "port%d: set startc(0x%x), stopc(0x%x)\n",
+ pnum, t->c_cc[VSTART], t->c_cc[VSTOP]));
+ fepcmd_b(port, SONOFFC, t->c_cc[VSTART], t->c_cc[VSTOP], 0);
+
+ if (sc->window != 0)
+ sc->towin(sc, 0);
+ if (window != 0)
+ sc->towin(sc, window);
+ splx(s);
+
+ return (0);
+}
+
+static void
+digi_intr(void *vp)
+{
+ struct digi_p *port;
+ char *cxcon;
+ struct digi_softc *sc;
+ int ehead, etail;
+ volatile struct board_chan *bc;
+ struct tty *tp;
+ int head, tail;
+ int wrapmask;
+ int size, window;
+ struct event {
+ u_char pnum;
+ u_char event;
+ u_char mstat;
+ u_char lstat;
+ } event;
+
+ sc = vp;
+
+ if (sc->status != DIGI_STATUS_ENABLED) {
+ DLOG(DIGIDB_IRQ, (sc->dev, "interrupt on disabled board !\n"));
+ return;
+ }
+
+#ifdef DIGI_INTERRUPT
+ microtime(&sc->intr_timestamp);
+#endif
+
+ window = sc->window;
+ sc->setwin(sc, 0);
+
+ if (sc->model >= PCXEM && W(sc->vmem + 0xd00)) {
+ struct con_bios *con = con_bios_list;
+ register u_char *ptr;
+
+ ptr = sc->vmem + W(sc->vmem + 0xd00);
+ while (con) {
+ if (ptr[1] && W(ptr + 2) == W(con->bios + 2))
+ /* Not first block -- exact match */
+ break;
+
+ if (W(ptr + 4) >= W(con->bios + 4) &&
+ W(ptr + 4) <= W(con->bios + 6))
+ /* Initial search concetrator BIOS */
+ break;
+ }
+
+ if (con == NULL) {
+ log(LOG_ERR, "digi%d: wanted bios LREV = 0x%04x"
+ " not found!\n", sc->res.unit, W(ptr + 4));
+ W(ptr + 10) = 0;
+ W(sc->vmem + 0xd00) = 0;
+ goto eoi;
+ }
+ cxcon = con->bios;
+ W(ptr + 4) = W(cxcon + 4);
+ W(ptr + 6) = W(cxcon + 6);
+ if (ptr[1] == 0)
+ W(ptr + 2) = W(cxcon + 2);
+ W(ptr + 8) = (ptr[1] << 6) + W(cxcon + 8);
+ size = W(cxcon + 10) - (ptr[1] << 10);
+ if (size <= 0) {
+ W(ptr + 8) = W(cxcon + 8);
+ W(ptr + 10) = 0;
+ } else {
+ if (size > 1024)
+ size = 1024;
+ W(ptr + 10) = size;
+ bcopy(cxcon + (ptr[1] << 10), ptr + 12, size);
+ }
+ W(sc->vmem + 0xd00) = 0;
+ goto eoi;
+ }
+
+ ehead = sc->gdata->ein;
+ etail = sc->gdata->eout;
+ if (ehead == etail) {
+#ifdef DEBUG
+ sc->intr_count++;
+ if (sc->intr_count % 6000 == 0) {
+ DLOG(DIGIDB_IRQ, (sc->dev,
+ "6000 useless polls %x %x\n", ehead, etail));
+ sc->intr_count = 0;
+ }
+#endif
+ goto eoi;
+ }
+ while (ehead != etail) {
+ event = *(volatile struct event *)(sc->memevent + etail);
+
+ etail = (etail + 4) & sc->gdata->imax;
+
+ if (event.pnum >= sc->numports) {
+ log(LOG_ERR, "digi%d: port %d: got event"
+ " on nonexisting port\n", sc->res.unit,
+ event.pnum);
+ continue;
+ }
+ port = &sc->ports[event.pnum];
+ bc = port->bc;
+ tp = port->tp;
+
+ if (!(tp->t_state & TS_ISOPEN) && !port->wopeners) {
+ DLOG(DIGIDB_IRQ, (sc->dev,
+ "port %d: event 0x%x on closed port\n",
+ event.pnum, event.event));
+ bc->rout = bc->rin;
+ bc->idata = 0;
+ bc->iempty = 0;
+ bc->ilow = 0;
+ bc->mint = 0;
+ continue;
+ }
+ if (event.event & ~ALL_IND)
+ log(LOG_ERR, "digi%d: port%d: ? event 0x%x mstat 0x%x"
+ " lstat 0x%x\n", sc->res.unit, event.pnum,
+ event.event, event.mstat, event.lstat);
+
+ if (event.event & DATA_IND) {
+ DLOG(DIGIDB_IRQ, (sc->dev, "port %d: DATA_IND\n",
+ event.pnum));
+ wrapmask = port->rxbufsize - 1;
+ head = bc->rin;
+ tail = bc->rout;
+
+ size = 0;
+ if (!(tp->t_state & TS_ISOPEN)) {
+ bc->rout = head;
+ goto end_of_data;
+ }
+ while (head != tail) {
+ int top;
+
+ DLOG(DIGIDB_INT, (sc->dev,
+ "port %d: p rx head = %d tail = %d\n",
+ event.pnum, head, tail));
+ top = (head > tail) ? head : wrapmask + 1;
+ sc->towin(sc, port->rxwin);
+ size = top - tail;
+ if (tp->t_state & TS_CAN_BYPASS_L_RINT) {
+ size = b_to_q((char *)port->rxbuf +
+ tail, size, &tp->t_rawq);
+ tail = top - size;
+ ttwakeup(tp);
+ } else for (; tail < top;) {
+ linesw[tp->t_line].
+ l_rint(port->rxbuf[tail], tp);
+ sc->towin(sc, port->rxwin);
+ size--;
+ tail++;
+ if (tp->t_state & TS_TBLOCK)
+ break;
+ }
+ tail &= wrapmask;
+ sc->setwin(sc, 0);
+ bc->rout = tail;
+ head = bc->rin;
+ if (size)
+ break;
+ }
+
+ if (bc->orun) {
+ CE_RECORD(port, CE_OVERRUN);
+ log(LOG_ERR, "digi%d: port%d: %s\n",
+ sc->res.unit, event.pnum,
+ digi_errortxt(CE_OVERRUN));
+ bc->orun = 0;
+ }
+end_of_data:
+ if (size) {
+ tp->t_state |= TS_TBLOCK;
+ port->status |= PAUSE_RX;
+ DLOG(DIGIDB_RX, (sc->dev, "port %d: pause RX\n",
+ event.pnum));
+ } else {
+ bc->idata = 1;
+ }
+ }
+
+ if (event.event & MODEMCHG_IND) {
+ DLOG(DIGIDB_MODEM, (sc->dev, "port %d: MODEMCHG_IND\n",
+ event.pnum));
+
+ if ((event.mstat ^ event.lstat) & port->cd) {
+ sc->hidewin(sc);
+ linesw[tp->t_line].l_modem
+ (tp, event.mstat & port->cd);
+ sc->setwin(sc, 0);
+ wakeup(TSA_CARR_ON(tp));
+ }
+
+ if (event.mstat & sc->csigs->ri) {
+ DLOG(DIGIDB_RI, (sc->dev, "port %d: RING\n",
+ event.pnum));
+ if (port->send_ring) {
+ linesw[tp->t_line].l_rint('R', tp);
+ linesw[tp->t_line].l_rint('I', tp);
+ linesw[tp->t_line].l_rint('N', tp);
+ linesw[tp->t_line].l_rint('G', tp);
+ linesw[tp->t_line].l_rint('\r', tp);
+ linesw[tp->t_line].l_rint('\n', tp);
+ }
+ }
+ }
+ if (event.event & BREAK_IND) {
+ DLOG(DIGIDB_MODEM, (sc->dev, "port %d: BREAK_IND\n",
+ event.pnum));
+ linesw[tp->t_line].l_rint(TTY_BI, tp);
+ }
+ if (event.event & (LOWTX_IND | EMPTYTX_IND)) {
+ DLOG(DIGIDB_IRQ, (sc->dev, "port %d:%s%s\n",
+ event.pnum,
+ event.event & LOWTX_IND ? " LOWTX" : "",
+ event.event & EMPTYTX_IND ? " EMPTYTX" : ""));
+ (*linesw[tp->t_line].l_start)(tp);
+ }
+ }
+ sc->gdata->eout = etail;
+eoi:
+ if (sc->window != 0)
+ sc->towin(sc, 0);
+ if (window != 0)
+ sc->towin(sc, window);
+}
+
+static void
+digistart(struct tty *tp)
+{
+ int unit;
+ int pnum;
+ struct digi_p *port;
+ struct digi_softc *sc;
+ volatile struct board_chan *bc;
+ int head, tail;
+ int size, ocount, totcnt = 0;
+ int s;
+ int wmask;
+
+ unit = MINOR_TO_UNIT(minor(tp->t_dev));
+ pnum = MINOR_TO_PORT(minor(tp->t_dev));
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ KASSERT(sc, ("digi%d: softc not allocated in digistart\n", unit));
+
+ port = &sc->ports[pnum];
+ bc = port->bc;
+
+ wmask = port->txbufsize - 1;
+
+ s = spltty();
+ port->lcc = tp->t_outq.c_cc;
+ sc->setwin(sc, 0);
+ if (!(tp->t_state & TS_TBLOCK)) {
+ if (port->status & PAUSE_RX) {
+ DLOG(DIGIDB_RX, (sc->dev, "port %d: resume RX\n",
+ pnum));
+ /*
+ * CAREFUL - braces are needed here if the DLOG is
+ * optimised out!
+ */
+ }
+ port->status &= ~PAUSE_RX;
+ bc->idata = 1;
+ }
+ if (!(tp->t_state & TS_TTSTOP) && port->status & PAUSE_TX) {
+ DLOG(DIGIDB_TX, (sc->dev, "port %d: resume TX\n", pnum));
+ port->status &= ~PAUSE_TX;
+ fepcmd_w(port, RESUMETX, 0, 10);
+ }
+ if (tp->t_outq.c_cc == 0)
+ tp->t_state &= ~TS_BUSY;
+ else
+ tp->t_state |= TS_BUSY;
+
+ head = bc->tin;
+ while (tp->t_outq.c_cc != 0) {
+ tail = bc->tout;
+ DLOG(DIGIDB_INT, (sc->dev, "port%d: s tx head = %d tail = %d\n",
+ pnum, head, tail));
+
+ if (head < tail)
+ size = tail - head - 1;
+ else {
+ size = port->txbufsize - head;
+ if (tail == 0)
+ size--;
+ }
+
+ if (size == 0)
+ break;
+ sc->towin(sc, port->txwin);
+ ocount = q_to_b(&tp->t_outq, port->txbuf + head, size);
+ totcnt += ocount;
+ head += ocount;
+ head &= wmask;
+ sc->setwin(sc, 0);
+ bc->tin = head;
+ bc->iempty = 1;
+ bc->ilow = 1;
+ }
+ port->lostcc = tp->t_outq.c_cc;
+ tail = bc->tout;
+ if (head < tail)
+ size = port->txbufsize - tail + head;
+ else
+ size = head - tail;
+
+ port->lbuf = size;
+ DLOG(DIGIDB_INT, (sc->dev, "port%d: s total cnt = %d\n", pnum, totcnt));
+ ttwwakeup(tp);
+ splx(s);
+}
+
+static void
+digistop(struct tty *tp, int rw)
+{
+ struct digi_softc *sc;
+ int unit;
+ int pnum;
+ struct digi_p *port;
+
+ unit = MINOR_TO_UNIT(minor(tp->t_dev));
+ pnum = MINOR_TO_PORT(minor(tp->t_dev));
+
+ sc = (struct digi_softc *)devclass_get_softc(digi_devclass, unit);
+ KASSERT(sc, ("digi%d: softc not allocated in digistop\n", unit));
+ port = sc->ports + pnum;
+
+ DLOG(DIGIDB_TX, (sc->dev, "port %d: pause TX\n", pnum));
+ port->status |= PAUSE_TX;
+ fepcmd_w(port, PAUSETX, 0, 10);
+}
+
+static void
+fepcmd(struct digi_p *port, int cmd, int op1, int ncmds)
+{
+ u_char *mem;
+ unsigned tail, head;
+ int count, n;
+
+ mem = port->sc->memcmd;
+
+ port->sc->setwin(port->sc, 0);
+
+ head = port->sc->gdata->cin;
+ mem[head + 0] = cmd;
+ mem[head + 1] = port->pnum;
+ *(ushort *)(mem + head + 2) = op1;
+
+ head = (head + 4) & port->sc->gdata->cmax;
+ port->sc->gdata->cin = head;
+
+ for (count = FEPTIMEOUT; count > 0; count--) {
+ head = port->sc->gdata->cin;
+ tail = port->sc->gdata->cout;
+ n = (head - tail) & port->sc->gdata->cmax;
+
+ if (n <= ncmds * sizeof(short) * 4)
+ break;
+ }
+ if (count == 0)
+ log(LOG_ERR, "digi%d: port%d: timeout on FEP command\n",
+ port->sc->res.unit, port->pnum);
+}
+
+const char *
+digi_errortxt(int id)
+{
+ static const char *error_desc[] = {
+ "silo overflow",
+ "interrupt-level buffer overflow",
+ "tty-level buffer overflow",
+ };
+
+ KASSERT(id >= 0 && id < sizeof(error_desc) / sizeof(error_desc[0]),
+ ("Unexpected digi error id %d\n", id));
+
+ return (error_desc[id]);
+}
+
+int
+digi_attach(struct digi_softc *sc)
+{
+ sc->res.ctldev = make_dev(&digi_sw,
+ (sc->res.unit << 16) | CTRL_DEV, UID_ROOT, GID_WHEEL,
+ 0600, "digi%r.ctl", sc->res.unit);
+
+ digi_loadmoduledata(sc);
+ digi_init(sc);
+ digi_freemoduledata(sc);
+
+ return (0);
+}
+
+static int
+digi_inuse(struct digi_softc *sc)
+{
+ int i;
+
+ for (i = 0; i < sc->numports; i++)
+ if (sc->ttys[i].t_state & TS_ISOPEN) {
+ DLOG(DIGIDB_INIT, (sc->dev, "port%d: busy\n", i));
+ return (1);
+ } else if (sc->ports[i].wopeners || sc->ports[i].opencnt) {
+ DLOG(DIGIDB_INIT, (sc->dev, "port%d: blocked in open\n",
+ i));
+ return (1);
+ }
+ return (0);
+}
+
+static void
+digi_free_state(struct digi_softc *sc)
+{
+ int d, i;
+
+ /* Blow it all away */
+
+ for (i = 0; i < sc->numports; i++)
+ for (d = 0; d < 6; d++)
+ destroy_dev(sc->ports[i].dev[d]);
+
+ untimeout(digi_poll, sc, sc->callout);
+ callout_handle_init(&sc->callout);
+ untimeout(digi_int_test, sc, sc->inttest);
+ callout_handle_init(&sc->inttest);
+
+ bus_teardown_intr(sc->dev, sc->res.irq, sc->res.irqHandler);
+#ifdef DIGI_INTERRUPT
+ if (sc->res.irq != NULL) {
+ bus_release_resource(dev, SYS_RES_IRQ, sc->res.irqrid,
+ sc->res.irq);
+ sc->res.irq = NULL;
+ }
+#endif
+ if (sc->numports) {
+ KASSERT(sc->ports, ("digi%d: Lost my ports ?", sc->res.unit));
+ KASSERT(sc->ttys, ("digi%d: Lost my ttys ?", sc->res.unit));
+ free(sc->ports, M_TTYS);
+ sc->ports = NULL;
+ free(sc->ttys, M_TTYS);
+ sc->ttys = NULL;
+ sc->numports = 0;
+ }
+
+ sc->status = DIGI_STATUS_NOTINIT;
+}
+
+int
+digi_detach(device_t dev)
+{
+ struct digi_softc *sc = device_get_softc(dev);
+
+ DLOG(DIGIDB_INIT, (sc->dev, "detaching\n"));
+
+ /* If we're INIT'd, numports must be 0 */
+ KASSERT(sc->numports == 0 || sc->status != DIGI_STATUS_NOTINIT,
+ ("digi%d: numports(%d) & status(%d) are out of sync",
+ sc->res.unit, sc->numports, (int)sc->status));
+
+ if (digi_inuse(sc))
+ return (EBUSY);
+
+ digi_free_state(sc);
+
+ destroy_dev(makedev(CDEV_MAJOR,
+ (sc->res.unit << 16) | CTRL_DEV));
+
+ if (sc->res.mem != NULL) {
+ bus_release_resource(dev, SYS_RES_MEMORY, sc->res.mrid,
+ sc->res.mem);
+ sc->res.mem = NULL;
+ }
+ if (sc->res.io != NULL) {
+ bus_release_resource(dev, SYS_RES_IOPORT, sc->res.iorid,
+ sc->res.io);
+ sc->res.io = NULL;
+ }
+
+ return (0);
+}
+
+int
+digi_shutdown(device_t dev)
+{
+ return (0);
+}
+
+MODULE_VERSION(digi, 1);
OpenPOWER on IntegriCloud